77 Commits

Author SHA1 Message Date
mbabienco 03d2cf30d0 Bump to 1.4.3 2026-04-13 19:19:22 +09:00
mbabienco 0d07bc468c Add OS/comp arch to debug log 2026-04-13 19:19:03 +09:00
mbabienco 80bc33b85e Simplify expression 2026-04-13 19:13:04 +09:00
mbabienco 3482ed7336 Improve watermark for date display 2026-04-13 19:10:33 +09:00
mbabienco 5b0508721f Fix year-month-day ordering 2026-04-13 19:05:54 +09:00
mbabienco 69292b6410 Fix comment version number 2026-04-09 22:37:34 +09:00
mbabienco 4d722c0220 Downgrade Magick.NET for x64 computers 2026-04-09 22:18:01 +09:00
mbabienco 6fa58d3a62 On macOS x64, use x64 Magick.NET package 2026-04-09 16:12:39 +09:00
mbabienco 15038f5feb Bump avalonia version to 11.3.13 2026-04-09 15:45:15 +09:00
mbabienco 3b776e2534 Show all inner exceptions 2026-04-09 15:24:06 +09:00
mbabienco ec99595303 Use report date setting and tweak default 2026-04-08 22:35:07 +09:00
mbabienco 5fd61f1f84 Misc UI tweaks/fixes 2026-04-08 22:29:05 +09:00
mbabienco 23e8b2533a Update DateFormatConverter 2026-04-08 22:18:12 +09:00
mbabienco 4d89e49c96 Use multibinding for receipt date format 2026-04-08 22:16:56 +09:00
mbabienco 58c59dfc0a Fix bad namespace 2026-04-08 22:16:38 +09:00
mbabienco 867f57dcff Bind parent to UserControl, not grid 2026-04-08 21:51:29 +09:00
mbabienco 72abb6cbb3 Use custom date format for CalendarDatePicker 2026-04-08 21:48:36 +09:00
mbabienco 62307e09a1 Add binding member for data grid date format 2026-04-08 21:48:10 +09:00
mbabienco f39b643b00 Add date format pickers (settings not used yet) 2026-04-08 16:09:20 +09:00
mbabienco d90cd1354f Bump version 2026-03-13 08:18:32 +09:00
mbabienco f4dd498d22 Shortcut to view settings dir 2026-03-13 08:14:50 +09:00
mbabienco cd71df8a8e Add yyyy.MM.dd to acceptable formats 2026-03-13 08:10:11 +09:00
mbabienco de621fe9dc Add another file name date parsing format
Also redid code so more formats would be easy
2026-03-13 08:05:11 +09:00
mbabienco a1858443e8 Bump version number 2026-03-12 21:17:27 +09:00
mbabienco f34a7092e4 Resize PDF images too (Docnet rendering) 2026-03-12 21:09:34 +09:00
mbabienco aed85c7555 Use page size and pg items to calc max image size 2026-03-12 20:58:20 +09:00
mbabienco 25f739667d Allow resolving Noto Sans font 2026-03-12 20:56:38 +09:00
mbabienco 1fae3f5341 Redo image resizing
Hack fix guesswork sizing
2026-03-12 20:21:41 +09:00
mbabienco 5dda88521d Add black border around images/pdfs
Makes viewing things with no border/white border much easier.
2026-03-12 19:34:56 +09:00
mbabienco 742ecb148a Simply if statement 2026-03-12 19:07:25 +09:00
mbabienco 4ad4a0852f Bump Magick.NET for vulnerability 2026-03-12 19:02:17 +09:00
mbabienco c44093c5ee Fix wrong About menu title 2026-03-04 21:10:21 +09:00
mbabienco 71f4fda2d4 Remove unneeded TODO comment 2026-03-03 12:54:53 +09:00
mbabienco 478f67aa82 Convert remaining files to file-scoped namespace 2026-03-03 12:54:27 +09:00
mbabienco 1efd9993bf Clean before release in linux build 2026-03-03 11:52:34 +09:00
mbabienco 9dd843ea69 Use process dir to find license file 2026-03-03 11:41:44 +09:00
mbabienco 1132d475b1 Add font license 2026-03-03 11:32:54 +09:00
mbabienco b00ad7669c Copy FontAwesome and LICENSES.txt to output 2026-03-03 11:31:41 +09:00
mbabienco 27aff81016 Fix typo in build-linux.sh 2026-03-03 11:26:39 +09:00
mbabienco 8da9574e2b Bump version number 2026-03-03 11:13:40 +09:00
mbabienco 32228e143f Show licenses in About view
Closes #4
2026-03-03 11:10:56 +09:00
mbabienco 94c8945140 Copy program log to clipboard button
Closes #9
2026-03-03 10:59:29 +09:00
mbabienco 94cf073012 Allow saving report JSON to internal dir
Closes #2
2026-03-03 10:49:06 +09:00
mbabienco abc97eea2b Show error if output dir does not exist 2026-03-03 09:43:47 +09:00
mbabienco 3e91edb457 Downsize MB is now user-configurable
Probably doesn't matter most of the time since we resize down HEIC and PNG anyway...

Closes #7
2026-03-03 09:39:09 +09:00
mbabienco 03da58277e Allow changing output directory
Closes #5
2026-03-03 09:28:03 +09:00
mbabienco 193f57df02 Adjust warning view max width 2026-03-03 09:23:58 +09:00
mbabienco 97edee3ef5 Fix a typo 2026-03-03 09:07:44 +09:00
mbabienco e822963d60 Add version to About screen
Closes #6
2026-03-02 18:35:06 +09:00
mbabienco 03aa1aadc9 Clean up converted directory when done
Closes #1
2026-03-02 18:33:27 +09:00
mbabienco b359c8b15f Use internal data dir for converted files
Closes #3
2026-03-02 18:33:17 +09:00
mbabienco 0f37f189ac Refactor internal data path finding to Utilities 2026-03-02 18:32:44 +09:00
mbabienco 445c7bd353 Remove unneeded using 2026-03-02 18:31:33 +09:00
mbabienco 3be81f136f Update Windows installer script 2026-03-02 18:10:34 +09:00
mbabienco 2f14270379 Improve Linux build script with auto-zip 2026-03-02 17:50:57 +09:00
mbabienco 70e5215ca9 Fix some trimming issues 2026-03-02 17:13:33 +09:00
mbabienco cd5a89e35a Bump version 2026-03-02 16:39:18 +09:00
mbabienco 47fb765239 Sort on load if no prior report 2026-03-02 16:38:32 +09:00
mbabienco 68cf8a4145 Lock aspect ration 2026-03-02 16:34:40 +09:00
mbabienco e80d252b25 Fix image width on PDF 2026-03-02 16:34:27 +09:00
mbabienco fc89854bfc Add settings page for using legacy PDF mode 2026-03-02 16:32:40 +09:00
mbabienco d51017bb73 Allow rendering with Docnet PDF 2026-03-02 16:24:05 +09:00
mbabienco 5f99d0e4eb Fix log message for converted file paths 2026-03-02 14:33:36 +09:00
mbabienco 3720a6a25a Ensure output converted name is always right 2026-03-02 14:30:05 +09:00
mbabienco 6d4e7a7bfe Only open file long enough to read in pages 2026-03-02 14:18:36 +09:00
mbabienco be1fc5ec18 Bump MigraDoc version 2026-03-02 14:16:07 +09:00
mbabienco baaf6f8e2b Swap to Magick.NET-Q8 for faster processing 2026-02-27 17:36:34 +09:00
mbabienco bb6cdf0abf Tweak where height/width loaded/set 2026-02-25 09:33:44 +09:00
mbabienco 3c754dc196 Refine log messages 2026-02-25 09:15:02 +09:00
mbabienco af8cfa31e5 Fix comment 2026-02-25 09:14:28 +09:00
mbabienco f32568c918 Downsize if > 1.5 MB 2026-02-25 07:50:28 +09:00
mbabienco b42da7603b Auto-rotate images based on EXIF data 2026-02-25 07:45:51 +09:00
mbabienco 851772398c If file > 2.5 MB reduce size 2026-02-25 07:37:11 +09:00
mbabienco dfc1c557e0 Refactor extensions to Constants class 2026-02-25 07:35:51 +09:00
mbabienco f927667732 Add remove all button 2026-02-25 07:29:43 +09:00
mbabienco d77f9bab8c Don't show file not added for project json file 2026-02-24 21:03:14 +09:00
mbabienco d508222901 Update AppScreenshot.png 2026-02-24 21:02:16 +09:00
35 changed files with 2215 additions and 350 deletions
+29
View File
@@ -1,3 +1,32 @@
magick "2026-01-29 — \$210 — WORK PERMIT.pdf" -background white -alpha background -alpha off -density 288 -resize 25% page-%03d.jpg
https://github.com/dlemstra/Magick.NET/blob/main/docs/ConvertPDF.md
https://stackoverflow.com/questions/65089839/add-an-external-pdf-page-to-pdfsharp-migradoc
https://github.com/mephraim/ghostscriptsharp
https://stackoverflow.com/a/71485279/3938401
need a PDF viewer that works with annotations
https://github.com/BobLd/PdfPig.Rendering.Skia/ works but quality is low for some reason, I don't know why. probably need a bug report or some option that is hidden.
using (var document = UglyToad.PdfPig.PdfDocument.Open(filePath, UglyToad.PdfPig.Rendering.Skia.SkiaRenderingParsingOptions.Instance))
{
document.AddSkiaPageFactory(); // Same as document.AddPageFactory<SKPicture, SkiaPageFactory>() and document.AddPageFactory<PdfPageSize, PageSizeFactory>()
for (int p = 1; p <= document.NumberOfPages; p++)
{
using var skBitmap = document.GetPageAsSKBitmap(p, 1);
using (var fs = new FileStream(Path.Combine(convertedDir, $"{fileName}_{p}PIG.jpeg"), FileMode.Create))
using (var ms = document.GetPageAsPng(p, 1, 4))
{
MemoryStream memoryStream = new MemoryStream();
skBitmap.Encode(memoryStream, SkiaSharp.SKEncodedImageFormat.Jpeg, 100);
memoryStream.Position = 0L;
ms.WriteTo(fs);
}
}
}
https://github.com/GowenGit/docnet -- may need to fork this and publish our own package if some OS doesn't work
*-add more items *-add more items
*-save last opened folder to settings somewhere *-save last opened folder to settings somewhere
Binary file not shown.

Before

Width:  |  Height:  |  Size: 421 KiB

After

Width:  |  Height:  |  Size: 236 KiB

