Compare commits
62 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 00d35a5ead | |||
| 7b854d7216 | |||
| af0a5d0501 | |||
| c3a4c4ae96 | |||
| bb6cdf0abf | |||
| 3c754dc196 | |||
| af8cfa31e5 | |||
| f32568c918 | |||
| b42da7603b | |||
| 851772398c | |||
| dfc1c557e0 | |||
| f927667732 | |||
| d77f9bab8c | |||
| d508222901 | |||
| 45e45d44af | |||
| d0d39ccd62 | |||
| ff96d727f1 | |||
| 828430adf8 | |||
| a6b60a4cc8 | |||
| 2fb0e1f73b | |||
| 2a8bbf76bf | |||
| bc5ce3e311 | |||
| 64dc54bb70 | |||
| c6273ed8b3 | |||
| 16f6aa511c | |||
| b2917eda6b | |||
| 2075a0be20 | |||
| cc821da4da | |||
| a466f04d20 | |||
| f4ed36ad38 | |||
| f6c7a205a8 | |||
| b197f43341 | |||
| 6e23371858 | |||
| 75beb3d584 | |||
| 9ed6755072 | |||
| 820187c19d | |||
| b76ed1e627 | |||
| 23d1d202d0 | |||
| 615f8e3f61 | |||
| 55b5013184 | |||
| f3dedb0a2e | |||
| a1b1e24bea | |||
| c9206bb065 | |||
| 3b04f73270 | |||
| dbcbdfe452 | |||
| a57c5f7af6 | |||
| 96aaa43d1c | |||
| 07a369e462 | |||
| 5b03ab8dc3 | |||
| 4166da1eb2 | |||
| 2531beab05 | |||
| 08f0db97c1 | |||
| b18a8ead6a | |||
| 0d4c1b90c2 | |||
| 83f2ea88b5 | |||
| b501abf447 | |||
| e9eaa0c98c | |||
| 460da5dda8 | |||
| b3b9aa5c79 | |||
| ff38722e6c | |||
| f9294be2dd | |||
| 4f569c6fa7 |
@@ -10,7 +10,7 @@
|
||||
"request": "launch",
|
||||
"preLaunchTask": "build",
|
||||
// If you have changed target frameworks, make sure to update the program path.
|
||||
"program": "${workspaceFolder}/bin/Debug/net10.0/osx-arm64/ReceiptPDFBuilder.dll",
|
||||
"program": "${workspaceFolder}/src/bin/Debug/net10.0/osx-arm64/MayShow.dll",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}",
|
||||
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"type": "process",
|
||||
"args": [
|
||||
"build",
|
||||
"${workspaceFolder}/ReceiptPDFBuilder.csproj",
|
||||
"${workspaceFolder}/src/MayShow.csproj",
|
||||
"/property:GenerateFullPaths=true",
|
||||
"/consoleloggerparameters:NoSummary;ForceNoAlign"
|
||||
],
|
||||
@@ -19,7 +19,7 @@
|
||||
"type": "process",
|
||||
"args": [
|
||||
"publish",
|
||||
"${workspaceFolder}/ReceiptPDFBuilder.csproj",
|
||||
"${workspaceFolder}/src/MayShow.csproj",
|
||||
"/property:GenerateFullPaths=true",
|
||||
"/consoleloggerparameters:NoSummary;ForceNoAlign"
|
||||
],
|
||||
@@ -33,7 +33,7 @@
|
||||
"watch",
|
||||
"run",
|
||||
"--project",
|
||||
"${workspaceFolder}/ReceiptPDFBuilder.csproj"
|
||||
"${workspaceFolder}/src/MayShow.csproj"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
using ReceiptPDFBuilder.ViewModels;
|
||||
|
||||
namespace ReceiptPDFBuilder.Interfaces
|
||||
{
|
||||
interface IChangeViewModel
|
||||
{
|
||||
void PushViewModel(BaseViewModel model);
|
||||
void PopViewModel();
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace ReceiptPDFBuilder.Interfaces
|
||||
{
|
||||
interface ITopLevelGrabber
|
||||
{
|
||||
TopLevel GetTopLevel();
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,26 @@
|
||||
The MIT with no AI License
|
||||
MIT NON-AI License
|
||||
|
||||
Copyright (c) 2026 Deadpikle
|
||||
Copyright (c) 2026, Deadpikle
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of the software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions.
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
In addition, the following restrictions apply:
|
||||
|
||||
Under no circumstances can this software, its builds, its information, or its code be used for any AI-related training, code, programs, etc.
|
||||
1. The Software and any modifications made to it may not be used for the purpose of training or improving machine learning algorithms,
|
||||
including but not limited to artificial intelligence, natural language processing, or data mining. This condition applies to any derivatives,
|
||||
modifications, or updates based on the Software code. Any usage of the Software in an AI-training dataset is considered a breach of this License.
|
||||
|
||||
2. The Software may not be included in any dataset used for training or improving machine learning algorithms,
|
||||
including but not limited to artificial intelligence, natural language processing, or data mining.
|
||||
|
||||
3. Any person or organization found to be in violation of these restrictions will be subject to legal action and may be held liable
|
||||
for any damages resulting from such use.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
||||
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -1,19 +0,0 @@
|
||||
using Avalonia.Controls;
|
||||
using ReceiptPDFBuilder.Interfaces;
|
||||
using ReceiptPDFBuilder.ViewModels;
|
||||
|
||||
namespace ReceiptPDFBuilder;
|
||||
|
||||
public partial class MainWindow : Window, ITopLevelGrabber
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = new MainWindowViewModel(this);
|
||||
}
|
||||
|
||||
public TopLevel GetTopLevel()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,11 @@
|
||||
# MayShow
|
||||
|
||||
MayShow (an intentional misspelling of 明証, pronounced may-shō, a Japanese word meaning proof, evidence, or corroboration) is a PDF report creation tool.
|
||||
|
||||
Throws a folder of images and PDFs into a single PDF. Simple tool for my own use, really, but maybe helpful to someone else.
|
||||
|
||||
Icon from [Font Awesome](https://fontawesome.com/license/free).
|
||||

|
||||
|
||||
Icon is from [Font Awesome](https://fontawesome.com/license/free) and generated via [Gauger's FontAwesome icon tool](https://gauger.me/fonticon/) and the macOS software [Icon Composer](https://developer.apple.com/icon-composer/).
|
||||
|
||||
Most contributions are welcome; however, no contributions made from AI tools or related software are allowed.
|
||||
@@ -1,3 +1,22 @@
|
||||
-Add ImageMagick attribution to about page
|
||||
-auto detect receipts in an image and auto-crop?
|
||||
-https://imagemagick.org/api/feature.php#gsc.tab=0 canny edge image
|
||||
-https://blog.jiayu.co/2019/05/edge-detection-with-imagemagick/
|
||||
-https://pyimagesearch.com/2021/10/27/automatically-ocring-receipts-and-scans/ using open CV
|
||||
-https://www.kaggle.com/code/dmitryyemelyanov/receipt-ocr-part-1-image-segmentation-by-opencv manip done before edge detect
|
||||
-https://www.luisllamas.es/en/how-to-use-opencv-in-net-with-opencvsharp/ (some basic code but also line detect)
|
||||
-opencv for macOS? https://www.nuget.org/packages/OpenCvSharp4.runtime.osx.10.15-universal
|
||||
macOS arm64: https://www.nuget.org/packages/OpenCvSharp4.runtime.osx_arm64/4.8.1-rc
|
||||
-can use the normal nuget for windows, linnux
|
||||
-if we can get openCV working then we can probably hack something together...
|
||||
-https://github.com/shimat/opencvsharp/issues/949 -- requires ffmpeg?!
|
||||
-https://github.com/shimat/opencvsharp
|
||||
-https://www.emgu.com/wiki/index.php?title=Main_Page (GPL...)
|
||||
-https://stackoverflow.com/questions/30296710/detecting-paper-edge-and-crop-it
|
||||
//https://developers.goalist.co.jp/entry/2019/02/13/150126
|
||||
|
||||
|
||||
---------------
|
||||
*-add more items
|
||||
*-save last opened folder to settings somewhere
|
||||
|
||||
@@ -11,7 +30,15 @@
|
||||
*-add preview or open button or something to each item
|
||||
https://gitflic.ru/project/jackhammer/pdf-forge-net/file?commit=7bdce6132a70ab12664a8f2482003836c7af2ab6
|
||||
|
||||
*-check if last remembered dir exists on auto-load!
|
||||
*-show working directory
|
||||
*-set DMG title in publish
|
||||
*-Get linux publish command working and zip up/publish
|
||||
dotnet publish -c Release -r linux-x64 -p:StripSymbols=False -p:PublishAot=False
|
||||
dotnet publish -c Release -r linux-arm64 -p:StripSymbols=False -p:PublishAot=False
|
||||
|
||||
---Publishing---
|
||||
*-Published app has unneeded .DSYM file (fixed via .app builder)
|
||||
*-Published app has Assets folder already copied to it; don't want that in output macOS folder but it's being copied there anyway (fixed via .app builder)
|
||||
*-macOS x64 build
|
||||
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
using System;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace ReceiptPDFBuilder.Views
|
||||
{
|
||||
public partial class MainView : UserControl
|
||||
{
|
||||
public MainView()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
LogBlock.PropertyChanged += LogBlock_PropertyChanged;
|
||||
}
|
||||
|
||||
private void LogBlock_PropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.Property.ToString() == "Text")
|
||||
{
|
||||
LogScrollView.ScrollToEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 236 KiB |
|
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 1.9 MiB |
|
After Width: | Height: | Size: 36 KiB |
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"fill" : "system-dark",
|
||||
"groups" : [
|
||||
{
|
||||
"layers" : [
|
||||
{
|
||||
"image-name" : "favicon(2).png",
|
||||
"name" : "favicon(2)"
|
||||
}
|
||||
],
|
||||
"shadow" : {
|
||||
"kind" : "neutral",
|
||||
"opacity" : 0.5
|
||||
},
|
||||
"translucency" : {
|
||||
"enabled" : true,
|
||||
"value" : 0.5
|
||||
}
|
||||
}
|
||||
],
|
||||
"supported-platforms" : {
|
||||
"circles" : [
|
||||
"watchOS"
|
||||
],
|
||||
"squares" : "shared"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.9 MiB |
@@ -2,10 +2,10 @@
|
||||
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
|
||||
; Non-commercial use only
|
||||
|
||||
#define MyAppName "Receipt PDF Builder"
|
||||
#define MyAppVersion "1.0.0"
|
||||
#define MyAppName "MayShow"
|
||||
#define MyAppVersion "1.2.0"
|
||||
#define MyAppPublisher "Quickity Quack Productions"
|
||||
#define MyAppExeName "ReceiptPDFBuilder.exe"
|
||||
#define MyAppExeName "MayShow.exe"
|
||||
|
||||
[Setup]
|
||||
; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
|
||||
@@ -31,8 +31,8 @@ DisableProgramGroupPage=yes
|
||||
OutputBaseFilename=Install {#MyAppName} {#MyAppVersion}
|
||||
SolidCompression=yes
|
||||
WizardStyle=modern dynamic
|
||||
SetupIconFile=ReceiptPDFBuilder-icon.ico
|
||||
OutputDir=bin
|
||||
SetupIconFile=../src/MayShow-icon.ico
|
||||
OutputDir=..\src\bin
|
||||
|
||||
[Languages]
|
||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||
@@ -41,12 +41,12 @@ Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
||||
|
||||
[Files]
|
||||
Source: "bin\Release\net10.0\win-x64\publish\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "bin\Release\net10.0\win-x64\publish\av_libglesv2.dll"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "bin\Release\net10.0\win-x64\publish\libHarfBuzzSharp.dll"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "bin\Release\net10.0\win-x64\publish\libSkiaSharp.dll"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "bin\Release\net10.0\win-x64\publish\Magick.Native-Q16-x64.dll"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "bin\Release\net10.0\win-x64\publish\Assets\*"; DestDir: "{app}\Assets"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
Source: "..\src\bin\Release\net10.0\win-x64\publish\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "..\src\bin\Release\net10.0\win-x64\publish\av_libglesv2.dll"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "..\src\bin\Release\net10.0\win-x64\publish\libHarfBuzzSharp.dll"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "..\src\bin\Release\net10.0\win-x64\publish\libSkiaSharp.dll"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "..\src\bin\Release\net10.0\win-x64\publish\Magick.Native-Q16-x64.dll"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "..\src\bin\Release\net10.0\win-x64\publish\Assets\*"; DestDir: "{app}\Assets"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
||||
|
||||
[Icons]
|
||||
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
|
||||
SRC_DIR="src" # user ran script from main folder
|
||||
if [ ! -d "$SRC_DIR" ]; then
|
||||
SRC_DIR= "../src" # try
|
||||
fi
|
||||
if [ ! -d "$SRC_DIR" ]; then
|
||||
echo "Please run from "installers" dir or from main repo directory"
|
||||
exit 1
|
||||
fi
|
||||
cd "$SRC_DIR"
|
||||
echo "Building release for linux-x64..."
|
||||
dotnet publish -c Release -r linux-x64 -p:StripSymbols=False -p:PublishAot=False
|
||||
echo "Building release for linux-arm64..."
|
||||
dotnet publish -c Release -r linux-arm64 -p:StripSymbols=False -p:PublishAot=False
|
||||
# TODO: add automatic zipping and version number setting for ease of use
|
||||
@@ -1,13 +1,13 @@
|
||||
<Application xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="ReceiptPDFBuilder.App"
|
||||
xmlns:viewModels="clr-namespace:ReceiptPDFBuilder.ViewModels"
|
||||
xmlns:views="clr-namespace:ReceiptPDFBuilder.Views"
|
||||
x:Class="MayShow.App"
|
||||
xmlns:viewModels="clr-namespace:MayShow.ViewModels"
|
||||
xmlns:views="clr-namespace:MayShow.Views"
|
||||
xmlns:dialogHostAvalonia="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
|
||||
xmlns:behaviors="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
|
||||
xmlns:helpers="clr-namespace:ReceiptPDFBuilder.Helpers"
|
||||
xmlns:helpers="clr-namespace:MayShow.Helpers"
|
||||
RequestedThemeVariant="Default"
|
||||
Name="Receipt PDF Builder">
|
||||
Name="MayShow">
|
||||
<Application.Styles>
|
||||
<FluentTheme />
|
||||
<StyleInclude Source="avares://AvaloniaProgressRing/Styles/ProgressRing.xaml"/>
|
||||
@@ -88,7 +88,7 @@
|
||||
<DataTemplate DataType="{x:Type viewModels:MainViewModel}">
|
||||
<views:MainView/>
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="{x:Type viewModels:WarningDeleteItemModel}">
|
||||
<DataTemplate DataType="{x:Type viewModels:WarningDeleteItemViewModel}">
|
||||
<views:WarningDeleteItem/>
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="{x:Type viewModels:EditFileViewModel}">
|
||||
@@ -100,6 +100,12 @@
|
||||
<DataTemplate DataType="{x:Type viewModels:WarningViewModel}">
|
||||
<views:WarningView/>
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="{x:Type viewModels:ShutdownCheckViewModel}">
|
||||
<views:ShutdownCheckView/>
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="{x:Type viewModels:ConfirmViewModel}">
|
||||
<views:ConfirmView/>
|
||||
</DataTemplate>
|
||||
</Application.DataTemplates>
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
@@ -116,8 +122,8 @@
|
||||
<PathGeometry x:Key="IconGrabberGeometry">M15 18a1 1 0 100-2 1 1 0 000 2zm1-6a1 1 0 11-2 0 1 1 0 012 0zm-7 6a1 1 0 100-2 1 1 0 000 2zm0-5a1 1 0 100-2 1 1 0 000 2zm7-6a1 1 0 11-2 0 1 1 0 012 0zM9 8a1 1 0 100-2 1 1 0 000 2z</PathGeometry>
|
||||
<GeometryDrawing x:Key="IconGrabber" Brush="{DynamicResource DragDropBrush}" Geometry="{StaticResource IconGrabberGeometry}" />
|
||||
|
||||
<FontFamily x:Key="FontAwesomeRegular">avares://ReceiptPDFBuilder/Assets/Fonts/FontAwesome/Font Awesome 7 Free-Regular-400.otf#Font Awesome 7 Free Regular</FontFamily>
|
||||
<FontFamily x:Key="FontAwesomeSolid">avares://ReceiptPDFBuilder/Assets/Fonts/FontAwesome/Font Awesome 7 Free-Solid-900.otf#Font Awesome 7 Free Solid</FontFamily>
|
||||
<FontFamily x:Key="FontAwesomeRegular">avares://MayShow/Assets/Fonts/FontAwesome/Font Awesome 7 Free-Regular-400.otf#Font Awesome 7 Free Regular</FontFamily>
|
||||
<FontFamily x:Key="FontAwesomeSolid">avares://MayShow/Assets/Fonts/FontAwesome/Font Awesome 7 Free-Solid-900.otf#Font Awesome 7 Free Solid</FontFamily>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
<NativeMenu.Menu>
|
||||
@@ -4,10 +4,10 @@ using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using DialogHostAvalonia;
|
||||
using ReceiptPDFBuilder;
|
||||
using ReceiptPDFBuilder.ViewModels;
|
||||
using MayShow;
|
||||
using MayShow.ViewModels;
|
||||
|
||||
namespace ReceiptPDFBuilder;
|
||||
namespace MayShow;
|
||||
|
||||
public partial class App : Application
|
||||
{
|
||||
@@ -4,7 +4,7 @@ using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
|
||||
namespace ReceiptPDFBuilder.Helpers
|
||||
namespace MayShow.Helpers
|
||||
{
|
||||
|
||||
// https://docs.microsoft.com/en-us/dotnet/api/system.componentmodel.inotifypropertychanged?view=netframework-4.7.2
|
||||
@@ -1,11 +1,14 @@
|
||||
|
||||
using System;
|
||||
|
||||
namespace ReceiptPDFBuilder.Helpers;
|
||||
namespace MayShow.Helpers;
|
||||
|
||||
class Constants
|
||||
{
|
||||
public static string AppVersion = "1.0.0";
|
||||
public static string AppVersion = "1.2.0";
|
||||
|
||||
public static string[] AllowedFileExtensionPatterns = [ "*.png", "*.jpg", "*.jpeg", "*.gif", "*.bmp", "*.webp", "*.pdf", "*.heic", ];
|
||||
public static string[] AllowedFileExtensionsNoStar = [ "png", "jpg", "jpeg", "gif", "bmp", "webp", "pdf", "heic", ];
|
||||
|
||||
public static string[] GetQuotes()
|
||||
{
|
||||
@@ -17,7 +20,7 @@ class Constants
|
||||
return [
|
||||
"When in the crucible of life, always remember to take your friends with you.",
|
||||
"What do you call a paper airplane that won't fly? Stationary.",
|
||||
"I used to be addicted to Dad jokes, but now I'm all groan up.",
|
||||
"I used to be addicted to dad jokes, but now I'm all groan up.",
|
||||
"I used to have a phobia about speed bumps. But I'm slowly getting over it.",
|
||||
"Be careful trusting stairs. They're always up to something.",
|
||||
"What do you call a fish with no eye? A fsh",
|
||||
@@ -3,10 +3,10 @@ using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.VisualTree;
|
||||
using Avalonia.Xaml.Interactions.DragAndDrop;
|
||||
using ReceiptPDFBuilder.Models;
|
||||
using ReceiptPDFBuilder.ViewModels;
|
||||
using MayShow.Models;
|
||||
using MayShow.ViewModels;
|
||||
|
||||
namespace ReceiptPDFBuilder.Helpers;
|
||||
namespace MayShow.Helpers;
|
||||
|
||||
class DataGridDropHandler : BaseDataGridDropHandler<ReportFile>
|
||||
{
|
||||
@@ -2,7 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace ReceiptPDFBuilders.Helpers;
|
||||
namespace MayShows.Helpers;
|
||||
|
||||
public static class ThreadSafeRandom
|
||||
{
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using ReceiptPDFBuilder.Models;
|
||||
using MayShow.Models;
|
||||
|
||||
namespace ReceiptPDFBuilder.Helpers;
|
||||
namespace MayShow.Helpers;
|
||||
|
||||
[JsonSerializable(typeof(Settings))]
|
||||
[JsonSerializable(typeof(ReportFile))]
|
||||
@@ -5,7 +5,7 @@ using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace ReceiptPDFBuilders.Helpers;
|
||||
namespace MayShows.Helpers;
|
||||
|
||||
class Utilities
|
||||
{
|
||||
@@ -0,0 +1,8 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MayShow.Interfaces;
|
||||
|
||||
interface ICanCheckShutdown
|
||||
{
|
||||
Task<bool> CheckIsSafeToShutdown();
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using MayShow.ViewModels;
|
||||
|
||||
namespace MayShow.Interfaces;
|
||||
|
||||
interface IChangeViewModel
|
||||
{
|
||||
void PushViewModel(BaseViewModel model);
|
||||
void PopViewModel();
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace MayShow.Interfaces;
|
||||
|
||||
interface ITopLevelGrabber
|
||||
{
|
||||
TopLevel GetTopLevel();
|
||||
}
|
||||
@@ -3,17 +3,18 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="ReceiptPDFBuilder.MainWindow"
|
||||
Title="Receipt PDF Builder"
|
||||
xmlns:vm="clr-namespace:ReceiptPDFBuilder.ViewModels"
|
||||
x:Class="MayShow.MainWindow"
|
||||
Title="MayShow"
|
||||
xmlns:vm="clr-namespace:MayShow.ViewModels"
|
||||
xmlns:dialogHost="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
|
||||
x:DataType="vm:MainWindowViewModel"
|
||||
Width="800"
|
||||
MinWidth="400"
|
||||
MinWidth="550"
|
||||
Height="650"
|
||||
MinHeight="450">
|
||||
MinHeight="550">
|
||||
<dialogHost:DialogHost CloseOnClickAway="False"
|
||||
Identifier="DialogHost">
|
||||
Identifier="DialogHost"
|
||||
x:Name="WindowDialogHost">
|
||||
<dialogHost:DialogHost.DialogContent>
|
||||
<StackPanel/>
|
||||
</dialogHost:DialogHost.DialogContent>
|
||||
@@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using DialogHostAvalonia;
|
||||
using MayShow.Interfaces;
|
||||
using MayShow.ViewModels;
|
||||
|
||||
namespace MayShow;
|
||||
|
||||
public partial class MainWindow : Window, ITopLevelGrabber
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = new MainWindowViewModel(this);
|
||||
|
||||
Closing += WindowIsClosing;
|
||||
|
||||
var lifetime = Application.Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime;
|
||||
// lifetime?.ShutdownRequested += ApplicationIsShuttingDown;
|
||||
}
|
||||
|
||||
private async void WindowIsClosing(object? sender, WindowClosingEventArgs e)
|
||||
{
|
||||
e.Cancel = true; // async -> need to cancel immediately
|
||||
if (await CheckIfClosePossible())
|
||||
{
|
||||
Closing -= WindowIsClosing;
|
||||
var lifetime = Application.Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime;
|
||||
lifetime?.ShutdownRequested -= ApplicationIsShuttingDown;
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
private async void ApplicationIsShuttingDown(object? sender, ShutdownRequestedEventArgs e)
|
||||
{
|
||||
e.Cancel = true; // async -> need to cancel immediately
|
||||
if (await CheckIfClosePossible())
|
||||
{
|
||||
Closing -= WindowIsClosing;
|
||||
var lifetime = Application.Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime;
|
||||
lifetime?.ShutdownRequested -= ApplicationIsShuttingDown;
|
||||
lifetime?.TryShutdown();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> CheckIfClosePossible()
|
||||
{
|
||||
var canShutdown = true;
|
||||
if (DataContext is MainWindowViewModel mwvm)
|
||||
{
|
||||
if (mwvm is ICanCheckShutdown canCheck)
|
||||
{
|
||||
canShutdown = await canCheck.CheckIsSafeToShutdown();
|
||||
}
|
||||
// only checking 1 level but for this app that is OK
|
||||
if (canShutdown && mwvm.CurrentViewModel is ICanCheckShutdown currModel)
|
||||
{
|
||||
try
|
||||
{
|
||||
canShutdown = await currModel.CheckIsSafeToShutdown();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
canShutdown = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return canShutdown;
|
||||
}
|
||||
|
||||
public TopLevel GetTopLevel()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 401 KiB After Width: | Height: | Size: 401 KiB |
@@ -11,9 +11,9 @@
|
||||
<SelfContained>true</SelfContained>
|
||||
<PublishTrimmed>true</PublishTrimmed>
|
||||
<PublishAot>true</PublishAot>
|
||||
<AssemblyName>ReceiptPDFBuilder</AssemblyName>
|
||||
<AssemblyVersion>1.0.0</AssemblyVersion> <!-- Also update Constants version -->
|
||||
<ApplicationIcon>ReceiptPDFBuilder-icon.ico</ApplicationIcon>
|
||||
<AssemblyName>MayShow</AssemblyName>
|
||||
<AssemblyVersion>1.2.0</AssemblyVersion> <!-- Also update Constants version -->
|
||||
<ApplicationIcon>MayShow-icon.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<TrimmerRootAssembly Include="PdfSharp" />
|
||||
@@ -47,9 +47,13 @@
|
||||
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="PDFsharp-MigraDoc" Version="6.2.3" />
|
||||
<PackageReference Include="Magick.NET-Q16-AnyCPU" Version="14.10.2" />
|
||||
<PackageReference Include="Magick.NET-Q8-AnyCPU" Version="14.10.3" />
|
||||
<PackageReference Include="Deadpikle.AvaloniaProgressRing" Version="0.10.11-preview20251127001" />
|
||||
<PackageReference Include="DialogHost.Avalonia" Version="0.10.4" />
|
||||
<PackageReference Include="Xaml.Behaviors.Interactions.DragAndDrop.DataGrid" Version="11.3.9.5" />
|
||||
<PackageReference Include="OpenCvSharp4" Version="4.13.0.20260226" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition=" '$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::OSX)))' ">
|
||||
<PackageReference Include="OpenCvSharp4.runtime.osx.10.15-universal" Version="4.7.0.20230224" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -5,9 +5,9 @@ using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using ReceiptPDFBuilder.Helpers;
|
||||
using MayShow.Helpers;
|
||||
|
||||
namespace ReceiptPDFBuilder.Models;
|
||||
namespace MayShow.Models;
|
||||
|
||||
class PDFReport : ChangeNotifier
|
||||
{
|
||||
@@ -1,9 +1,9 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.Json.Serialization;
|
||||
using ReceiptPDFBuilder.Helpers;
|
||||
using MayShow.Helpers;
|
||||
|
||||
namespace ReceiptPDFBuilder.Models;
|
||||
namespace MayShow.Models;
|
||||
|
||||
class ReportFile : ChangeNotifier
|
||||
{
|
||||
@@ -60,7 +60,13 @@ class ReportFile : ChangeNotifier
|
||||
public string FilePath
|
||||
{
|
||||
get => _filePath;
|
||||
set { _filePath = value; NotifyPropertyChanged(); }
|
||||
set
|
||||
{
|
||||
_filePath = value;
|
||||
NotifyPropertyChanged();
|
||||
NotifyPropertyChanged(nameof(FileName));
|
||||
NotifyPropertyChanged(nameof(IsFileFoundOnDisk));
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
@@ -68,4 +74,10 @@ class ReportFile : ChangeNotifier
|
||||
{
|
||||
get => Path.GetFileName(_filePath);
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsFileFoundOnDisk
|
||||
{
|
||||
get => File.Exists(FilePath);
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,10 @@ using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using ReceiptPDFBuilder.Helpers;
|
||||
using ReceiptPDFBuilders.Helpers;
|
||||
using MayShow.Helpers;
|
||||
using MayShows.Helpers;
|
||||
|
||||
namespace ReceiptPDFBuilder.Models;
|
||||
namespace MayShow.Models;
|
||||
|
||||
class Settings : ChangeNotifier
|
||||
{
|
||||
@@ -34,7 +34,7 @@ class Settings : ChangeNotifier
|
||||
{
|
||||
var path = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||
"ReceiptPDFBuilder"
|
||||
"MayShow"
|
||||
);
|
||||
if (!Directory.Exists(path))
|
||||
{
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace MayShow.Models;
|
||||
|
||||
enum ShutdownCheckOptions
|
||||
{
|
||||
SaveAndShutdown,
|
||||
NoSaveShutdown,
|
||||
CancelShutdown,
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using Avalonia;
|
||||
using System;
|
||||
|
||||
namespace ReceiptPDFBuilder;
|
||||
namespace MayShow;
|
||||
|
||||
class Program
|
||||
{
|
||||
@@ -15,10 +15,10 @@ using MigraDoc.Rendering;
|
||||
using PdfSharp.Fonts;
|
||||
using PdfSharp.Pdf.IO;
|
||||
using PdfSharp.Snippets.Font;
|
||||
using ReceiptPDFBuilder.Interfaces;
|
||||
using ReceiptPDFBuilder.Models;
|
||||
using MayShow.Interfaces;
|
||||
using MayShow.Models;
|
||||
|
||||
namespace ReceiptPDFBuilder.ViewModels;
|
||||
namespace MayShow.ViewModels;
|
||||
|
||||
class AboutViewModel
|
||||
{
|
||||
@@ -1,11 +1,11 @@
|
||||
using Avalonia.Controls;
|
||||
using ReceiptPDFBuilder.Helpers;
|
||||
using ReceiptPDFBuilder.Interfaces;
|
||||
using MayShow.Helpers;
|
||||
using MayShow.Interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace ReceiptPDFBuilder.ViewModels
|
||||
namespace MayShow.ViewModels
|
||||
{
|
||||
class BaseViewModel : ChangeNotifier
|
||||
{
|
||||
@@ -0,0 +1,52 @@
|
||||
#nullable enable
|
||||
|
||||
using DialogHostAvalonia;
|
||||
using MayShow.Helpers;
|
||||
|
||||
namespace MayShow.ViewModels;
|
||||
|
||||
class ConfirmViewModel
|
||||
{
|
||||
private string _title;
|
||||
private string _message;
|
||||
private string _confirmTitle;
|
||||
private string _declineTitle;
|
||||
|
||||
public ConfirmViewModel(string title, string message, string confirmTitle = "Yes", string declineTitle = "No")
|
||||
{
|
||||
_title = title;
|
||||
_message = message;
|
||||
_confirmTitle = confirmTitle;
|
||||
_declineTitle = declineTitle;
|
||||
}
|
||||
|
||||
public string Title
|
||||
{
|
||||
get => _title;
|
||||
}
|
||||
|
||||
public string Message
|
||||
{
|
||||
get => _message;
|
||||
}
|
||||
|
||||
public string ConfirmTitle
|
||||
{
|
||||
get => _confirmTitle;
|
||||
}
|
||||
|
||||
public string DeclineTitle
|
||||
{
|
||||
get => _declineTitle;
|
||||
}
|
||||
|
||||
public void Confirm()
|
||||
{
|
||||
DialogHost.Close("DialogHost", true);
|
||||
}
|
||||
|
||||
public void Decline()
|
||||
{
|
||||
DialogHost.Close("DialogHost", false);
|
||||
}
|
||||
}
|
||||
@@ -15,10 +15,10 @@ using MigraDoc.Rendering;
|
||||
using PdfSharp.Fonts;
|
||||
using PdfSharp.Pdf.IO;
|
||||
using PdfSharp.Snippets.Font;
|
||||
using ReceiptPDFBuilder.Interfaces;
|
||||
using ReceiptPDFBuilder.Models;
|
||||
using MayShow.Interfaces;
|
||||
using MayShow.Models;
|
||||
|
||||
namespace ReceiptPDFBuilder.ViewModels;
|
||||
namespace MayShow.ViewModels;
|
||||
|
||||
class EditFileViewModel : BaseViewModel
|
||||
{
|
||||
@@ -16,15 +16,20 @@ using MigraDoc.Rendering;
|
||||
using PdfSharp.Fonts;
|
||||
using PdfSharp.Pdf.IO;
|
||||
using PdfSharp.Snippets.Font;
|
||||
using ReceiptPDFBuilder.Helpers;
|
||||
using ReceiptPDFBuilder.Interfaces;
|
||||
using ReceiptPDFBuilder.Models;
|
||||
using ReceiptPDFBuilders.Helpers;
|
||||
using MayShow.Helpers;
|
||||
using MayShow.Interfaces;
|
||||
using MayShow.Models;
|
||||
using MayShows.Helpers;
|
||||
using OpenCvSharp;
|
||||
using System.Reflection.Metadata.Ecma335;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace ReceiptPDFBuilder.ViewModels;
|
||||
namespace MayShow.ViewModels;
|
||||
|
||||
class MainViewModel : BaseViewModel, IFontResolver
|
||||
class MainViewModel : BaseViewModel, IFontResolver, ICanCheckShutdown
|
||||
{
|
||||
private bool _isPerformingInitialLoad;
|
||||
private string _processDir;
|
||||
private bool _isCreatingPDF;
|
||||
private string _createPDFLog;
|
||||
@@ -36,21 +41,24 @@ class MainViewModel : BaseViewModel, IFontResolver
|
||||
|
||||
private Settings _settings;
|
||||
|
||||
private bool _hasUnsavedWork;
|
||||
|
||||
public MainViewModel(IChangeViewModel viewModelChanger) : base(viewModelChanger)
|
||||
{
|
||||
_isPerformingInitialLoad = true;
|
||||
_processDir = Path.GetDirectoryName(Environment.ProcessPath) ?? "";
|
||||
Console.WriteLine("Process is running from: {0}", _processDir);
|
||||
_isCreatingPDF = false;
|
||||
var quotes = Constants.GetQuotes();
|
||||
Random random = new Random();
|
||||
var quoteIndex = random.Next(0, quotes.Length);
|
||||
_createPDFLog = "----- Receipt PDF Builder v" + Constants.AppVersion + "------" + Environment.NewLine;
|
||||
_createPDFLog = "----- MayShow v" + Constants.AppVersion + " ------" + Environment.NewLine;
|
||||
_createPDFLog += quotes[quoteIndex] + Environment.NewLine;
|
||||
_createPDFLog += "---------------------------------------" + Environment.NewLine;
|
||||
_createPDFLog += "Ready to create PDF!";
|
||||
_createPDFLog += "Loaded and ready to create report!" + Environment.NewLine;
|
||||
_createPDFLog += "Please copy and send this Program Log when reporting any issues with the software.";
|
||||
_workingFolder = "";
|
||||
_reportFiles = new ObservableCollection<ReportFile>();
|
||||
_reportFiles.CollectionChanged += ( sender, e ) => { NotifyPropertyChanged(nameof(IsCreatePDFButtonEnabled)); };
|
||||
ReportFiles = _reportFiles = new ObservableCollection<ReportFile>();
|
||||
_reportTitle = "";
|
||||
_lastGeneratedTime = null;
|
||||
_settings = Settings.LoadSettings();
|
||||
@@ -59,12 +67,24 @@ class MainViewModel : BaseViewModel, IFontResolver
|
||||
LogInfo("Loading data at last used path of {0}", _settings.LastUsedPath);
|
||||
ScanFolder(_settings.LastUsedPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogInfo("Choose a receipt folder to begin...");
|
||||
}
|
||||
HasUnsavedWork = false;
|
||||
_isPerformingInitialLoad = false;
|
||||
}
|
||||
|
||||
public string ReportTitle
|
||||
{
|
||||
get => _reportTitle;
|
||||
set { _reportTitle = value; NotifyPropertyChanged(); NotifyPropertyChanged(nameof(IsTitleBoxVisible)); }
|
||||
set
|
||||
{
|
||||
_reportTitle = value;
|
||||
NotifyPropertyChanged();
|
||||
NotifyPropertyChanged(nameof(IsTitleBoxVisible));
|
||||
NotifyPropertyChanged(nameof(CanAddItem));
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsTitleBoxVisible
|
||||
@@ -72,10 +92,22 @@ class MainViewModel : BaseViewModel, IFontResolver
|
||||
get => !string.IsNullOrWhiteSpace(_workingFolder);
|
||||
}
|
||||
|
||||
public bool CanAddItem
|
||||
{
|
||||
get => IsTitleBoxVisible && !IsCreatingPDF;
|
||||
}
|
||||
|
||||
public bool IsCreatingPDF
|
||||
{
|
||||
get => _isCreatingPDF;
|
||||
set { _isCreatingPDF = value; NotifyPropertyChanged(); NotifyPropertyChanged(nameof(IsCreatePDFButtonEnabled)); }
|
||||
set
|
||||
{
|
||||
_isCreatingPDF = value;
|
||||
NotifyPropertyChanged();
|
||||
NotifyPropertyChanged(nameof(IsCreatePDFButtonEnabled));
|
||||
NotifyPropertyChanged(nameof(HasWorkingFolderAndNotMakingPDF));
|
||||
NotifyPropertyChanged(nameof(CanAddItem));
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsCreatePDFButtonEnabled
|
||||
@@ -83,16 +115,57 @@ class MainViewModel : BaseViewModel, IFontResolver
|
||||
get => !_isCreatingPDF && _reportFiles.Count > 0;
|
||||
}
|
||||
|
||||
public bool HasWorkingFolder
|
||||
{
|
||||
get => !string.IsNullOrWhiteSpace(_workingFolder) && Directory.Exists(_workingFolder);
|
||||
}
|
||||
|
||||
public bool HasWorkingFolderAndNotMakingPDF
|
||||
{
|
||||
get => !string.IsNullOrWhiteSpace(_workingFolder) && Directory.Exists(_workingFolder) && !_isCreatingPDF;
|
||||
}
|
||||
|
||||
public string WorkingFolder
|
||||
{
|
||||
get => _workingFolder;
|
||||
set
|
||||
{
|
||||
_workingFolder = value;
|
||||
NotifyPropertyChanged();
|
||||
NotifyPropertyChanged(nameof(HasWorkingFolder));
|
||||
NotifyPropertyChanged(nameof(HasWorkingFolderAndNotMakingPDF));
|
||||
}
|
||||
}
|
||||
|
||||
public string CreatePDFLog
|
||||
{
|
||||
get => _createPDFLog;
|
||||
set { _createPDFLog = value; NotifyPropertyChanged(); }
|
||||
}
|
||||
|
||||
public bool HasUnsavedWork
|
||||
{
|
||||
get => _hasUnsavedWork;
|
||||
set
|
||||
{
|
||||
_hasUnsavedWork = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<ReportFile> ReportFiles
|
||||
{
|
||||
get => _reportFiles;
|
||||
set { _reportFiles = value; NotifyPropertyChanged(); }
|
||||
set
|
||||
{
|
||||
_reportFiles = value;
|
||||
NotifyPropertyChanged();
|
||||
_reportFiles.CollectionChanged += ( sender, e ) =>
|
||||
{
|
||||
NotifyPropertyChanged(nameof(IsCreatePDFButtonEnabled));
|
||||
HasUnsavedWork = true;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private void LogInfo(string message, params object[]? arguments)
|
||||
@@ -121,6 +194,7 @@ class MainViewModel : BaseViewModel, IFontResolver
|
||||
_settings.LastUsedPath = folder.Path.LocalPath;
|
||||
await _settings.SaveSettingsAsync();
|
||||
ResortPDFItemsByDate();
|
||||
HasUnsavedWork = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -129,8 +203,9 @@ class MainViewModel : BaseViewModel, IFontResolver
|
||||
{
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
_workingFolder = path;
|
||||
WorkingFolder = path;
|
||||
NotifyPropertyChanged(nameof(IsTitleBoxVisible));
|
||||
NotifyPropertyChanged(nameof(CanAddItem));
|
||||
var reportFilePath = Path.Combine(path, GetReportSavedDataFileName());
|
||||
var successfullyLoadedPriorReport = false;
|
||||
if (File.Exists(reportFilePath))
|
||||
@@ -143,7 +218,7 @@ class MainViewModel : BaseViewModel, IFontResolver
|
||||
{
|
||||
ReportFiles = new ObservableCollection<ReportFile>(report.Files);
|
||||
ReportTitle = report.Title;
|
||||
_workingFolder = report.BaseFolder;
|
||||
WorkingFolder = report.BaseFolder;
|
||||
_lastGeneratedTime = report.LastGenerated ?? null;
|
||||
LogInfo("Reloaded report last saved at {0}", report.LastSaved);
|
||||
successfullyLoadedPriorReport = true;
|
||||
@@ -152,14 +227,25 @@ class MainViewModel : BaseViewModel, IFontResolver
|
||||
if (!successfullyLoadedPriorReport)
|
||||
{
|
||||
// Scan folder for files and display in DataGrid
|
||||
ReportFiles.Clear();
|
||||
ReportTitle = "";
|
||||
var filePaths = Directory.GetFiles(_workingFolder);
|
||||
foreach (var filePath in filePaths)
|
||||
{
|
||||
AddFileBasedOnPath(filePath);
|
||||
}
|
||||
ResortPDFItemsByDate();
|
||||
if (!_isPerformingInitialLoad)
|
||||
{
|
||||
ResortPDFItemsByDate();
|
||||
}
|
||||
HasUnsavedWork = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogInfo("Error: The directory {0} does not exist. Please select another folder.", path);
|
||||
}
|
||||
NotifyPropertyChanged(nameof(IsCreatePDFButtonEnabled));
|
||||
}
|
||||
|
||||
public void ShowAbout()
|
||||
@@ -171,13 +257,14 @@ class MainViewModel : BaseViewModel, IFontResolver
|
||||
|
||||
public async void RemoveFileImpl(ReportFile file)
|
||||
{
|
||||
var result = await DialogHost.Show(new WarningDeleteItemModel(file));
|
||||
var result = await DialogHost.Show(new WarningDeleteItemViewModel(file));
|
||||
if (result != null && (bool)result)
|
||||
{
|
||||
var idx = ReportFiles.IndexOf(file);
|
||||
if (idx != -1)
|
||||
{
|
||||
ReportFiles.RemoveAt(idx);
|
||||
HasUnsavedWork = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -193,20 +280,10 @@ class MainViewModel : BaseViewModel, IFontResolver
|
||||
file.Title = updatedData.Title;
|
||||
file.ReceiptDateTime = updatedData.ReceiptDateTime;
|
||||
file.Notes = updatedData.Notes;
|
||||
HasUnsavedWork = true;
|
||||
}
|
||||
}
|
||||
|
||||
private string[] GetAllowedFileExtensionPatterns()
|
||||
{
|
||||
return [ "*.png", "*.jpg", "*.jpeg", "*.gif", "*.bmp", "*.webp", "*.pdf", "*.heic", ];
|
||||
}
|
||||
|
||||
private string[] GetAllowedFileExtensionPatternsWithoutStar()
|
||||
{
|
||||
var list = GetAllowedFileExtensionPatterns();
|
||||
return list.Select(x => x.Replace("*.", "")).ToArray();
|
||||
}
|
||||
|
||||
public async void AddItem()
|
||||
{
|
||||
var topLevel = TopLevelGrabber?.GetTopLevel();
|
||||
@@ -219,7 +296,7 @@ class MainViewModel : BaseViewModel, IFontResolver
|
||||
FileTypeFilter = [
|
||||
new FilePickerFileType("All Types")
|
||||
{
|
||||
Patterns = GetAllowedFileExtensionPatterns(),
|
||||
Patterns = Constants.AllowedFileExtensionPatterns,
|
||||
AppleUniformTypeIdentifiers = [ "public.image", "com.adobe.pdf", "public.heic" ],
|
||||
MimeTypes = [ "image/*", "application/pdf", "image/heic" ]
|
||||
},
|
||||
@@ -249,11 +326,11 @@ class MainViewModel : BaseViewModel, IFontResolver
|
||||
if (!string.IsNullOrWhiteSpace(filePath) && File.Exists(filePath) && !filePath.EndsWith(".DS_Store"))
|
||||
{
|
||||
// make sure extensions are OK
|
||||
var fileExtensions = GetAllowedFileExtensionPatternsWithoutStar();
|
||||
var fileExtensions = Constants.AllowedFileExtensionsNoStar;
|
||||
var didMatch = false;
|
||||
foreach (var fileExtension in fileExtensions)
|
||||
{
|
||||
if (filePath.EndsWith("." + fileExtension))
|
||||
if (filePath.ToLower().EndsWith("." + fileExtension.ToLower()))
|
||||
{
|
||||
didMatch = true;
|
||||
break;
|
||||
@@ -261,7 +338,10 @@ class MainViewModel : BaseViewModel, IFontResolver
|
||||
}
|
||||
if (!didMatch)
|
||||
{
|
||||
LogInfo("File {0} did not match allowed file extension types, so it was not added.", filePath);
|
||||
if (!filePath.EndsWith(GetReportSavedDataFileName()))
|
||||
{
|
||||
LogInfo("File {0} did not match allowed file extension types, so it was not added.", filePath);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -273,6 +353,54 @@ class MainViewModel : BaseViewModel, IFontResolver
|
||||
Notes = "",
|
||||
FilePath = filePath,
|
||||
});
|
||||
HasUnsavedWork = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async void RemoveAllItems()
|
||||
{
|
||||
var result = await DialogHost.Show(new ConfirmViewModel("Warning!", "Are you sure you want to remove all items from this report?", "Remove All Items", "Cancel"));
|
||||
if (result != null && (bool)result)
|
||||
{
|
||||
ReportFiles.Clear();
|
||||
HasUnsavedWork = true;
|
||||
NotifyPropertyChanged(nameof(IsCreatePDFButtonEnabled));
|
||||
}
|
||||
}
|
||||
|
||||
public void LocateFile(object f) => LocateFileImpl((ReportFile) f);
|
||||
public async void LocateFileImpl(ReportFile reportFile)
|
||||
{
|
||||
var topLevel = TopLevelGrabber?.GetTopLevel();
|
||||
if (topLevel is not null)
|
||||
{
|
||||
var files = await topLevel.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions()
|
||||
{
|
||||
Title = "Choose image or PDF file...",
|
||||
AllowMultiple = false,
|
||||
FileTypeFilter = [
|
||||
new FilePickerFileType("All Types")
|
||||
{
|
||||
Patterns = Constants.AllowedFileExtensionPatterns,
|
||||
AppleUniformTypeIdentifiers = [ "public.image", "com.adobe.pdf", "public.heic" ],
|
||||
MimeTypes = [ "image/*", "application/pdf", "image/heic" ]
|
||||
},
|
||||
FilePickerFileTypes.ImageAll,
|
||||
new FilePickerFileType("HEIC Images")
|
||||
{
|
||||
Patterns = [ "*.heic" ],
|
||||
AppleUniformTypeIdentifiers = [ "public.heic" ],
|
||||
MimeTypes = [ "image/heic" ]
|
||||
},
|
||||
FilePickerFileTypes.Pdf,
|
||||
],
|
||||
});
|
||||
if (files.Count > 0)
|
||||
{
|
||||
var file = files[0];
|
||||
reportFile.FilePath = file.Path.LocalPath;
|
||||
HasUnsavedWork = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -311,6 +439,7 @@ class MainViewModel : BaseViewModel, IFontResolver
|
||||
{
|
||||
LogInfo("Sorting report files list...");
|
||||
ReportFiles = new ObservableCollection<ReportFile>(ReportFiles.OrderBy(x => x.ReceiptDateTime));
|
||||
HasUnsavedWork = true;
|
||||
}
|
||||
|
||||
public async void BuildPDF()
|
||||
@@ -345,7 +474,7 @@ class MainViewModel : BaseViewModel, IFontResolver
|
||||
}
|
||||
}
|
||||
|
||||
public async void SaveInterimReportInfo()
|
||||
public async Task SaveInterimReportInfo()
|
||||
{
|
||||
var report = new PDFReport()
|
||||
{
|
||||
@@ -369,6 +498,7 @@ class MainViewModel : BaseViewModel, IFontResolver
|
||||
var savePath = Path.Combine(_workingFolder, GetReportSavedDataFileName());
|
||||
await File.WriteAllTextAsync(savePath, json);
|
||||
LogInfo("Saved report information to {0}", savePath);
|
||||
HasUnsavedWork = false;
|
||||
}
|
||||
|
||||
private async Task CreateAndSaveReportObjectAfterReportCreation()
|
||||
@@ -390,6 +520,190 @@ class MainViewModel : BaseViewModel, IFontResolver
|
||||
return "report_data.json";
|
||||
}
|
||||
|
||||
public void TestReceiptFinding(object f) => TestReceiptFindingImpl((ReportFile)f);
|
||||
|
||||
|
||||
private Mat? b_algor(ReportFile file)
|
||||
{
|
||||
using var orig = new Mat(file.FilePath);
|
||||
using var src = new Mat(file.FilePath, ImreadModes.Grayscale);
|
||||
if (src.Empty())
|
||||
{
|
||||
LogInfo("File was empty?");
|
||||
return null;
|
||||
}
|
||||
using var blur = orig.GaussianBlur(new Size(5, 5), 0.0);
|
||||
using var kernel = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(9,9));
|
||||
using var dilated = blur.Dilate(kernel, anchor: null, iterations: 4);
|
||||
using var edges = dilated.Canny(100, 200, 3);
|
||||
using var heirarchy = new Mat();
|
||||
Cv2.FindContours(edges, out Mat[] contours, heirarchy, RetrievalModes.External, ContourApproximationModes.ApproxSimple);
|
||||
using var orgWithContours = new Mat();
|
||||
orig.CopyTo(orgWithContours);
|
||||
Cv2.DrawContours(orgWithContours, contours, -1, Scalar.Cyan, 3);
|
||||
// largest contours
|
||||
var largestContours = contours.OrderByDescending(x => x.ContourArea()).Take(1).ToArray();
|
||||
var orgWithLargestContours = new Mat();
|
||||
orig.CopyTo(orgWithLargestContours);
|
||||
Cv2.DrawContours(orgWithLargestContours, largestContours, -1, Scalar.Cyan, 5);
|
||||
|
||||
using (new OpenCvSharp.Window("blur", blur))
|
||||
using (new OpenCvSharp.Window("dilated", dilated))
|
||||
using (new OpenCvSharp.Window("edges", edges))
|
||||
using (new OpenCvSharp.Window("contours", orgWithContours))
|
||||
// using (new OpenCvSharp.Window("w biggest contours", orgWithBiggestContours))
|
||||
{
|
||||
Cv2.WaitKey();
|
||||
}
|
||||
|
||||
return orgWithLargestContours;
|
||||
}
|
||||
|
||||
private void TestReceiptFindingImpl(ReportFile file)
|
||||
{
|
||||
LogInfo("Running receipt edge detection on file at path {0} with OpenCV {1}...", file.FilePath, Cv2.GetVersionString() ?? "");
|
||||
using var orig = new Mat(file.FilePath);
|
||||
using var src = new Mat(file.FilePath, ImreadModes.Grayscale);
|
||||
using var dst = new Mat();
|
||||
using var wContours = new Mat();
|
||||
if (src.Empty())
|
||||
{
|
||||
LogInfo("File was empty?");
|
||||
return;
|
||||
}
|
||||
var threshold = src.Threshold(90,255, ThresholdTypes.Binary);
|
||||
|
||||
var blur = orig.GaussianBlur(new Size(3.0, 3.0), 0.0);
|
||||
var kernel = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(9,9));
|
||||
var dilated = blur.Dilate(kernel, null, 1);
|
||||
var edges = dilated.Canny(50, 200, 3);
|
||||
//# Detect all contours in Canny-edged image
|
||||
using var heirarchy = new Mat();
|
||||
Cv2.FindContours(threshold, out Mat[] contours, heirarchy, RetrievalModes.External, ContourApproximationModes.ApproxSimple);
|
||||
var orgWithContours = new Mat();
|
||||
orig.CopyTo(orgWithContours);
|
||||
Cv2.DrawContours(orgWithContours, contours, -1, Scalar.Cyan, 3);
|
||||
// # find full contours
|
||||
|
||||
var poly = new List<Mat>();
|
||||
foreach (var contour in contours)
|
||||
{
|
||||
var hull = contour.ConvexHull();
|
||||
poly.Add(hull.ApproxPolyDP(0.01 * Cv2.ArcLength(hull, true), false));
|
||||
}
|
||||
Console.WriteLine("How many? {0}", poly.Count);
|
||||
var orgWithAllPly = new Mat();
|
||||
orig.CopyTo(orgWithAllPly);
|
||||
Cv2.DrawContours(orgWithAllPly, poly, -1, Scalar.Red, 3);
|
||||
|
||||
var largestPoly = poly.OrderByDescending(x => x.ContourArea()).Take(10).ToArray();
|
||||
var orgWithLargestContoursPly = new Mat();
|
||||
orig.CopyTo(orgWithLargestContoursPly);
|
||||
Cv2.DrawContours(orgWithLargestContoursPly, largestPoly, -1, Scalar.Red, 5);
|
||||
// using (new OpenCvSharp.Window("poly", orgWithAllPly))
|
||||
// using (new OpenCvSharp.Window("pol2y", orgWithLargestContoursPly))
|
||||
// {
|
||||
// Cv2.WaitKey();
|
||||
// }
|
||||
//
|
||||
|
||||
// # Get 10 largest contours
|
||||
Console.WriteLine(contours);
|
||||
var orgWithLargestContours = new Mat();
|
||||
orig.CopyTo(orgWithLargestContours);
|
||||
var largestContours = contours.OrderByDescending(x => x.ContourArea()).Take(10).ToArray();
|
||||
Cv2.DrawContours(orgWithLargestContours, largestContours, -1, Scalar.Cyan, 5);
|
||||
//
|
||||
Mat approximate_contour(Mat contour)
|
||||
{
|
||||
var perimeter = Cv2.ArcLength(contour, true);
|
||||
return contour.ApproxPolyDP(0.02 * perimeter, true);
|
||||
}
|
||||
Mat? get_receipt_counter(Mat[] contours)
|
||||
{
|
||||
var poly = new List<Mat>();
|
||||
foreach (var contour in contours)
|
||||
{
|
||||
// var hull = contour.ConvexHull();
|
||||
// var arcLength = hull.ArcLength(true);
|
||||
// var x = contour.ApproxPolyDP(0.01 * arcLength, true);
|
||||
// poly.Add(x);
|
||||
|
||||
|
||||
|
||||
var approx = approximate_contour(contour);
|
||||
if (approx.Total() == 4)
|
||||
{
|
||||
return approx;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
var highestY = 0;
|
||||
var lowestY = 9999;
|
||||
var highestX = 0;
|
||||
var lowestX = 9999;
|
||||
foreach (var contour in largestContours)
|
||||
{
|
||||
Console.WriteLine("Rows: {0}", contour.Rows);
|
||||
if (contour.Rows > 10) // eliminate small things?
|
||||
{
|
||||
for (var i = 0; i < contour.Rows; i++)
|
||||
{
|
||||
var pt = contour.At<Point>(i);
|
||||
if (pt.X < lowestX)
|
||||
{
|
||||
lowestX = pt.X;
|
||||
}
|
||||
if (pt.X > highestX)
|
||||
{
|
||||
highestX = pt.X;
|
||||
}
|
||||
if (pt.Y < lowestY)
|
||||
{
|
||||
lowestY = pt.Y;
|
||||
}
|
||||
if (pt.Y > highestY)
|
||||
{
|
||||
highestY = pt.Y;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Console.WriteLine("Low X: {0}, High X: {1}", lowestX, highestX);
|
||||
Console.WriteLine("Low Y: {0}, High Y: {1}", lowestY, highestY);
|
||||
var rect = new Rect(lowestX, lowestY, Math.Abs(highestX - lowestX), Math.Abs(highestY - lowestY));
|
||||
Console.WriteLine(rect);
|
||||
using var crop = new Mat(orig, rect);
|
||||
|
||||
using (new OpenCvSharp.Window("w largest contours", orgWithLargestContours))
|
||||
using (new OpenCvSharp.Window("crop", crop))
|
||||
using (new OpenCvSharp.Window("crop_b", b_algor(file)))
|
||||
// using (new OpenCvSharp.Window("w biggest contours", orgWithBiggestContours))
|
||||
{
|
||||
Cv2.WaitKey();
|
||||
}
|
||||
return;
|
||||
|
||||
|
||||
var largest = get_receipt_counter(largestContours);
|
||||
Console.WriteLine(largest);
|
||||
var orgWithBiggestContours = new Mat();
|
||||
orig.CopyTo(orgWithBiggestContours);
|
||||
Cv2.DrawContours(orgWithBiggestContours, [largest], -1, Scalar.Cyan, 3);
|
||||
////
|
||||
using (new OpenCvSharp.Window("src image", src))
|
||||
// using (new OpenCvSharp.Window("blur image", blur))
|
||||
// using (new OpenCvSharp.Window("dilated image", dilated))
|
||||
using (new OpenCvSharp.Window("orig with all contours", orgWithContours))
|
||||
using (new OpenCvSharp.Window("w largest contours", orgWithLargestContours))
|
||||
// using (new OpenCvSharp.Window("w biggest contours", orgWithBiggestContours))
|
||||
{
|
||||
Cv2.WaitKey();
|
||||
}
|
||||
}
|
||||
|
||||
public byte[]? GetFont(string faceName)
|
||||
{
|
||||
LogInfo(string.Format("Loading font {0}", faceName));
|
||||
@@ -480,6 +794,12 @@ class MainViewModel : BaseViewModel, IFontResolver
|
||||
reportTitlePar.Format.Font.Name = "Noto Sans JP"; // has english letters in it, too
|
||||
reportTitlePar.AddText(ReportTitle);
|
||||
//
|
||||
var convertedDir = Path.Combine(folderPath, "converted");
|
||||
if (!Directory.Exists(convertedDir))
|
||||
{
|
||||
Directory.CreateDirectory(convertedDir);
|
||||
}
|
||||
//
|
||||
GlobalFontSettings.FontResolver = this;
|
||||
GlobalFontSettings.FallbackFontResolver = new FailsafeFontResolver();
|
||||
var hasAddedData = false;
|
||||
@@ -507,7 +827,7 @@ class MainViewModel : BaseViewModel, IFontResolver
|
||||
imageTitlePar.Format.Font.Size = 12;
|
||||
imageTitlePar.Format.Font.Bold = true;
|
||||
imageTitlePar.Format.Font.Name = "Noto Sans JP"; // has english letters in it, too
|
||||
imageTitlePar.AddText(file.Title);
|
||||
imageTitlePar.AddText(string.IsNullOrWhiteSpace(file.Title) ? file.FileName : file.Title);
|
||||
var receiptDatePar = section.AddParagraph();
|
||||
receiptDatePar.Format.Alignment = ParagraphAlignment.Center;
|
||||
receiptDatePar.Format.Font.Size = 12;
|
||||
@@ -525,35 +845,70 @@ class MainViewModel : BaseViewModel, IFontResolver
|
||||
}
|
||||
section.AddParagraph(); // add empty line for spacing
|
||||
// now add the image
|
||||
var isPDF = fileName.EndsWith(".pdf");
|
||||
var lowerName = fileName.ToLower();
|
||||
var isPDF = lowerName.EndsWith(".pdf");
|
||||
// convert heic, webp, or png to JPEG for size and ease of use
|
||||
// (and probably compat reasons too, though I haven't tested that...)
|
||||
var lowerName = fileName.ToLower();
|
||||
var isHEIC = lowerName.EndsWith(".heic");
|
||||
var isWebp = lowerName.EndsWith(".webp");
|
||||
var isPNG = lowerName.EndsWith(".png");
|
||||
if (isHEIC || isWebp || isPNG)
|
||||
var info = new FileInfo(file.FilePath);
|
||||
uint loadedImageWidth = 0;
|
||||
uint loadedImageHeight = 0;
|
||||
if (!isPDF)
|
||||
{
|
||||
var convertedDir = Path.Combine(folderPath, "converted");
|
||||
if (!Directory.Exists(convertedDir))
|
||||
{
|
||||
Directory.CreateDirectory(convertedDir);
|
||||
}
|
||||
var info = new FileInfo(file.FilePath);
|
||||
using var mImage = new MagickImage(info.FullName);
|
||||
// Save frame as jpg
|
||||
var outputPath = Path.Combine(convertedDir, info.Name + ".jpg");
|
||||
mImage.Quality = 80;
|
||||
mImage.Scale((uint)Math.Floor(mImage.Width * 0.5), (uint)Math.Floor(mImage.Height * 0.5));
|
||||
await mImage.WriteAsync(outputPath);
|
||||
filePath = Path.Combine("Converted", info.Name + ".jpg");
|
||||
LogInfo(string.Format("Converted image to JPEG; fileName is now {0}", file.FilePath));
|
||||
loadedImageWidth = mImage.Width;
|
||||
loadedImageHeight = mImage.Height;
|
||||
var convertedOutputPath = Path.Combine(convertedDir, info.Name + ".jpg");
|
||||
var didAdjust = false;
|
||||
LogInfo("Image orientation of {0} is {1}", fileName, mImage.Orientation);
|
||||
if (mImage.Orientation != OrientationType.TopLeft)
|
||||
{
|
||||
LogInfo("Auto-adjusted image orientation of {0}", fileName);
|
||||
mImage.AutoOrient();
|
||||
didAdjust = true;
|
||||
}
|
||||
// perform needed image manipulations
|
||||
if (isHEIC || isWebp || isPNG || (!isPDF && info.Length > 1.5 * 1024 * 1024 /* 1.5 MB */))
|
||||
{
|
||||
// Save image as jpg
|
||||
mImage.Quality = 80;
|
||||
if (mImage.Width >= 400 || mImage.Height >= 400)
|
||||
{
|
||||
loadedImageWidth = (uint)Math.Floor(mImage.Width * 0.5);
|
||||
loadedImageHeight = (uint)Math.Floor(mImage.Height * 0.5);
|
||||
mImage.Scale(loadedImageWidth, loadedImageHeight);
|
||||
LogInfo("Image {2} scaled to {0}x{1}", loadedImageWidth, loadedImageHeight, fileName);
|
||||
}
|
||||
didAdjust = true;
|
||||
LogInfo("Converted image {0} to JPEG", fileName);
|
||||
}
|
||||
else
|
||||
{
|
||||
// load height/width
|
||||
loadedImageWidth = mImage.Width;
|
||||
loadedImageHeight = mImage.Height;
|
||||
}
|
||||
if (didAdjust)
|
||||
{
|
||||
await mImage.WriteAsync(convertedOutputPath);
|
||||
filePath = Path.Combine("Converted", info.Name + ".jpg");
|
||||
LogInfo(string.Format("Saved adjusted image to JPEG; fileName is now {0}", file.FilePath));
|
||||
}
|
||||
}
|
||||
var paragraph = section.AddParagraph();
|
||||
paragraph.Format.Alignment = ParagraphAlignment.Center;
|
||||
var image = paragraph.AddImage(filePath);
|
||||
image.LockAspectRatio = true;
|
||||
image.Width = imageWidth; // can't be too wide now...not sure why...maybe due to margins...
|
||||
if (!isPDF && loadedImageHeight > 600)
|
||||
{
|
||||
image.Height = 550; // make sure it will fit on one page
|
||||
}
|
||||
else
|
||||
{
|
||||
image.Width = imageWidth; // can't be too wide now...not sure why...maybe due to margins...
|
||||
}
|
||||
LogInfo(string.Format("Added image: {0} ({1})", file.Title, filePath));
|
||||
if (isPDF)
|
||||
{
|
||||
@@ -580,14 +935,43 @@ class MainViewModel : BaseViewModel, IFontResolver
|
||||
Document = pdfDoc,
|
||||
WorkingDirectory = folderPath
|
||||
};
|
||||
LogInfo("Rendering document...");
|
||||
LogInfo("Rendering document to PDF file...");
|
||||
pdfRenderer.RenderDocument();
|
||||
string outputPDFFileName = Path.Join(folderPath, outputFileName);
|
||||
LogInfo("Saving document to disk...");
|
||||
LogInfo("Saving PDF document to disk...");
|
||||
pdfRenderer.PdfDocument.Save(outputPDFFileName);
|
||||
LogInfo("Saved PDF output to: " + outputPDFFileName);
|
||||
LogInfo("Finished saving PDF output to: " + outputPDFFileName);
|
||||
await CreateAndSaveReportObjectAfterReportCreation();
|
||||
OpenFolderForFileInFileViewer(outputPDFFileName);
|
||||
IsCreatingPDF = false;
|
||||
}
|
||||
|
||||
public async Task<bool> CheckIsSafeToShutdown()
|
||||
{
|
||||
if (!HasUnsavedWork || string.IsNullOrWhiteSpace(WorkingFolder))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = await DialogHost.Show(new ShutdownCheckViewModel());
|
||||
if (result != null && result is ShutdownCheckOptions opt)
|
||||
{
|
||||
if (opt == ShutdownCheckOptions.SaveAndShutdown)
|
||||
{
|
||||
await SaveInterimReportInfo();
|
||||
return true;
|
||||
}
|
||||
else if (opt == ShutdownCheckOptions.NoSaveShutdown)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (opt == ShutdownCheckOptions.CancelShutdown)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
using ReceiptPDFBuilder.Helpers;
|
||||
using ReceiptPDFBuilder.Interfaces;
|
||||
using MayShow.Helpers;
|
||||
using MayShow.Interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace ReceiptPDFBuilder.ViewModels
|
||||
namespace MayShow.ViewModels
|
||||
{
|
||||
class MainWindowViewModel : ChangeNotifier, IChangeViewModel
|
||||
{
|
||||
@@ -0,0 +1,29 @@
|
||||
#nullable enable
|
||||
|
||||
using DialogHostAvalonia;
|
||||
using MayShow.Models;
|
||||
|
||||
namespace MayShow.ViewModels;
|
||||
|
||||
class ShutdownCheckViewModel
|
||||
{
|
||||
|
||||
public ShutdownCheckViewModel()
|
||||
{
|
||||
}
|
||||
|
||||
public void SaveAndShutdown()
|
||||
{
|
||||
DialogHost.Close("DialogHost", ShutdownCheckOptions.SaveAndShutdown);
|
||||
}
|
||||
|
||||
public void DoNotSaveAndShutdown()
|
||||
{
|
||||
DialogHost.Close("DialogHost", ShutdownCheckOptions.NoSaveShutdown);
|
||||
}
|
||||
|
||||
public void CancelShutdown()
|
||||
{
|
||||
DialogHost.Close("DialogHost", ShutdownCheckOptions.CancelShutdown);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
using DialogHostAvalonia;
|
||||
using ReceiptPDFBuilder.Helpers;
|
||||
using ReceiptPDFBuilder.Models;
|
||||
using MayShow.Helpers;
|
||||
using MayShow.Models;
|
||||
|
||||
namespace ReceiptPDFBuilder.ViewModels
|
||||
namespace MayShow.ViewModels
|
||||
{
|
||||
class WarningDeleteItemModel : ChangeNotifier
|
||||
class WarningDeleteItemViewModel : ChangeNotifier
|
||||
{
|
||||
ReportFile _file;
|
||||
|
||||
public WarningDeleteItemModel(ReportFile file)
|
||||
public WarningDeleteItemViewModel(ReportFile file)
|
||||
{
|
||||
_file = file;
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
using DialogHostAvalonia;
|
||||
|
||||
namespace ReceiptPDFBuilder.ViewModels;
|
||||
namespace MayShow.ViewModels;
|
||||
|
||||
class WarningViewModel
|
||||
{
|
||||
@@ -3,26 +3,30 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="ReceiptPDFBuilder.Views.AboutView"
|
||||
xmlns:models="clr-namespace:ReceiptPDFBuilder.Models"
|
||||
xmlns:vm="clr-namespace:ReceiptPDFBuilder.ViewModels"
|
||||
x:Class="MayShow.Views.AboutView"
|
||||
xmlns:models="clr-namespace:MayShow.Models"
|
||||
xmlns:vm="clr-namespace:MayShow.ViewModels"
|
||||
xmlns:dialogHost="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
|
||||
x:DataType="vm:AboutViewModel">
|
||||
x:DataType="vm:AboutViewModel"
|
||||
MaxWidth="450">
|
||||
<StackPanel Orientation="Vertical"
|
||||
Spacing="4">
|
||||
<TextBlock Text="Receipt PDF Builder"
|
||||
<TextBlock Text="MayShow"
|
||||
HorizontalAlignment="Center"
|
||||
TextWrapping="Wrap"
|
||||
FontSize="18"
|
||||
FontWeight="Bold"/>
|
||||
<TextBlock Text="Receipt PDF Builder was built by MB for A in 2026. May the quacking of ducks always be in your favor. Thanks for using!"
|
||||
MaxWidth="300"
|
||||
<TextBlock Text="MayShow (an intentional misspelling of 明証, pronounced may-shō, a Japanese word meaning proof, evidence, or corroboration) is a PDF report creation tool. It was built by MB for A in 2026. May the quacking of ducks always be in your favor. Thanks for using our software!"
|
||||
TextWrapping="Wrap"
|
||||
FontSize="14"/>
|
||||
<TextBlock Text="App icon made using https://gauger.me/fonticon/ with FontAwesome icon 'file-invoice-dollar' and the macOS software Icon Composer."
|
||||
MaxWidth="300"
|
||||
TextWrapping="Wrap"
|
||||
FontSize="14"/>
|
||||
<TextBlock Text="Copyright 2026 - Quickity Quack Productions"
|
||||
TextWrapping="Wrap"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="0,4,0,4"
|
||||
FontSize="12"/>
|
||||
<Button Command="{Binding Close}"
|
||||
Classes="accent"
|
||||
Content="Close"
|
||||
@@ -3,7 +3,7 @@ using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace ReceiptPDFBuilder.Views
|
||||
namespace MayShow.Views
|
||||
{
|
||||
public partial class AboutView : UserControl
|
||||
{
|
||||
@@ -0,0 +1,35 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="MayShow.Views.ConfirmView"
|
||||
xmlns:models="clr-namespace:MayShow.Models"
|
||||
xmlns:vm="clr-namespace:MayShow.ViewModels"
|
||||
x:DataType="vm:ConfirmViewModel">
|
||||
<StackPanel Orientation="Vertical"
|
||||
Spacing="4">
|
||||
<TextBlock TextAlignment="Center"
|
||||
FontWeight="Bold"
|
||||
FontSize="18"
|
||||
Text="{Binding Title}"/>
|
||||
<TextBlock TextAlignment="Center"
|
||||
FontWeight="Bold"
|
||||
TextWrapping="Wrap"
|
||||
FontSize="14"
|
||||
MaxWidth="350"
|
||||
Text="{Binding Message}"/>
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Spacing="12"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="4">
|
||||
<Button Command="{Binding Decline}"
|
||||
Content="{Binding DeclineTitle}"
|
||||
HorizontalAlignment="Right"/>
|
||||
<Button Command="{Binding Confirm}"
|
||||
Classes="accent"
|
||||
Content="{Binding ConfirmTitle}"
|
||||
HorizontalAlignment="Right"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace MayShow.Views;
|
||||
|
||||
public partial class ConfirmView : UserControl
|
||||
{
|
||||
public ConfirmView()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,12 @@
|
||||
mc:Ignorable="d"
|
||||
d:DesignWidth="800"
|
||||
d:DesignHeight="450"
|
||||
x:Class="ReceiptPDFBuilder.Views.EditFile"
|
||||
xmlns:models="clr-namespace:ReceiptPDFBuilder.Models"
|
||||
xmlns:vm="clr-namespace:ReceiptPDFBuilder.ViewModels"
|
||||
x:Class="MayShow.Views.EditFile"
|
||||
xmlns:models="clr-namespace:MayShow.Models"
|
||||
xmlns:vm="clr-namespace:MayShow.ViewModels"
|
||||
xmlns:dialogHost="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
|
||||
x:DataType="vm:EditFileViewModel">
|
||||
x:DataType="vm:EditFileViewModel"
|
||||
MaxWidth="350">
|
||||
<ScrollViewer AllowAutoHide="False">
|
||||
<StackPanel Orientation="Vertical"
|
||||
Spacing="4"
|
||||
@@ -33,7 +34,8 @@
|
||||
<Label Content="Receipt Date" />
|
||||
<Calendar SelectionMode="SingleDate"
|
||||
SelectedDate="{Binding ClonedFile.ReceiptDateTime}"
|
||||
DisplayDate="{Binding ClonedFile.ReceiptDateTime}" />
|
||||
DisplayDate="{Binding ClonedFile.ReceiptDateTime}"
|
||||
IsTodayHighlighted="False" />
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Spacing="12"
|
||||
Margin="0,4,0,0"
|
||||
@@ -3,7 +3,7 @@ using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace ReceiptPDFBuilder.Views
|
||||
namespace MayShow.Views
|
||||
{
|
||||
public partial class EditFile : UserControl
|
||||
{
|
||||
@@ -3,10 +3,11 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="ReceiptPDFBuilder.Views.MainView"
|
||||
xmlns:helpers="clr-namespace:ReceiptPDFBuilder.Helpers"
|
||||
xmlns:models="clr-namespace:ReceiptPDFBuilder.Models"
|
||||
xmlns:vm="clr-namespace:ReceiptPDFBuilder.ViewModels"
|
||||
x:Class="MayShow.Views.MainView"
|
||||
xmlns:helpers="clr-namespace:MayShow.Helpers"
|
||||
xmlns:models="clr-namespace:MayShow.Models"
|
||||
xmlns:views="clr-namespace:MayShow.Views"
|
||||
xmlns:vm="clr-namespace:MayShow.ViewModels"
|
||||
xmlns:progRing="clr-namespace:AvaloniaProgressRing;assembly=AvaloniaProgressRing"
|
||||
x:DataType="vm:MainViewModel">
|
||||
<Grid ColumnDefinitions="*"
|
||||
@@ -20,19 +21,35 @@
|
||||
</Button>
|
||||
<StackPanel Orientation="Vertical"
|
||||
Spacing="2">
|
||||
<Label Content="Easy Receipt Folder -> PDF Builder"
|
||||
<Label Content="MayShow: Report Builder"
|
||||
FontSize="20"
|
||||
FontWeight="Bold"
|
||||
HorizontalAlignment="Center"/>
|
||||
<Button Content="Choose Receipt Folder"
|
||||
Command="{Binding ChooseFolder}"
|
||||
IsEnabled="{Binding !IsCreatingPDF}" />
|
||||
<Grid ColumnDefinitions="Auto, *"
|
||||
Margin="4,0,0,0">
|
||||
<Button Content="Choose Receipt Folder"
|
||||
Command="{Binding ChooseFolder}"
|
||||
IsEnabled="{Binding !IsCreatingPDF}"
|
||||
Grid.Column="0" />
|
||||
<TextBlock Text="{Binding WorkingFolder}"
|
||||
VerticalAlignment="Center"
|
||||
TextWrapping="NoWrap"
|
||||
Margin="4,0,4,0"
|
||||
TextTrimming="PrefixCharacterEllipsis"
|
||||
Grid.Column="1"/>
|
||||
</Grid>
|
||||
<Label Content="Report Title"
|
||||
IsVisible="{Binding IsTitleBoxVisible}" />
|
||||
<TextBox Text="{Binding ReportTitle}"
|
||||
IsVisible="{Binding IsTitleBoxVisible}"
|
||||
Watermark="Receipts December 2024"
|
||||
Margin="2,0,2,4"/>
|
||||
Margin="2,0,2,4"
|
||||
Classes="clearButton"
|
||||
Name="TitleTextBox">
|
||||
<TextBox.KeyBindings>
|
||||
<KeyBinding Command="{Binding $parent[views:MainView].UnfocusTextbox}" Gesture="Enter" />
|
||||
</TextBox.KeyBindings>
|
||||
</TextBox>
|
||||
</StackPanel>
|
||||
<DataGrid x:Name="FilesGrid"
|
||||
Classes="DragAndDrop ItemsDragAndDrop"
|
||||
@@ -61,19 +78,60 @@
|
||||
<Style Selector="TextBox">
|
||||
<Setter Property="TextWrapping" Value="NoWrap" />
|
||||
</Style>
|
||||
<Style Selector="ToolTip">
|
||||
<Setter Property="MaxWidth" Value="1000" />
|
||||
</Style>
|
||||
</DataGrid.Styles>
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="Title"
|
||||
Binding="{Binding Title}"
|
||||
IsReadOnly="False"
|
||||
Width="*"/>
|
||||
<DataGridTemplateColumn Header="Title"
|
||||
IsReadOnly="False"
|
||||
Width="*">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Grid ColumnDefinitions="Auto, *">
|
||||
<Button Command="{Binding $parent[DataGrid].((vm:MainViewModel)DataContext).LocateFile}"
|
||||
CommandParameter="{Binding}"
|
||||
IsVisible="{Binding !IsFileFoundOnDisk}"
|
||||
Margin="2"
|
||||
Content=""
|
||||
VerticalContentAlignment="Center"
|
||||
Background="Transparent"
|
||||
Grid.Column="0"
|
||||
FontFamily="{StaticResource FontAwesomeSolid}"
|
||||
ToolTip.Tip="File not found; click to locate..."
|
||||
IsEnabled="{Binding !$parent[DataGrid].((vm:MainViewModel)DataContext).IsCreatingPDF}"/>
|
||||
<TextBlock Text="{Binding Title}"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
TextWrapping="NoWrap"
|
||||
ToolTip.Tip="{Binding Title}"
|
||||
Grid.Column="1"
|
||||
Margin="8,0,4,0"
|
||||
VerticalAlignment="Center"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate>
|
||||
<DataTemplate DataType="models:ReportFile">
|
||||
<TextBox Text="{Binding Title}"
|
||||
Watermark="Title"
|
||||
ToolTip.Tip="{Binding Title}"
|
||||
Classes="clearButton">
|
||||
<TextBox.KeyBindings>
|
||||
<KeyBinding Command="{Binding $parent[views:MainView].UnfocusTextbox}" Gesture="Enter" />
|
||||
</TextBox.KeyBindings>
|
||||
</TextBox>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellEditingTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<DataGridTemplateColumn Header="Receipt Date"
|
||||
IsReadOnly="False"
|
||||
Width="*">
|
||||
IsReadOnly="False"
|
||||
Width="125">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Label Content="{Binding ReceiptDate}"
|
||||
VerticalAlignment="Center"/>
|
||||
VerticalAlignment="Center"
|
||||
Margin="8,0,8,0"
|
||||
HorizontalAlignment="Left"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate>
|
||||
@@ -83,10 +141,19 @@
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellEditingTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<DataGridTextColumn Header="File Name"
|
||||
Binding="{Binding FileName}"
|
||||
IsReadOnly="True"
|
||||
Width="*" />
|
||||
<DataGridTemplateColumn Header="File Name"
|
||||
IsReadOnly="True"
|
||||
Width="*">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding FileName}"
|
||||
VerticalAlignment="Center"
|
||||
ToolTip.Tip="{Binding FileName}"
|
||||
Margin="8,0,8,0"
|
||||
HorizontalAlignment="Left"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<DataGridTemplateColumn Header=""
|
||||
IsReadOnly="True"
|
||||
Width="*">
|
||||
@@ -100,7 +167,7 @@
|
||||
Margin="2"
|
||||
IsEnabled="{Binding !$parent[DataGrid].((vm:MainViewModel)DataContext).IsCreatingPDF}">
|
||||
<Button.Content>
|
||||
<TextBlock><Run Text="" FontFamily="{StaticResource FontAwesomeSolid}"/> Edit</TextBlock>
|
||||
<TextBlock><Run Text="" FontFamily="{StaticResource FontAwesomeSolid}"/> Edit</TextBlock>
|
||||
</Button.Content>
|
||||
</Button>
|
||||
<Button Command="{Binding $parent[DataGrid].((vm:MainViewModel)DataContext).RemoveFile}"
|
||||
@@ -119,11 +186,18 @@
|
||||
</DataGrid.Columns>
|
||||
<DataGrid.RowDetailsTemplate>
|
||||
<DataTemplate x:DataType="models:ReportFile">
|
||||
<TextBlock TextWrapping="Wrap" Margin="4,4,4,0">
|
||||
<Run FontWeight="Bold" Text="File Path"/>: <Run Text="{Binding FilePath}"/>
|
||||
<LineBreak/>
|
||||
<Grid ColumnDefinitions="*"
|
||||
RowDefinitions="Auto, Auto, Auto">
|
||||
<TextBlock TextWrapping="Wrap" Margin="4" Grid.Row="0">
|
||||
<Run FontWeight="Bold" Text="File Path"/>: <Run Text="{Binding FilePath}"/>
|
||||
</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap" Margin="4" Grid.Row="1">
|
||||
<Run FontWeight="Bold" Text="Notes"/>: <Run Text="{Binding Notes}"/>
|
||||
</TextBlock>
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Spacing="8">
|
||||
Spacing="8"
|
||||
Margin="4"
|
||||
Grid.Row="2">
|
||||
<Button Command="{Binding $parent[DataGrid].((vm:MainViewModel)DataContext).OpenFileLocation}"
|
||||
CommandParameter="{Binding}">
|
||||
<Button.Content>
|
||||
@@ -136,10 +210,14 @@
|
||||
<TextBlock FontSize="12"><Run Text="" FontFamily="{StaticResource FontAwesomeSolid}"/> Open File</TextBlock>
|
||||
</Button.Content>
|
||||
</Button>
|
||||
<Button Command="{Binding $parent[DataGrid].((vm:MainViewModel)DataContext).TestReceiptFinding}"
|
||||
CommandParameter="{Binding}">
|
||||
<Button.Content>
|
||||
<TextBlock FontSize="12"><Run Text="" FontFamily="{StaticResource FontAwesomeSolid}"/> TEST RECEIPT FIND</TextBlock>
|
||||
</Button.Content>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<LineBreak/>
|
||||
<Run FontWeight="Bold" Text="Notes"/>: <Run Text="{Binding Notes}"/>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</DataGrid.RowDetailsTemplate>
|
||||
</DataGrid>
|
||||
@@ -151,27 +229,31 @@
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Spacing="4">
|
||||
<Button Command="{Binding AddItem}"
|
||||
IsEnabled="{Binding IsCreatePDFButtonEnabled}">
|
||||
<TextBlock><Run Text="+" FontFamily="{StaticResource FontAwesomeSolid}"/> Add Item</TextBlock>
|
||||
IsEnabled="{Binding CanAddItem}">
|
||||
<TextBlock><Run Text="+" FontFamily="{StaticResource FontAwesomeSolid}"/> Add Item(s)</TextBlock>
|
||||
</Button>
|
||||
<Button Command="{Binding SaveInterimReportInfo}"
|
||||
IsEnabled="{Binding IsCreatePDFButtonEnabled}">
|
||||
<TextBlock><Run Text="" FontFamily="{StaticResource FontAwesomeSolid}"/> Save Report Info</TextBlock>
|
||||
<Button Command="{Binding RemoveAllItems}"
|
||||
IsEnabled="{Binding IsCreatePDFButtonEnabled}"
|
||||
Classes="Danger">
|
||||
<TextBlock><Run Text="" FontFamily="{StaticResource FontAwesomeSolid}"/> Remove All Items</TextBlock>
|
||||
</Button>
|
||||
<Button Command="{Binding ResortPDFItemsByDate}"
|
||||
IsEnabled="{Binding IsCreatePDFButtonEnabled}">
|
||||
<TextBlock><Run Text="" FontFamily="{StaticResource FontAwesomeSolid}"/> Re-sort PDF Items</TextBlock>
|
||||
</Button>
|
||||
<Button Command="{Binding BuildPDF}"
|
||||
Classes="accent"
|
||||
IsEnabled="{Binding IsCreatePDFButtonEnabled}">
|
||||
<TextBlock><Run Text="" FontFamily="{StaticResource FontAwesomeSolid}"/> Create Report PDF</TextBlock>
|
||||
<Button Command="{Binding SaveInterimReportInfo}"
|
||||
IsEnabled="{Binding HasWorkingFolderAndNotMakingPDF}">
|
||||
<TextBlock><Run Text="" FontFamily="{StaticResource FontAwesomeSolid}"/> Save Report Info</TextBlock>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal"
|
||||
IsVisible="{Binding IsCreatingPDF}"
|
||||
Spacing="6"
|
||||
HorizontalAlignment="Center">
|
||||
<Button Command="{Binding BuildPDF}"
|
||||
Classes="accent"
|
||||
IsEnabled="{Binding IsCreatePDFButtonEnabled}">
|
||||
<TextBlock><Run Text="" FontFamily="{StaticResource FontAwesomeSolid}"/> Create Report PDF</TextBlock>
|
||||
</Button>
|
||||
<Label Content="Creating PDF..."
|
||||
IsVisible="{Binding IsCreatingPDF}"
|
||||
VerticalAlignment="Center"/>
|
||||
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using MayShow.ViewModels;
|
||||
|
||||
namespace MayShow.Views
|
||||
{
|
||||
public partial class MainView : UserControl
|
||||
{
|
||||
public MainView()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
LogBlock.PropertyChanged += LogBlock_PropertyChanged;
|
||||
FilesGrid.CellEditEnded += FileCellEditEnded;
|
||||
}
|
||||
|
||||
private void LogBlock_PropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.Property.ToString() == "Text")
|
||||
{
|
||||
LogScrollView.ScrollToEnd();
|
||||
}
|
||||
}
|
||||
|
||||
public void UnfocusTextbox()
|
||||
{
|
||||
var topLevel = TopLevel.GetTopLevel(this);
|
||||
topLevel?.FocusManager?.ClearFocus();
|
||||
if (DataContext is MainViewModel mvm)
|
||||
{
|
||||
mvm?.HasUnsavedWork = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void FileCellEditEnded(object? sender, DataGridCellEditEndedEventArgs args)
|
||||
{
|
||||
if (args.EditAction == DataGridEditAction.Commit && DataContext is MainViewModel mvm)
|
||||
{
|
||||
mvm?.HasUnsavedWork = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="MayShow.Views.ShutdownCheckView"
|
||||
xmlns:models="clr-namespace:MayShow.Models"
|
||||
xmlns:vm="clr-namespace:MayShow.ViewModels"
|
||||
xmlns:dialogHost="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
|
||||
x:DataType="vm:ShutdownCheckViewModel">
|
||||
<StackPanel Orientation="Vertical"
|
||||
Spacing="4">
|
||||
<TextBlock TextAlignment="Center"
|
||||
FontWeight="Bold"
|
||||
FontSize="18"
|
||||
Text="Warning: You have unsaved report data!"/>
|
||||
<TextBlock TextAlignment="Center"
|
||||
FontWeight="Bold"
|
||||
TextWrapping="Wrap"
|
||||
FontSize="14"
|
||||
Text="Do you want to save your data before the program is closed?"/>
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Spacing="8">
|
||||
<Button Command="{Binding SaveAndShutdown}"
|
||||
Classes="accent"
|
||||
Content="Save Data and Close"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="0,4,0,4"/>
|
||||
<Button Command="{Binding DoNotSaveAndShutdown}"
|
||||
Content="Do NOT Save Data and Close"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="0,4,0,4"/>
|
||||
<Button Command="{Binding CancelShutdown}"
|
||||
Content="Cancel Program Shutdown"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="0,4,0,4"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace MayShow.Views;
|
||||
|
||||
public partial class ShutdownCheckView : UserControl
|
||||
{
|
||||
public ShutdownCheckView()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,11 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="ReceiptPDFBuilder.Views.WarningDeleteItem"
|
||||
xmlns:models="clr-namespace:ReceiptPDFBuilder.Models"
|
||||
xmlns:vm="clr-namespace:ReceiptPDFBuilder.ViewModels"
|
||||
x:Class="MayShow.Views.WarningDeleteItem"
|
||||
xmlns:models="clr-namespace:MayShow.Models"
|
||||
xmlns:vm="clr-namespace:MayShow.ViewModels"
|
||||
xmlns:dialogHost="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
|
||||
x:DataType="vm:WarningDeleteItemModel">
|
||||
x:DataType="vm:WarningDeleteItemViewModel">
|
||||
<StackPanel HorizontalAlignment="Center"
|
||||
Margin="6"
|
||||
Spacing="8">
|
||||
@@ -3,7 +3,7 @@ using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace ReceiptPDFBuilder.Views
|
||||
namespace MayShow.Views
|
||||
{
|
||||
public partial class WarningDeleteItem : UserControl
|
||||
{
|
||||
@@ -3,9 +3,9 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="ReceiptPDFBuilder.Views.WarningView"
|
||||
xmlns:models="clr-namespace:ReceiptPDFBuilder.Models"
|
||||
xmlns:vm="clr-namespace:ReceiptPDFBuilder.ViewModels"
|
||||
x:Class="MayShow.Views.WarningView"
|
||||
xmlns:models="clr-namespace:MayShow.Models"
|
||||
xmlns:vm="clr-namespace:MayShow.ViewModels"
|
||||
xmlns:dialogHost="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
|
||||
x:DataType="vm:WarningViewModel">
|
||||
<StackPanel Orientation="Vertical"
|
||||
@@ -3,7 +3,7 @@ using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace ReceiptPDFBuilder.Views
|
||||
namespace MayShow.Views
|
||||
{
|
||||
public partial class WarningView : UserControl
|
||||
{
|
||||
@@ -3,7 +3,7 @@
|
||||
<!-- This manifest is used on Windows only.
|
||||
Don't remove it as it might cause problems with window transparency and embedded controls.
|
||||
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
|
||||
<assemblyIdentity version="1.0.0.0" name="ReceiptPDFBuilder.Desktop"/>
|
||||
<assemblyIdentity version="1.2.0.0" name="MayShow.Desktop"/>
|
||||
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||