+4 -2
View File
@@ -3,7 +3,7 @@
; Non-commercial use only ; Non-commercial use only
#define MyAppName "MayShow" #define MyAppName "MayShow"
#define MyAppVersion "1.2.0" #define MyAppVersion "1.4.3"
#define MyAppPublisher "Quickity Quack Productions" #define MyAppPublisher "Quickity Quack Productions"
#define MyAppExeName "MayShow.exe" #define MyAppExeName "MayShow.exe"
@@ -45,7 +45,9 @@ Source: "..\src\bin\Release\net10.0\win-x64\publish\{#MyAppExeName}"; DestDir: "
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\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\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\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\LICENSE"; DestDir: "{app}"; DestName: "PDFium-license.txt"; Flags: ignoreversion
Source: "..\src\bin\Release\net10.0\win-x64\publish\Magick.Native-Q8-x64.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\src\bin\Release\net10.0\win-x64\publish\pdfium.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\src\bin\Release\net10.0\win-x64\publish\Assets\*"; DestDir: "{app}\Assets"; Flags: ignoreversion recursesubdirs createallsubdirs 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 ; NOTE: Don't use "Flags: ignoreversion" on any shared system files
+11 -1
View File
@@ -1,5 +1,6 @@
#!/bin/bash #!/bin/bash
VERSION="1.4.3"
SRC_DIR="src" # user ran script from main folder SRC_DIR="src" # user ran script from main folder
if [ ! -d "$SRC_DIR" ]; then if [ ! -d "$SRC_DIR" ]; then
SRC_DIR= "../src" # try SRC_DIR= "../src" # try
@@ -10,7 +11,16 @@ if [ ! -d "$SRC_DIR" ]; then
fi fi
cd "$SRC_DIR" cd "$SRC_DIR"
echo "Building release for linux-x64..." echo "Building release for linux-x64..."
dotnet clean -c Release
dotnet publish -c Release -r linux-x64 -p:StripSymbols=False -p:PublishAot=False dotnet publish -c Release -r linux-x64 -p:StripSymbols=False -p:PublishAot=False
echo "Zipping up linux-x64..."
cd bin/Release/net10.0/linux-x64/publish
zip -r "../../../../MayShow $VERSION linux-x64.zip" .
cd ../../../../../
# -----
echo "Building release for linux-arm64..." echo "Building release for linux-arm64..."
dotnet clean -c Release
dotnet publish -c Release -r linux-arm64 -p:StripSymbols=False -p:PublishAot=False 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 cd bin/Release/net10.0/linux-arm64/publish
zip -r "../../../../MayShow $VERSION linux-arm64.zip" .
cd ../../../../../
+7 -1
View File
@@ -103,6 +103,12 @@
<DataTemplate DataType="{x:Type viewModels:ShutdownCheckViewModel}"> <DataTemplate DataType="{x:Type viewModels:ShutdownCheckViewModel}">
<views:ShutdownCheckView/> <views:ShutdownCheckView/>
</DataTemplate> </DataTemplate>
<DataTemplate DataType="{x:Type viewModels:ConfirmViewModel}">
<views:ConfirmView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:SettingsViewModel}">
<views:SettingsView/>
</DataTemplate>
</Application.DataTemplates> </Application.DataTemplates>
<Application.Resources> <Application.Resources>
<ResourceDictionary> <ResourceDictionary>
@@ -125,7 +131,7 @@
</Application.Resources> </Application.Resources>
<NativeMenu.Menu> <NativeMenu.Menu>
<NativeMenu> <NativeMenu>
<NativeMenuItem Header="About ReceiptBuilder" Click="AboutOnClick" /> <NativeMenuItem Header="About MayShow" Click="AboutOnClick" />
</NativeMenu> </NativeMenu>
</NativeMenu.Menu> </NativeMenu.Menu>
</Application> </Application>
+785
View File
@@ -0,0 +1,785 @@
--Avalonia--
The MIT License (MIT)
Copyright (c) AvaloniaUI OÜ All Rights Reserved
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:
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.
--PDFsharp-MigraDoc--
Copyright (c) 2001-2026 empira Software GmbH, Troisdorf (Cologne Area), Germany
http://docs.pdfsharp.net
MIT License
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:
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.
--ImageMagick--
Terms and Conditions for Use, Reproduction, and Distribution
The legally binding and authoritative terms and conditions for use, reproduction, and distribution of ImageMagick follow:
Copyright © 1999 ImageMagick Studio LLC, a non-profit organization dedicated to making software imaging solutions freely available.
1. Definitions.
License shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
Licensor shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
Legal Entity shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, control means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
You (or Your) shall mean an individual or Legal Entity exercising permissions granted by this License.
Source form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
Object form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
Work shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
Derivative Works shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
Contribution shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as Not a Contribution.
Contributor shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
You must give any other recipients of the Work or Derivative Works a copy of this License; and
You must cause any modified files to carry prominent notices stating that You changed the files; and
You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an AS IS BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
--Magick.NET--
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
--Deadpikle.AvaloniaProgressRing--
MIT License
Copyright (c) 2020 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:
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.
--DialogHost.Avalonia--
MIT License
Copyright (c) 2021 SKProCH
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:
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.
--Xaml.Behaviors--
The MIT License (MIT)
Copyright (c) Wiesław Šoltés
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:
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.
--Docnet.Core--
MIT License
Copyright (c) 2018 Modestas Petravicius
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:
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.
--SixLabors.ImageSharp--
(Note: MayShow is open source and thus is licensed to use SixLabors.ImageSharp under the Apache License, Version 2.0)
Six Labors Split License
Version 1.0, June 2022
Copyright (c) Six Labors
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications, including but not limited to software source
code, documentation source, and configuration files.
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including
but not limited to compiled object code, generated documentation, and conversions to other media types.
"Work" (or "Works") shall mean any Six Labors software made available under the License, as indicated by a
copyright notice that is included in or attached to the work.
"Direct Package Dependency" shall mean any Work in Source or Object form that is installed directly by You.
"Transitive Package Dependency" shall mean any Work in Object form that is installed indirectly by a third party
dependency unrelated to Six Labors.
2. License
Works in Source or Object form are split licensed and may be licensed under the Apache License, Version 2.0 or a
Six Labors Commercial Use License.
Licenses are granted based upon You meeting the qualified criteria as stated. Once granted,
You must reference the granted license only in all documentation.
Works in Source or Object form are licensed to You under the Apache License, Version 2.0 if.
- You are consuming the Work in for use in software licensed under an Open Source or Source Available license.
- You are consuming the Work as a Transitive Package Dependency.
- You are consuming the Work as a Direct Package Dependency in the capacity of a For-profit company/individual with
less than 1M USD annual gross revenue.
- You are consuming the Work as a Direct Package Dependency in the capacity of a Non-profit organization
or Registered Charity.
For all other scenarios, Works in Source or Object form are licensed to You under the Six Labors Commercial License
which may be purchased by visiting https://sixlabors.com/pricing/.
--FontAwesome--
Fonticons, Inc. (https://fontawesome.com)
--------------------------------------------------------------------------------
Font Awesome Free License
Font Awesome Free is free, open source, and GPL friendly. You can use it for
commercial projects, open source projects, or really almost whatever you want.
Full Font Awesome Free license: https://fontawesome.com/license/free.
--------------------------------------------------------------------------------
# Icons: CC BY 4.0 License (https://creativecommons.org/licenses/by/4.0/)
The Font Awesome Free download is licensed under a Creative Commons
Attribution 4.0 International License and applies to all icons packaged
as SVG and JS file types.
--------------------------------------------------------------------------------
# Fonts: SIL OFL 1.1 License
In the Font Awesome Free download, the SIL OFL license applies to all icons
packaged as web and desktop font files.
Copyright (c) 2026 Fonticons, Inc. (https://fontawesome.com)
with Reserved Font Name: "Font Awesome".
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
SIL OPEN FONT LICENSE
Version 1.1 - 26 February 2007
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting — in part or in whole — any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
--------------------------------------------------------------------------------
# Code: MIT License (https://opensource.org/licenses/MIT)
In the Font Awesome Free download, the MIT license applies to all non-font and
non-icon files.
Copyright 2026 Fonticons, Inc.
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:
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.
--------------------------------------------------------------------------------
# Attribution
Attribution is required by MIT, SIL OFL, and CC BY licenses. Downloaded Font
Awesome Free files already contain embedded comments with sufficient
attribution, so you shouldn't need to do anything additional when using these
files normally.
We've kept attribution comments terse, so we ask that you do not actively work
to remove them from files, especially code. They're a great way for folks to
learn about Font Awesome.
--------------------------------------------------------------------------------
# Brand Icons
All brand icons are trademarks of their respective owners. The use of these
trademarks does not indicate endorsement of the trademark holder by Font
Awesome, nor vice versa. **Please do not use brand logos for any purpose except
to represent the company, product, or service to which they refer.**
--Noto Sans Font--
Copyright 2022 The Noto Project Authors (https://github.com/notofonts/latin-greek-cyrillic)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://openfontlicense.org
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
--Noto Sans JP Font--
Copyright 2014-2021 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://openfontlicense.org
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
+1 -1
View File
@@ -1,5 +1,5 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<AvaloniaVersion>11.3.12</AvaloniaVersion> <AvaloniaVersion>11.3.13</AvaloniaVersion>
</PropertyGroup> </PropertyGroup>
</Project> </Project>
+7 -9
View File
@@ -4,16 +4,14 @@ using System.ComponentModel;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text; using System.Text;
namespace MayShow.Helpers namespace MayShow.Helpers;
{
// https://docs.microsoft.com/en-us/dotnet/api/system.componentmodel.inotifypropertychanged?view=netframework-4.7.2 // https://docs.microsoft.com/en-us/dotnet/api/system.componentmodel.inotifypropertychanged?view=netframework-4.7.2
class ChangeNotifier : INotifyPropertyChanged class ChangeNotifier : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{ {
public event PropertyChangedEventHandler? PropertyChanged; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
} }
} }
+24 -2
View File
@@ -1,11 +1,33 @@
using System; using System.Collections.Generic;
using MayShow.Models;
namespace MayShow.Helpers; namespace MayShow.Helpers;
class Constants class Constants
{ {
public static string AppVersion = "1.2.0"; public static string AppVersion = "1.4.3";
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 ReportSavedDataFileName = "report_data.json";
public static List<DateDisplayFormat> GetDateDisplayFormats()
{
return [
new DateDisplayFormat("Month/Day/Year", "4/5/2026", "M/d/yyyy"),
new DateDisplayFormat("Year-Month-Day", "2026-04-05", "yyyy-MM-dd"),
new DateDisplayFormat("Month Day, Year", "April 5, 2026", "MMMM d, yyyy"),
new DateDisplayFormat("DOW, Month Day, Year", "Sunday, April 5, 2026", "dddd, MMMM d, yyyy"),
new DateDisplayFormat("Abbreviated-Month Day, Year", "Apr 5, 2026", "MMM d, yyyy"),
new DateDisplayFormat("DOW, Abbreviated-Month Day, Year", "Sunday, Apr 5, 2026", "dddd, MMM d, yyyy"),
new DateDisplayFormat("Day Month, Year", "5 April 2026", "d MMMM yyyy"),
new DateDisplayFormat("Day Abbreviated-Month, Year", "5 Apr 2026", "d MMM yyyy"),
new DateDisplayFormat("Day Month, Year", "05 April 2026", "dd MMMM yyyy"),
new DateDisplayFormat("Day Abbreviated-Month, Year", "05 Apr 2026", "dd MMM yyyy"),
];
}
public static string[] GetQuotes() public static string[] GetQuotes()
{ {
+35
View File
@@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using Avalonia.Data.Converters;
namespace MayShow.Helpers;
public class DateFormatConverter : IMultiValueConverter
{
public object? Convert(
IList<object?> values,
Type targetType,
object? parameter,
CultureInfo culture)
{
if (values.Count >= 2 && values[0] is DateOnly date && values[1] is string format)
{
return date.ToString(format);
}
if (values.Count >= 2 && values[0] is string dateFormat && values[1] is DateOnly dateOnly)
{
return dateOnly.ToString(dateFormat);
}
if (values.Count >= 2 && values[0] is DateTime dateTime && values[1] is string format3)
{
return dateTime.ToString(format3);
}
if (values.Count >= 2 && values[0] is string format4 && values[1] is DateTime dateTime2)
{
return dateTime2.ToString(format4);
}
return "";
}
}
+1 -1
View File
@@ -2,7 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
namespace MayShows.Helpers; namespace MayShow.Helpers;
public static class ThreadSafeRandom public static class ThreadSafeRandom
{ {
+37 -10
View File
@@ -1,11 +1,14 @@
using System; using System;
using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Tmds.DBus.Protocol;
namespace MayShows.Helpers; namespace MayShow.Helpers;
class Utilities class Utilities
{ {
@@ -22,17 +25,41 @@ class Utilities
public static DateOnly? CheckValidDateInString(string str) public static DateOnly? CheckValidDateInString(string str)
{ {
// https://stackoverflow.com/a/14918404/3938401 // https://stackoverflow.com/a/14918404/3938401
var rgx = new Regex(@"\d{4}-\d{2}-\d{2}"); // formats = regex format -> DateTime parsing format
var mat = rgx.Match(str); var formats = new Dictionary<string, string>
if (mat.Success)
{ {
var dtStr = mat.ToString(); {@"\d{4}-\d{2}-\d{2}", "yyyy-MM-dd"},
string[] formats = ["yyyy-MM-dd"]; {@"\d{4}.d{2}.d{2}", "yyyy.MM.dd"},
DateTime parsedDateTime; {@"\d{8}", "yyyyMMdd"}
var didWork = DateTime.TryParseExact(dtStr, formats, CultureInfo.InvariantCulture, };
DateTimeStyles.None, out parsedDateTime); foreach (var data in formats)
return didWork ? DateOnly.FromDateTime(parsedDateTime) : null; {
var rgx = new Regex(data.Key);
var mat = rgx.Match(str);
if (mat.Success)
{
var dtStr = mat.ToString();
var didWork = DateTime.TryParseExact(dtStr, [data.Value], CultureInfo.InvariantCulture,
DateTimeStyles.None, out var parsedDateTime);
if (didWork)
{
return DateOnly.FromDateTime(parsedDateTime);
}
}
} }
return null; return null;
} }
public static string GetInternalDataPath()
{
var path = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"MayShow"
);
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
return path;
}
} }
+18 -3
View File
@@ -12,12 +12,17 @@
<PublishTrimmed>true</PublishTrimmed> <PublishTrimmed>true</PublishTrimmed>
<PublishAot>true</PublishAot> <PublishAot>true</PublishAot>
<AssemblyName>MayShow</AssemblyName> <AssemblyName>MayShow</AssemblyName>
<AssemblyVersion>1.2.0</AssemblyVersion> <!-- Also update Constants version --> <AssemblyVersion>1.4.3</AssemblyVersion> <!-- Also update Constants version -->
<ApplicationIcon>MayShow-icon.ico</ApplicationIcon> <ApplicationIcon>MayShow-icon.ico</ApplicationIcon>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<TrimmerRootAssembly Include="MigraDoc.DocumentObjectModel" />
<TrimmerRootAssembly Include="MigraDoc.Rendering" />
<TrimmerRootAssembly Include="MigraDoc.RtfRendering" />
<TrimmerRootAssembly Include="PdfSharp" /> <TrimmerRootAssembly Include="PdfSharp" />
<TrimmerRootAssembly Include="Avalonia.Controls.DataGrid" /> <TrimmerRootAssembly Include="Avalonia.Controls.DataGrid" />
<!-- <TrimmerRootAssembly Include="Docnet.Core" />
<TrimmerRootAssembly Include="SixLabors.ImageSharp" /> -->
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<AvaloniaResource Include="Assets\**" /> <AvaloniaResource Include="Assets\**" />
@@ -35,6 +40,12 @@
<Content Include="Assets\Fonts\Noto_Sans_JP\static\*.*"> <Content Include="Assets\Fonts\Noto_Sans_JP\static\*.*">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>
<Content Include="Assets\Fonts\FontAwesome\*.*">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Assets\LICENSES.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia" Version="$(AvaloniaVersion)" /> <PackageReference Include="Avalonia" Version="$(AvaloniaVersion)" />
@@ -46,10 +57,14 @@
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets> <IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets> <PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
</PackageReference> </PackageReference>
<PackageReference Include="PDFsharp-MigraDoc" Version="6.2.3" /> <PackageReference Include="PDFsharp-MigraDoc" Version="6.2.4" />
<PackageReference Include="Magick.NET-Q16-AnyCPU" Version="14.10.2" /> <PackageReference Include="Magick.NET-Q8-AnyCPU" Version="14.11.1" Condition="'$(RuntimeIdentifier)' != 'osx-x64'" />
<!-- DO NOT UPDATE BEYOND 14.9.1 OR YOU WILL BREAK macOS MONTEREY USERS -->
<PackageReference Include="Magick.NET-Q8-x64" Version="14.9.1" Condition="'$(RuntimeIdentifier)' == 'osx-x64'" />
<PackageReference Include="Deadpikle.AvaloniaProgressRing" Version="0.10.11-preview20251127001" /> <PackageReference Include="Deadpikle.AvaloniaProgressRing" Version="0.10.11-preview20251127001" />
<PackageReference Include="DialogHost.Avalonia" Version="0.10.4" /> <PackageReference Include="DialogHost.Avalonia" Version="0.10.4" />
<PackageReference Include="Xaml.Behaviors.Interactions.DragAndDrop.DataGrid" Version="11.3.9.5" /> <PackageReference Include="Xaml.Behaviors.Interactions.DragAndDrop.DataGrid" Version="11.3.9.5" />
<PackageReference Include="Docnet.Core" Version="2.6.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.12" />
</ItemGroup> </ItemGroup>
</Project> </Project>
+35
View File
@@ -0,0 +1,35 @@
using MayShow.Helpers;
namespace MayShow.Models;
class DateDisplayFormat : ChangeNotifier
{
private string _title;
private string _example;
private string _value;
public DateDisplayFormat(string title, string example, string value)
{
_title = title;
_example = example;
_value = value;
}
public string Title
{
get => _title;
set { _title = value; NotifyPropertyChanged(); }
}
public string Example
{
get => _example;
set { _example = value; NotifyPropertyChanged(); }
}
public string Value
{
get => _value;
set { _value = value; NotifyPropertyChanged(); }
}
}
+3 -3
View File
@@ -23,7 +23,7 @@ class ReportFile : ChangeNotifier
public ReportFile(ReportFile other) public ReportFile(ReportFile other)
{ {
Title = _title = other.Title; Title = _title = other.Title;
ReceiptDateTime = _receiptDateTime = other.ReceiptDateTime; ReceiptDateTime = _receiptDateTime = other.ReceiptDateTime ?? DateTime.Now;
Notes = _notes = other.Notes; Notes = _notes = other.Notes;
FilePath = _filePath = other.FilePath; FilePath = _filePath = other.FilePath;
} }
@@ -34,12 +34,12 @@ class ReportFile : ChangeNotifier
set { _title = value; NotifyPropertyChanged(); } set { _title = value; NotifyPropertyChanged(); }
} }
public DateTime ReceiptDateTime public DateTime? ReceiptDateTime
{ {
get => _receiptDateTime; get => _receiptDateTime;
set set
{ {
_receiptDateTime = value; _receiptDateTime = value ?? DateTime.Now;
NotifyPropertyChanged(); NotifyPropertyChanged();
NotifyPropertyChanged(nameof(ReceiptDate)); NotifyPropertyChanged(nameof(ReceiptDate));
} }
+111 -12
View File
@@ -1,21 +1,53 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using System.Threading.Tasks; using System.Threading.Tasks;
using MayShow.Helpers; using MayShow.Helpers;
using MayShows.Helpers;
namespace MayShow.Models; namespace MayShow.Models;
class Settings : ChangeNotifier class Settings : ChangeNotifier
{ {
private string _lastUsedPath; private string _lastUsedPath;
private bool _useDocnetPDFImageRendering;
private bool _saveOutputPdfInWorkingDir;
private string _outputPdfDir;
private decimal _imageResizeThreshold;
private bool _saveReportJsonDataInInternalDir;
private Dictionary<string, string> _workingFolderToInternalFolderName;
public string _dataGridDateFormat;
public string _reportDateFormat;
public int _settingsVersion;
public Settings() public Settings()
{ {
_lastUsedPath = ""; _lastUsedPath = "";
_useDocnetPDFImageRendering = true;
_saveOutputPdfInWorkingDir = true;
_outputPdfDir = "";
_imageResizeThreshold = 1.5m;
_saveReportJsonDataInInternalDir = false;
_workingFolderToInternalFolderName = [];
_settingsVersion = 1;
_dataGridDateFormat = "dd/MM/yyyy";
_reportDateFormat = "yyyy-MM-dd";
}
public Settings(Settings other)
{
_lastUsedPath = other.LastUsedPath;
_useDocnetPDFImageRendering = other.UseDocnetPDFImageRendering;
_saveOutputPdfInWorkingDir = other.SaveOutputPdfInWorkingDir;
_outputPdfDir = other.OutputPdfDir;
_imageResizeThreshold = other.ImageResizeThreshold;
_saveReportJsonDataInInternalDir = other.SaveReportJsonDataInInternalDir;
_workingFolderToInternalFolderName = other.WorkingFolderToInternalFolderName;
_settingsVersion = other.SettingsVersion;
_dataGridDateFormat = "yyyy-MM-dd";
_reportDateFormat = "yyyy-MM-dd";
} }
[JsonInclude] [JsonInclude]
@@ -25,22 +57,89 @@ class Settings : ChangeNotifier
set { _lastUsedPath = value; NotifyPropertyChanged(); } set { _lastUsedPath = value; NotifyPropertyChanged(); }
} }
public static string GetSettingsFileName() [JsonInclude]
[JsonPropertyName("UseDocnetPFDImageRendering")] // ...this typo now has to live because people have this saved on disk...
public bool UseDocnetPDFImageRendering
{ {
return "settings.json"; get => _useDocnetPDFImageRendering;
set { _useDocnetPDFImageRendering = value; NotifyPropertyChanged(); }
} }
[JsonInclude]
public bool SaveOutputPdfInWorkingDir
{
get => _saveOutputPdfInWorkingDir;
set { _saveOutputPdfInWorkingDir = value; NotifyPropertyChanged(); }
}
[JsonInclude]
public string OutputPdfDir
{
get => _outputPdfDir;
set { _outputPdfDir = value; NotifyPropertyChanged(); }
}
[JsonInclude]
public decimal ImageResizeThreshold
{
get => _imageResizeThreshold;
set { _imageResizeThreshold = value; NotifyPropertyChanged(); }
}
[JsonInclude]
public bool SaveReportJsonDataInInternalDir
{
get => _saveReportJsonDataInInternalDir;
set { _saveReportJsonDataInInternalDir = value; NotifyPropertyChanged(); }
}
[JsonInclude]
public Dictionary<string, string> WorkingFolderToInternalFolderName
{
get => _workingFolderToInternalFolderName;
set { _workingFolderToInternalFolderName = value; NotifyPropertyChanged(); }
}
[JsonInclude]
public int SettingsVersion
{
get => _settingsVersion;
set { _settingsVersion = value; NotifyPropertyChanged(); }
}
[JsonInclude]
public string DataGridDateFormat
{
get => _dataGridDateFormat;
set { _dataGridDateFormat = value; NotifyPropertyChanged(); }
}
[JsonInclude]
public string ReportDateFormat
{
get => _reportDateFormat;
set { _reportDateFormat = value; NotifyPropertyChanged(); }
}
public static string SettingsFileName = "settings.json";
public static string GetSettingsPath() public static string GetSettingsPath()
{ {
var path = Path.Combine( var path = Utilities.GetInternalDataPath();
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), return Path.Combine(path, SettingsFileName);
"MayShow" }
);
if (!Directory.Exists(path))
{ public string SaveSettingsNotAsync()
Directory.CreateDirectory(path); {
} var jsonContext = new SourceGenerationContext(Utilities.GetSerializerOptions());
return Path.Combine(path, GetSettingsFileName()); using MemoryStream memoryStream = new MemoryStream();
JsonSerializer.Serialize(memoryStream, this, jsonContext.Settings);
memoryStream.Position = 0;
using var reader = new StreamReader(memoryStream);
var json = reader.ReadToEnd();
File.WriteAllText(GetSettingsPath(), json);
return json;
} }
public async Task<string> SaveSettingsAsync() public async Task<string> SaveSettingsAsync()
+35 -36
View File
@@ -5,43 +5,42 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
namespace MayShow.ViewModels namespace MayShow.ViewModels;
class BaseViewModel : ChangeNotifier
{ {
class BaseViewModel : ChangeNotifier IChangeViewModel _viewModelChanger;
ITopLevelGrabber? _topLevelGrabber;
public BaseViewModel(IChangeViewModel viewModelChanger)
{ {
IChangeViewModel _viewModelChanger; _viewModelChanger = viewModelChanger;
ITopLevelGrabber? _topLevelGrabber; _topLevelGrabber = null;
public BaseViewModel(IChangeViewModel viewModelChanger)
{
_viewModelChanger = viewModelChanger;
_topLevelGrabber = null;
}
public ITopLevelGrabber? TopLevelGrabber
{
get => _topLevelGrabber;
set { _topLevelGrabber = value; }
}
public IChangeViewModel ViewModelChanger
{
get { return _viewModelChanger; }
set { _viewModelChanger = value; }
}
#region IChangeViewModel
public void PopViewModel()
{
_viewModelChanger?.PopViewModel();
}
public void PushViewModel(BaseViewModel model)
{
_viewModelChanger?.PushViewModel(model);
}
#endregion
} }
public ITopLevelGrabber? TopLevelGrabber
{
get => _topLevelGrabber;
set { _topLevelGrabber = value; }
}
public IChangeViewModel ViewModelChanger
{
get { return _viewModelChanger; }
set { _viewModelChanger = value; }
}
#region IChangeViewModel
public void PopViewModel()
{
_viewModelChanger?.PopViewModel();
}
public void PushViewModel(BaseViewModel model)
{
_viewModelChanger?.PushViewModel(model);
}
#endregion
} }
+52
View File
@@ -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);
}
}
+451 -132
View File
@@ -19,7 +19,17 @@ using PdfSharp.Snippets.Font;
using MayShow.Helpers; using MayShow.Helpers;
using MayShow.Interfaces; using MayShow.Interfaces;
using MayShow.Models; using MayShow.Models;
using MayShows.Helpers;
using Docnet.Core.Models;
using Docnet.Core;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using System.Reflection.Metadata.Ecma335;
using Docnet.Core.Readers;
using MigraDoc.DocumentObjectModel.Visitors;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace MayShow.ViewModels; namespace MayShow.ViewModels;
@@ -28,7 +38,7 @@ class MainViewModel : BaseViewModel, IFontResolver, ICanCheckShutdown
private bool _isPerformingInitialLoad; private bool _isPerformingInitialLoad;
private string _processDir; private string _processDir;
private bool _isCreatingPDF; private bool _isCreatingPDF;
private string _createPDFLog; private string _programLog;
private string _workingFolder; private string _workingFolder;
private string _reportTitle; private string _reportTitle;
@@ -36,6 +46,7 @@ class MainViewModel : BaseViewModel, IFontResolver, ICanCheckShutdown
private DateTime? _lastGeneratedTime; private DateTime? _lastGeneratedTime;
private Settings _settings; private Settings _settings;
private List<DateDisplayFormat> _dateDisplayFormats;
private bool _hasUnsavedWork; private bool _hasUnsavedWork;
@@ -43,21 +54,26 @@ class MainViewModel : BaseViewModel, IFontResolver, ICanCheckShutdown
{ {
_isPerformingInitialLoad = true; _isPerformingInitialLoad = true;
_processDir = Path.GetDirectoryName(Environment.ProcessPath) ?? ""; _processDir = Path.GetDirectoryName(Environment.ProcessPath) ?? "";
Console.WriteLine("Process is running from: {0}", _processDir); Console.WriteLine("Internal storage directory is: {0}", Utilities.GetInternalDataPath());
_isCreatingPDF = false; _isCreatingPDF = false;
var quotes = Constants.GetQuotes(); var quotes = Constants.GetQuotes();
Random random = new Random(); Random random = new Random();
var quoteIndex = random.Next(0, quotes.Length); var quoteIndex = random.Next(0, quotes.Length);
_createPDFLog = "----- MayShow v" + Constants.AppVersion + " ------" + Environment.NewLine; var compDetails = RuntimeInformation.OSDescription + " | " +
_createPDFLog += quotes[quoteIndex] + Environment.NewLine; RuntimeInformation.OSArchitecture.ToString();
_createPDFLog += "---------------------------------------" + Environment.NewLine; _programLog = "----- MayShow v" + Constants.AppVersion + " | " + compDetails + " ------" + Environment.NewLine;
_createPDFLog += "Loaded and ready to create report!" + Environment.NewLine; _programLog += quotes[quoteIndex] + Environment.NewLine;
_createPDFLog += "Please copy and send this Program Log when reporting any issues with the software."; _programLog += "---------------------------------------" + Environment.NewLine;
_programLog += "Loaded and ready to create report!" + Environment.NewLine;
_programLog += "Please copy and send this Program Log when reporting any issues with the software.";
_workingFolder = ""; _workingFolder = "";
ReportFiles = _reportFiles = new ObservableCollection<ReportFile>(); ReportFiles = _reportFiles = new ObservableCollection<ReportFile>();
_reportTitle = ""; _reportTitle = "";
_lastGeneratedTime = null; _lastGeneratedTime = null;
_settings = Settings.LoadSettings(); _settings = Settings.LoadSettings();
_dateDisplayFormats = Constants.GetDateDisplayFormats();
NotifyPropertyChanged(nameof(DataGridDateFormat));
NotifyPropertyChanged(nameof(DataGridDateFormatWatermark));
if (!string.IsNullOrWhiteSpace(_settings.LastUsedPath)) if (!string.IsNullOrWhiteSpace(_settings.LastUsedPath))
{ {
LogInfo("Loading data at last used path of {0}", _settings.LastUsedPath); LogInfo("Loading data at last used path of {0}", _settings.LastUsedPath);
@@ -85,7 +101,7 @@ class MainViewModel : BaseViewModel, IFontResolver, ICanCheckShutdown
public bool IsTitleBoxVisible public bool IsTitleBoxVisible
{ {
get => !string.IsNullOrWhiteSpace(_workingFolder); get => !string.IsNullOrWhiteSpace(WorkingFolder);
} }
public bool CanAddItem public bool CanAddItem
@@ -113,12 +129,12 @@ class MainViewModel : BaseViewModel, IFontResolver, ICanCheckShutdown
public bool HasWorkingFolder public bool HasWorkingFolder
{ {
get => !string.IsNullOrWhiteSpace(_workingFolder) && Directory.Exists(_workingFolder); get => !string.IsNullOrWhiteSpace(WorkingFolder) && Directory.Exists(WorkingFolder);
} }
public bool HasWorkingFolderAndNotMakingPDF public bool HasWorkingFolderAndNotMakingPDF
{ {
get => !string.IsNullOrWhiteSpace(_workingFolder) && Directory.Exists(_workingFolder) && !_isCreatingPDF; get => !string.IsNullOrWhiteSpace(WorkingFolder) && Directory.Exists(WorkingFolder) && !_isCreatingPDF;
} }
public string WorkingFolder public string WorkingFolder
@@ -133,10 +149,10 @@ class MainViewModel : BaseViewModel, IFontResolver, ICanCheckShutdown
} }
} }
public string CreatePDFLog public string ProgramLog
{ {
get => _createPDFLog; get => _programLog;
set { _createPDFLog = value; NotifyPropertyChanged(); } set { _programLog = value; NotifyPropertyChanged(); }
} }
public bool HasUnsavedWork public bool HasUnsavedWork
@@ -164,11 +180,21 @@ class MainViewModel : BaseViewModel, IFontResolver, ICanCheckShutdown
} }
} }
public string DataGridDateFormat
{
get => _settings.DataGridDateFormat;
}
public string DataGridDateFormatWatermark
{
get => _dateDisplayFormats.FirstOrDefault(x => x.Value == _settings.DataGridDateFormat)?.Example ?? "2025-12-04";
}
private void LogInfo(string message, params object[]? arguments) private void LogInfo(string message, params object[]? arguments)
{ {
var timestamp = string.Format("[{0:s}]", DateTime.Now); var timestamp = string.Format("[{0:s}]", DateTime.Now);
Console.WriteLine(timestamp + " " + message, arguments); Console.WriteLine(timestamp + " " + message, arguments);
CreatePDFLog += Environment.NewLine + string.Format(message, arguments ?? []); ProgramLog += Environment.NewLine + string.Format(message, arguments ?? []);
} }
public async void ChooseFolder() public async void ChooseFolder()
@@ -195,6 +221,41 @@ class MainViewModel : BaseViewModel, IFontResolver, ICanCheckShutdown
} }
} }
private string GetReportSavedDataPath(string folderPath)
{
if (_settings.SaveReportJsonDataInInternalDir)
{
var internalPath = Utilities.GetInternalDataPath();
if (!_settings.WorkingFolderToInternalFolderName.ContainsKey(folderPath))
{
var uuid = "";
var potentialPath = "";
var isDone = false;
// make sure uuid not already used...just in case...because paranoia...
do
{
uuid = Guid.NewGuid().ToString();
potentialPath = Path.Combine(internalPath, uuid);
isDone = !Directory.Exists(potentialPath);
} while (!isDone);
// make internal dir -- using dir so we have option to copy data into dir later if needed
// (if we ever implement a more robust report system where we keep all files)
Directory.CreateDirectory(potentialPath);
_settings.WorkingFolderToInternalFolderName[folderPath] = uuid;
_settings.SaveSettingsNotAsync(); // save new key/value pair
}
return Path.Combine(
internalPath,
_settings.WorkingFolderToInternalFolderName[folderPath],
Constants.ReportSavedDataFileName
);
}
else
{
return Path.Combine(folderPath, Constants.ReportSavedDataFileName);
}
}
private void ScanFolder(string path) private void ScanFolder(string path)
{ {
if (Directory.Exists(path)) if (Directory.Exists(path))
@@ -202,7 +263,7 @@ class MainViewModel : BaseViewModel, IFontResolver, ICanCheckShutdown
WorkingFolder = path; WorkingFolder = path;
NotifyPropertyChanged(nameof(IsTitleBoxVisible)); NotifyPropertyChanged(nameof(IsTitleBoxVisible));
NotifyPropertyChanged(nameof(CanAddItem)); NotifyPropertyChanged(nameof(CanAddItem));
var reportFilePath = Path.Combine(path, GetReportSavedDataFileName()); var reportFilePath = GetReportSavedDataPath(path);
var successfullyLoadedPriorReport = false; var successfullyLoadedPriorReport = false;
if (File.Exists(reportFilePath)) if (File.Exists(reportFilePath))
{ {
@@ -212,6 +273,7 @@ class MainViewModel : BaseViewModel, IFontResolver, ICanCheckShutdown
var report = JsonSerializer.Deserialize<PDFReport>(json, jsonContext.PDFReport); var report = JsonSerializer.Deserialize<PDFReport>(json, jsonContext.PDFReport);
if (report != null && report.Files.Count > 0) if (report != null && report.Files.Count > 0)
{ {
Console.WriteLine("Loading prior report data at {0}", reportFilePath);
ReportFiles = new ObservableCollection<ReportFile>(report.Files); ReportFiles = new ObservableCollection<ReportFile>(report.Files);
ReportTitle = report.Title; ReportTitle = report.Title;
WorkingFolder = report.BaseFolder; WorkingFolder = report.BaseFolder;
@@ -225,15 +287,12 @@ class MainViewModel : BaseViewModel, IFontResolver, ICanCheckShutdown
// Scan folder for files and display in DataGrid // Scan folder for files and display in DataGrid
ReportFiles.Clear(); ReportFiles.Clear();
ReportTitle = ""; ReportTitle = "";
var filePaths = Directory.GetFiles(_workingFolder); var filePaths = Directory.GetFiles(WorkingFolder);
foreach (var filePath in filePaths) foreach (var filePath in filePaths)
{ {
AddFileBasedOnPath(filePath); AddFileBasedOnPath(filePath);
} }
if (!_isPerformingInitialLoad) ResortPDFItemsByDate();
{
ResortPDFItemsByDate();
}
HasUnsavedWork = true; HasUnsavedWork = true;
} }
} }
@@ -249,6 +308,19 @@ class MainViewModel : BaseViewModel, IFontResolver, ICanCheckShutdown
DialogHost.Show(new AboutViewModel()); DialogHost.Show(new AboutViewModel());
} }
public async Task ShowSettings()
{
var updatedSettings = await DialogHost.Show(new SettingsViewModel(_settings, TopLevelGrabber));
if (updatedSettings != null)
{
_settings = (Settings)updatedSettings;
await _settings.SaveSettingsAsync();
LogInfo("Saved updated settings!");
NotifyPropertyChanged(nameof(DataGridDateFormat));
NotifyPropertyChanged(nameof(DataGridDateFormatWatermark));
}
}
public void RemoveFile(object f) => RemoveFileImpl((ReportFile)f); public void RemoveFile(object f) => RemoveFileImpl((ReportFile)f);
public async void RemoveFileImpl(ReportFile file) public async void RemoveFileImpl(ReportFile file)
@@ -280,18 +352,6 @@ class MainViewModel : BaseViewModel, IFontResolver, ICanCheckShutdown
} }
} }
private string[] GetAllowedFileExtensionPatterns()
{
// update GetAllowedFileExtensionPatternsWithoutStar if this is edited
return [ "*.png", "*.jpg", "*.jpeg", "*.gif", "*.bmp", "*.webp", "*.pdf", "*.heic", ];
}
private string[] GetAllowedFileExtensionPatternsWithoutStar()
{
// update GetAllowedFileExtensionPatterns if this is edited
return [ "png", "jpg", "jpeg", "gif", "bmp", "webp", "pdf", "heic", ];
}
public async void AddItem() public async void AddItem()
{ {
var topLevel = TopLevelGrabber?.GetTopLevel(); var topLevel = TopLevelGrabber?.GetTopLevel();
@@ -304,7 +364,7 @@ class MainViewModel : BaseViewModel, IFontResolver, ICanCheckShutdown
FileTypeFilter = [ FileTypeFilter = [
new FilePickerFileType("All Types") new FilePickerFileType("All Types")
{ {
Patterns = GetAllowedFileExtensionPatterns(), Patterns = Constants.AllowedFileExtensionPatterns,
AppleUniformTypeIdentifiers = [ "public.image", "com.adobe.pdf", "public.heic" ], AppleUniformTypeIdentifiers = [ "public.image", "com.adobe.pdf", "public.heic" ],
MimeTypes = [ "image/*", "application/pdf", "image/heic" ] MimeTypes = [ "image/*", "application/pdf", "image/heic" ]
}, },
@@ -334,7 +394,7 @@ class MainViewModel : BaseViewModel, IFontResolver, ICanCheckShutdown
if (!string.IsNullOrWhiteSpace(filePath) && File.Exists(filePath) && !filePath.EndsWith(".DS_Store")) if (!string.IsNullOrWhiteSpace(filePath) && File.Exists(filePath) && !filePath.EndsWith(".DS_Store"))
{ {
// make sure extensions are OK // make sure extensions are OK
var fileExtensions = GetAllowedFileExtensionPatternsWithoutStar(); var fileExtensions = Constants.AllowedFileExtensionsNoStar;
var didMatch = false; var didMatch = false;
foreach (var fileExtension in fileExtensions) foreach (var fileExtension in fileExtensions)
{ {
@@ -346,7 +406,10 @@ class MainViewModel : BaseViewModel, IFontResolver, ICanCheckShutdown
} }
if (!didMatch) if (!didMatch)
{ {
LogInfo("File {0} did not match allowed file extension types, so it was not added.", filePath); if (!filePath.EndsWith(Constants.ReportSavedDataFileName))
{
LogInfo("File {0} did not match allowed file extension types, so it was not added.", filePath);
}
} }
else else
{ {
@@ -363,6 +426,17 @@ class MainViewModel : BaseViewModel, IFontResolver, ICanCheckShutdown
} }
} }
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 void LocateFile(object f) => LocateFileImpl((ReportFile) f);
public async void LocateFileImpl(ReportFile reportFile) public async void LocateFileImpl(ReportFile reportFile)
{ {
@@ -376,7 +450,7 @@ class MainViewModel : BaseViewModel, IFontResolver, ICanCheckShutdown
FileTypeFilter = [ FileTypeFilter = [
new FilePickerFileType("All Types") new FilePickerFileType("All Types")
{ {
Patterns = GetAllowedFileExtensionPatterns(), Patterns = Constants.AllowedFileExtensionPatterns,
AppleUniformTypeIdentifiers = [ "public.image", "com.adobe.pdf", "public.heic" ], AppleUniformTypeIdentifiers = [ "public.image", "com.adobe.pdf", "public.heic" ],
MimeTypes = [ "image/*", "application/pdf", "image/heic" ] MimeTypes = [ "image/*", "application/pdf", "image/heic" ]
}, },
@@ -446,7 +520,7 @@ class MainViewModel : BaseViewModel, IFontResolver, ICanCheckShutdown
{ {
try try
{ {
await Task.Run(() => CreatePDF(_workingFolder)); await Task.Run(() => CreatePDF(WorkingFolder));
} catch (Exception e) } catch (Exception e)
{ {
LogInfo("PDF process failed! Reason: " + e.Message); LogInfo("PDF process failed! Reason: " + e.Message);
@@ -454,13 +528,15 @@ class MainViewModel : BaseViewModel, IFontResolver, ICanCheckShutdown
{ {
LogInfo(e.StackTrace); LogInfo(e.StackTrace);
} }
if (e.InnerException != null) var otherException = e.InnerException;
while (otherException != null)
{ {
LogInfo("Inner exception: " + e.InnerException.Message); LogInfo(">> Inner exception: " + otherException.Message);
if (e.InnerException.StackTrace != null) if (otherException.StackTrace != null)
{ {
LogInfo(e.InnerException.StackTrace); LogInfo(otherException.StackTrace);
} }
otherException = otherException.InnerException;
} }
LogInfo("Please report this error to a programmer or fix the issue listed above."); LogInfo("Please report this error to a programmer or fix the issue listed above.");
IsCreatingPDF = false; IsCreatingPDF = false;
@@ -474,7 +550,7 @@ class MainViewModel : BaseViewModel, IFontResolver, ICanCheckShutdown
{ {
Title = ReportTitle, Title = ReportTitle,
Files = ReportFiles.ToList(), Files = ReportFiles.ToList(),
BaseFolder = _workingFolder, BaseFolder = WorkingFolder,
LastSaved = DateTime.Now, LastSaved = DateTime.Now,
LastGenerated = _lastGeneratedTime, LastGenerated = _lastGeneratedTime,
}; };
@@ -489,7 +565,7 @@ class MainViewModel : BaseViewModel, IFontResolver, ICanCheckShutdown
memoryStream.Position = 0; memoryStream.Position = 0;
using var reader = new StreamReader(memoryStream); using var reader = new StreamReader(memoryStream);
var json = await reader.ReadToEndAsync(); var json = await reader.ReadToEndAsync();
var savePath = Path.Combine(_workingFolder, GetReportSavedDataFileName()); var savePath = GetReportSavedDataPath(WorkingFolder);
await File.WriteAllTextAsync(savePath, json); await File.WriteAllTextAsync(savePath, json);
LogInfo("Saved report information to {0}", savePath); LogInfo("Saved report information to {0}", savePath);
HasUnsavedWork = false; HasUnsavedWork = false;
@@ -501,7 +577,7 @@ class MainViewModel : BaseViewModel, IFontResolver, ICanCheckShutdown
{ {
Title = ReportTitle, Title = ReportTitle,
Files = ReportFiles.ToList(), Files = ReportFiles.ToList(),
BaseFolder = _workingFolder, BaseFolder = WorkingFolder,
LastSaved = DateTime.Now, LastSaved = DateTime.Now,
LastGenerated = DateTime.Now, LastGenerated = DateTime.Now,
}; };
@@ -509,14 +585,37 @@ class MainViewModel : BaseViewModel, IFontResolver, ICanCheckShutdown
await SavePDFReportDataToDisk(report); await SavePDFReportDataToDisk(report);
} }
private string GetReportSavedDataFileName() public async Task CopyLogToClipboard()
{ {
return "report_data.json"; var clipboard = TopLevelGrabber?.GetTopLevel().Clipboard;
if (clipboard != null)
{
await clipboard.SetTextAsync(ProgramLog);
LogInfo("Program log has been copied to the clipboard!");
}
} }
public byte[]? GetFont(string faceName) public byte[]? GetFont(string faceName)
{ {
LogInfo(string.Format("Loading font {0}", faceName)); LogInfo(string.Format("Loading font {0}", faceName));
if (faceName == "Noto Sans")
{
var path = Path.Combine(_processDir, "Assets/Fonts/Noto_Sans/static/NotoSans-Regular.ttf");
if (!File.Exists(path))
{
path = Path.Combine(_processDir, "../Resources/Assets/Fonts/Noto_Sans/static/NotoSans-Regular.ttf");
}
return File.ReadAllBytes(path);
}
if (faceName == "Noto Sans Bold")
{
var path = Path.Combine(_processDir, "Assets/Fonts/Noto_Sans/static/NotoSans-Bold.ttf");
if (!File.Exists(path))
{
path = Path.Combine(_processDir, "../Resources/Assets/Fonts/Noto_Sans/static/NotoSans-Bold.ttf");
}
return File.ReadAllBytes(path);
}
if (faceName == "Noto Sans JP") if (faceName == "Noto Sans JP")
{ {
var path = Path.Combine(_processDir, "Assets/Fonts/Noto_Sans_JP/static/NotoSansJP-Regular.ttf"); var path = Path.Combine(_processDir, "Assets/Fonts/Noto_Sans_JP/static/NotoSansJP-Regular.ttf");
@@ -541,6 +640,14 @@ class MainViewModel : BaseViewModel, IFontResolver, ICanCheckShutdown
public FontResolverInfo? ResolveTypeface(string familyName, bool bold, bool italic) public FontResolverInfo? ResolveTypeface(string familyName, bool bold, bool italic)
{ {
// LogInfo(string.Format("Resolving font name {0}", familyName)); // LogInfo(string.Format("Resolving font name {0}", familyName));
if (familyName == "Noto Sans")
{
if (bold)
{
return new FontResolverInfo(familyName + " Bold");
}
return new FontResolverInfo(familyName);
}
if (familyName == "Noto Sans JP") if (familyName == "Noto Sans JP")
{ {
if (bold) if (bold)
@@ -552,16 +659,74 @@ class MainViewModel : BaseViewModel, IFontResolver, ICanCheckShutdown
return null; return null;
} }
private Paragraph GetFooterParagraph()
{
var footerPar = new Paragraph();
footerPar.Format.Alignment = ParagraphAlignment.Center;
footerPar.Format.Font.Size = 10;
footerPar.AddText("--Page ");
footerPar.AddPageField();
footerPar.AddText(" of ");
footerPar.AddNumPagesField();
footerPar.AddText("--");
footerPar.AddLineBreak();
footerPar.AddText("Report generated on " + DateTime.Now.ToString("f"));
footerPar.Tag = "FooterPar";
footerPar.Format.Font.Name = "Noto Sans";
return footerPar;
}
private decimal GetExistingPageItemHeight(PdfDocumentRenderer pdfRenderer, decimal footerParagraphHeight)
{
pdfRenderer.DocumentRenderer.PrepareDocument();
var currPageCount = pdfRenderer.DocumentRenderer.FormattedDocument?.PageCount;
var heightForExistingItemsOnPage = footerParagraphHeight;
if (currPageCount.HasValue)
{
var renderInfo = pdfRenderer.DocumentRenderer.GetRenderInfoFromPage(currPageCount.Value);
if (renderInfo != null)
{
// Console.WriteLine("Got render info for page: {0}", currPageCount);
foreach (var item in renderInfo)
{
heightForExistingItemsOnPage += (decimal)item.LayoutInfo.ContentArea.Height.Inch;
}
}
}
return heightForExistingItemsOnPage;
}
// https://forum.pdfsharp.net/viewtopic.php?f=2&t=1025 // https://forum.pdfsharp.net/viewtopic.php?f=2&t=1025
private async Task CreatePDF(string folderPath) private async Task CreatePDF(string folderPath)
{ {
// TODO: calculate needed width for images based on page width and margins and all that? // TODO: calculate needed width for images based on page width and margins and all that?
// TODO: resize (non-HEIC) images for smaller size...? // safety checks
var outputDir = _settings.SaveOutputPdfInWorkingDir ? folderPath : _settings.OutputPdfDir;
if (!Directory.Exists(outputDir))
{
await DialogHost.Show(new WarningViewModel("Output directory not found! Please adjust your application Settings before continuing. Output directory: " + outputDir));
return;
}
// setup globals and consts...
GlobalFontSettings.FontResolver = this;
GlobalFontSettings.FallbackFontResolver = new FailsafeFontResolver();
const decimal pageWidth = 8.5m;
const decimal pageHeight = 11.0m;
const decimal margin = 0.5m;
const int imageResolution = 72;
const int imageInsertMarginPixels = 30; // we calculate max available; use max - this # for max image size
var maxItemPxWidth = ((pageWidth - (2 * margin)) * imageResolution) - imageInsertMarginPixels;
// start making PDF!
IsCreatingPDF = true; IsCreatingPDF = true;
var pdfDoc = new Document(); var pdfDoc = new Document();
var outputFileName = ReportTitle + ".pdf"; var outputFileName = ReportTitle + ".pdf";
var folderName = new DirectoryInfo(folderPath).Name; var folderName = new DirectoryInfo(folderPath).Name;
const int imageWidth = 425; const int maxImageWidth = 425;
var imageLineFormat = new MigraDoc.DocumentObjectModel.Shapes.LineFormat()
{
Color = Colors.Black,
Width = Unit.FromPoint(2),
};;
if (folderName.Contains('-')) if (folderName.Contains('-'))
{ {
// see if year/month format // see if year/month format
@@ -576,36 +741,63 @@ class MainViewModel : BaseViewModel, IFontResolver, ICanCheckShutdown
LogInfo("Auto-changed output file name to " + outputFileName); LogInfo("Auto-changed output file name to " + outputFileName);
} }
} }
// setup initial section (for page characteristics)
var section = pdfDoc.AddSection(); var section = pdfDoc.AddSection();
section.PageSetup.PageFormat = PageFormat.Letter; section.PageSetup.PageFormat = PageFormat.Letter;
section.PageSetup.PageWidth = "8.5in"; section.PageSetup.PageWidth = pageWidth + "in";
section.PageSetup.PageHeight = "11in"; section.PageSetup.PageHeight = pageHeight + "in";
section.PageSetup.TopMargin = "0.5in"; section.PageSetup.TopMargin = margin + "in";
section.PageSetup.RightMargin = "0.5in"; section.PageSetup.RightMargin = margin + "in";
section.PageSetup.BottomMargin = "0.5in"; section.PageSetup.BottomMargin = margin + "in";
section.PageSetup.LeftMargin = "0.5in"; section.PageSetup.LeftMargin = margin + "in";
// setup footer for page number // setup footer for page number
var footerPar = new Paragraph(); var footerPar = GetFooterParagraph();
footerPar.Format.Alignment = ParagraphAlignment.Center;
footerPar.Format.Font.Size = 10;
footerPar.AddText("--Page ");
footerPar.AddPageField();
footerPar.AddText(" of ");
footerPar.AddNumPagesField();
footerPar.AddText("--");
footerPar.AddLineBreak();
footerPar.AddText("Report generated on " + DateTime.Now.ToString("f"));
section.Footers.Primary.Add(footerPar); section.Footers.Primary.Add(footerPar);
// add report title // create a quick PDF doc renderer to measure footer paragraph height
var footerParagraphHeight = 0.4m; // estimate
var footerOnlyPdfDoc = new Document();
var sectionClone = section.Clone();
footerOnlyPdfDoc.Add(sectionClone);
sectionClone.Add(GetFooterParagraph());
var footerPdfRenderer = new PdfDocumentRenderer
{
Document = footerOnlyPdfDoc
};
footerPdfRenderer.DocumentRenderer.PrepareDocument();
var footerRenderInfo = footerPdfRenderer.DocumentRenderer.GetRenderInfoFromPage(1);
if (footerRenderInfo != null)
{
foreach (var item in footerRenderInfo)
{
if (item.DocumentObject.Tag?.ToString() == "FooterPar")
{
Console.WriteLine("Got footer paragraph height!");
footerParagraphHeight = (decimal)item.LayoutInfo.ContentArea.Height.Inch;
break;
}
}
}
// continue setting up document
// First page only: add report title
var reportTitlePar = section.AddParagraph(); var reportTitlePar = section.AddParagraph();
reportTitlePar.Format.Alignment = ParagraphAlignment.Center; reportTitlePar.Format.Alignment = ParagraphAlignment.Center;
reportTitlePar.Format.Font.Size = 16; reportTitlePar.Format.Font.Size = 16;
reportTitlePar.Format.Font.Bold = true; reportTitlePar.Format.Font.Bold = true;
reportTitlePar.Format.Font.Name = "Noto Sans JP"; // has english letters in it, too reportTitlePar.Format.Font.Name = "Noto Sans JP"; // has english letters in it, too
reportTitlePar.AddText(ReportTitle); reportTitlePar.AddText(ReportTitle);
reportTitlePar.Tag = "TitlePar";
// get converted files directory path and create it if necessary
var convertedDir = Path.Combine(Utilities.GetInternalDataPath(), "converted");
if (!Directory.Exists(convertedDir))
{
Directory.CreateDirectory(convertedDir);
}
// //
GlobalFontSettings.FontResolver = this; var pdfRenderer = new PdfDocumentRenderer
GlobalFontSettings.FallbackFontResolver = new FailsafeFontResolver(); {
Document = pdfDoc,
WorkingDirectory = folderPath
};
var hasAddedData = false; var hasAddedData = false;
for (var i = 0; i < ReportFiles.Count; i++) for (var i = 0; i < ReportFiles.Count; i++)
{ {
@@ -632,12 +824,14 @@ class MainViewModel : BaseViewModel, IFontResolver, ICanCheckShutdown
imageTitlePar.Format.Font.Bold = true; imageTitlePar.Format.Font.Bold = true;
imageTitlePar.Format.Font.Name = "Noto Sans JP"; // has english letters in it, too imageTitlePar.Format.Font.Name = "Noto Sans JP"; // has english letters in it, too
imageTitlePar.AddText(string.IsNullOrWhiteSpace(file.Title) ? file.FileName : file.Title); imageTitlePar.AddText(string.IsNullOrWhiteSpace(file.Title) ? file.FileName : file.Title);
imageTitlePar.Tag = "ReceiptTitlePar";
var receiptDatePar = section.AddParagraph(); var receiptDatePar = section.AddParagraph();
receiptDatePar.Format.Alignment = ParagraphAlignment.Center; receiptDatePar.Format.Alignment = ParagraphAlignment.Center;
receiptDatePar.Format.Font.Size = 12; receiptDatePar.Format.Font.Size = 12;
receiptDatePar.Format.Font.Bold = true; receiptDatePar.Format.Font.Bold = true;
receiptDatePar.Format.Font.Name = "Noto Sans JP"; // has english letters in it, too receiptDatePar.Format.Font.Name = "Noto Sans JP"; // has english letters in it, too
receiptDatePar.AddText(file.ReceiptDate.ToString("yyyy-MM-dd")); receiptDatePar.AddText(file.ReceiptDate.ToString(_settings.ReportDateFormat));
receiptDatePar.Tag = "ReceiptDatePar";
if (!string.IsNullOrWhiteSpace(file.Notes)) if (!string.IsNullOrWhiteSpace(file.Notes))
{ {
var imageNotesPar = section.AddParagraph(); var imageNotesPar = section.AddParagraph();
@@ -646,8 +840,10 @@ class MainViewModel : BaseViewModel, IFontResolver, ICanCheckShutdown
imageNotesPar.Format.Font.Bold = false; imageNotesPar.Format.Font.Bold = false;
imageNotesPar.Format.Font.Name = "Noto Sans JP"; imageNotesPar.Format.Font.Name = "Noto Sans JP";
imageNotesPar.AddText(file.Notes); imageNotesPar.AddText(file.Notes);
imageNotesPar.Tag = "ReceiptNotesPar";
} }
section.AddParagraph(); // add empty line for spacing var emptyPar = section.AddParagraph(); // add empty line for spacing
emptyPar.Tag = "EmptyParagraph";
// now add the image // now add the image
var lowerName = fileName.ToLower(); var lowerName = fileName.ToLower();
var isPDF = lowerName.EndsWith(".pdf"); var isPDF = lowerName.EndsWith(".pdf");
@@ -659,82 +855,205 @@ class MainViewModel : BaseViewModel, IFontResolver, ICanCheckShutdown
var info = new FileInfo(file.FilePath); var info = new FileInfo(file.FilePath);
uint loadedImageWidth = 0; uint loadedImageWidth = 0;
uint loadedImageHeight = 0; uint loadedImageHeight = 0;
if (isHEIC || isWebp || isPNG) // get max pixel height remaining for items on this page
// (For multi-page PDFs, showing page 2 and on will have more height since they have no title,
// but to keep things consistent we will use the same height for all PDF pages.)
// render up to now on this page and get height remaining in inches
var currPageCount = pdfRenderer.DocumentRenderer.FormattedDocument?.PageCount;
var heightForExistingItemsOnPage = GetExistingPageItemHeight(pdfRenderer, footerParagraphHeight);
var remainingHeightInches = pageHeight - (2 * margin) - heightForExistingItemsOnPage;
var remainingHeightPixels = (remainingHeightInches * imageResolution) - imageInsertMarginPixels;
if (!isPDF)
{ {
// Save image as jpg
var convertedDir = Path.Combine(folderPath, "converted");
if (!Directory.Exists(convertedDir))
{
Directory.CreateDirectory(convertedDir);
}
var outputPath = Path.Combine(convertedDir, info.Name + ".jpg");
using var mImage = new MagickImage(info.FullName); using var mImage = new MagickImage(info.FullName);
loadedImageWidth = mImage.Width; loadedImageWidth = mImage.Width;
loadedImageHeight = mImage.Height; loadedImageHeight = mImage.Height;
mImage.Quality = 80; var convertedOutputPath = Path.Combine(convertedDir, info.Name + ".jpg");
if (mImage.Width >= 400 || mImage.Height >= 400) var didAdjust = false;
LogInfo("Image orientation of {0} is {1}", fileName, mImage.Orientation);
if (mImage.Orientation != OrientationType.TopLeft)
{ {
loadedImageWidth = (uint)Math.Floor(mImage.Width * 0.5); LogInfo("Auto-adjusted image orientation of {0}", fileName);
loadedImageHeight = (uint)Math.Floor(mImage.Height * 0.5); mImage.AutoOrient();
mImage.Scale(loadedImageWidth, loadedImageHeight); didAdjust = true;
} }
await mImage.WriteAsync(outputPath); // perform needed image manipulations
filePath = Path.Combine("Converted", info.Name + ".jpg"); if (isHEIC || isWebp || isPNG || (!isPDF && info.Length > _settings.ImageResizeThreshold * 1024 * 1024))
LogInfo(string.Format("Converted image to JPEG; fileName is now {0}", file.FilePath)); {
// 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 = convertedOutputPath;
LogInfo(string.Format("Saved adjusted image to JPEG; file path is now {0}", filePath));
}
// write to PDF
var paragraph = section.AddParagraph();
paragraph.Format.Alignment = ParagraphAlignment.Center;
var image = paragraph.AddImage(filePath);
image.Resolution = imageResolution; // dots per inch
image.Tag = "ReceiptImageTag";
paragraph.Tag = "ReceiptImageParagraphTag";
image.LineFormat = imageLineFormat.Clone();
// resize down until it will fit on the page
while (loadedImageHeight > remainingHeightPixels || loadedImageWidth > maxItemPxWidth)
{
// Console.WriteLine("Image height = {0}, width = {1}; decreasing size by 5% to h={2}, w={3}", loadedImageHeight, loadedImageWidth, (uint)Math.Floor(loadedImageHeight * 0.95), (uint)Math.Floor(loadedImageWidth * 0.95));
// keep reducing size by 5% (little by little) until it fits on the page
// ...might skew ever so slightly but should not be noticable...
loadedImageHeight = (uint)Math.Floor(loadedImageHeight * 0.95);
loadedImageWidth = (uint)Math.Floor(loadedImageWidth * 0.95);
}
image.Height = loadedImageHeight;
image.Width = loadedImageWidth;
} }
else if (!isPDF) else // isPDF
{ {
// load height/width // need to render PDF to images
using var mImage = new MagickImage(info.FullName); if (_settings.UseDocnetPDFImageRendering)
loadedImageWidth = mImage.Width; {
loadedImageHeight = mImage.Height; // render using Docnet library (which utilizes pdfium, the chrome renderer)
} string RenderPdfPageToImage(IDocReader docReader, int pgNum)
var paragraph = section.AddParagraph(); {
paragraph.Format.Alignment = ParagraphAlignment.Center; Console.WriteLine("Rendering pg " + pgNum);
var image = paragraph.AddImage(filePath); using var pageReader = docReader.GetPageReader(pgNum);
image.LockAspectRatio = true; Console.WriteLine("Getting image for page " + pgNum);
if (!isPDF && loadedImageHeight > 600) var rawBytes = pageReader.GetImage(RenderFlags.RenderAnnotations);
{ Console.WriteLine("Getting width & height for page " + pgNum);
image.Height = 550; // make sure it will fit on one page var width = pageReader.GetPageWidth();
} var height = pageReader.GetPageHeight();
else Console.WriteLine("Loading pixel data for page " + pgNum);
{ using var img = Image.LoadPixelData<Bgra32>(rawBytes, width, height);
image.Width = imageWidth; // can't be too wide now...not sure why...maybe due to margins... // you are likely going to want this as well otherwise you might end up with transparent parts.
img.Mutate(x => x.BackgroundColor(SixLabors.ImageSharp.Color.White));
var pdfPageImageOutputPath = Path.Combine(convertedDir, info.Name + "-Page-"
+ (pgNum + 1).ToString().PadLeft(3, '0') + ".jpg");
img.Save(pdfPageImageOutputPath);
Console.WriteLine("Done rendering pg " + pgNum);
return pdfPageImageOutputPath;
}
// render all pages to images
var docReader = DocLib.Instance.GetDocReader(
filePath,
new PageDimensions(1080, 1920)); // TODO: are these dims right?
// add to document
var pgCount = docReader.GetPageCount();
if (pgCount > 0)
{
var convertedPdfImagePath = RenderPdfPageToImage(docReader, 0);
imageTitlePar.AddText(string.Format(" (PDF with {0} page{1}) ",
pgCount,
pgCount == 1 ? "" : "s"));
var paragraph = section.AddParagraph();
paragraph.Format.Alignment = ParagraphAlignment.Center;
// get image height/width off of disk so we can resize down if needed
var image = paragraph.AddImage(convertedPdfImagePath);
image.LockAspectRatio = true;
image.LineFormat = imageLineFormat.Clone();
using (var firstPdfPageImage = new MagickImage(convertedPdfImagePath))
{
var pdfPageImageWidth = firstPdfPageImage.Width;
var pdfPageImageHeight = firstPdfPageImage.Height;
// resize down until it will fit on the page
while (pdfPageImageHeight > remainingHeightPixels || pdfPageImageWidth > maxItemPxWidth)
{
pdfPageImageHeight = (uint)Math.Floor(pdfPageImageHeight * 0.95);
pdfPageImageWidth = (uint)Math.Floor(pdfPageImageWidth * 0.95);
}
image.Height = pdfPageImageHeight;
image.Width = pdfPageImageWidth;
}
for (var j = 1; j < pgCount; j++)
{
section.AddPageBreak();
paragraph = section.AddParagraph();
paragraph.Format.Alignment = ParagraphAlignment.Center;
convertedPdfImagePath = RenderPdfPageToImage(docReader, j);
image = paragraph.AddImage(convertedPdfImagePath);
image.LockAspectRatio = true;
image.Width = maxImageWidth;
image.LineFormat = imageLineFormat.Clone();
using (var otherPdfPageImage = new MagickImage(convertedPdfImagePath))
{
var pdfPageImageWidth = otherPdfPageImage.Width;
var pdfPageImageHeight = otherPdfPageImage.Height;
// resize down until it will fit on the page
while (pdfPageImageHeight > remainingHeightPixels || pdfPageImageWidth > maxItemPxWidth)
{
pdfPageImageHeight = (uint)Math.Floor(pdfPageImageHeight * 0.95);
pdfPageImageWidth = (uint)Math.Floor(pdfPageImageWidth * 0.95);
}
image.Height = pdfPageImageHeight;
image.Width = pdfPageImageWidth;
}
}
}
}
else
{
// use older, not-docnet rendering method.
// uses MigraDoc rendering. Does not work with annotations, and since Migradoc
// doesn't let us know how big the image is, we can't do the image resizing, so
// we just do our best.
// render first page (eventually need to improve code to just do everything in a loop)
var paragraph = section.AddParagraph();
paragraph.Format.Alignment = ParagraphAlignment.Center;
var image = paragraph.AddImage(filePath);
image.LockAspectRatio = true;
image.Width = maxImageWidth; // can't be too wide now...not sure why...maybe due to margins...
image.LineFormat = imageLineFormat.Clone();
// render other PDF pages, if any
// see: https://stackoverflow.com/a/65091204/3938401
var pdfFileToAdd = PdfReader.Open(filePath, PdfDocumentOpenMode.Import);
var pgCount = pdfFileToAdd.PageCount;
imageTitlePar.AddText(string.Format(" (PDF with {0} page{1}) ",
pgCount,
pgCount == 1 ? "" : "s"));
for (var j = 2; j <= pgCount; j++)
{
section.AddPageBreak();
paragraph = section.AddParagraph();
paragraph.Format.Alignment = ParagraphAlignment.Center;
image = paragraph.AddImage(filePath + "#" + j);
image.LockAspectRatio = true;
image.Width = maxImageWidth;
image.LineFormat = imageLineFormat.Clone();
}
}
} }
LogInfo(string.Format("Added image: {0} ({1})", file.Title, filePath)); LogInfo(string.Format("Added image: {0} ({1})", file.Title, filePath));
if (isPDF)
{
// add other PDF pages
// see: https://stackoverflow.com/a/65091204/3938401
var pdfFileToAdd = PdfReader.Open(filePath);
imageTitlePar.AddText(string.Format(" (PDF with {0} page{1}) ",
pdfFileToAdd.PageCount,
pdfFileToAdd.PageCount == 1 ? "" : "s"));
for (var j = 2; j <= pdfFileToAdd.PageCount; j++)
{
section.AddPageBreak();
paragraph = section.AddParagraph();
paragraph.Format.Alignment = ParagraphAlignment.Center;
image = paragraph.AddImage(filePath + "#" + j);
image.LockAspectRatio = true;
image.Width = imageWidth;
}
}
hasAddedData = true; hasAddedData = true;
} }
var pdfRenderer = new PdfDocumentRenderer LogInfo("Rendering document to PDF file...");
{ pdfRenderer.DocumentRenderer.PrepareDocument(); // needed if you make edits after first PrepareDocument() is called
Document = pdfDoc,
WorkingDirectory = folderPath
};
LogInfo("Rendering document...");
pdfRenderer.RenderDocument(); pdfRenderer.RenderDocument();
string outputPDFFileName = Path.Join(folderPath, outputFileName); // actually save to disk now
LogInfo("Saving document to disk..."); string outputPDFFilePath = Path.Join(outputDir, outputFileName);
pdfRenderer.PdfDocument.Save(outputPDFFileName); LogInfo("Saving PDF document to disk...");
LogInfo("Saved PDF output to: " + outputPDFFileName); pdfRenderer.PdfDocument.Save(outputPDFFilePath);
LogInfo("Finished saving PDF output to: " + outputPDFFilePath);
await CreateAndSaveReportObjectAfterReportCreation(); await CreateAndSaveReportObjectAfterReportCreation();
OpenFolderForFileInFileViewer(outputPDFFileName); // clean up data dir
Directory.Delete(convertedDir, true);
// show output folder to user
OpenFolderForFileInFileViewer(outputPDFFilePath);
IsCreatingPDF = false; IsCreatingPDF = false;
} }
+38 -39
View File
@@ -4,47 +4,46 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
namespace MayShow.ViewModels namespace MayShow.ViewModels;
class MainWindowViewModel : ChangeNotifier, IChangeViewModel
{ {
class MainWindowViewModel : ChangeNotifier, IChangeViewModel BaseViewModel _currentViewModel;
Stack<BaseViewModel> _viewModels;
public MainWindowViewModel(ITopLevelGrabber topLevelGrabber)
{ {
BaseViewModel _currentViewModel; _viewModels = new Stack<BaseViewModel>();
Stack<BaseViewModel> _viewModels; var initialViewModel = new MainViewModel(this)
public MainWindowViewModel(ITopLevelGrabber topLevelGrabber)
{ {
_viewModels = new Stack<BaseViewModel>(); TopLevelGrabber = topLevelGrabber
var initialViewModel = new MainViewModel(this) };
{ _viewModels.Push(initialViewModel);
TopLevelGrabber = topLevelGrabber _currentViewModel = initialViewModel;
};
_viewModels.Push(initialViewModel);
_currentViewModel = initialViewModel;
}
public BaseViewModel CurrentViewModel
{
get { return _currentViewModel; }
set { _currentViewModel = value; NotifyPropertyChanged(); }
}
#region IChangeViewModel
public void PushViewModel(BaseViewModel model)
{
_viewModels.Push(model);
CurrentViewModel = model;
}
public void PopViewModel()
{
if (_viewModels.Count > 1)
{
_viewModels.Pop();
CurrentViewModel = _viewModels.Peek();
}
}
#endregion
} }
public BaseViewModel CurrentViewModel
{
get { return _currentViewModel; }
set { _currentViewModel = value; NotifyPropertyChanged(); }
}
#region IChangeViewModel
public void PushViewModel(BaseViewModel model)
{
_viewModels.Push(model);
CurrentViewModel = model;
}
public void PopViewModel()
{
if (_viewModels.Count > 1)
{
_viewModels.Pop();
CurrentViewModel = _viewModels.Peek();
}
}
#endregion
} }
+200
View File
@@ -0,0 +1,200 @@
#nullable enable
using System;
using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Platform.Storage;
using Avalonia.Themes.Fluent;
using DialogHostAvalonia;
using ImageMagick;
using MigraDoc.DocumentObjectModel;
using MigraDoc.Rendering;
using PdfSharp.Fonts;
using PdfSharp.Pdf.IO;
using PdfSharp.Snippets.Font;
using MayShow.Interfaces;
using MayShow.Models;
using MayShow.Helpers;
using System.Collections.Generic;
namespace MayShow.ViewModels;
class SettingsViewModel: ChangeNotifier
{
private Settings _previousSettings;
private Settings _settings;
private string _errorMessage;
private ITopLevelGrabber? _topLevelGrabber;
private List<DateDisplayFormat> _dateFormats;
private int _gridDisplayDateFormatSelectedIndex;
private int _reportDisplayDateFormatSelectedIndex;
public SettingsViewModel(Settings settingsToEdit, ITopLevelGrabber? topLevelGrabber)
{
_previousSettings = settingsToEdit;
_settings = new Settings(settingsToEdit); // clone it
_errorMessage = "";
_topLevelGrabber = topLevelGrabber;
_dateFormats = Constants.GetDateDisplayFormats();
_gridDisplayDateFormatSelectedIndex = _dateFormats.FindIndex(x => x.Value == _previousSettings.DataGridDateFormat);
if (_gridDisplayDateFormatSelectedIndex == -1)
{
_gridDisplayDateFormatSelectedIndex = 0;
}
_reportDisplayDateFormatSelectedIndex = _dateFormats.FindIndex(x => x.Value == _previousSettings.ReportDateFormat);
if (_reportDisplayDateFormatSelectedIndex == -1)
{
_reportDisplayDateFormatSelectedIndex = 0;
}
}
public bool UseDocnetPDFImageRendering
{
get => _settings.UseDocnetPDFImageRendering;
set
{
_settings.UseDocnetPDFImageRendering = value;
NotifyPropertyChanged();
}
}
public bool SaveOutputPdfInWorkingDir
{
get => _settings.SaveOutputPdfInWorkingDir;
set
{
_settings.SaveOutputPdfInWorkingDir = value;
NotifyPropertyChanged();
}
}
public string OutputPdfDirPath
{
get => _settings.OutputPdfDir;
set
{
_settings.OutputPdfDir = value;
NotifyPropertyChanged();
NotifyPropertyChanged(nameof(IsOutputPdfDirValid));
}
}
public bool IsOutputPdfDirValid
{
get => SaveOutputPdfInWorkingDir || (!SaveOutputPdfInWorkingDir && Directory.Exists(OutputPdfDirPath));
}
public bool HasErrorMessage
{
get => !string.IsNullOrWhiteSpace(_errorMessage);
}
public string ErrorMessage
{
get => _errorMessage;
set
{
_errorMessage = value;
NotifyPropertyChanged();
NotifyPropertyChanged(nameof(HasErrorMessage));
}
}
public decimal ImageResizeThreshold
{
get => _settings.ImageResizeThreshold;
set
{
_settings.ImageResizeThreshold = value;
NotifyPropertyChanged();
}
}
public bool SaveReportJsonDataInInternalDir
{
get => _settings.SaveReportJsonDataInInternalDir;
set
{
_settings.SaveReportJsonDataInInternalDir = value;
NotifyPropertyChanged();
}
}
public List<DateDisplayFormat> DateFormats
{
get => _dateFormats;
}
public int DataGridDisplayDateFormatSelectedIndex
{
get => _gridDisplayDateFormatSelectedIndex;
set
{
_gridDisplayDateFormatSelectedIndex = value;
_settings.DataGridDateFormat = _dateFormats[value].Value;
NotifyPropertyChanged();
}
}
public int ReportDisplayDateFormatSelectedIndex
{
get => _reportDisplayDateFormatSelectedIndex;
set
{
_reportDisplayDateFormatSelectedIndex = value;
_settings.ReportDateFormat = _dateFormats[value].Value;
NotifyPropertyChanged();
}
}
public async void ChooseOutputFolder()
{
var topLevel = _topLevelGrabber?.GetTopLevel();
if (topLevel != null)
{
var folders = await topLevel.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions()
{
Title = "Choose where to save your report file...",
AllowMultiple = false,
});
if (folders.Count == 1)
{
var folder = folders[0];
OutputPdfDirPath = folder.Path.LocalPath;
}
}
}
public void OpenSettingsDir()
{
var topLevel = _topLevelGrabber?.GetTopLevel();
Console.WriteLine(Utilities.GetInternalDataPath());
var dirName = Utilities.GetInternalDataPath();
if (topLevel is not null && dirName != null)
{
var launcher = topLevel.Launcher;
launcher.LaunchUriAsync(new Uri(dirName));
}
}
public void Cancel()
{
DialogHost.Close("DialogHost", null);
}
public void Save()
{
if (!IsOutputPdfDirValid)
{
ErrorMessage = "Output directory cannot be found!";
}
else
{
ErrorMessage = "";
DialogHost.Close("DialogHost", _settings);
}
}
}
+19 -20
View File
@@ -2,30 +2,29 @@
using MayShow.Helpers; using MayShow.Helpers;
using MayShow.Models; using MayShow.Models;
namespace MayShow.ViewModels namespace MayShow.ViewModels;
class WarningDeleteItemViewModel : ChangeNotifier
{ {
class WarningDeleteItemViewModel : ChangeNotifier ReportFile _file;
public WarningDeleteItemViewModel(ReportFile file)
{ {
ReportFile _file; _file = file;
}
public WarningDeleteItemViewModel(ReportFile file) public ReportFile File
{ {
_file = file; get => _file;
} }
public ReportFile File public void KeepItem()
{ {
get => _file; DialogHost.Close("DialogHost", false);
} }
public void KeepItem() public void RemoveItem()
{ {
DialogHost.Close("DialogHost", false); DialogHost.Close("DialogHost", true);
}
public void RemoveItem()
{
DialogHost.Close("DialogHost", true);
}
} }
} }
+13 -1
View File
@@ -11,7 +11,7 @@
MaxWidth="450"> MaxWidth="450">
<StackPanel Orientation="Vertical" <StackPanel Orientation="Vertical"
Spacing="4"> Spacing="4">
<TextBlock Text="MayShow" <TextBlock Text="MayShow 1.4.3"
HorizontalAlignment="Center" HorizontalAlignment="Center"
TextWrapping="Wrap" TextWrapping="Wrap"
FontSize="18" FontSize="18"
@@ -22,6 +22,18 @@
<TextBlock Text="App icon made using https://gauger.me/fonticon/ with FontAwesome icon 'file-invoice-dollar' and the macOS software Icon Composer." <TextBlock Text="App icon made using https://gauger.me/fonticon/ with FontAwesome icon 'file-invoice-dollar' and the macOS software Icon Composer."
TextWrapping="Wrap" TextWrapping="Wrap"
FontSize="14"/> FontSize="14"/>
<Label Content="Open Source Licenses:"/>
<ScrollViewer Margin="2"
Grid.Row="4"
x:Name="LogScrollView"
VerticalScrollBarVisibility="Visible"
AllowAutoHide="False"
MaxHeight="250">
<SelectableTextBlock Margin="2"
TextWrapping="Wrap"
FontSize="10"
x:Name="LicenseTextBlock"/>
</ScrollViewer>
<TextBlock Text="Copyright 2026 - Quickity Quack Productions" <TextBlock Text="Copyright 2026 - Quickity Quack Productions"
TextWrapping="Wrap" TextWrapping="Wrap"
HorizontalAlignment="Center" HorizontalAlignment="Center"
+27 -4
View File
@@ -1,15 +1,38 @@
using System; using System;
using System.Diagnostics;
using System.IO;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
namespace MayShow.Views namespace MayShow.Views;
public partial class AboutView : UserControl
{ {
public partial class AboutView : UserControl public AboutView()
{ {
public AboutView() this.InitializeComponent();
// set license text
var processDir = Path.GetDirectoryName(Environment.ProcessPath) ?? "";
var licenseFileName = Path.Combine(processDir, "Assets", "LICENSES.txt");
var licenseText = "";
if (File.Exists(licenseFileName))
{ {
this.InitializeComponent(); licenseText = File.ReadAllText(licenseFileName);
} }
else
{
licenseFileName = Path.Combine(processDir, "../Resources/Assets/LICENSES.txt");
if (File.Exists(licenseFileName))
{
licenseText = File.ReadAllText(licenseFileName);
}
else
{
licenseText = "Error: Unable to find license file!";
}
}
LicenseTextBlock.Text = licenseText.Trim();
} }
} }
+35
View File
@@ -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>
+14
View File
@@ -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 -6
View File
@@ -3,13 +3,12 @@ using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
namespace MayShow.Views namespace MayShow.Views;
public partial class EditFile : UserControl
{ {
public partial class EditFile : UserControl public EditFile()
{ {
public EditFile() this.InitializeComponent();
{
this.InitializeComponent();
}
} }
} }
+63 -26
View File
@@ -10,17 +10,28 @@
xmlns:vm="clr-namespace:MayShow.ViewModels" xmlns:vm="clr-namespace:MayShow.ViewModels"
xmlns:progRing="clr-namespace:AvaloniaProgressRing;assembly=AvaloniaProgressRing" xmlns:progRing="clr-namespace:AvaloniaProgressRing;assembly=AvaloniaProgressRing"
x:DataType="vm:MainViewModel"> x:DataType="vm:MainViewModel">
<UserControl.Resources>
<helpers:DateFormatConverter x:Key="DateFormatter" />
</UserControl.Resources>
<Grid ColumnDefinitions="*" <Grid ColumnDefinitions="*"
RowDefinitions="Auto, 2*, Auto, Auto, *"> RowDefinitions="Auto, 2*, Auto, Auto, *">
<Button Command="{Binding ShowSettings}"
Grid.Row="0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="4,4,0,4">
<TextBlock><Run Text="&#xf013;" FontFamily="{StaticResource FontAwesomeSolid}"/> Settings</TextBlock>
</Button>
<Button Command="{Binding ShowAbout}" <Button Command="{Binding ShowAbout}"
Grid.Row="0" Grid.Row="0"
HorizontalAlignment="Right" HorizontalAlignment="Right"
VerticalAlignment="Top" VerticalAlignment="Top"
Margin="0,2,4,0"> Margin="0,4,4,4">
<TextBlock><Run Text="&#xf059;" FontFamily="{StaticResource FontAwesomeSolid}"/> About</TextBlock> <TextBlock><Run Text="&#xf059;" FontFamily="{StaticResource FontAwesomeSolid}"/> About</TextBlock>
</Button> </Button>
<StackPanel Orientation="Vertical" <StackPanel Orientation="Vertical"
Spacing="2"> Spacing="2"
Margin="0,4,0,0">
<Label Content="MayShow: Report Builder" <Label Content="MayShow: Report Builder"
FontSize="20" FontSize="20"
FontWeight="Bold" FontWeight="Bold"
@@ -89,7 +100,7 @@
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate> <DataTemplate>
<Grid ColumnDefinitions="Auto, *"> <Grid ColumnDefinitions="Auto, *">
<Button Command="{Binding $parent[DataGrid].((vm:MainViewModel)DataContext).LocateFile}" <Button Command="{Binding $parent[UserControl].((vm:MainViewModel)DataContext).LocateFile}"
CommandParameter="{Binding}" CommandParameter="{Binding}"
IsVisible="{Binding !IsFileFoundOnDisk}" IsVisible="{Binding !IsFileFoundOnDisk}"
Margin="2" Margin="2"
@@ -99,7 +110,7 @@
Grid.Column="0" Grid.Column="0"
FontFamily="{StaticResource FontAwesomeSolid}" FontFamily="{StaticResource FontAwesomeSolid}"
ToolTip.Tip="File not found; click to locate..." ToolTip.Tip="File not found; click to locate..."
IsEnabled="{Binding !$parent[DataGrid].((vm:MainViewModel)DataContext).IsCreatingPDF}"/> IsEnabled="{Binding !$parent[UserControl].((vm:MainViewModel)DataContext).IsCreatingPDF}"/>
<TextBlock Text="{Binding Title}" <TextBlock Text="{Binding Title}"
TextTrimming="CharacterEllipsis" TextTrimming="CharacterEllipsis"
TextWrapping="NoWrap" TextWrapping="NoWrap"
@@ -125,19 +136,29 @@
</DataGridTemplateColumn> </DataGridTemplateColumn>
<DataGridTemplateColumn Header="Receipt Date" <DataGridTemplateColumn Header="Receipt Date"
IsReadOnly="False" IsReadOnly="False"
Width="125"> Width="150">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate> <DataTemplate>
<Label Content="{Binding ReceiptDate}" <Label VerticalAlignment="Center"
VerticalAlignment="Center"
Margin="8,0,8,0" Margin="8,0,8,0"
HorizontalAlignment="Left"/> HorizontalAlignment="Left">
<Label.Content>
<MultiBinding Converter="{StaticResource DateFormatter}">
<Binding Path="ReceiptDate" />
<Binding Path="$parent[UserControl].((vm:MainViewModel)DataContext).DataGridDateFormat" />
</MultiBinding>
</Label.Content>
</Label>
</DataTemplate> </DataTemplate>
</DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate> <DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate DataType="models:ReportFile"> <DataTemplate DataType="models:ReportFile">
<CalendarDatePicker SelectedDate="{Binding ReceiptDateTime}" <CalendarDatePicker SelectedDate="{Binding ReceiptDateTime}"
DisplayDate="{Binding ReceiptDateTime}"/> DisplayDate="{Binding ReceiptDateTime}"
SelectedDateFormat="Custom"
HorizontalAlignment="Stretch"
Watermark="{Binding $parent[UserControl].((vm:MainViewModel)DataContext).DataGridDateFormatWatermark}"
CustomDateFormatString="{Binding $parent[UserControl].((vm:MainViewModel)DataContext).DataGridDateFormat}"/>
</DataTemplate> </DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate> </DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn> </DataGridTemplateColumn>
@@ -148,6 +169,7 @@
<DataTemplate> <DataTemplate>
<TextBlock Text="{Binding FileName}" <TextBlock Text="{Binding FileName}"
VerticalAlignment="Center" VerticalAlignment="Center"
TextTrimming="PrefixCharacterEllipsis"
ToolTip.Tip="{Binding FileName}" ToolTip.Tip="{Binding FileName}"
Margin="8,0,8,0" Margin="8,0,8,0"
HorizontalAlignment="Left"/> HorizontalAlignment="Left"/>
@@ -156,25 +178,27 @@
</DataGridTemplateColumn> </DataGridTemplateColumn>
<DataGridTemplateColumn Header="" <DataGridTemplateColumn Header=""
IsReadOnly="True" IsReadOnly="True"
Width="*"> Width="200">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate> <DataTemplate>
<StackPanel Orientation="Horizontal" <StackPanel Orientation="Horizontal"
Spacing="4"> Spacing="4">
<Button Command="{Binding $parent[DataGrid].((vm:MainViewModel)DataContext).EditFileProperties}" <Button Command="{Binding $parent[UserControl].((vm:MainViewModel)DataContext).EditFileProperties}"
CommandParameter="{Binding}" CommandParameter="{Binding}"
Classes="accent" Classes="accent"
Margin="2" Margin="2"
IsEnabled="{Binding !$parent[DataGrid].((vm:MainViewModel)DataContext).IsCreatingPDF}"> FontSize="12"
IsEnabled="{Binding !$parent[UserControl].((vm:MainViewModel)DataContext).IsCreatingPDF}">
<Button.Content> <Button.Content>
<TextBlock><Run Text="&#xf044;" FontFamily="{StaticResource FontAwesomeSolid}"/> Edit</TextBlock> <TextBlock><Run Text="&#xf044;" FontFamily="{StaticResource FontAwesomeSolid}"/> Edit</TextBlock>
</Button.Content> </Button.Content>
</Button> </Button>
<Button Command="{Binding $parent[DataGrid].((vm:MainViewModel)DataContext).RemoveFile}" <Button Command="{Binding $parent[UserControl].((vm:MainViewModel)DataContext).RemoveFile}"
CommandParameter="{Binding}" CommandParameter="{Binding}"
Classes="Danger" Classes="Danger"
Margin="2" Margin="2"
IsEnabled="{Binding !$parent[DataGrid].((vm:MainViewModel)DataContext).IsCreatingPDF}"> FontSize="12"
IsEnabled="{Binding !$parent[UserControl].((vm:MainViewModel)DataContext).IsCreatingPDF}">
<Button.Content> <Button.Content>
<TextBlock><Run Text="&#xf1f8;" FontFamily="{StaticResource FontAwesomeSolid}"/> Remove</TextBlock> <TextBlock><Run Text="&#xf1f8;" FontFamily="{StaticResource FontAwesomeSolid}"/> Remove</TextBlock>
</Button.Content> </Button.Content>
@@ -198,13 +222,13 @@
Spacing="8" Spacing="8"
Margin="4" Margin="4"
Grid.Row="2"> Grid.Row="2">
<Button Command="{Binding $parent[DataGrid].((vm:MainViewModel)DataContext).OpenFileLocation}" <Button Command="{Binding $parent[UserControl].((vm:MainViewModel)DataContext).OpenFileLocation}"
CommandParameter="{Binding}"> CommandParameter="{Binding}">
<Button.Content> <Button.Content>
<TextBlock FontSize="12"><Run Text="&#xf07c;" FontFamily="{StaticResource FontAwesomeSolid}"/> Open File Location</TextBlock> <TextBlock FontSize="12"><Run Text="&#xf07c;" FontFamily="{StaticResource FontAwesomeSolid}"/> Open File Location</TextBlock>
</Button.Content> </Button.Content>
</Button> </Button>
<Button Command="{Binding $parent[DataGrid].((vm:MainViewModel)DataContext).OpenFile}" <Button Command="{Binding $parent[UserControl].((vm:MainViewModel)DataContext).OpenFile}"
CommandParameter="{Binding}"> CommandParameter="{Binding}">
<Button.Content> <Button.Content>
<TextBlock FontSize="12"><Run Text="&#xf07c;" FontFamily="{StaticResource FontAwesomeSolid}"/> Open File</TextBlock> <TextBlock FontSize="12"><Run Text="&#xf07c;" FontFamily="{StaticResource FontAwesomeSolid}"/> Open File</TextBlock>
@@ -226,24 +250,28 @@
IsEnabled="{Binding CanAddItem}"> IsEnabled="{Binding CanAddItem}">
<TextBlock><Run Text="&#x002b;" FontFamily="{StaticResource FontAwesomeSolid}"/> Add Item(s)</TextBlock> <TextBlock><Run Text="&#x002b;" FontFamily="{StaticResource FontAwesomeSolid}"/> Add Item(s)</TextBlock>
</Button> </Button>
<Button Command="{Binding SaveInterimReportInfo}" <Button Command="{Binding RemoveAllItems}"
IsEnabled="{Binding HasWorkingFolderAndNotMakingPDF}"> IsEnabled="{Binding IsCreatePDFButtonEnabled}"
<TextBlock><Run Text="&#xf0c7;" FontFamily="{StaticResource FontAwesomeSolid}"/> Save Report Info</TextBlock> Classes="Danger">
<TextBlock><Run Text="&#xf1f8;" FontFamily="{StaticResource FontAwesomeSolid}"/> Remove All Items</TextBlock>
</Button> </Button>
<Button Command="{Binding ResortPDFItemsByDate}" <Button Command="{Binding ResortPDFItemsByDate}"
IsEnabled="{Binding IsCreatePDFButtonEnabled}"> IsEnabled="{Binding IsCreatePDFButtonEnabled}">
<TextBlock><Run Text="&#xf162;" FontFamily="{StaticResource FontAwesomeSolid}"/> Re-sort PDF Items</TextBlock> <TextBlock><Run Text="&#xf162;" FontFamily="{StaticResource FontAwesomeSolid}"/> Re-sort PDF Items</TextBlock>
</Button> </Button>
<Button Command="{Binding BuildPDF}" <Button Command="{Binding SaveInterimReportInfo}"
Classes="accent" IsEnabled="{Binding HasWorkingFolderAndNotMakingPDF}">
IsEnabled="{Binding IsCreatePDFButtonEnabled}"> <TextBlock><Run Text="&#xf0c7;" FontFamily="{StaticResource FontAwesomeSolid}"/> Save Report Info</TextBlock>
<TextBlock><Run Text="&#xf1c1;" FontFamily="{StaticResource FontAwesomeSolid}"/> Create Report PDF</TextBlock>
</Button> </Button>
</StackPanel> </StackPanel>
<StackPanel Orientation="Horizontal" <StackPanel Orientation="Horizontal"
IsVisible="{Binding IsCreatingPDF}"
Spacing="6" Spacing="6"
HorizontalAlignment="Center"> HorizontalAlignment="Center">
<Button Command="{Binding BuildPDF}"
Classes="accent"
IsEnabled="{Binding IsCreatePDFButtonEnabled}">
<TextBlock><Run Text="&#xf1c1;" FontFamily="{StaticResource FontAwesomeSolid}"/> Create Report PDF</TextBlock>
</Button>
<Label Content="Creating PDF..." <Label Content="Creating PDF..."
IsVisible="{Binding IsCreatingPDF}" IsVisible="{Binding IsCreatingPDF}"
VerticalAlignment="Center"/> VerticalAlignment="Center"/>
@@ -258,14 +286,23 @@
Spacing="2" Spacing="2"
Grid.Row="3"> Grid.Row="3">
<Rectangle Fill="Gray" Height="3" HorizontalAlignment="Stretch"/> <Rectangle Fill="Gray" Height="3" HorizontalAlignment="Stretch"/>
<Label Content="Program Log" FontSize="14" FontWeight="Bold"/> <Grid ColumnDefinitions="Auto, *">
<Label Content="Program Log" FontSize="14" FontWeight="Bold" Grid.Column="0"/>
<Button Command="{Binding CopyLogToClipboard}"
FontSize="10"
Grid.Column="1"
HorizontalAlignment="Right"
Margin="0,2,8,2">
<TextBlock><Run Text="&#xf0c5;" FontFamily="{StaticResource FontAwesomeSolid}"/> Copy Program Log to Clipboard</TextBlock>
</Button>
</Grid>
</StackPanel> </StackPanel>
<ScrollViewer Margin="2" <ScrollViewer Margin="2"
Grid.Row="4" Grid.Row="4"
x:Name="LogScrollView" x:Name="LogScrollView"
VerticalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Visible"
AllowAutoHide="False"> AllowAutoHide="False">
<SelectableTextBlock Text="{Binding CreatePDFLog}" <SelectableTextBlock Text="{Binding ProgramLog}"
Margin="2" Margin="2"
TextWrapping="Wrap" TextWrapping="Wrap"
x:Name="LogBlock"/> x:Name="LogBlock"/>
+24 -25
View File
@@ -5,41 +5,40 @@ using Avalonia.Input;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using MayShow.ViewModels; using MayShow.ViewModels;
namespace MayShow.Views namespace MayShow.Views;
public partial class MainView : UserControl
{ {
public partial class MainView : UserControl public MainView()
{ {
public MainView() this.InitializeComponent();
{ LogBlock.PropertyChanged += LogBlock_PropertyChanged;
this.InitializeComponent(); FilesGrid.CellEditEnded += FileCellEditEnded;
LogBlock.PropertyChanged += LogBlock_PropertyChanged; }
FilesGrid.CellEditEnded += FileCellEditEnded;
}
private void LogBlock_PropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) private void LogBlock_PropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property.ToString() == "Text")
{ {
if (e.Property.ToString() == "Text") LogScrollView.ScrollToEnd();
{
LogScrollView.ScrollToEnd();
}
} }
}
public void UnfocusTextbox() public void UnfocusTextbox()
{
var topLevel = TopLevel.GetTopLevel(this);
topLevel?.FocusManager?.ClearFocus();
if (DataContext is MainViewModel mvm)
{ {
var topLevel = TopLevel.GetTopLevel(this); mvm?.HasUnsavedWork = true;
topLevel?.FocusManager?.ClearFocus();
if (DataContext is MainViewModel mvm)
{
mvm?.HasUnsavedWork = true;
}
} }
}
private void FileCellEditEnded(object? sender, DataGridCellEditEndedEventArgs args) private void FileCellEditEnded(object? sender, DataGridCellEditEndedEventArgs args)
{
if (args.EditAction == DataGridEditAction.Commit && DataContext is MainViewModel mvm)
{ {
if (args.EditAction == DataGridEditAction.Commit && DataContext is MainViewModel mvm) mvm?.HasUnsavedWork = true;
{
mvm?.HasUnsavedWork = true;
}
} }
} }
} }
+103
View File
@@ -0,0 +1,103 @@
<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.SettingsView"
xmlns:models="clr-namespace:MayShow.Models"
xmlns:vm="clr-namespace:MayShow.ViewModels"
x:DataType="vm:SettingsViewModel"
MaxWidth="450">
<ScrollViewer AllowAutoHide="False">
<StackPanel Orientation="Vertical"
Spacing="8"
Margin="12,4,12,4">
<Label Content="Settings"
HorizontalAlignment="Center"
FontSize="16"
FontWeight="Bold" />
<TextBlock TextWrapping="Wrap"
HorizontalAlignment="Left"
Width="350">
Always reduce size of images if above this file size in megabytes (MB):
</TextBlock>
<NumericUpDown Value="{Binding ImageResizeThreshold}"
Increment="0.1"
Minimum="0.5"
FormatString="0.00"
Maximum="10"
Width="150"
HorizontalAlignment="Left"
Margin="0,0,0,4" />
<CheckBox IsChecked="{Binding !UseDocnetPDFImageRendering}">Use legacy PDF handling (does not work with macOS annotations)</CheckBox>
<CheckBox IsChecked="{Binding SaveOutputPdfInWorkingDir}">Always save report PDF in working directory</CheckBox>
<Grid ColumnDefinitions="Auto, *"
IsVisible="{Binding !SaveOutputPdfInWorkingDir}">
<Button Content="Choose report output folder..."
Grid.Column="0"
Command="{Binding ChooseOutputFolder}"
VerticalAlignment="Top"/>
<TextBlock Text="{Binding OutputPdfDirPath}"
Margin="4,0,0,0"
TextWrapping="Wrap"
Grid.Column="1"
HorizontalAlignment="Left"
VerticalAlignment="Top"/>
</Grid>
<Label Content="File List Date Format"
HorizontalAlignment="Left"
FontSize="14" />
<ComboBox SelectedIndex="{Binding DataGridDisplayDateFormatSelectedIndex}"
ItemsSource="{Binding DateFormats}"
MaxDropDownHeight="300">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="models:DateDisplayFormat">
<TextBlock Text="{Binding Example}"
TextWrapping="Wrap"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Label Content="PDF Report Date Format"
HorizontalAlignment="Left"
FontSize="14" />
<ComboBox SelectedIndex="{Binding ReportDisplayDateFormatSelectedIndex}"
ItemsSource="{Binding DateFormats}"
MaxDropDownHeight="300">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="models:DateDisplayFormat">
<TextBlock Text="{Binding Example}"
TextWrapping="Wrap"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<CheckBox IsChecked="{Binding SaveReportJsonDataInInternalDir}">Save report data (names, notes, etc.) in MayShow settings directory (saves in working directory by default)</CheckBox>
<Button Command="{Binding OpenSettingsDir}">
<TextBlock>
<Run Text="&#xf07c;"
FontFamily="{StaticResource FontAwesomeSolid}" /> Open MayShow Settings Directory</TextBlock>
</Button>
<TextBlock TextWrapping="Wrap"
Foreground="Red"
Text="{Binding ErrorMessage}"
IsVisible="{Binding HasErrorMessage}"/>
<StackPanel Orientation="Horizontal"
Spacing="12"
Margin="0,4,0,0"
HorizontalAlignment="Right">
<Button Command="{Binding Cancel}">
<TextBlock>
<Run Text="&#xf0e2;"
FontFamily="{StaticResource FontAwesomeSolid}" /> Cancel</TextBlock>
</Button>
<Button Command="{Binding Save}"
Classes="accent">
<TextBlock>
<Run Text="&#xf0c7;"
FontFamily="{StaticResource FontAwesomeSolid}" /> Save</TextBlock>
</Button>
</StackPanel>
</StackPanel>
</ScrollViewer>
</UserControl>
+14
View File
@@ -0,0 +1,14 @@
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace MayShow.Views;
public partial class SettingsView : UserControl
{
public SettingsView()
{
this.InitializeComponent();
}
}
+5 -6
View File
@@ -3,13 +3,12 @@ using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
namespace MayShow.Views namespace MayShow.Views;
public partial class WarningDeleteItem : UserControl
{ {
public partial class WarningDeleteItem : UserControl public WarningDeleteItem()
{ {
public WarningDeleteItem() this.InitializeComponent();
{
this.InitializeComponent();
}
} }
} }
+1 -1
View File
@@ -18,7 +18,7 @@
FontWeight="Bold" FontWeight="Bold"
TextWrapping="Wrap" TextWrapping="Wrap"
FontSize="14" FontSize="14"
MaxWidth="350" MaxWidth="400"
Text="{Binding Error}"/> Text="{Binding Error}"/>
<Button Command="{Binding Close}" <Button Command="{Binding Close}"
Classes="accent" Classes="accent"
+5 -6
View File
@@ -3,13 +3,12 @@ using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
namespace MayShow.Views namespace MayShow.Views;
public partial class WarningView : UserControl
{ {
public partial class WarningView : UserControl public WarningView()
{ {
public WarningView() this.InitializeComponent();
{
this.InitializeComponent();
}
} }
} }
+1 -1
View File
@@ -3,7 +3,7 @@
<!-- This manifest is used on Windows only. <!-- This manifest is used on Windows only.
Don't remove it as it might cause problems with window transparency and embedded controls. 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 --> For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
<assemblyIdentity version="1.2.0.0" name="MayShow.Desktop"/> <assemblyIdentity version="1.4.3.0" name="MayShow.Desktop"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application> <application>