diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..8bd06d20 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,4 @@ +# These are supported funding model platforms + +github: VorlonCD + diff --git a/.gitignore b/.gitignore index 3c4efe20..b5c91243 100644 --- a/.gitignore +++ b/.gitignore @@ -258,4 +258,12 @@ paket-files/ # Python Tools for Visual Studio (PTVS) __pycache__/ -*.pyc \ No newline at end of file +*.pyc +/src/UI/.vshistory +/src/UI/NamedPipeWrapper/.vshistory +/src/UI/OSVersionExt/.vshistory +/src/UI/Properties/.vshistory/AssemblyInfo.cs +/src/UI/SQLite/.vshistory +/src/AITool.Service/.vshistory +/src/.vshistory/bi-aidetection.sln +/src/AITool.Service/Properties/.vshistory/AssemblyInfo.cs diff --git a/README.md b/README.md index 54d3456d..f8063a49 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,19 @@ # AI Detection for Blue Iris Alarm system for Blue Iris based on Artificial Intellience. Can send alerts to Telegram. +This is the VorlonCD fork of gentlepumpkins awesome AITool project. + +Changes are listed in the commit history (hit ... for each to expand): +https://github.com/VorlonCD/bi-aidetection/commits/master + ### Download -https://github.com/gentlepumpkin/bi-aidetection/releases -Click "> Assets" to find the .zip file. +https://github.com/VorlonCD/bi-aidetection/tree/master/src/UI/Installer ### Install guide and discussion https://ipcamtalk.com/threads/tool-tutorial-free-ai-person-detection-for-blue-iris.37330/ +* [MQTT Configuration Guide](mqtt.md) + ### Key Features - analyze Blue Iris motion alerts using Artificial Intelligence and sort out false alerts - detect humans and selected objects diff --git a/mqtt.md b/mqtt.md new file mode 100644 index 00000000..ae3b7306 --- /dev/null +++ b/mqtt.md @@ -0,0 +1,28 @@ +# MQTT with AI Tool & Blue Iris + +Blue Iris [MQTT](https://mqtt.org/) ([ELI5](https://www.reddit.com/r/homeautomation/comments/515fh5/eli5_mqtt/)) +support provides a way to [control the system](https://wiki.instar.com/Software/Windows/Blue_Iris_v5/INSTAR_MQTT/#controlling-blueiris-through-mqtt) +without relying on HTTP and URL based authentication. + +## Requirements + +A MQTT broker running on your local network, https://mosquitto.org/ is a commonly used OSS MQTT Broker. + +## Setup + +1. [Configure Blue Iris](https://wiki.instar.com/Software/Windows/Blue_Iris_v5/INSTAR_MQTT/#configuring-the-blueiris-mqtt-service) + to connect to your MQTT broker. +1. Configure AI Tool to connect to your MQTT Broker + * Cameras > [camera] > Action Settings > MQTT Settings + * ![MQTT Config](https://imgur.com/S4sDjzs.png) +1. Configure AI Tool to trigger the Camera via MQTT + * Cameras > [camera] > Action Settings + * MQTT Trigger Topic: `ai/[camera]/motion | BlueIris/admin` + * MQTT Trigger Payload: `[detections] | camera=[camera]&trigger&memo=[SummaryNonEscaped]` + +NOTE: Use `[SummaryNonEscaped]` instead of `[Summary]` in the BlueIris payload to avoid getting escaped characters in +the Blue Iris UI. + +Now when AI Tool triggers it will publish to two MQTT topics `ai/[camera]/motion` and `BlueIris/admin`. BlueIris +subscribes to the `BlueIris/admin` topic and will trigger the named camera with the specified memo. With this setup +you no longer need the URL based integrations with BlueIris. \ No newline at end of file diff --git a/src/.editorconfig b/src/.editorconfig new file mode 100644 index 00000000..c9c3fb0f --- /dev/null +++ b/src/.editorconfig @@ -0,0 +1,4 @@ +[*.cs] + +# CS4014: Because this call is not awaited, execution of the current method continues before the call is completed +dotnet_diagnostic.CS4014.severity = silent diff --git a/src/AITool.Service/AITool.Service.csproj b/src/AITool.Service/AITool.Service.csproj new file mode 100644 index 00000000..c93cba65 --- /dev/null +++ b/src/AITool.Service/AITool.Service.csproj @@ -0,0 +1,79 @@ + + + + + Debug + AnyCPU + {4FA49B76-97E6-4729-8F87-CFCEBACBAB9F} + WinExe + AITool.Service + AITool.Service + v4.7.2 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + logo.ico + + + AITool.Service.Program + + + + ..\packages\AWSSDK.Core.3.7.10.3\lib\net45\AWSSDK.Core.dll + + + ..\packages\AWSSDK.Rekognition.3.7.7.33\lib\net45\AWSSDK.Rekognition.dll + + + + + + + + + + + + + + Component + + + AIToolsService.cs + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/AITool.Service/AIToolsService.Designer.cs b/src/AITool.Service/AIToolsService.Designer.cs new file mode 100644 index 00000000..58d57f19 --- /dev/null +++ b/src/AITool.Service/AIToolsService.Designer.cs @@ -0,0 +1,37 @@ +namespace AITool.Service +{ + partial class AIToolsService + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + components = new System.ComponentModel.Container(); + this.ServiceName = "Service1"; + } + + #endregion + } +} diff --git a/src/AITool.Service/AIToolsService.cs b/src/AITool.Service/AIToolsService.cs new file mode 100644 index 00000000..2cd2ee0c --- /dev/null +++ b/src/AITool.Service/AIToolsService.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Diagnostics; +using System.Linq; +using System.ServiceProcess; +using System.Text; +using System.Threading.Tasks; + +namespace AITool.Service +{ + public partial class AIToolsService : ServiceBase + { + public AIToolsService() + { + InitializeComponent(); + } + + public override EventLog EventLog => base.EventLog; + + protected override void OnContinue() + { + base.OnContinue(); + } + + protected override void OnCustomCommand(int command) + { + base.OnCustomCommand(command); + } + + protected override void OnPause() + { + base.OnPause(); + } + + protected override bool OnPowerEvent(PowerBroadcastStatus powerStatus) + { + return base.OnPowerEvent(powerStatus); + } + + protected override void OnSessionChange(SessionChangeDescription changeDescription) + { + base.OnSessionChange(changeDescription); + } + + protected override void OnShutdown() + { + base.OnShutdown(); + } + + protected override void OnStart(string[] args) + { + } + + protected override void OnStop() + { + } + } +} diff --git a/src/AITool.Service/App.config b/src/AITool.Service/App.config new file mode 100644 index 00000000..56efbc7b --- /dev/null +++ b/src/AITool.Service/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/AITool.Service/Program.cs b/src/AITool.Service/Program.cs new file mode 100644 index 00000000..0e6e36a5 --- /dev/null +++ b/src/AITool.Service/Program.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.ServiceProcess; +using System.Text; +using System.Threading.Tasks; + +namespace AITool.Service +{ + static class Program + { + /// + /// The main entry point for the application. + /// + static void Main() + { + ServiceBase[] ServicesToRun; + ServicesToRun = new ServiceBase[] + { + new AIToolsService() + }; + ServiceBase.Run(ServicesToRun); + } + } +} diff --git a/src/AITool.Service/Properties/AssemblyInfo.cs b/src/AITool.Service/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..f36ac0ea --- /dev/null +++ b/src/AITool.Service/Properties/AssemblyInfo.cs @@ -0,0 +1,38 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("AITool.Service")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("AITool.Service")] +[assembly: AssemblyCopyright("Copyright © 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("4fa49b76-97e6-4729-8f87-cfcebacbab9f")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.441.8548")] +[assembly: AssemblyFileVersion("1.0.441.8548")] + +[assembly: AssemblyInformationalVersion("0.0.8.8548")] \ No newline at end of file diff --git a/src/UI/logo.ico b/src/AITool.Service/logo.ico similarity index 100% rename from src/UI/logo.ico rename to src/AITool.Service/logo.ico diff --git a/src/AITool.Service/packages.config b/src/AITool.Service/packages.config new file mode 100644 index 00000000..ff5a007f --- /dev/null +++ b/src/AITool.Service/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/AITool.Setup/BUILD.bat b/src/AITool.Setup/BUILD.bat new file mode 100644 index 00000000..d788b07a --- /dev/null +++ b/src/AITool.Setup/BUILD.bat @@ -0,0 +1,2 @@ +"%~dp0INNO\ISCC.exe" "%~dp0Script.iss" +::pause \ No newline at end of file diff --git a/src/AITool.Setup/INNO/Compil32.exe b/src/AITool.Setup/INNO/Compil32.exe new file mode 100644 index 00000000..76991b09 Binary files /dev/null and b/src/AITool.Setup/INNO/Compil32.exe differ diff --git a/src/AITool.Setup/INNO/Default.isl b/src/AITool.Setup/INNO/Default.isl new file mode 100644 index 00000000..ce9b70e2 --- /dev/null +++ b/src/AITool.Setup/INNO/Default.isl @@ -0,0 +1,384 @@ +; *** Inno Setup version 6.1.0+ English messages *** +; +; To download user-contributed translations of this file, go to: +; https://jrsoftware.org/files/istrans/ +; +; Note: When translating this text, do not add periods (.) to the end of +; messages that didn't have them already, because on those messages Inno +; Setup adds the periods automatically (appending a period would result in +; two periods being displayed). + +[LangOptions] +; The following three entries are very important. Be sure to read and +; understand the '[LangOptions] section' topic in the help file. +LanguageName=English +LanguageID=$0409 +LanguageCodePage=0 +; If the language you are translating to requires special font faces or +; sizes, uncomment any of the following entries and change them accordingly. +;DialogFontName= +;DialogFontSize=8 +;WelcomeFontName=Verdana +;WelcomeFontSize=12 +;TitleFontName=Arial +;TitleFontSize=29 +;CopyrightFontName=Arial +;CopyrightFontSize=8 + +[Messages] + +; *** Application titles +SetupAppTitle=Setup +SetupWindowTitle=Setup - %1 +UninstallAppTitle=Uninstall +UninstallAppFullTitle=%1 Uninstall + +; *** Misc. common +InformationTitle=Information +ConfirmTitle=Confirm +ErrorTitle=Error + +; *** SetupLdr messages +SetupLdrStartupMessage=This will install %1. Do you wish to continue? +LdrCannotCreateTemp=Unable to create a temporary file. Setup aborted +LdrCannotExecTemp=Unable to execute file in the temporary directory. Setup aborted +HelpTextNote= + +; *** Startup error messages +LastErrorMessage=%1.%n%nError %2: %3 +SetupFileMissing=The file %1 is missing from the installation directory. Please correct the problem or obtain a new copy of the program. +SetupFileCorrupt=The setup files are corrupted. Please obtain a new copy of the program. +SetupFileCorruptOrWrongVer=The setup files are corrupted, or are incompatible with this version of Setup. Please correct the problem or obtain a new copy of the program. +InvalidParameter=An invalid parameter was passed on the command line:%n%n%1 +SetupAlreadyRunning=Setup is already running. +WindowsVersionNotSupported=This program does not support the version of Windows your computer is running. +WindowsServicePackRequired=This program requires %1 Service Pack %2 or later. +NotOnThisPlatform=This program will not run on %1. +OnlyOnThisPlatform=This program must be run on %1. +OnlyOnTheseArchitectures=This program can only be installed on versions of Windows designed for the following processor architectures:%n%n%1 +WinVersionTooLowError=This program requires %1 version %2 or later. +WinVersionTooHighError=This program cannot be installed on %1 version %2 or later. +AdminPrivilegesRequired=You must be logged in as an administrator when installing this program. +PowerUserPrivilegesRequired=You must be logged in as an administrator or as a member of the Power Users group when installing this program. +SetupAppRunningError=Setup has detected that %1 is currently running.%n%nPlease close all instances of it now, then click OK to continue, or Cancel to exit. +UninstallAppRunningError=Uninstall has detected that %1 is currently running.%n%nPlease close all instances of it now, then click OK to continue, or Cancel to exit. + +; *** Startup questions +PrivilegesRequiredOverrideTitle=Select Setup Install Mode +PrivilegesRequiredOverrideInstruction=Select install mode +PrivilegesRequiredOverrideText1=%1 can be installed for all users (requires administrative privileges), or for you only. +PrivilegesRequiredOverrideText2=%1 can be installed for you only, or for all users (requires administrative privileges). +PrivilegesRequiredOverrideAllUsers=Install for &all users +PrivilegesRequiredOverrideAllUsersRecommended=Install for &all users (recommended) +PrivilegesRequiredOverrideCurrentUser=Install for &me only +PrivilegesRequiredOverrideCurrentUserRecommended=Install for &me only (recommended) + +; *** Misc. errors +ErrorCreatingDir=Setup was unable to create the directory "%1" +ErrorTooManyFilesInDir=Unable to create a file in the directory "%1" because it contains too many files + +; *** Setup common messages +ExitSetupTitle=Exit Setup +ExitSetupMessage=Setup is not complete. If you exit now, the program will not be installed.%n%nYou may run Setup again at another time to complete the installation.%n%nExit Setup? +AboutSetupMenuItem=&About Setup... +AboutSetupTitle=About Setup +AboutSetupMessage=%1 version %2%n%3%n%n%1 home page:%n%4 +AboutSetupNote= +TranslatorNote= + +; *** Buttons +ButtonBack=< &Back +ButtonNext=&Next > +ButtonInstall=&Install +ButtonOK=OK +ButtonCancel=Cancel +ButtonYes=&Yes +ButtonYesToAll=Yes to &All +ButtonNo=&No +ButtonNoToAll=N&o to All +ButtonFinish=&Finish +ButtonBrowse=&Browse... +ButtonWizardBrowse=B&rowse... +ButtonNewFolder=&Make New Folder + +; *** "Select Language" dialog messages +SelectLanguageTitle=Select Setup Language +SelectLanguageLabel=Select the language to use during the installation. + +; *** Common wizard text +ClickNext=Click Next to continue, or Cancel to exit Setup. +BeveledLabel= +BrowseDialogTitle=Browse For Folder +BrowseDialogLabel=Select a folder in the list below, then click OK. +NewFolderName=New Folder + +; *** "Welcome" wizard page +WelcomeLabel1=Welcome to the [name] Setup Wizard +WelcomeLabel2=This will install [name/ver] on your computer.%n%nIt is recommended that you close all other applications before continuing. + +; *** "Password" wizard page +WizardPassword=Password +PasswordLabel1=This installation is password protected. +PasswordLabel3=Please provide the password, then click Next to continue. Passwords are case-sensitive. +PasswordEditLabel=&Password: +IncorrectPassword=The password you entered is not correct. Please try again. + +; *** "License Agreement" wizard page +WizardLicense=License Agreement +LicenseLabel=Please read the following important information before continuing. +LicenseLabel3=Please read the following License Agreement. You must accept the terms of this agreement before continuing with the installation. +LicenseAccepted=I &accept the agreement +LicenseNotAccepted=I &do not accept the agreement + +; *** "Information" wizard pages +WizardInfoBefore=Information +InfoBeforeLabel=Please read the following important information before continuing. +InfoBeforeClickLabel=When you are ready to continue with Setup, click Next. +WizardInfoAfter=Information +InfoAfterLabel=Please read the following important information before continuing. +InfoAfterClickLabel=When you are ready to continue with Setup, click Next. + +; *** "User Information" wizard page +WizardUserInfo=User Information +UserInfoDesc=Please enter your information. +UserInfoName=&User Name: +UserInfoOrg=&Organization: +UserInfoSerial=&Serial Number: +UserInfoNameRequired=You must enter a name. + +; *** "Select Destination Location" wizard page +WizardSelectDir=Select Destination Location +SelectDirDesc=Where should [name] be installed? +SelectDirLabel3=Setup will install [name] into the following folder. +SelectDirBrowseLabel=To continue, click Next. If you would like to select a different folder, click Browse. +DiskSpaceGBLabel=At least [gb] GB of free disk space is required. +DiskSpaceMBLabel=At least [mb] MB of free disk space is required. +CannotInstallToNetworkDrive=Setup cannot install to a network drive. +CannotInstallToUNCPath=Setup cannot install to a UNC path. +InvalidPath=You must enter a full path with drive letter; for example:%n%nC:\APP%n%nor a UNC path in the form:%n%n\\server\share +InvalidDrive=The drive or UNC share you selected does not exist or is not accessible. Please select another. +DiskSpaceWarningTitle=Not Enough Disk Space +DiskSpaceWarning=Setup requires at least %1 KB of free space to install, but the selected drive only has %2 KB available.%n%nDo you want to continue anyway? +DirNameTooLong=The folder name or path is too long. +InvalidDirName=The folder name is not valid. +BadDirName32=Folder names cannot include any of the following characters:%n%n%1 +DirExistsTitle=Folder Exists +DirExists=The folder:%n%n%1%n%nalready exists. Would you like to install to that folder anyway? +DirDoesntExistTitle=Folder Does Not Exist +DirDoesntExist=The folder:%n%n%1%n%ndoes not exist. Would you like the folder to be created? + +; *** "Select Components" wizard page +WizardSelectComponents=Select Components +SelectComponentsDesc=Which components should be installed? +SelectComponentsLabel2=Select the components you want to install; clear the components you do not want to install. Click Next when you are ready to continue. +FullInstallation=Full installation +; if possible don't translate 'Compact' as 'Minimal' (I mean 'Minimal' in your language) +CompactInstallation=Compact installation +CustomInstallation=Custom installation +NoUninstallWarningTitle=Components Exist +NoUninstallWarning=Setup has detected that the following components are already installed on your computer:%n%n%1%n%nDeselecting these components will not uninstall them.%n%nWould you like to continue anyway? +ComponentSize1=%1 KB +ComponentSize2=%1 MB +ComponentsDiskSpaceGBLabel=Current selection requires at least [gb] GB of disk space. +ComponentsDiskSpaceMBLabel=Current selection requires at least [mb] MB of disk space. + +; *** "Select Additional Tasks" wizard page +WizardSelectTasks=Select Additional Tasks +SelectTasksDesc=Which additional tasks should be performed? +SelectTasksLabel2=Select the additional tasks you would like Setup to perform while installing [name], then click Next. + +; *** "Select Start Menu Folder" wizard page +WizardSelectProgramGroup=Select Start Menu Folder +SelectStartMenuFolderDesc=Where should Setup place the program's shortcuts? +SelectStartMenuFolderLabel3=Setup will create the program's shortcuts in the following Start Menu folder. +SelectStartMenuFolderBrowseLabel=To continue, click Next. If you would like to select a different folder, click Browse. +MustEnterGroupName=You must enter a folder name. +GroupNameTooLong=The folder name or path is too long. +InvalidGroupName=The folder name is not valid. +BadGroupName=The folder name cannot include any of the following characters:%n%n%1 +NoProgramGroupCheck2=&Don't create a Start Menu folder + +; *** "Ready to Install" wizard page +WizardReady=Ready to Install +ReadyLabel1=Setup is now ready to begin installing [name] on your computer. +ReadyLabel2a=Click Install to continue with the installation, or click Back if you want to review or change any settings. +ReadyLabel2b=Click Install to continue with the installation. +ReadyMemoUserInfo=User information: +ReadyMemoDir=Destination location: +ReadyMemoType=Setup type: +ReadyMemoComponents=Selected components: +ReadyMemoGroup=Start Menu folder: +ReadyMemoTasks=Additional tasks: + +; *** TDownloadWizardPage wizard page and DownloadTemporaryFile +DownloadingLabel=Downloading additional files... +ButtonStopDownload=&Stop download +StopDownload=Are you sure you want to stop the download? +ErrorDownloadAborted=Download aborted +ErrorDownloadFailed=Download failed: %1 %2 +ErrorDownloadSizeFailed=Getting size failed: %1 %2 +ErrorFileHash1=File hash failed: %1 +ErrorFileHash2=Invalid file hash: expected %1, found %2 +ErrorProgress=Invalid progress: %1 of %2 +ErrorFileSize=Invalid file size: expected %1, found %2 + +; *** "Preparing to Install" wizard page +WizardPreparing=Preparing to Install +PreparingDesc=Setup is preparing to install [name] on your computer. +PreviousInstallNotCompleted=The installation/removal of a previous program was not completed. You will need to restart your computer to complete that installation.%n%nAfter restarting your computer, run Setup again to complete the installation of [name]. +CannotContinue=Setup cannot continue. Please click Cancel to exit. +ApplicationsFound=The following applications are using files that need to be updated by Setup. It is recommended that you allow Setup to automatically close these applications. +ApplicationsFound2=The following applications are using files that need to be updated by Setup. It is recommended that you allow Setup to automatically close these applications. After the installation has completed, Setup will attempt to restart the applications. +CloseApplications=&Automatically close the applications +DontCloseApplications=&Do not close the applications +ErrorCloseApplications=Setup was unable to automatically close all applications. It is recommended that you close all applications using files that need to be updated by Setup before continuing. +PrepareToInstallNeedsRestart=Setup must restart your computer. After restarting your computer, run Setup again to complete the installation of [name].%n%nWould you like to restart now? + +; *** "Installing" wizard page +WizardInstalling=Installing +InstallingLabel=Please wait while Setup installs [name] on your computer. + +; *** "Setup Completed" wizard page +FinishedHeadingLabel=Completing the [name] Setup Wizard +FinishedLabelNoIcons=Setup has finished installing [name] on your computer. +FinishedLabel=Setup has finished installing [name] on your computer. The application may be launched by selecting the installed shortcuts. +ClickFinish=Click Finish to exit Setup. +FinishedRestartLabel=To complete the installation of [name], Setup must restart your computer. Would you like to restart now? +FinishedRestartMessage=To complete the installation of [name], Setup must restart your computer.%n%nWould you like to restart now? +ShowReadmeCheck=Yes, I would like to view the README file +YesRadio=&Yes, restart the computer now +NoRadio=&No, I will restart the computer later +; used for example as 'Run MyProg.exe' +RunEntryExec=Run %1 +; used for example as 'View Readme.txt' +RunEntryShellExec=View %1 + +; *** "Setup Needs the Next Disk" stuff +ChangeDiskTitle=Setup Needs the Next Disk +SelectDiskLabel2=Please insert Disk %1 and click OK.%n%nIf the files on this disk can be found in a folder other than the one displayed below, enter the correct path or click Browse. +PathLabel=&Path: +FileNotInDir2=The file "%1" could not be located in "%2". Please insert the correct disk or select another folder. +SelectDirectoryLabel=Please specify the location of the next disk. + +; *** Installation phase messages +SetupAborted=Setup was not completed.%n%nPlease correct the problem and run Setup again. +AbortRetryIgnoreSelectAction=Select action +AbortRetryIgnoreRetry=&Try again +AbortRetryIgnoreIgnore=&Ignore the error and continue +AbortRetryIgnoreCancel=Cancel installation + +; *** Installation status messages +StatusClosingApplications=Closing applications... +StatusCreateDirs=Creating directories... +StatusExtractFiles=Extracting files... +StatusCreateIcons=Creating shortcuts... +StatusCreateIniEntries=Creating INI entries... +StatusCreateRegistryEntries=Creating registry entries... +StatusRegisterFiles=Registering files... +StatusSavingUninstall=Saving uninstall information... +StatusRunProgram=Finishing installation... +StatusRestartingApplications=Restarting applications... +StatusRollback=Rolling back changes... + +; *** Misc. errors +ErrorInternal2=Internal error: %1 +ErrorFunctionFailedNoCode=%1 failed +ErrorFunctionFailed=%1 failed; code %2 +ErrorFunctionFailedWithMessage=%1 failed; code %2.%n%3 +ErrorExecutingProgram=Unable to execute file:%n%1 + +; *** Registry errors +ErrorRegOpenKey=Error opening registry key:%n%1\%2 +ErrorRegCreateKey=Error creating registry key:%n%1\%2 +ErrorRegWriteKey=Error writing to registry key:%n%1\%2 + +; *** INI errors +ErrorIniEntry=Error creating INI entry in file "%1". + +; *** File copying errors +FileAbortRetryIgnoreSkipNotRecommended=&Skip this file (not recommended) +FileAbortRetryIgnoreIgnoreNotRecommended=&Ignore the error and continue (not recommended) +SourceIsCorrupted=The source file is corrupted +SourceDoesntExist=The source file "%1" does not exist +ExistingFileReadOnly2=The existing file could not be replaced because it is marked read-only. +ExistingFileReadOnlyRetry=&Remove the read-only attribute and try again +ExistingFileReadOnlyKeepExisting=&Keep the existing file +ErrorReadingExistingDest=An error occurred while trying to read the existing file: +FileExistsSelectAction=Select action +FileExists2=The file already exists. +FileExistsOverwriteExisting=&Overwrite the existing file +FileExistsKeepExisting=&Keep the existing file +FileExistsOverwriteOrKeepAll=&Do this for the next conflicts +ExistingFileNewerSelectAction=Select action +ExistingFileNewer2=The existing file is newer than the one Setup is trying to install. +ExistingFileNewerOverwriteExisting=&Overwrite the existing file +ExistingFileNewerKeepExisting=&Keep the existing file (recommended) +ExistingFileNewerOverwriteOrKeepAll=&Do this for the next conflicts +ErrorChangingAttr=An error occurred while trying to change the attributes of the existing file: +ErrorCreatingTemp=An error occurred while trying to create a file in the destination directory: +ErrorReadingSource=An error occurred while trying to read the source file: +ErrorCopying=An error occurred while trying to copy a file: +ErrorReplacingExistingFile=An error occurred while trying to replace the existing file: +ErrorRestartReplace=RestartReplace failed: +ErrorRenamingTemp=An error occurred while trying to rename a file in the destination directory: +ErrorRegisterServer=Unable to register the DLL/OCX: %1 +ErrorRegSvr32Failed=RegSvr32 failed with exit code %1 +ErrorRegisterTypeLib=Unable to register the type library: %1 + +; *** Uninstall display name markings +; used for example as 'My Program (32-bit)' +UninstallDisplayNameMark=%1 (%2) +; used for example as 'My Program (32-bit, All users)' +UninstallDisplayNameMarks=%1 (%2, %3) +UninstallDisplayNameMark32Bit=32-bit +UninstallDisplayNameMark64Bit=64-bit +UninstallDisplayNameMarkAllUsers=All users +UninstallDisplayNameMarkCurrentUser=Current user + +; *** Post-installation errors +ErrorOpeningReadme=An error occurred while trying to open the README file. +ErrorRestartingComputer=Setup was unable to restart the computer. Please do this manually. + +; *** Uninstaller messages +UninstallNotFound=File "%1" does not exist. Cannot uninstall. +UninstallOpenError=File "%1" could not be opened. Cannot uninstall +UninstallUnsupportedVer=The uninstall log file "%1" is in a format not recognized by this version of the uninstaller. Cannot uninstall +UninstallUnknownEntry=An unknown entry (%1) was encountered in the uninstall log +ConfirmUninstall=Are you sure you want to completely remove %1 and all of its components? +UninstallOnlyOnWin64=This installation can only be uninstalled on 64-bit Windows. +OnlyAdminCanUninstall=This installation can only be uninstalled by a user with administrative privileges. +UninstallStatusLabel=Please wait while %1 is removed from your computer. +UninstalledAll=%1 was successfully removed from your computer. +UninstalledMost=%1 uninstall complete.%n%nSome elements could not be removed. These can be removed manually. +UninstalledAndNeedsRestart=To complete the uninstallation of %1, your computer must be restarted.%n%nWould you like to restart now? +UninstallDataCorrupted="%1" file is corrupted. Cannot uninstall + +; *** Uninstallation phase messages +ConfirmDeleteSharedFileTitle=Remove Shared File? +ConfirmDeleteSharedFile2=The system indicates that the following shared file is no longer in use by any programs. Would you like for Uninstall to remove this shared file?%n%nIf any programs are still using this file and it is removed, those programs may not function properly. If you are unsure, choose No. Leaving the file on your system will not cause any harm. +SharedFileNameLabel=File name: +SharedFileLocationLabel=Location: +WizardUninstalling=Uninstall Status +StatusUninstalling=Uninstalling %1... + +; *** Shutdown block reasons +ShutdownBlockReasonInstallingApp=Installing %1. +ShutdownBlockReasonUninstallingApp=Uninstalling %1. + +; The custom messages below aren't used by Setup itself, but if you make +; use of them in your scripts, you'll want to translate them. + +[CustomMessages] + +NameAndVersion=%1 version %2 +AdditionalIcons=Additional shortcuts: +CreateDesktopIcon=Create a &desktop shortcut +CreateQuickLaunchIcon=Create a &Quick Launch shortcut +ProgramOnTheWeb=%1 on the Web +UninstallProgram=Uninstall %1 +LaunchProgram=Launch %1 +AssocFileExtension=&Associate %1 with the %2 file extension +AssocingFileExtension=Associating %1 with the %2 file extension... +AutoStartProgramGroupDescription=Startup: +AutoStartProgram=Automatically start %1 +AddonHostProgramNotFound=%1 could not be located in the folder you selected.%n%nDo you want to continue anyway? diff --git a/src/AITool.Setup/INNO/Examples/64Bit.iss b/src/AITool.Setup/INNO/Examples/64Bit.iss new file mode 100644 index 00000000..823f363a --- /dev/null +++ b/src/AITool.Setup/INNO/Examples/64Bit.iss @@ -0,0 +1,33 @@ +; -- 64Bit.iss -- +; Demonstrates installation of a program built for the x64 (a.k.a. AMD64) +; architecture. +; To successfully run this installation and the program it installs, +; you must have a "x64" edition of Windows. + +; SEE THE DOCUMENTATION FOR DETAILS ON CREATING .ISS SCRIPT FILES! + +[Setup] +AppName=My Program +AppVersion=1.5 +WizardStyle=modern +DefaultDirName={autopf}\My Program +DefaultGroupName=My Program +UninstallDisplayIcon={app}\MyProg.exe +Compression=lzma2 +SolidCompression=yes +OutputDir=userdocs:Inno Setup Examples Output +; "ArchitecturesAllowed=x64" specifies that Setup cannot run on +; anything but x64. +ArchitecturesAllowed=x64 +; "ArchitecturesInstallIn64BitMode=x64" requests that the install be +; done in "64-bit mode" on x64, meaning it should use the native +; 64-bit Program Files directory and the 64-bit view of the registry. +ArchitecturesInstallIn64BitMode=x64 + +[Files] +Source: "MyProg-x64.exe"; DestDir: "{app}"; DestName: "MyProg.exe" +Source: "MyProg.chm"; DestDir: "{app}" +Source: "Readme.txt"; DestDir: "{app}"; Flags: isreadme + +[Icons] +Name: "{group}\My Program"; Filename: "{app}\MyProg.exe" diff --git a/src/AITool.Setup/INNO/Examples/64BitThreeArch.iss b/src/AITool.Setup/INNO/Examples/64BitThreeArch.iss new file mode 100644 index 00000000..332b5089 --- /dev/null +++ b/src/AITool.Setup/INNO/Examples/64BitThreeArch.iss @@ -0,0 +1,53 @@ +; -- 64BitThreeArch.iss -- +; Demonstrates how to install a program built for three different +; architectures (x86, x64, ARM64) using a single installer. + +; SEE THE DOCUMENTATION FOR DETAILS ON CREATING .ISS SCRIPT FILES! + +[Setup] +AppName=My Program +AppVersion=1.5 +WizardStyle=modern +DefaultDirName={autopf}\My Program +DefaultGroupName=My Program +UninstallDisplayIcon={app}\MyProg.exe +Compression=lzma2 +SolidCompression=yes +OutputDir=userdocs:Inno Setup Examples Output +; "ArchitecturesInstallIn64BitMode=x64 arm64" requests that the install +; be done in "64-bit mode" on x64 & ARM64, meaning it should use the +; native 64-bit Program Files directory and the 64-bit view of the +; registry. On all other architectures it will install in "32-bit mode". +ArchitecturesInstallIn64BitMode=x64 arm64 + +[Files] +; Install MyProg-x64.exe if running on x64, MyProg-ARM64.exe if +; running on ARM64, MyProg.exe otherwise. +; Place all x64 files here +Source: "MyProg-x64.exe"; DestDir: "{app}"; DestName: "MyProg.exe"; Check: InstallX64 +; Place all ARM64 files here, first one should be marked 'solidbreak' +Source: "MyProg-ARM64.exe"; DestDir: "{app}"; DestName: "MyProg.exe"; Check: InstallARM64; Flags: solidbreak +; Place all x86 files here, first one should be marked 'solidbreak' +Source: "MyProg.exe"; DestDir: "{app}"; Check: InstallOtherArch; Flags: solidbreak +; Place all common files here, first one should be marked 'solidbreak' +Source: "MyProg.chm"; DestDir: "{app}"; Flags: solidbreak +Source: "Readme.txt"; DestDir: "{app}"; Flags: isreadme + +[Icons] +Name: "{group}\My Program"; Filename: "{app}\MyProg.exe" + +[Code] +function InstallX64: Boolean; +begin + Result := Is64BitInstallMode and (ProcessorArchitecture = paX64); +end; + +function InstallARM64: Boolean; +begin + Result := Is64BitInstallMode and (ProcessorArchitecture = paARM64); +end; + +function InstallOtherArch: Boolean; +begin + Result := not InstallX64 and not InstallARM64; +end; diff --git a/src/AITool.Setup/INNO/Examples/64BitTwoArch.iss b/src/AITool.Setup/INNO/Examples/64BitTwoArch.iss new file mode 100644 index 00000000..70a7fc27 --- /dev/null +++ b/src/AITool.Setup/INNO/Examples/64BitTwoArch.iss @@ -0,0 +1,41 @@ +; -- 64BitTwoArch.iss -- +; Demonstrates how to install a program built for two different +; architectures (x86 and x64) using a single installer: on a "x86" +; edition of Windows the x86 version of the program will be +; installed but on a "x64" edition of Windows the x64 version will +; be installed. + +; SEE THE DOCUMENTATION FOR DETAILS ON CREATING .ISS SCRIPT FILES! + +[Setup] +AppName=My Program +AppVersion=1.5 +DefaultDirName={autopf}\My Program +DefaultGroupName=My Program +UninstallDisplayIcon={app}\MyProg.exe +WizardStyle=modern +Compression=lzma2 +SolidCompression=yes +OutputDir=userdocs:Inno Setup Examples Output +; "ArchitecturesInstallIn64BitMode=x64" requests that the install be +; done in "64-bit mode" on x64, meaning it should use the native +; 64-bit Program Files directory and the 64-bit view of the registry. +; On all other architectures it will install in "32-bit mode". +ArchitecturesInstallIn64BitMode=x64 +; Note: We don't set ProcessorsAllowed because we want this +; installation to run on all architectures (including Itanium, +; since it's capable of running 32-bit code too). + +[Files] +; Install MyProg-x64.exe if running in 64-bit mode (x64; see above), +; MyProg.exe otherwise. +; Place all x64 files here +Source: "MyProg-x64.exe"; DestDir: "{app}"; DestName: "MyProg.exe"; Check: Is64BitInstallMode +; Place all x86 files here, first one should be marked 'solidbreak' +Source: "MyProg.exe"; DestDir: "{app}"; Check: not Is64BitInstallMode; Flags: solidbreak +; Place all common files here, first one should be marked 'solidbreak' +Source: "MyProg.chm"; DestDir: "{app}"; Flags: solidbreak +Source: "Readme.txt"; DestDir: "{app}"; Flags: isreadme + +[Icons] +Name: "{group}\My Program"; Filename: "{app}\MyProg.exe" diff --git a/src/AITool.Setup/INNO/Examples/AllPagesExample.iss b/src/AITool.Setup/INNO/Examples/AllPagesExample.iss new file mode 100644 index 00000000..158085c2 --- /dev/null +++ b/src/AITool.Setup/INNO/Examples/AllPagesExample.iss @@ -0,0 +1,130 @@ +; -- AllPagesExample.iss -- +; Same as Example1.iss, but shows all the wizard pages Setup may potentially display + +; SEE THE DOCUMENTATION FOR DETAILS ON CREATING .ISS SCRIPT FILES! + +[Setup] +AppName=My Program +AppVersion=1.5 +WizardStyle=modern +DefaultDirName={autopf}\My Program +DefaultGroupName=My Program +UninstallDisplayIcon={app}\MyProg.exe +Compression=lzma2 +SolidCompression=yes +OutputDir=userdocs:Inno Setup Examples Output + +DisableWelcomePage=no +LicenseFile=license.txt +#define Password 'password' +Password={#Password} +InfoBeforeFile=readme.txt +UserInfoPage=yes +PrivilegesRequired=lowest +DisableDirPage=no +DisableProgramGroupPage=no +InfoAfterFile=readme.txt + +[Files] +Source: "MyProg.exe"; DestDir: "{app}" +Source: "MyProg.chm"; DestDir: "{app}" +Source: "Readme.txt"; DestDir: "{app}"; Flags: isreadme + +[Icons] +Name: "{group}\My Program"; Filename: "{app}\MyProg.exe" + +[Components] +Name: "component"; Description: "Component"; + +[Tasks] +Name: "task"; Description: "Task"; + +[Code] +var + OutputProgressWizardPage: TOutputProgressWizardPage; + OutputMarqueeProgressWizardPage: TOutputMarqueeProgressWizardPage; + OutputProgressWizardPagesAfterID: Integer; + +procedure InitializeWizard; +var + InputQueryWizardPage: TInputQueryWizardPage; + InputOptionWizardPage: TInputOptionWizardPage; + InputDirWizardPage: TInputDirWizardPage; + InputFileWizardPage: TInputFileWizardPage; + OutputMsgWizardPage: TOutputMsgWizardPage; + OutputMsgMemoWizardPage: TOutputMsgMemoWizardPage; + AfterID: Integer; +begin + WizardForm.LicenseAcceptedRadio.Checked := True; + WizardForm.PasswordEdit.Text := '{#Password}'; + WizardForm.UserInfoNameEdit.Text := 'Username'; + + AfterID := wpSelectTasks; + + AfterID := CreateCustomPage(AfterID, 'CreateCustomPage', 'ADescription').ID; + + InputQueryWizardPage := CreateInputQueryPage(AfterID, 'CreateInputQueryPage', 'ADescription', 'ASubCaption'); + InputQueryWizardPage.Add('&APrompt:', False); + AfterID := InputQueryWizardPage.ID; + + InputOptionWizardPage := CreateInputOptionPage(AfterID, 'CreateInputOptionPage', 'ADescription', 'ASubCaption', False, False); + InputOptionWizardPage.Add('&AOption'); + AfterID := InputOptionWizardPage.ID; + + InputDirWizardPage := CreateInputDirPage(AfterID, 'CreateInputDirPage', 'ADescription', 'ASubCaption', False, 'ANewFolderName'); + InputDirWizardPage.Add('&APrompt:'); + InputDirWizardPage.Values[0] := 'C:\'; + AfterID := InputDirWizardPage.ID; + + InputFileWizardPage := CreateInputFilePage(AfterID, 'CreateInputFilePage', 'ADescription', 'ASubCaption'); + InputFileWizardPage.Add('&APrompt:', 'Executable files|*.exe|All files|*.*', '.exe'); + AfterID := InputFileWizardPage.ID; + + OutputMsgWizardPage := CreateOutputMsgPage(AfterID, 'CreateOutputMsgPage', 'ADescription', 'AMsg'); + AfterID := OutputMsgWizardPage.ID; + + OutputMsgMemoWizardPage := CreateOutputMsgMemoPage(AfterID, 'CreateOutputMsgMemoPage', 'ADescription', 'ASubCaption', 'AMsg'); + AfterID := OutputMsgMemoWizardPage.ID; + + OutputProgressWizardPage := CreateOutputProgressPage('CreateOutputProgressPage', 'ADescription'); + OutputMarqueeProgressWizardPage := CreateOutputMarqueeProgressPage('CreateOutputMarqueeProgressPage', 'ADescription'); + OutputProgressWizardPagesAfterID := AfterID; + + { See CodeDownloadFiles.iss for a CreateDownloadPage example } +end; + +function NextButtonClick(CurPageID: Integer): Boolean; +var + I, Max: Integer; +begin + if CurPageID = OutputProgressWizardPagesAfterID then begin + try + Max := 50; + for I := 0 to Max do begin + OutputProgressWizardPage.SetProgress(I, Max); + if I = 0 then + OutputProgressWizardPage.Show; + Sleep(2000 div Max); + end; + finally + OutputProgressWizardPage.Hide; + end; + try + Max := 50; + OutputMarqueeProgressWizardPage.Show; + for I := 0 to Max do begin + OutputMarqueeProgressWizardPage.Animate; + Sleep(2000 div Max); + end; + finally + OutputMarqueeProgressWizardPage.Hide; + end; + end; + Result := True; +end; + +function PrepareToInstall(var NeedsRestart: Boolean): String; +begin + if SuppressibleMsgBox('Do you want to stop Setup at the Preparing To Install wizard page?', mbConfirmation, MB_YESNO, IDNO) = IDYES then + Result := 'Stopped by user'; +end; \ No newline at end of file diff --git a/src/AITool.Setup/INNO/Examples/CodeAutomation.iss b/src/AITool.Setup/INNO/Examples/CodeAutomation.iss new file mode 100644 index 00000000..e36a5d08 --- /dev/null +++ b/src/AITool.Setup/INNO/Examples/CodeAutomation.iss @@ -0,0 +1,311 @@ +; -- CodeAutomation.iss -- +; +; This script shows how to use IDispatch based COM Automation objects. + +[Setup] +AppName=My Program +AppVersion=1.5 +WizardStyle=modern +DisableWelcomePage=no +CreateAppDir=no +DisableProgramGroupPage=yes +DefaultGroupName=My Program +UninstallDisplayIcon={app}\MyProg.exe +OutputDir=userdocs:Inno Setup Examples Output + +[Code] + +{--- SQLDMO ---} + +const + SQLServerName = 'localhost'; + SQLDMOGrowth_MB = 0; + +procedure SQLDMOButtonOnClick(Sender: TObject); +var + SQLServer, Database, DBFile, LogFile: Variant; + IDColumn, NameColumn, Table: Variant; +begin + if MsgBox('Setup will now connect to Microsoft SQL Server ''' + SQLServerName + ''' via a trusted connection and create a database. Do you want to continue?', mbInformation, mb_YesNo) = idNo then + Exit; + + { Create the main SQLDMO COM Automation object } + + try + SQLServer := CreateOleObject('SQLDMO.SQLServer'); + except + RaiseException('Please install Microsoft SQL server connectivity tools first.'#13#13'(Error ''' + GetExceptionMessage + ''' occurred)'); + end; + + { Connect to the Microsoft SQL Server } + + SQLServer.LoginSecure := True; + SQLServer.Connect(SQLServerName); + + MsgBox('Connected to Microsoft SQL Server ''' + SQLServerName + '''.', mbInformation, mb_Ok); + + { Setup a database } + + Database := CreateOleObject('SQLDMO.Database'); + Database.Name := 'Inno Setup'; + + DBFile := CreateOleObject('SQLDMO.DBFile'); + DBFile.Name := 'ISData1'; + DBFile.PhysicalName := 'c:\program files\microsoft sql server\mssql\data\IS.mdf'; + DBFile.PrimaryFile := True; + DBFile.FileGrowthType := SQLDMOGrowth_MB; + DBFile.FileGrowth := 1; + + Database.FileGroups.Item('PRIMARY').DBFiles.Add(DBFile); + + LogFile := CreateOleObject('SQLDMO.LogFile'); + LogFile.Name := 'ISLog1'; + LogFile.PhysicalName := 'c:\program files\microsoft sql server\mssql\data\IS.ldf'; + + Database.TransactionLog.LogFiles.Add(LogFile); + + { Add the database } + + SQLServer.Databases.Add(Database); + + MsgBox('Added database ''' + Database.Name + '''.', mbInformation, mb_Ok); + + { Setup some columns } + + IDColumn := CreateOleObject('SQLDMO.Column'); + IDColumn.Name := 'id'; + IDColumn.Datatype := 'int'; + IDColumn.Identity := True; + IDColumn.IdentityIncrement := 1; + IDColumn.IdentitySeed := 1; + IDColumn.AllowNulls := False; + + NameColumn := CreateOleObject('SQLDMO.Column'); + NameColumn.Name := 'name'; + NameColumn.Datatype := 'varchar'; + NameColumn.Length := '64'; + NameColumn.AllowNulls := False; + + { Setup a table } + + Table := CreateOleObject('SQLDMO.Table'); + Table.Name := 'authors'; + Table.FileGroup := 'PRIMARY'; + + { Add the columns and the table } + + Table.Columns.Add(IDColumn); + Table.Columns.Add(NameColumn); + + Database.Tables.Add(Table); + + MsgBox('Added table ''' + Table.Name + '''.', mbInformation, mb_Ok); +end; + +{--- IIS ---} + +const + IISServerName = 'localhost'; + IISServerNumber = '1'; + IISURL = 'http://127.0.0.1'; + +procedure IISButtonOnClick(Sender: TObject); +var + IIS, WebSite, WebServer, WebRoot, VDir: Variant; + ErrorCode: Integer; +begin + if MsgBox('Setup will now connect to Microsoft IIS Server ''' + IISServerName + ''' and create a virtual directory. Do you want to continue?', mbInformation, mb_YesNo) = idNo then + Exit; + + { Create the main IIS COM Automation object } + + try + IIS := CreateOleObject('IISNamespace'); + except + RaiseException('Please install Microsoft IIS first.'#13#13'(Error ''' + GetExceptionMessage + ''' occurred)'); + end; + + { Connect to the IIS server } + + WebSite := IIS.GetObject('IIsWebService', IISServerName + '/w3svc'); + WebServer := WebSite.GetObject('IIsWebServer', IISServerNumber); + WebRoot := WebServer.GetObject('IIsWebVirtualDir', 'Root'); + + { (Re)create a virtual dir } + + try + WebRoot.Delete('IIsWebVirtualDir', 'innosetup'); + WebRoot.SetInfo(); + except + end; + + VDir := WebRoot.Create('IIsWebVirtualDir', 'innosetup'); + VDir.AccessRead := True; + VDir.AppFriendlyName := 'Inno Setup'; + VDir.Path := 'C:\inetpub\innosetup'; + VDir.AppCreate(True); + VDir.SetInfo(); + + MsgBox('Created virtual directory ''' + VDir.Path + '''.', mbInformation, mb_Ok); + + { Write some html and display it } + + if MsgBox('Setup will now write some HTML and display the virtual directory. Do you want to continue?', mbInformation, mb_YesNo) = idNo then + Exit; + + ForceDirectories(VDir.Path); + SaveStringToFile(VDir.Path + '/index.htm', 'Inno Setup rocks!', False); + if not ShellExecAsOriginalUser('open', IISURL + '/innosetup/index.htm', '', '', SW_SHOWNORMAL, ewNoWait, ErrorCode) then + MsgBox('Can''t display the created virtual directory: ''' + SysErrorMessage(ErrorCode) + '''.', mbError, mb_Ok); +end; + +{--- MSXML ---} + +const + XMLURL = 'http://jrsoftware.github.io/issrc/ISHelp/isxfunc.xml'; + XMLFileName = 'isxfunc.xml'; + XMLFileName2 = 'isxfuncmodified.xml'; + +procedure MSXMLButtonOnClick(Sender: TObject); +var + XMLHTTP, XMLDoc, NewNode, RootNode: Variant; + Path: String; +begin + if MsgBox('Setup will now use MSXML to download XML file ''' + XMLURL + ''' and save it to disk.'#13#13'Setup will then load, modify and save this XML file. Do you want to continue?', mbInformation, mb_YesNo) = idNo then + Exit; + + { Create the main MSXML COM Automation object } + + try + XMLHTTP := CreateOleObject('MSXML2.ServerXMLHTTP'); + except + RaiseException('Please install MSXML first.'#13#13'(Error ''' + GetExceptionMessage + ''' occurred)'); + end; + + { Download the XML file } + + XMLHTTP.Open('GET', XMLURL, False); + XMLHTTP.Send(); + + Path := ExpandConstant('{src}\'); + XMLHTTP.responseXML.Save(Path + XMLFileName); + + MsgBox('Downloaded the XML file and saved it as ''' + XMLFileName + '''.', mbInformation, mb_Ok); + + { Load the XML File } + + XMLDoc := CreateOleObject('MSXML2.DOMDocument'); + XMLDoc.async := False; + XMLDoc.resolveExternals := False; + XMLDoc.load(Path + XMLFileName); + if XMLDoc.parseError.errorCode <> 0 then + RaiseException('Error on line ' + IntToStr(XMLDoc.parseError.line) + ', position ' + IntToStr(XMLDoc.parseError.linepos) + ': ' + XMLDoc.parseError.reason); + + MsgBox('Loaded the XML file.', mbInformation, mb_Ok); + + { Modify the XML document } + + NewNode := XMLDoc.createElement('isxdemo'); + RootNode := XMLDoc.documentElement; + RootNode.appendChild(NewNode); + RootNode.lastChild.text := 'Hello, World'; + + { Save the XML document } + + XMLDoc.Save(Path + XMLFileName2); + + MsgBox('Saved the modified XML as ''' + XMLFileName2 + '''.', mbInformation, mb_Ok); +end; + + +{--- Word ---} + +procedure WordButtonOnClick(Sender: TObject); +var + Word: Variant; +begin + if MsgBox('Setup will now check whether Microsoft Word is running. Do you want to continue?', mbInformation, mb_YesNo) = idNo then + Exit; + + { Try to get an active Word COM Automation object } + + try + Word := GetActiveOleObject('Word.Application'); + except + end; + + if VarIsEmpty(Word) then + MsgBox('Microsoft Word is not running.', mbInformation, mb_Ok) + else + MsgBox('Microsoft Word is running.', mbInformation, mb_Ok) +end; + +{--- Windows Firewall ---} + +const + NET_FW_IP_VERSION_ANY = 2; + NET_FW_SCOPE_ALL = 0; + +procedure FirewallButtonOnClick(Sender: TObject); +var + Firewall, Application: Variant; +begin + if MsgBox('Setup will now add itself to Windows Firewall as an authorized application for the current profile (' + GetUserNameString + '). Do you want to continue?', mbInformation, mb_YesNo) = idNo then + Exit; + + { Create the main Windows Firewall COM Automation object } + + try + Firewall := CreateOleObject('HNetCfg.FwMgr'); + except + RaiseException('Please install Windows Firewall first.'#13#13'(Error ''' + GetExceptionMessage + ''' occurred)'); + end; + + { Add the authorization } + + Application := CreateOleObject('HNetCfg.FwAuthorizedApplication'); + Application.Name := 'Setup'; + Application.IPVersion := NET_FW_IP_VERSION_ANY; + Application.ProcessImageFileName := ExpandConstant('{srcexe}'); + Application.Scope := NET_FW_SCOPE_ALL; + Application.Enabled := True; + + Firewall.LocalPolicy.CurrentProfile.AuthorizedApplications.Add(Application); + + MsgBox('Setup is now an authorized application for the current profile', mbInformation, mb_Ok); +end; + +{---} + +procedure CreateButton(ALeft, ATop: Integer; ACaption: String; ANotifyEvent: TNotifyEvent); +begin + with TNewButton.Create(WizardForm) do begin + Left := ALeft; + Top := ATop; + Width := WizardForm.CancelButton.Width; + Height := WizardForm.CancelButton.Height; + Caption := ACaption; + OnClick := ANotifyEvent; + Parent := WizardForm.WelcomePage; + end; +end; + +procedure InitializeWizard(); +var + Left, LeftInc, Top, TopInc: Integer; +begin + Left := WizardForm.WelcomeLabel2.Left; + LeftInc := WizardForm.CancelButton.Width + ScaleX(8); + TopInc := WizardForm.CancelButton.Height + ScaleY(8); + Top := WizardForm.WelcomeLabel2.Top + WizardForm.WelcomeLabel2.Height - 4*TopInc; + + CreateButton(Left, Top, '&SQLDMO...', @SQLDMOButtonOnClick); + Top := Top + TopInc; + CreateButton(Left + LeftInc, Top, '&Firewall...', @FirewallButtonOnClick); + Top := Top + TopInc; + CreateButton(Left, Top, '&IIS...', @IISButtonOnClick); + Top := Top + TopInc; + CreateButton(Left, Top, '&MSXML...', @MSXMLButtonOnClick); + Top := Top + TopInc; + CreateButton(Left, Top, '&Word...', @WordButtonOnClick); +end; \ No newline at end of file diff --git a/src/AITool.Setup/INNO/Examples/CodeAutomation2.iss b/src/AITool.Setup/INNO/Examples/CodeAutomation2.iss new file mode 100644 index 00000000..62f1d218 --- /dev/null +++ b/src/AITool.Setup/INNO/Examples/CodeAutomation2.iss @@ -0,0 +1,298 @@ +; -- CodeAutomation2.iss -- +; +; This script shows how to use IUnknown based COM Automation objects. +; +; Note: some unneeded interface functions which had special types have been replaced +; by dummies to avoid having to define those types. Do not remove these dummies as +; that would change the function indices which is bad. Also, not all function +; prototypes have been tested, only those used by this example. + +[Setup] +AppName=My Program +AppVersion=1.5 +WizardStyle=modern +DisableWelcomePage=no +CreateAppDir=no +DisableProgramGroupPage=yes +DefaultGroupName=My Program +UninstallDisplayIcon={app}\MyProg.exe +OutputDir=userdocs:Inno Setup Examples Output + +[Code] + +{--- IShellLink ---} + +const + CLSID_ShellLink = '{00021401-0000-0000-C000-000000000046}'; + +type + IShellLinkW = interface(IUnknown) + '{000214F9-0000-0000-C000-000000000046}' + procedure Dummy; + procedure Dummy2; + procedure Dummy3; + function GetDescription(pszName: String; cchMaxName: Integer): HResult; + function SetDescription(pszName: String): HResult; + function GetWorkingDirectory(pszDir: String; cchMaxPath: Integer): HResult; + function SetWorkingDirectory(pszDir: String): HResult; + function GetArguments(pszArgs: String; cchMaxPath: Integer): HResult; + function SetArguments(pszArgs: String): HResult; + function GetHotkey(var pwHotkey: Word): HResult; + function SetHotkey(wHotkey: Word): HResult; + function GetShowCmd(out piShowCmd: Integer): HResult; + function SetShowCmd(iShowCmd: Integer): HResult; + function GetIconLocation(pszIconPath: String; cchIconPath: Integer; + out piIcon: Integer): HResult; + function SetIconLocation(pszIconPath: String; iIcon: Integer): HResult; + function SetRelativePath(pszPathRel: String; dwReserved: DWORD): HResult; + function Resolve(Wnd: HWND; fFlags: DWORD): HResult; + function SetPath(pszFile: String): HResult; + end; + + IPersist = interface(IUnknown) + '{0000010C-0000-0000-C000-000000000046}' + function GetClassID(var classID: TGUID): HResult; + end; + + IPersistFile = interface(IPersist) + '{0000010B-0000-0000-C000-000000000046}' + function IsDirty: HResult; + function Load(pszFileName: String; dwMode: Longint): HResult; + function Save(pszFileName: String; fRemember: BOOL): HResult; + function SaveCompleted(pszFileName: String): HResult; + function GetCurFile(out pszFileName: String): HResult; + end; + +procedure IShellLinkButtonOnClick(Sender: TObject); +var + Obj: IUnknown; + SL: IShellLinkW; + PF: IPersistFile; +begin + { Create the main ShellLink COM Automation object } + Obj := CreateComObject(StringToGuid(CLSID_ShellLink)); + + { Set the shortcut properties } + SL := IShellLinkW(Obj); + OleCheck(SL.SetPath(ExpandConstant('{srcexe}'))); + OleCheck(SL.SetArguments('')); + OleCheck(SL.SetShowCmd(SW_SHOWNORMAL)); + + { Save the shortcut } + PF := IPersistFile(Obj); + OleCheck(PF.Save(ExpandConstant('{autodesktop}\CodeAutomation2 Test.lnk'), True)); + + MsgBox('Saved a shortcut named ''CodeAutomation2 Test'' on the desktop.', mbInformation, mb_Ok); +end; + +{--- ITaskScheduler ---} + +const + CLSID_TaskScheduler = '{148BD52A-A2AB-11CE-B11F-00AA00530503}'; + CLSID_Task = '{148BD520-A2AB-11CE-B11F-00AA00530503}'; + IID_Task = '{148BD524-A2AB-11CE-B11F-00AA00530503}'; + TASK_TIME_TRIGGER_DAILY = 1; + +type + ITaskScheduler = interface(IUnknown) + '{148BD527-A2AB-11CE-B11F-00AA00530503}' + function SetTargetComputer(pwszComputer: String): HResult; + function GetTargetComputer(out ppwszComputer: String): HResult; + procedure Dummy; + function Activate(pwszName: String; var riid: TGUID; out ppUnk: IUnknown): HResult; + function Delete(pwszName: String): HResult; + function NewWorkItem(pwszTaskName: String; var rclsid: TGUID; var riid: TGUID; out ppUnk: IUnknown): HResult; + procedure Dummy2; + function IsOfType(pwszName: String; var riid: TGUID): HResult; + end; + + TDaily = record + DaysInterval: WORD; + end; + + TWeekly = record + WeeksInterval: WORD; + rgfDaysOfTheWeek: WORD; + end; + + TMonthlyDate = record + rgfDays: DWORD; + rgfMonths: WORD; + end; + + TMonthlyDow = record + wWhichWeek: WORD; + rgfDaysOfTheWeek: WORD; + rgfMonths: WORD; + end; + + { ROPS doesn't support unions, replace this with the type you need and adjust padding (end size has to be 48). } + TTriggerTypeUnion = record + Daily: TDaily; + Pad1: WORD; + Pad2: WORD; + Pad3: WORD; + end; + + TTaskTrigger = record + cbTriggerSize: WORD; + Reserved1: WORD; + wBeginYear: WORD; + wBeginMonth: WORD; + wBeginDay: WORD; + wEndYear: WORD; + wEndMonth: WORD; + wEndDay: WORD; + wStartHour: WORD; + wStartMinute: WORD; + MinutesDuration: DWORD; + MinutesInterval: DWORD; + rgFlags: DWORD; + TriggerType: DWORD; + Type_: TTriggerTypeUnion; + Reserved2: WORD; + wRandomMinutesInterval: WORD; + end; + + ITaskTrigger = interface(IUnknown) + '{148BD52B-A2AB-11CE-B11F-00AA00530503}' + function SetTrigger(var pTrigger: TTaskTrigger): HResult; + function GetTrigger(var pTrigger: TTaskTrigger): HResult; + function GetTriggerString(var ppwszTrigger: String): HResult; + end; + + IScheduledWorkItem = interface(IUnknown) + '{A6B952F0-A4B1-11D0-997D-00AA006887EC}' + function CreateTrigger(out piNewTrigger: Word; out ppTrigger: ITaskTrigger): HResult; + function DeleteTrigger(iTrigger: Word): HResult; + function GetTriggerCount(out pwCount: Word): HResult; + function GetTrigger(iTrigger: Word; var ppTrigger: ITaskTrigger): HResult; + function GetTriggerString(iTrigger: Word; out ppwszTrigger: String): HResult; + procedure Dummy; + procedure Dummy2; + function SetIdleWait(wIdleMinutes: Word; wDeadlineMinutes: Word): HResult; + function GetIdleWait(out pwIdleMinutes: Word; out pwDeadlineMinutes: Word): HResult; + function Run: HResult; + function Terminate: HResult; + function EditWorkItem(hParent: HWND; dwReserved: DWORD): HResult; + procedure Dummy3; + function GetStatus(out phrStatus: HResult): HResult; + function GetExitCode(out pdwExitCode: DWORD): HResult; + function SetComment(pwszComment: String): HResult; + function GetComment(out ppwszComment: String): HResult; + function SetCreator(pwszCreator: String): HResult; + function GetCreator(out ppwszCreator: String): HResult; + function SetWorkItemData(cbData: Word; var rgbData: Byte): HResult; + function GetWorkItemData(out pcbData: Word; out prgbData: Byte): HResult; + function SetErrorRetryCount(wRetryCount: Word): HResult; + function GetErrorRetryCount(out pwRetryCount: Word): HResult; + function SetErrorRetryInterval(wRetryInterval: Word): HResult; + function GetErrorRetryInterval(out pwRetryInterval: Word): HResult; + function SetFlags(dwFlags: DWORD): HResult; + function GetFlags(out pdwFlags: DWORD): HResult; + function SetAccountInformation(pwszAccountName: String; pwszPassword: String): HResult; + function GetAccountInformation(out ppwszAccountName: String): HResult; + end; + + ITask = interface(IScheduledWorkItem) + '{148BD524-A2AB-11CE-B11F-00AA00530503}' + function SetApplicationName(pwszApplicationName: String): HResult; + function GetApplicationName(out ppwszApplicationName: String): HResult; + function SetParameters(pwszParameters: String): HResult; + function GetParameters(out ppwszParameters: String): HResult; + function SetWorkingDirectory(pwszWorkingDirectory: String): HResult; + function GetWorkingDirectory(out ppwszWorkingDirectory: String): HResult; + function SetPriority(dwPriority: DWORD): HResult; + function GetPriority(out pdwPriority: DWORD): HResult; + function SetTaskFlags(dwFlags: DWORD): HResult; + function GetTaskFlags(out pdwFlags: DWORD): HResult; + function SetMaxRunTime(dwMaxRunTimeMS: DWORD): HResult; + function GetMaxRunTime(out pdwMaxRunTimeMS: DWORD): HResult; + end; + + +procedure ITaskSchedulerButtonOnClick(Sender: TObject); +var + Obj, Obj2: IUnknown; + TaskScheduler: ITaskScheduler; + G1, G2: TGUID; + Task: ITask; + iNewTrigger: WORD; + TaskTrigger: ITaskTrigger; + TaskTrigger2: TTaskTrigger; + PF: IPersistFile; +begin + { Create the main TaskScheduler COM Automation object } + Obj := CreateComObject(StringToGuid(CLSID_TaskScheduler)); + + { Create the Task COM automation object } + TaskScheduler := ITaskScheduler(Obj); + G1 := StringToGuid(CLSID_Task); + G2 := StringToGuid(IID_Task); + //This will throw an exception if the task already exists + OleCheck(TaskScheduler.NewWorkItem('CodeAutomation2 Test', G1, G2, Obj2)); + + { Set the task properties } + Task := ITask(Obj2); + OleCheck(Task.SetComment('CodeAutomation2 Test Comment')); + OleCheck(Task.SetApplicationName(ExpandConstant('{srcexe}'))); + + { Set the task account information } + //Uncomment the following and provide actual user info to get a runnable task + //OleCheck(Task.SetAccountInformation('username', 'password')); + + { Create the TaskTrigger COM automation object } + OleCheck(Task.CreateTrigger(iNewTrigger, TaskTrigger)); + + { Set the task trigger properties } + with TaskTrigger2 do begin + cbTriggerSize := SizeOf(TaskTrigger2); + wBeginYear := 2009; + wBeginMonth := 10; + wBeginDay := 1; + wStartHour := 12; + TriggerType := TASK_TIME_TRIGGER_DAILY; + Type_.Daily.DaysInterval := 1; + end; + OleCheck(TaskTrigger.SetTrigger(TaskTrigger2)); + + { Save the task } + PF := IPersistFile(Obj2); + OleCheck(PF.Save('', True)); + + MsgBox('Created a daily task named named ''CodeAutomation2 Test''.' + #13#13 + 'Note: Account information not set so the task won''t actually run, uncomment the SetAccountInfo call and provide actual user info to get a runnable task.', mbInformation, mb_Ok); +end; + +{---} + +procedure CreateButton(ALeft, ATop: Integer; ACaption: String; ANotifyEvent: TNotifyEvent); +begin + with TNewButton.Create(WizardForm) do begin + Left := ALeft; + Top := ATop; + Width := (WizardForm.CancelButton.Width*3)/2; + Height := WizardForm.CancelButton.Height; + Caption := ACaption; + OnClick := ANotifyEvent; + Parent := WizardForm.WelcomePage; + end; +end; + +procedure InitializeWizard(); +var + Left, LeftInc, Top, TopInc: Integer; +begin + Left := WizardForm.WelcomeLabel2.Left; + LeftInc := (WizardForm.CancelButton.Width*3)/2 + ScaleX(8); + TopInc := WizardForm.CancelButton.Height + ScaleY(8); + Top := WizardForm.WelcomeLabel2.Top + WizardForm.WelcomeLabel2.Height - 4*TopInc; + + CreateButton(Left, Top, '&IShellLink...', @IShellLinkButtonOnClick); + Top := Top + TopInc; + CreateButton(Left, Top, '&ITaskScheduler...', @ITaskSchedulerButtonOnClick); +end; + + + + + diff --git a/src/AITool.Setup/INNO/Examples/CodeClasses.iss b/src/AITool.Setup/INNO/Examples/CodeClasses.iss new file mode 100644 index 00000000..4610cec0 --- /dev/null +++ b/src/AITool.Setup/INNO/Examples/CodeClasses.iss @@ -0,0 +1,426 @@ +; -- CodeClasses.iss -- +; +; This script shows how to use the WizardForm object and the various VCL classes. + +[Setup] +AppName=My Program +AppVersion=1.5 +WizardStyle=modern +CreateAppDir=no +DisableProgramGroupPage=yes +DefaultGroupName=My Program +UninstallDisplayIcon={app}\MyProg.exe +OutputDir=userdocs:Inno Setup Examples Output +PrivilegesRequired=lowest + +; Uncomment the following three lines to test the layout when scaling and rtl are active +;[LangOptions] +;RightToLeft=yes +;DialogFontSize=12 + +[Files] +Source: compiler:WizClassicSmallImage.bmp; Flags: dontcopy + +[Code] +procedure ButtonOnClick(Sender: TObject); +begin + MsgBox('You clicked the button!', mbInformation, mb_Ok); +end; + +procedure BitmapImageOnClick(Sender: TObject); +begin + MsgBox('You clicked the image!', mbInformation, mb_Ok); +end; + +procedure FormButtonOnClick(Sender: TObject); +var + Form: TSetupForm; + Edit: TNewEdit; + OKButton, CancelButton: TNewButton; + W: Integer; +begin + Form := CreateCustomForm(); + try + Form.ClientWidth := ScaleX(256); + Form.ClientHeight := ScaleY(128); + Form.Caption := 'TSetupForm'; + + Edit := TNewEdit.Create(Form); + Edit.Top := ScaleY(10); + Edit.Left := ScaleX(10); + Edit.Width := Form.ClientWidth - ScaleX(2 * 10); + Edit.Height := ScaleY(23); + Edit.Anchors := [akLeft, akTop, akRight]; + Edit.Text := 'TNewEdit'; + Edit.Parent := Form; + + OKButton := TNewButton.Create(Form); + OKButton.Parent := Form; + OKButton.Caption := 'OK'; + OKButton.Left := Form.ClientWidth - ScaleX(75 + 6 + 75 + 10); + OKButton.Top := Form.ClientHeight - ScaleY(23 + 10); + OKButton.Height := ScaleY(23); + OKButton.Anchors := [akRight, akBottom] + OKButton.ModalResult := mrOk; + OKButton.Default := True; + + CancelButton := TNewButton.Create(Form); + CancelButton.Parent := Form; + CancelButton.Caption := 'Cancel'; + CancelButton.Left := Form.ClientWidth - ScaleX(75 + 10); + CancelButton.Top := Form.ClientHeight - ScaleY(23 + 10); + CancelButton.Height := ScaleY(23); + CancelButton.Anchors := [akRight, akBottom] + CancelButton.ModalResult := mrCancel; + CancelButton.Cancel := True; + + W := Form.CalculateButtonWidth([OKButton.Caption, CancelButton.Caption]); + OKButton.Width := W; + CancelButton.Width := W; + + Form.ActiveControl := Edit; + { Keep the form from sizing vertically since we don't have any controls which can size vertically } + Form.KeepSizeY := True; + { Center on WizardForm. Without this call it will still automatically center, but on the screen } + Form.FlipSizeAndCenterIfNeeded(True, WizardForm, False); + + if Form.ShowModal() = mrOk then + MsgBox('You clicked OK.', mbInformation, MB_OK); + finally + Form.Free(); + end; +end; + +procedure TaskDialogButtonOnClick(Sender: TObject); +begin + { TaskDialogMsgBox isn't a class but showing it anyway since it fits with the theme } + + case TaskDialogMsgBox('Choose A or B', + 'You can choose A or B.', + mbInformation, + MB_YESNOCANCEL, ['I choose &A'#13#10'A will be chosen.', 'I choose &B'#13#10'B will be chosen.'], + IDYES) of + IDYES: MsgBox('You chose A.', mbInformation, MB_OK); + IDNO: MsgBox('You chose B.', mbInformation, MB_OK); + end; +end; + +procedure CreateTheWizardPages; +var + Page: TWizardPage; + Button, FormButton, TaskDialogButton: TNewButton; + Panel: TPanel; + CheckBox: TNewCheckBox; + Edit: TNewEdit; + PasswordEdit: TPasswordEdit; + Memo: TNewMemo; + ComboBox: TNewComboBox; + ListBox: TNewListBox; + StaticText, ProgressBarLabel: TNewStaticText; + ProgressBar, ProgressBar2, ProgressBar3: TNewProgressBar; + CheckListBox, CheckListBox2: TNewCheckListBox; + FolderTreeView: TFolderTreeView; + BitmapImage, BitmapImage2, BitmapImage3: TBitmapImage; + BitmapFileName: String; + RichEditViewer: TRichEditViewer; +begin + { TButton and others } + + Page := CreateCustomPage(wpWelcome, 'Custom wizard page controls', 'TButton and others'); + + Button := TNewButton.Create(Page); + Button.Caption := 'TNewButton'; + Button.Width := WizardForm.CalculateButtonWidth([Button.Caption]); + Button.Height := ScaleY(23); + Button.OnClick := @ButtonOnClick; + Button.Parent := Page.Surface; + + Panel := TPanel.Create(Page); + Panel.Width := Page.SurfaceWidth div 2 - ScaleX(8); + Panel.Left := Page.SurfaceWidth - Panel.Width; + Panel.Height := Button.Height * 2; + Panel.Anchors := [akLeft, akTop, akRight]; + Panel.Caption := 'TPanel'; + Panel.Color := clWindow; + Panel.BevelKind := bkFlat; + Panel.BevelOuter := bvNone; + Panel.ParentBackground := False; + Panel.Parent := Page.Surface; + + CheckBox := TNewCheckBox.Create(Page); + CheckBox.Top := Button.Top + Button.Height + ScaleY(8); + CheckBox.Width := Page.SurfaceWidth div 2; + CheckBox.Height := ScaleY(17); + CheckBox.Caption := 'TNewCheckBox'; + CheckBox.Checked := True; + CheckBox.Parent := Page.Surface; + + Edit := TNewEdit.Create(Page); + Edit.Top := CheckBox.Top + CheckBox.Height + ScaleY(8); + Edit.Width := Page.SurfaceWidth div 2 - ScaleX(8); + Edit.Text := 'TNewEdit'; + Edit.Parent := Page.Surface; + + PasswordEdit := TPasswordEdit.Create(Page); + PasswordEdit.Left := Page.SurfaceWidth - Edit.Width; + PasswordEdit.Top := CheckBox.Top + CheckBox.Height + ScaleY(8); + PasswordEdit.Width := Edit.Width; + PasswordEdit.Anchors := [akLeft, akTop, akRight]; + PasswordEdit.Text := 'TPasswordEdit'; + PasswordEdit.Parent := Page.Surface; + + Memo := TNewMemo.Create(Page); + Memo.Top := Edit.Top + Edit.Height + ScaleY(8); + Memo.Width := Page.SurfaceWidth; + Memo.Height := ScaleY(89); + Memo.Anchors := [akLeft, akTop, akRight, akBottom]; + Memo.ScrollBars := ssVertical; + Memo.Text := 'TNewMemo'; + Memo.Parent := Page.Surface; + + FormButton := TNewButton.Create(Page); + FormButton.Caption := 'TSetupForm'; + FormButton.Top := Memo.Top + Memo.Height + ScaleY(8); + FormButton.Width := WizardForm.CalculateButtonWidth([FormButton.Caption]); + FormButton.Height := ScaleY(23); + FormButton.Anchors := [akLeft, akBottom]; + FormButton.OnClick := @FormButtonOnClick; + FormButton.Parent := Page.Surface; + + TaskDialogButton := TNewButton.Create(Page); + TaskDialogButton.Caption := 'TaskDialogMsgBox'; + TaskDialogButton.Top := FormButton.Top; + TaskDialogButton.Left := FormButton.Left + FormButton.Width + ScaleX(8); + TaskDialogButton.Width := WizardForm.CalculateButtonWidth([TaskDialogButton.Caption]); + TaskDialogButton.Height := ScaleY(23); + TaskDialogButton.Anchors := [akLeft, akBottom]; + TaskDialogButton.OnClick := @TaskDialogButtonOnClick; + TaskDialogButton.Parent := Page.Surface; + + { TComboBox and others } + + Page := CreateCustomPage(Page.ID, 'Custom wizard page controls', 'TComboBox and others'); + + ComboBox := TNewComboBox.Create(Page); + ComboBox.Width := Page.SurfaceWidth; + ComboBox.Anchors := [akLeft, akTop, akRight]; + ComboBox.Parent := Page.Surface; + ComboBox.Style := csDropDownList; + ComboBox.Items.Add('TComboBox'); + ComboBox.ItemIndex := 0; + + ListBox := TNewListBox.Create(Page); + ListBox.Top := ComboBox.Top + ComboBox.Height + ScaleY(8); + ListBox.Width := Page.SurfaceWidth; + ListBox.Height := ScaleY(97); + ListBox.Anchors := [akLeft, akTop, akRight, akBottom]; + ListBox.Parent := Page.Surface; + ListBox.Items.Add('TListBox'); + ListBox.ItemIndex := 0; + + StaticText := TNewStaticText.Create(Page); + StaticText.Top := ListBox.Top + ListBox.Height + ScaleY(8); + StaticText.Anchors := [akLeft, akRight, akBottom]; + StaticText.Caption := 'TNewStaticText'; + StaticText.AutoSize := True; + StaticText.Parent := Page.Surface; + + ProgressBarLabel := TNewStaticText.Create(Page); + ProgressBarLabel.Top := StaticText.Top + StaticText.Height + ScaleY(8); + ProgressBarLabel.Anchors := [akLeft, akBottom]; + ProgressBarLabel.Caption := 'TNewProgressBar'; + ProgressBarLabel.AutoSize := True; + ProgressBarLabel.Parent := Page.Surface; + + ProgressBar := TNewProgressBar.Create(Page); + ProgressBar.Left := ProgressBarLabel.Width + ScaleX(8); + ProgressBar.Top := ProgressBarLabel.Top; + ProgressBar.Width := Page.SurfaceWidth - ProgressBar.Left; + ProgressBar.Height := ProgressBarLabel.Height + ScaleY(8); + ProgressBar.Anchors := [akLeft, akRight, akBottom]; + ProgressBar.Parent := Page.Surface; + ProgressBar.Position := 25; + + ProgressBar2 := TNewProgressBar.Create(Page); + ProgressBar2.Left := ProgressBarLabel.Width + ScaleX(8); + ProgressBar2.Top := ProgressBar.Top + ProgressBar.Height + ScaleY(4); + ProgressBar2.Width := Page.SurfaceWidth - ProgressBar.Left; + ProgressBar2.Height := ProgressBarLabel.Height + ScaleY(8); + ProgressBar2.Anchors := [akLeft, akRight, akBottom]; + ProgressBar2.Parent := Page.Surface; + ProgressBar2.Position := 50; + ProgressBar2.State := npbsError; + + ProgressBar3 := TNewProgressBar.Create(Page); + ProgressBar3.Left := ProgressBarLabel.Width + ScaleX(8); + ProgressBar3.Top := ProgressBar2.Top + ProgressBar2.Height + ScaleY(4); + ProgressBar3.Width := Page.SurfaceWidth - ProgressBar.Left; + ProgressBar3.Height := ProgressBarLabel.Height + ScaleY(8); + ProgressBar3.Anchors := [akLeft, akRight, akBottom]; + ProgressBar3.Parent := Page.Surface; + ProgressBar3.Style := npbstMarquee; + + { TNewCheckListBox } + + Page := CreateCustomPage(Page.ID, 'Custom wizard page controls', 'TNewCheckListBox'); + + CheckListBox := TNewCheckListBox.Create(Page); + CheckListBox.Width := Page.SurfaceWidth; + CheckListBox.Height := ScaleY(97); + CheckListBox.Anchors := [akLeft, akTop, akRight, akBottom]; + CheckListBox.Flat := True; + CheckListBox.Parent := Page.Surface; + CheckListBox.AddCheckBox('TNewCheckListBox', '', 0, True, True, False, True, nil); + CheckListBox.AddRadioButton('TNewCheckListBox', '', 1, True, True, nil); + CheckListBox.AddRadioButton('TNewCheckListBox', '', 1, False, True, nil); + CheckListBox.AddCheckBox('TNewCheckListBox', '', 0, True, True, False, True, nil); + CheckListBox.AddCheckBox('TNewCheckListBox', '', 1, True, True, False, True, nil); + CheckListBox.AddCheckBox('TNewCheckListBox', '123', 2, True, True, False, True, nil); + CheckListBox.AddCheckBox('TNewCheckListBox', '456', 2, False, True, False, True, nil); + CheckListBox.AddCheckBox('TNewCheckListBox', '', 1, False, True, False, True, nil); + CheckListBox.ItemFontStyle[5] := [fsBold]; + CheckListBox.SubItemFontStyle[5] := [fsBold]; + CheckListBox.ItemFontStyle[6] := [fsBold, fsItalic]; + CheckListBox.SubItemFontStyle[6] := [fsBold, fsUnderline]; + + CheckListBox2 := TNewCheckListBox.Create(Page); + CheckListBox2.Top := CheckListBox.Top + CheckListBox.Height + ScaleY(8); + CheckListBox2.Width := Page.SurfaceWidth; + CheckListBox2.Height := ScaleY(97); + CheckListBox2.Anchors := [akLeft, akRight, akBottom]; + CheckListBox2.BorderStyle := bsNone; + CheckListBox2.ParentColor := True; + CheckListBox2.MinItemHeight := WizardForm.TasksList.MinItemHeight; + CheckListBox2.ShowLines := False; + CheckListBox2.WantTabs := True; + CheckListBox2.Parent := Page.Surface; + CheckListBox2.AddGroup('TNewCheckListBox', '', 0, nil); + CheckListBox2.AddRadioButton('TNewCheckListBox', '', 0, True, True, nil); + CheckListBox2.AddRadioButton('TNewCheckListBox', '', 0, False, True, nil); + + { TFolderTreeView } + + Page := CreateCustomPage(Page.ID, 'Custom wizard page controls', 'TFolderTreeView'); + + FolderTreeView := TFolderTreeView.Create(Page); + FolderTreeView.Width := Page.SurfaceWidth; + FolderTreeView.Height := Page.SurfaceHeight; + FolderTreeView.Anchors := [akLeft, akTop, akRight, akBottom]; + FolderTreeView.Parent := Page.Surface; + FolderTreeView.Directory := ExpandConstant('{src}'); + + { TBitmapImage } + + Page := CreateCustomPage(Page.ID, 'Custom wizard page controls', 'TBitmapImage'); + + BitmapFileName := ExpandConstant('{tmp}\WizClassicSmallImage.bmp'); + ExtractTemporaryFile(ExtractFileName(BitmapFileName)); + + BitmapImage := TBitmapImage.Create(Page); + BitmapImage.AutoSize := True; + BitmapImage.Bitmap.LoadFromFile(BitmapFileName); + BitmapImage.Cursor := crHand; + BitmapImage.OnClick := @BitmapImageOnClick; + BitmapImage.Parent := Page.Surface; + + BitmapImage2 := TBitmapImage.Create(Page); + BitmapImage2.BackColor := $400000; + BitmapImage2.Bitmap := BitmapImage.Bitmap; + BitmapImage2.Center := True; + BitmapImage2.Left := BitmapImage.Width + 10; + BitmapImage2.Height := 2*BitmapImage.Height; + BitmapImage2.Width := 2*BitmapImage.Width; + BitmapImage2.Cursor := crHand; + BitmapImage2.OnClick := @BitmapImageOnClick; + BitmapImage2.Parent := Page.Surface; + + BitmapImage3 := TBitmapImage.Create(Page); + BitmapImage3.Bitmap := BitmapImage.Bitmap; + BitmapImage3.Stretch := True; + BitmapImage3.Left := 3*BitmapImage.Width + 20; + BitmapImage3.Height := 4*BitmapImage.Height; + BitmapImage3.Width := 4*BitmapImage.Width; + BitmapImage3.Anchors := [akLeft, akTop, akRight, akBottom]; + BitmapImage3.Cursor := crHand; + BitmapImage3.OnClick := @BitmapImageOnClick; + BitmapImage3.Parent := Page.Surface; + + { TRichViewer } + + Page := CreateCustomPage(Page.ID, 'Custom wizard page controls', 'TRichViewer'); + + RichEditViewer := TRichEditViewer.Create(Page); + RichEditViewer.Width := Page.SurfaceWidth; + RichEditViewer.Height := Page.SurfaceHeight; + RichEditViewer.Anchors := [akLeft, akTop, akRight, akBottom]; + RichEditViewer.BevelKind := bkFlat; + RichEditViewer.BorderStyle := bsNone; + RichEditViewer.Parent := Page.Surface; + RichEditViewer.ScrollBars := ssVertical; + RichEditViewer.UseRichEdit := True; + RichEditViewer.RTFText := '{\rtf1\ansi\ansicpg1252\deff0\deflang1043{\fonttbl{\f0\fswiss\fcharset0 Arial;}}{\colortbl ;\red255\green0\blue0;\red0\green128\blue0;\red0\green0\blue128;}\viewkind4\uc1\pard\f0\fs20 T\cf1 Rich\cf2 Edit\cf3 Viewer\cf0\par}'; + RichEditViewer.ReadOnly := True; +end; + +procedure AboutButtonOnClick(Sender: TObject); +begin + MsgBox('This demo shows some features of the various form objects and control classes.', mbInformation, mb_Ok); +end; + +procedure URLLabelOnClick(Sender: TObject); +var + ErrorCode: Integer; +begin + ShellExecAsOriginalUser('open', 'http://www.innosetup.com/', '', '', SW_SHOWNORMAL, ewNoWait, ErrorCode); +end; + +procedure CreateAboutButtonAndURLLabel(ParentForm: TSetupForm; CancelButton: TNewButton); +var + AboutButton: TNewButton; + URLLabel: TNewStaticText; +begin + AboutButton := TNewButton.Create(ParentForm); + AboutButton.Left := ParentForm.ClientWidth - CancelButton.Left - CancelButton.Width; + AboutButton.Top := CancelButton.Top; + AboutButton.Width := CancelButton.Width; + AboutButton.Height := CancelButton.Height; + AboutButton.Anchors := [akLeft, akBottom]; + AboutButton.Caption := '&About...'; + AboutButton.OnClick := @AboutButtonOnClick; + AboutButton.Parent := ParentForm; + + URLLabel := TNewStaticText.Create(ParentForm); + URLLabel.Caption := 'www.innosetup.com'; + URLLabel.Cursor := crHand; + URLLabel.OnClick := @URLLabelOnClick; + URLLabel.Parent := ParentForm; + { Alter Font *after* setting Parent so the correct defaults are inherited first } + URLLabel.Font.Style := URLLabel.Font.Style + [fsUnderline]; + URLLabel.Font.Color := clHotLight + URLLabel.Top := AboutButton.Top + AboutButton.Height - URLLabel.Height - 2; + URLLabel.Left := AboutButton.Left + AboutButton.Width + ScaleX(20); + URLLabel.Anchors := [akLeft, akBottom]; +end; + +procedure InitializeWizard(); +begin + { Custom wizard pages } + + CreateTheWizardPages; + + { Custom controls } + + CreateAboutButtonAndURLLabel(WizardForm, WizardForm.CancelButton); + + { Custom beveled label } + + WizardForm.BeveledLabel.Caption := ' Bevel '; +end; + +procedure InitializeUninstallProgressForm(); +begin + { Custom controls } + + CreateAboutButtonAndURLLabel(UninstallProgressForm, UninstallProgressForm.CancelButton); +end; + diff --git a/src/AITool.Setup/INNO/Examples/CodeDlg.iss b/src/AITool.Setup/INNO/Examples/CodeDlg.iss new file mode 100644 index 00000000..2fe57360 --- /dev/null +++ b/src/AITool.Setup/INNO/Examples/CodeDlg.iss @@ -0,0 +1,207 @@ +; -- CodeDlg.iss -- +; +; This script shows how to insert custom wizard pages into Setup and how to handle +; these pages. Furthermore it shows how to 'communicate' between the [Code] section +; and the regular Inno Setup sections using {code:...} constants. Finally it shows +; how to customize the settings text on the 'Ready To Install' page. + +[Setup] +AppName=My Program +AppVersion=1.5 +WizardStyle=modern +DisableWelcomePage=no +DefaultDirName={autopf}\My Program +DisableProgramGroupPage=yes +UninstallDisplayIcon={app}\MyProg.exe +OutputDir=userdocs:Inno Setup Examples Output +PrivilegesRequired=lowest + +[Files] +Source: "MyProg.exe"; DestDir: "{app}" +Source: "MyProg.chm"; DestDir: "{app}" +Source: "Readme.txt"; DestDir: "{app}"; Flags: isreadme + +[Registry] +Root: HKA; Subkey: "Software\My Company"; Flags: uninsdeletekeyifempty +Root: HKA; Subkey: "Software\My Company\My Program"; Flags: uninsdeletekey +Root: HKA; Subkey: "Software\My Company\My Program\Settings"; ValueType: string; ValueName: "Name"; ValueData: "{code:GetUser|Name}" +Root: HKA; Subkey: "Software\My Company\My Program\Settings"; ValueType: string; ValueName: "Company"; ValueData: "{code:GetUser|Company}" +Root: HKA; Subkey: "Software\My Company\My Program\Settings"; ValueType: string; ValueName: "DataDir"; ValueData: "{code:GetDataDir}" +; etc. + +[Dirs] +Name: {code:GetDataDir}; Flags: uninsneveruninstall + +[Code] +var + UserPage: TInputQueryWizardPage; + UsagePage: TInputOptionWizardPage; + LightMsgPage: TOutputMsgWizardPage; + KeyPage: TInputQueryWizardPage; + ProgressPage: TOutputProgressWizardPage; + DataDirPage: TInputDirWizardPage; + +procedure InitializeWizard; +begin + { Create the pages } + + UserPage := CreateInputQueryPage(wpWelcome, + 'Personal Information', 'Who are you?', + 'Please specify your name and the company for whom you work, then click Next.'); + UserPage.Add('Name:', False); + UserPage.Add('Company:', False); + + UsagePage := CreateInputOptionPage(UserPage.ID, + 'Personal Information', 'How will you use My Program?', + 'Please specify how you would like to use My Program, then click Next.', + True, False); + UsagePage.Add('Light mode (no ads, limited functionality)'); + UsagePage.Add('Sponsored mode (with ads, full functionality)'); + UsagePage.Add('Paid mode (no ads, full functionality)'); + + LightMsgPage := CreateOutputMsgPage(UsagePage.ID, + 'Personal Information', 'How will you use My Program?', + 'Note: to enjoy all features My Program can offer and to support its development, ' + + 'you can switch to sponsored or paid mode at any time by selecting ''Usage Mode'' ' + + 'in the ''Help'' menu of My Program after the installation has completed.'#13#13 + + 'Click Back if you want to change your usage mode setting now, or click Next to ' + + 'continue with the installation.'); + + KeyPage := CreateInputQueryPage(UsagePage.ID, + 'Personal Information', 'What''s your registration key?', + 'Please specify your registration key and click Next to continue. If you don''t ' + + 'have a valid registration key, click Back to choose a different usage mode.'); + KeyPage.Add('Registration key:', False); + + ProgressPage := CreateOutputProgressPage('Personal Information', + 'What''s your registration key?'); + + DataDirPage := CreateInputDirPage(wpSelectDir, + 'Select Personal Data Directory', 'Where should personal data files be installed?', + 'Select the folder in which Setup should install personal data files, then click Next.', + False, ''); + DataDirPage.Add(''); + + { Set default values, using settings that were stored last time if possible } + + UserPage.Values[0] := GetPreviousData('Name', ExpandConstant('{sysuserinfoname}')); + UserPage.Values[1] := GetPreviousData('Company', ExpandConstant('{sysuserinfoorg}')); + + case GetPreviousData('UsageMode', '') of + 'light': UsagePage.SelectedValueIndex := 0; + 'sponsored': UsagePage.SelectedValueIndex := 1; + 'paid': UsagePage.SelectedValueIndex := 2; + else + UsagePage.SelectedValueIndex := 1; + end; + + DataDirPage.Values[0] := GetPreviousData('DataDir', ''); +end; + +procedure RegisterPreviousData(PreviousDataKey: Integer); +var + UsageMode: String; +begin + { Store the settings so we can restore them next time } + SetPreviousData(PreviousDataKey, 'Name', UserPage.Values[0]); + SetPreviousData(PreviousDataKey, 'Company', UserPage.Values[1]); + case UsagePage.SelectedValueIndex of + 0: UsageMode := 'light'; + 1: UsageMode := 'sponsored'; + 2: UsageMode := 'paid'; + end; + SetPreviousData(PreviousDataKey, 'UsageMode', UsageMode); + SetPreviousData(PreviousDataKey, 'DataDir', DataDirPage.Values[0]); +end; + +function ShouldSkipPage(PageID: Integer): Boolean; +begin + { Skip pages that shouldn't be shown } + if (PageID = LightMsgPage.ID) and (UsagePage.SelectedValueIndex <> 0) then + Result := True + else if (PageID = KeyPage.ID) and (UsagePage.SelectedValueIndex <> 2) then + Result := True + else + Result := False; +end; + +function NextButtonClick(CurPageID: Integer): Boolean; +var + I: Integer; +begin + { Validate certain pages before allowing the user to proceed } + if CurPageID = UserPage.ID then begin + if UserPage.Values[0] = '' then begin + MsgBox('You must enter your name.', mbError, MB_OK); + Result := False; + end else begin + if DataDirPage.Values[0] = '' then + DataDirPage.Values[0] := 'C:\' + UserPage.Values[0]; + Result := True; + end; + end else if CurPageID = KeyPage.ID then begin + { Just to show how 'OutputProgress' pages work. + Always use a try..finally between the Show and Hide calls as shown below. } + ProgressPage.SetText('Authorizing registration key...', ''); + ProgressPage.SetProgress(0, 0); + ProgressPage.Show; + try + for I := 0 to 10 do begin + ProgressPage.SetProgress(I, 10); + Sleep(100); + end; + finally + ProgressPage.Hide; + end; + if GetSHA1OfString('codedlg' + KeyPage.Values[0]) = '8013f310d340dab18a0d0cda2b5b115d2dcd97e4' then + Result := True + else begin + MsgBox('You must enter a valid registration key. (Hint: The key is "inno".)', mbError, MB_OK); + Result := False; + end; + end else + Result := True; +end; + +function UpdateReadyMemo(Space, NewLine, MemoUserInfoInfo, MemoDirInfo, MemoTypeInfo, + MemoComponentsInfo, MemoGroupInfo, MemoTasksInfo: String): String; +var + S: String; +begin + { Fill the 'Ready Memo' with the normal settings and the custom settings } + S := ''; + S := S + 'Personal Information:' + NewLine; + S := S + Space + UserPage.Values[0] + NewLine; + if UserPage.Values[1] <> '' then + S := S + Space + UserPage.Values[1] + NewLine; + S := S + NewLine; + + S := S + 'Usage Mode:' + NewLine + Space; + case UsagePage.SelectedValueIndex of + 0: S := S + 'Light mode'; + 1: S := S + 'Sponsored mode'; + 2: S := S + 'Paid mode'; + end; + S := S + NewLine + NewLine; + + S := S + MemoDirInfo + NewLine; + S := S + Space + DataDirPage.Values[0] + ' (personal data files)' + NewLine; + + Result := S; +end; + +function GetUser(Param: String): String; +begin + { Return a user value } + { Could also be split into separate GetUserName and GetUserCompany functions } + if Param = 'Name' then + Result := UserPage.Values[0] + else if Param = 'Company' then + Result := UserPage.Values[1]; +end; + +function GetDataDir(Param: String): String; +begin + { Return the selected DataDir } + Result := DataDirPage.Values[0]; +end; diff --git a/src/AITool.Setup/INNO/Examples/CodeDll.iss b/src/AITool.Setup/INNO/Examples/CodeDll.iss new file mode 100644 index 00000000..3dd808a3 --- /dev/null +++ b/src/AITool.Setup/INNO/Examples/CodeDll.iss @@ -0,0 +1,95 @@ +; -- CodeDll.iss -- +; +; This script shows how to call functions in external DLLs (like Windows API functions) +; at runtime and how to perform direct callbacks from these functions to functions +; in the script. + +[Setup] +AppName=My Program +AppVersion=1.5 +WizardStyle=modern +DefaultDirName={autopf}\My Program +DisableProgramGroupPage=yes +DisableWelcomePage=no +UninstallDisplayIcon={app}\MyProg.exe +OutputDir=userdocs:Inno Setup Examples Output + +[Files] +Source: "MyProg.exe"; DestDir: "{app}" +Source: "MyProg.chm"; DestDir: "{app}" +Source: "Readme.txt"; DestDir: "{app}"; Flags: isreadme +; Install our DLL to {app} so we can access it at uninstall time. +; Use "Flags: dontcopy" if you don't need uninstall time access. +Source: "MyDll.dll"; DestDir: "{app}" + +[Code] +const + MB_ICONINFORMATION = $40; + +// Importing a Unicode Windows API function. +function MessageBox(hWnd: Integer; lpText, lpCaption: String; uType: Cardinal): Integer; +external 'MessageBoxW@user32.dll stdcall'; + +// Importing an ANSI custom DLL function, first for Setup, then for uninstall. +procedure MyDllFuncSetup(hWnd: Integer; lpText, lpCaption: AnsiString; uType: Cardinal); +external 'MyDllFunc@files:MyDll.dll stdcall setuponly'; + +procedure MyDllFuncUninstall(hWnd: Integer; lpText, lpCaption: AnsiString; uType: Cardinal); +external 'MyDllFunc@{app}\MyDll.dll stdcall uninstallonly'; + +// Importing an ANSI function for a DLL which might not exist at runtime. +procedure DelayLoadedFunc(hWnd: Integer; lpText, lpCaption: AnsiString; uType: Cardinal); +external 'DllFunc@DllWhichMightNotExist.dll stdcall delayload'; + +function NextButtonClick(CurPage: Integer): Boolean; +var + hWnd: Integer; +begin + if CurPage = wpWelcome then begin + hWnd := StrToInt(ExpandConstant('{wizardhwnd}')); + + MessageBox(hWnd, 'Hello from Windows API function', 'MessageBoxA', MB_OK or MB_ICONINFORMATION); + + MyDllFuncSetup(hWnd, 'Hello from custom DLL function', 'MyDllFunc', MB_OK or MB_ICONINFORMATION); + + try + // If this DLL does not exist (it shouldn't), an exception will be raised. Press F9 to continue. + DelayLoadedFunc(hWnd, 'Hello from delay loaded function', 'DllFunc', MB_OK or MB_ICONINFORMATION); + except + // + end; + end; + Result := True; +end; + +procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); +begin + // Call our function just before the actual uninstall process begins. + if CurUninstallStep = usUninstall then begin + MyDllFuncUninstall(0, 'Hello from custom DLL function', 'MyDllFunc', MB_OK or MB_ICONINFORMATION); + + // Now that we're finished with it, unload MyDll.dll from memory. + // We have to do this so that the uninstaller will be able to remove the DLL and the {app} directory. + UnloadDLL(ExpandConstant('{app}\MyDll.dll')); + end; +end; + +// The following shows how to use callbacks. + +function SetTimer(hWnd, nIDEvent, uElapse, lpTimerFunc: Longword): Longword; +external 'SetTimer@user32.dll stdcall'; + +var + TimerCount: Integer; + +procedure MyTimerProc(Arg1, Arg2, Arg3, Arg4: Longword); +begin + Inc(TimerCount); + WizardForm.BeveledLabel.Caption := ' Timer! ' + IntToStr(TimerCount) + ' '; + WizardForm.BeveledLabel.Visible := True; +end; + +procedure InitializeWizard; +begin + SetTimer(0, 0, 1000, CreateCallback(@MyTimerProc)); +end; diff --git a/src/AITool.Setup/INNO/Examples/CodeDownloadFiles.iss b/src/AITool.Setup/INNO/Examples/CodeDownloadFiles.iss new file mode 100644 index 00000000..f4c476cd --- /dev/null +++ b/src/AITool.Setup/INNO/Examples/CodeDownloadFiles.iss @@ -0,0 +1,67 @@ +; -- CodeDownloadFiles.iss -- +; +; This script shows how the CreateDownloadPage support function can be used to +; download temporary files while showing the download progress to the user. + +[Setup] +AppName=My Program +AppVersion=1.5 +WizardStyle=modern +DefaultDirName={autopf}\My Program +DefaultGroupName=My Program +UninstallDisplayIcon={app}\MyProg.exe +OutputDir=userdocs:Inno Setup Examples Output + +[Files] +; Place any regular files here +Source: "MyProg.exe"; DestDir: "{app}"; +Source: "MyProg.chm"; DestDir: "{app}"; +Source: "Readme.txt"; DestDir: "{app}"; Flags: isreadme; +; These files will be downloaded +Source: "{tmp}\innosetup-latest.exe"; DestDir: "{app}"; Flags: external +Source: "{tmp}\ISCrypt.dll"; DestDir: "{app}"; Flags: external + +[Icons] +Name: "{group}\My Program"; Filename: "{app}\MyProg.exe" + +[Code] +var + DownloadPage: TDownloadWizardPage; + +function OnDownloadProgress(const Url, FileName: String; const Progress, ProgressMax: Int64): Boolean; +begin + if Progress = ProgressMax then + Log(Format('Successfully downloaded file to {tmp}: %s', [FileName])); + Result := True; +end; + +procedure InitializeWizard; +begin + DownloadPage := CreateDownloadPage(SetupMessage(msgWizardPreparing), SetupMessage(msgPreparingDesc), @OnDownloadProgress); +end; + +function NextButtonClick(CurPageID: Integer): Boolean; +begin + if CurPageID = wpReady then begin + DownloadPage.Clear; + // Use AddEx to specify a username and password + DownloadPage.Add('https://jrsoftware.org/download.php/is.exe', 'innosetup-latest.exe', ''); + DownloadPage.Add('https://jrsoftware.org/download.php/iscrypt.dll', 'ISCrypt.dll', '2f6294f9aa09f59a574b5dcd33be54e16b39377984f3d5658cda44950fa0f8fc'); + DownloadPage.Show; + try + try + DownloadPage.Download; // This downloads the files to {tmp} + Result := True; + except + if DownloadPage.AbortedByUser then + Log('Aborted by user.') + else + SuppressibleMsgBox(AddPeriod(GetExceptionMessage), mbCriticalError, MB_OK, IDOK); + Result := False; + end; + finally + DownloadPage.Hide; + end; + end else + Result := True; +end; \ No newline at end of file diff --git a/src/AITool.Setup/INNO/Examples/CodeExample1.iss b/src/AITool.Setup/INNO/Examples/CodeExample1.iss new file mode 100644 index 00000000..1db9a711 --- /dev/null +++ b/src/AITool.Setup/INNO/Examples/CodeExample1.iss @@ -0,0 +1,167 @@ +; -- CodeExample1.iss -- +; +; This script shows various things you can achieve using a [Code] section. + +[Setup] +AppName=My Program +AppVersion=1.5 +WizardStyle=modern +DisableWelcomePage=no +DefaultDirName={code:MyConst}\My Program +DefaultGroupName=My Program +UninstallDisplayIcon={app}\MyProg.exe +InfoBeforeFile=Readme.txt +OutputDir=userdocs:Inno Setup Examples Output + +[Files] +Source: "MyProg.exe"; DestDir: "{app}"; Check: MyProgCheck; BeforeInstall: BeforeMyProgInstall('MyProg.exe'); AfterInstall: AfterMyProgInstall('MyProg.exe') +Source: "MyProg.chm"; DestDir: "{app}"; Check: MyProgCheck; BeforeInstall: BeforeMyProgInstall('MyProg.chm'); AfterInstall: AfterMyProgInstall('MyProg.chm') +Source: "Readme.txt"; DestDir: "{app}"; Flags: isreadme + +[Icons] +Name: "{group}\My Program"; Filename: "{app}\MyProg.exe" + +[Code] +var + MyProgChecked: Boolean; + MyProgCheckResult: Boolean; + FinishedInstall: Boolean; + +function InitializeSetup(): Boolean; +begin + Log('InitializeSetup called'); + Result := MsgBox('InitializeSetup:' #13#13 'Setup is initializing. Do you really want to start setup?', mbConfirmation, MB_YESNO) = idYes; + if Result = False then + MsgBox('InitializeSetup:' #13#13 'Ok, bye bye.', mbInformation, MB_OK); +end; + +procedure InitializeWizard; +begin + Log('InitializeWizard called'); +end; + + +procedure InitializeWizard2; +begin + Log('InitializeWizard2 called'); +end; + +procedure DeinitializeSetup(); +var + FileName: String; + ResultCode: Integer; +begin + Log('DeinitializeSetup called'); + if FinishedInstall then begin + if MsgBox('DeinitializeSetup:' #13#13 'The [Code] scripting demo has finished. Do you want to uninstall My Program now?', mbConfirmation, MB_YESNO) = idYes then begin + FileName := ExpandConstant('{uninstallexe}'); + if not Exec(FileName, '', '', SW_SHOWNORMAL, ewNoWait, ResultCode) then + MsgBox('DeinitializeSetup:' #13#13 'Execution of ''' + FileName + ''' failed. ' + SysErrorMessage(ResultCode) + '.', mbError, MB_OK); + end else + MsgBox('DeinitializeSetup:' #13#13 'Ok, bye bye.', mbInformation, MB_OK); + end; +end; + +procedure CurStepChanged(CurStep: TSetupStep); +begin + Log('CurStepChanged(' + IntToStr(Ord(CurStep)) + ') called'); + if CurStep = ssPostInstall then + FinishedInstall := True; +end; + +procedure CurInstallProgressChanged(CurProgress, MaxProgress: Integer); +begin + Log('CurInstallProgressChanged(' + IntToStr(CurProgress) + ', ' + IntToStr(MaxProgress) + ') called'); +end; + +function NextButtonClick(CurPageID: Integer): Boolean; +var + ResultCode: Integer; +begin + Log('NextButtonClick(' + IntToStr(CurPageID) + ') called'); + case CurPageID of + wpSelectDir: + MsgBox('NextButtonClick:' #13#13 'You selected: ''' + WizardDirValue + '''.', mbInformation, MB_OK); + wpSelectProgramGroup: + MsgBox('NextButtonClick:' #13#13 'You selected: ''' + WizardGroupValue + '''.', mbInformation, MB_OK); + wpReady: + begin + if MsgBox('NextButtonClick:' #13#13 'Using the script, files can be extracted before the installation starts. For example we could extract ''MyProg.exe'' now and run it.' #13#13 'Do you want to do this?', mbConfirmation, MB_YESNO) = idYes then begin + ExtractTemporaryFile('myprog.exe'); + if not ExecAsOriginalUser(ExpandConstant('{tmp}\myprog.exe'), '', '', SW_SHOWNORMAL, ewWaitUntilTerminated, ResultCode) then + MsgBox('NextButtonClick:' #13#13 'The file could not be executed. ' + SysErrorMessage(ResultCode) + '.', mbError, MB_OK); + end; + BringToFrontAndRestore(); + MsgBox('NextButtonClick:' #13#13 'The normal installation will now start.', mbInformation, MB_OK); + end; + end; + + Result := True; +end; + +function BackButtonClick(CurPageID: Integer): Boolean; +begin + Log('BackButtonClick(' + IntToStr(CurPageID) + ') called'); + Result := True; +end; + +function ShouldSkipPage(PageID: Integer): Boolean; +begin + Log('ShouldSkipPage(' + IntToStr(PageID) + ') called'); + { Skip wpInfoBefore page; show all others } + case PageID of + wpInfoBefore: + Result := True; + else + Result := False; + end; +end; + +procedure CurPageChanged(CurPageID: Integer); +begin + Log('CurPageChanged(' + IntToStr(CurPageID) + ') called'); + case CurPageID of + wpWelcome: + MsgBox('CurPageChanged:' #13#13 'Welcome to the [Code] scripting demo. This demo will show you some possibilities of the scripting support.' #13#13 'The scripting engine used is RemObjects Pascal Script by Carlo Kok. See http://www.remobjects.com/ps for more information.', mbInformation, MB_OK); + wpFinished: + MsgBox('CurPageChanged:' #13#13 'Welcome to final page of this demo. Click Finish to exit.', mbInformation, MB_OK); + end; +end; + +function PrepareToInstall(var NeedsRestart: Boolean): String; +begin + Log('PrepareToInstall() called'); + if MsgBox('PrepareToInstall:' #13#13 'Setup is preparing to install. Using the script you can install any prerequisites, abort Setup on errors, and request restarts. Do you want to return an error now?', mbConfirmation, MB_YESNO or MB_DEFBUTTON2) = idYes then + Result := '.' + else + Result := ''; +end; + +function MyProgCheck(): Boolean; +begin + Log('MyProgCheck() called'); + if not MyProgChecked then begin + MyProgCheckResult := MsgBox('MyProgCheck:' #13#13 'Using the script you can decide at runtime to include or exclude files from the installation. Do you want to install MyProg.exe and MyProg.chm to ' + ExtractFilePath(CurrentFileName) + '?', mbConfirmation, MB_YESNO) = idYes; + MyProgChecked := True; + end; + Result := MyProgCheckResult; +end; + +procedure BeforeMyProgInstall(S: String); +begin + Log('BeforeMyProgInstall(''' + S + ''') called'); + MsgBox('BeforeMyProgInstall:' #13#13 'Setup is now going to install ' + S + ' as ' + CurrentFileName + '.', mbInformation, MB_OK); +end; + +procedure AfterMyProgInstall(S: String); +begin + Log('AfterMyProgInstall(''' + S + ''') called'); + MsgBox('AfterMyProgInstall:' #13#13 'Setup just installed ' + S + ' as ' + CurrentFileName + '.', mbInformation, MB_OK); +end; + +function MyConst(Param: String): String; +begin + Log('MyConst(''' + Param + ''') called'); + Result := ExpandConstant('{autopf}'); +end; + diff --git a/src/AITool.Setup/INNO/Examples/CodePrepareToInstall.iss b/src/AITool.Setup/INNO/Examples/CodePrepareToInstall.iss new file mode 100644 index 00000000..bad5065a --- /dev/null +++ b/src/AITool.Setup/INNO/Examples/CodePrepareToInstall.iss @@ -0,0 +1,122 @@ +; -- CodePrepareToInstall.iss -- +; +; This script shows how the PrepareToInstall event function can be used to +; install prerequisites and handle any reboots in between, while remembering +; user selections across reboots. + +[Setup] +AppName=My Program +AppVersion=1.5 +WizardStyle=modern +DefaultDirName={autopf}\My Program +DefaultGroupName=My Program +UninstallDisplayIcon={app}\MyProg.exe +OutputDir=userdocs:Inno Setup Examples Output + +[Files] +; Place any prerequisite files here, for example: +; Source: "MyProg-Prerequisite-setup.exe"; Flags: dontcopy +; Place any regular files here, so *after* all your prerequisites. +Source: "MyProg.exe"; DestDir: "{app}"; +Source: "MyProg.chm"; DestDir: "{app}"; +Source: "Readme.txt"; DestDir: "{app}"; Flags: isreadme; + +[Icons] +Name: "{group}\My Program"; Filename: "{app}\MyProg.exe" + +[Code] +const + (*** Customize the following to your own name. ***) + RunOnceName = 'My Program Setup restart'; + + QuitMessageReboot = 'The installation of a prerequisite program was not completed. You will need to restart your computer to complete that installation.'#13#13'After restarting your computer, Setup will continue next time an administrator logs in.'; + QuitMessageError = 'Error. Cannot continue.'; + +var + Restarted: Boolean; + +function InitializeSetup(): Boolean; +begin + Restarted := ExpandConstant('{param:restart|0}') = '1'; + + if not Restarted then begin + Result := not RegValueExists(HKA, 'Software\Microsoft\Windows\CurrentVersion\RunOnce', RunOnceName); + if not Result then + MsgBox(QuitMessageReboot, mbError, mb_Ok); + end else + Result := True; +end; + +function DetectAndInstallPrerequisites: Boolean; +begin + (*** Place your prerequisite detection and extraction+installation code below. ***) + (*** Return False if missing prerequisites were detected but their installation failed, else return True. ***) + + // + //extraction example: ExtractTemporaryFile('MyProg-Prerequisite-setup.exe'); + + Result := True; + + (*** Remove the following block! Used by this demo to simulate a prerequisite install requiring a reboot. ***) + if not Restarted then + RestartReplace(ParamStr(0), ''); +end; + +function Quote(const S: String): String; +begin + Result := '"' + S + '"'; +end; + +function AddParam(const S, P, V: String): String; +begin + if V <> '""' then + Result := S + ' /' + P + '=' + V; +end; + +function AddSimpleParam(const S, P: String): String; +begin + Result := S + ' /' + P; +end; + +procedure CreateRunOnceEntry; +var + RunOnceData: String; +begin + RunOnceData := Quote(ExpandConstant('{srcexe}')) + ' /restart=1'; + RunOnceData := AddParam(RunOnceData, 'LANG', ExpandConstant('{language}')); + RunOnceData := AddParam(RunOnceData, 'DIR', Quote(WizardDirValue)); + RunOnceData := AddParam(RunOnceData, 'GROUP', Quote(WizardGroupValue)); + if WizardNoIcons then + RunOnceData := AddSimpleParam(RunOnceData, 'NOICONS'); + RunOnceData := AddParam(RunOnceData, 'TYPE', Quote(WizardSetupType(False))); + RunOnceData := AddParam(RunOnceData, 'COMPONENTS', Quote(WizardSelectedComponents(False))); + RunOnceData := AddParam(RunOnceData, 'TASKS', Quote(WizardSelectedTasks(False))); + + (*** Place any custom user selection you want to remember below. ***) + + // + + RegWriteStringValue(HKA, 'Software\Microsoft\Windows\CurrentVersion\RunOnce', RunOnceName, RunOnceData); +end; + +function PrepareToInstall(var NeedsRestart: Boolean): String; +var + ChecksumBefore, ChecksumAfter: String; +begin + ChecksumBefore := MakePendingFileRenameOperationsChecksum; + if DetectAndInstallPrerequisites then begin + ChecksumAfter := MakePendingFileRenameOperationsChecksum; + if ChecksumBefore <> ChecksumAfter then begin + CreateRunOnceEntry; + NeedsRestart := True; + Result := QuitMessageReboot; + end; + end else + Result := QuitMessageError; +end; + +function ShouldSkipPage(PageID: Integer): Boolean; +begin + Result := Restarted; +end; + diff --git a/src/AITool.Setup/INNO/Examples/Components.iss b/src/AITool.Setup/INNO/Examples/Components.iss new file mode 100644 index 00000000..46a20d61 --- /dev/null +++ b/src/AITool.Setup/INNO/Examples/Components.iss @@ -0,0 +1,34 @@ +; -- Components.iss -- +; Demonstrates a components-based installation. + +; SEE THE DOCUMENTATION FOR DETAILS ON CREATING .ISS SCRIPT FILES! + +[Setup] +AppName=My Program +AppVersion=1.5 +WizardStyle=modern +DefaultDirName={autopf}\My Program +DefaultGroupName=My Program +UninstallDisplayIcon={app}\MyProg.exe +OutputDir=userdocs:Inno Setup Examples Output + +[Types] +Name: "full"; Description: "Full installation" +Name: "compact"; Description: "Compact installation" +Name: "custom"; Description: "Custom installation"; Flags: iscustom + +[Components] +Name: "program"; Description: "Program Files"; Types: full compact custom; Flags: fixed +Name: "help"; Description: "Help File"; Types: full +Name: "readme"; Description: "Readme File"; Types: full +Name: "readme\en"; Description: "English"; Flags: exclusive +Name: "readme\de"; Description: "German"; Flags: exclusive + +[Files] +Source: "MyProg.exe"; DestDir: "{app}"; Components: program +Source: "MyProg.chm"; DestDir: "{app}"; Components: help +Source: "Readme.txt"; DestDir: "{app}"; Components: readme\en; Flags: isreadme +Source: "Readme-German.txt"; DestName: "Liesmich.txt"; DestDir: "{app}"; Components: readme\de; Flags: isreadme + +[Icons] +Name: "{group}\My Program"; Filename: "{app}\MyProg.exe" diff --git a/src/AITool.Setup/INNO/Examples/Example1.iss b/src/AITool.Setup/INNO/Examples/Example1.iss new file mode 100644 index 00000000..a00ec5b9 --- /dev/null +++ b/src/AITool.Setup/INNO/Examples/Example1.iss @@ -0,0 +1,23 @@ +; -- Example1.iss -- +; Demonstrates copying 3 files and creating an icon. + +; SEE THE DOCUMENTATION FOR DETAILS ON CREATING .ISS SCRIPT FILES! + +[Setup] +AppName=My Program +AppVersion=1.5 +WizardStyle=modern +DefaultDirName={autopf}\My Program +DefaultGroupName=My Program +UninstallDisplayIcon={app}\MyProg.exe +Compression=lzma2 +SolidCompression=yes +OutputDir=userdocs:Inno Setup Examples Output + +[Files] +Source: "MyProg.exe"; DestDir: "{app}" +Source: "MyProg.chm"; DestDir: "{app}" +Source: "Readme.txt"; DestDir: "{app}"; Flags: isreadme + +[Icons] +Name: "{group}\My Program"; Filename: "{app}\MyProg.exe" diff --git a/src/AITool.Setup/INNO/Examples/Example2.iss b/src/AITool.Setup/INNO/Examples/Example2.iss new file mode 100644 index 00000000..b74f3638 --- /dev/null +++ b/src/AITool.Setup/INNO/Examples/Example2.iss @@ -0,0 +1,27 @@ +; -- Example2.iss -- +; Same as Example1.iss, but creates its icon in the Programs folder of the +; Start Menu instead of in a subfolder, and also creates a desktop icon. + +; SEE THE DOCUMENTATION FOR DETAILS ON CREATING .ISS SCRIPT FILES! + +[Setup] +AppName=My Program +AppVersion=1.5 +WizardStyle=modern +DefaultDirName={autopf}\My Program +; Since no icons will be created in "{group}", we don't need the wizard +; to ask for a Start Menu folder name: +DisableProgramGroupPage=yes +UninstallDisplayIcon={app}\MyProg.exe +Compression=lzma2 +SolidCompression=yes +OutputDir=userdocs:Inno Setup Examples Output + +[Files] +Source: "MyProg.exe"; DestDir: "{app}" +Source: "MyProg.chm"; DestDir: "{app}" +Source: "Readme.txt"; DestDir: "{app}"; Flags: isreadme + +[Icons] +Name: "{autoprograms}\My Program"; Filename: "{app}\MyProg.exe" +Name: "{autodesktop}\My Program"; Filename: "{app}\MyProg.exe" diff --git a/src/AITool.Setup/INNO/Examples/Example3.iss b/src/AITool.Setup/INNO/Examples/Example3.iss new file mode 100644 index 00000000..8e51bc1d --- /dev/null +++ b/src/AITool.Setup/INNO/Examples/Example3.iss @@ -0,0 +1,62 @@ +; -- Example3.iss -- +; Same as Example1.iss, but creates some registry entries too and allows the end +; use to choose the install mode (administrative or non administrative). + +; SEE THE DOCUMENTATION FOR DETAILS ON CREATING .ISS SCRIPT FILES! + +[Setup] +AppName=My Program +AppVersion=1.5 +WizardStyle=modern +DefaultDirName={autopf}\My Program +DefaultGroupName=My Program +UninstallDisplayIcon={app}\MyProg.exe +Compression=lzma2 +SolidCompression=yes +OutputDir=userdocs:Inno Setup Examples Output +ChangesAssociations=yes +UserInfoPage=yes +PrivilegesRequiredOverridesAllowed=dialog + +[Files] +Source: "MyProg.exe"; DestDir: "{app}" +Source: "MyProg.chm"; DestDir: "{app}" +Source: "Readme.txt"; DestDir: "{app}"; Flags: isreadme + +[Icons] +Name: "{group}\My Program"; Filename: "{app}\MyProg.exe" + +; NOTE: Most apps do not need registry entries to be pre-created. If you +; don't know what the registry is or if you need to use it, then chances are +; you don't need a [Registry] section. + +[Registry] +; Create "Software\My Company\My Program" keys under CURRENT_USER or +; LOCAL_MACHINE depending on administrative or non administrative install +; mode. The flags tell it to always delete the "My Program" key upon +; uninstall, and delete the "My Company" key if there is nothing left in it. +Root: HKA; Subkey: "Software\My Company"; Flags: uninsdeletekeyifempty +Root: HKA; Subkey: "Software\My Company\My Program"; Flags: uninsdeletekey +Root: HKA; Subkey: "Software\My Company\My Program\Settings"; ValueType: string; ValueName: "Language"; ValueData: "{language}" +; Associate .myp files with My Program (requires ChangesAssociations=yes) +Root: HKA; Subkey: "Software\Classes\.myp\OpenWithProgids"; ValueType: string; ValueName: "MyProgramFile.myp"; ValueData: ""; Flags: uninsdeletevalue +Root: HKA; Subkey: "Software\Classes\MyProgramFile.myp"; ValueType: string; ValueName: ""; ValueData: "My Program File"; Flags: uninsdeletekey +Root: HKA; Subkey: "Software\Classes\MyProgramFile.myp\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\MyProg.exe,0" +Root: HKA; Subkey: "Software\Classes\MyProgramFile.myp\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\MyProg.exe"" ""%1""" +Root: HKA; Subkey: "Software\Classes\Applications\MyProg.exe\SupportedTypes"; ValueType: string; ValueName: ".myp"; ValueData: "" +; HKA (and HKCU) should only be used for settings which are compatible with +; roaming profiles so settings like paths should be written to HKLM, which +; is only possible in administrative install mode. +Root: HKLM; Subkey: "Software\My Company"; Flags: uninsdeletekeyifempty; Check: IsAdminInstallMode +Root: HKLM; Subkey: "Software\My Company\My Program"; Flags: uninsdeletekey; Check: IsAdminInstallMode +Root: HKLM; Subkey: "Software\My Company\My Program\Settings"; ValueType: string; ValueName: "InstallPath"; ValueData: "{app}"; Check: IsAdminInstallMode +; User specific settings should always be written to HKCU, which should only +; be done in non administrative install mode. Also see ShouldSkipPage below. +Root: HKCU; Subkey: "Software\My Company\My Program\Settings"; ValueType: string; ValueName: "UserName"; ValueData: "{userinfoname}"; Check: not IsAdminInstallMode +Root: HKCU; Subkey: "Software\My Company\My Program\Settings"; ValueType: string; ValueName: "UserOrganization"; ValueData: "{userinfoorg}"; Check: not IsAdminInstallMode + +[Code] +function ShouldSkipPage(PageID: Integer): Boolean; +begin + Result := IsAdminInstallMode and (PageID = wpUserInfo); +end; diff --git a/src/AITool.Setup/INNO/Examples/ISPPExample1.iss b/src/AITool.Setup/INNO/Examples/ISPPExample1.iss new file mode 100644 index 00000000..b5287b3a --- /dev/null +++ b/src/AITool.Setup/INNO/Examples/ISPPExample1.iss @@ -0,0 +1,38 @@ +// -- ISPPExample1.iss -- +// +// This script shows various basic things you can achieve using Inno Setup Preprocessor (ISPP). +// To enable commented #define's, either remove the '//' or use ISCC with the /D switch. +// +#pragma verboselevel 9 +// +//#define AppEnterprise +// +#ifdef AppEnterprise + #define AppName "My Program Enterprise Edition" +#else + #define AppName "My Program" +#endif +// +#define AppVersion GetVersionNumbersString(AddBackslash(SourcePath) + "MyProg.exe") +// +[Setup] +AppName={#AppName} +AppVersion={#AppVersion} +WizardStyle=modern +DefaultDirName={autopf}\{#AppName} +DefaultGroupName={#AppName} +UninstallDisplayIcon={app}\MyProg.exe +LicenseFile={#file AddBackslash(SourcePath) + "ISPPExample1License.txt"} +VersionInfoVersion={#AppVersion} +OutputDir=userdocs:Inno Setup Examples Output + +[Files] +Source: "MyProg.exe"; DestDir: "{app}" +#ifdef AppEnterprise +Source: "MyProg.chm"; DestDir: "{app}" +#endif +Source: "Readme.txt"; DestDir: "{app}"; \ + Flags: isreadme + +[Icons] +Name: "{group}\{#AppName}"; Filename: "{app}\MyProg.exe" diff --git a/src/AITool.Setup/INNO/Examples/ISPPExample1License.txt b/src/AITool.Setup/INNO/Examples/ISPPExample1License.txt new file mode 100644 index 00000000..0ae5a72a --- /dev/null +++ b/src/AITool.Setup/INNO/Examples/ISPPExample1License.txt @@ -0,0 +1,4 @@ +#pragma option -e+ +{#AppName} version {#AppVersion} License + +Bla bla bla \ No newline at end of file diff --git a/src/AITool.Setup/INNO/Examples/Languages.iss b/src/AITool.Setup/INNO/Examples/Languages.iss new file mode 100644 index 00000000..0d62bc1e --- /dev/null +++ b/src/AITool.Setup/INNO/Examples/Languages.iss @@ -0,0 +1,57 @@ +; -- Languages.iss -- +; Demonstrates a multilingual installation. + +; SEE THE DOCUMENTATION FOR DETAILS ON CREATING .ISS SCRIPT FILES! + +[Setup] +AppName={cm:MyAppName} +AppId=My Program +AppVerName={cm:MyAppVerName,1.5} +WizardStyle=modern +DefaultDirName={autopf}\{cm:MyAppName} +DefaultGroupName={cm:MyAppName} +UninstallDisplayIcon={app}\MyProg.exe +VersionInfoDescription=My Program Setup +VersionInfoProductName=My Program +OutputDir=userdocs:Inno Setup Examples Output +MissingMessagesWarning=yes +NotRecognizedMessagesWarning=yes +; Uncomment the following line to disable the "Select Setup Language" +; dialog and have it rely solely on auto-detection. +;ShowLanguageDialog=no + +[Languages] +Name: en; MessagesFile: "compiler:Default.isl" +Name: nl; MessagesFile: "compiler:Languages\Dutch.isl" +Name: de; MessagesFile: "compiler:Languages\German.isl" + +[Messages] +en.BeveledLabel=English +nl.BeveledLabel=Nederlands +de.BeveledLabel=Deutsch + +[CustomMessages] +en.MyDescription=My description +en.MyAppName=My Program +en.MyAppVerName=My Program %1 +nl.MyDescription=Mijn omschrijving +nl.MyAppName=Mijn programma +nl.MyAppVerName=Mijn programma %1 +de.MyDescription=Meine Beschreibung +de.MyAppName=Meine Anwendung +de.MyAppVerName=Meine Anwendung %1 + +[Files] +Source: "MyProg.exe"; DestDir: "{app}" +Source: "MyProg.chm"; DestDir: "{app}"; Languages: en +Source: "Readme.txt"; DestDir: "{app}"; Languages: en; Flags: isreadme +Source: "Readme-Dutch.txt"; DestName: "Leesmij.txt"; DestDir: "{app}"; Languages: nl; Flags: isreadme +Source: "Readme-German.txt"; DestName: "Liesmich.txt"; DestDir: "{app}"; Languages: de; Flags: isreadme + +[Icons] +Name: "{group}\{cm:MyAppName}"; Filename: "{app}\MyProg.exe" +Name: "{group}\{cm:UninstallProgram,{cm:MyAppName}}"; Filename: "{uninstallexe}" + +[Tasks] +; The following task doesn't do anything and is only meant to show [CustomMessages] usage +Name: mytask; Description: "{cm:MyDescription}" diff --git a/src/AITool.Setup/INNO/Examples/License.txt b/src/AITool.Setup/INNO/Examples/License.txt new file mode 100644 index 00000000..8d31eac1 --- /dev/null +++ b/src/AITool.Setup/INNO/Examples/License.txt @@ -0,0 +1 @@ +This is the LICENSE file for My Program. diff --git a/src/AITool.Setup/INNO/Examples/MyDll.dll b/src/AITool.Setup/INNO/Examples/MyDll.dll new file mode 100644 index 00000000..18d7f6e3 Binary files /dev/null and b/src/AITool.Setup/INNO/Examples/MyDll.dll differ diff --git a/src/AITool.Setup/INNO/Examples/MyDll/C#/MyDll.cs b/src/AITool.Setup/INNO/Examples/MyDll/C#/MyDll.cs new file mode 100644 index 00000000..1ce728a9 --- /dev/null +++ b/src/AITool.Setup/INNO/Examples/MyDll/C#/MyDll.cs @@ -0,0 +1,25 @@ +using System; + +using System.Runtime.InteropServices; +using RGiesecke.DllExport; + +namespace Mydll +{ + public class Mydll + { + [DllExport("MyDllFunc", CallingConvention=CallingConvention.StdCall)] + public static void MyDllFunc(IntPtr hWnd, [MarshalAs(UnmanagedType.LPStr)] string text, [MarshalAs(UnmanagedType.LPStr)] string caption, int options) + { + MessageBox(hWnd, text, caption, options); + } + + [DllExport("MyDllFuncW", CallingConvention=CallingConvention.StdCall)] + public static void MyDllFuncW(IntPtr hWnd, string text, string caption, int options) + { + MessageBox(hWnd, text, caption, options); + } + + [DllImport("user32.dll", CharSet=CharSet.Auto)] + static extern int MessageBox(IntPtr hWnd, String text, String caption, int options); + } +} diff --git a/src/AITool.Setup/INNO/Examples/MyDll/C#/MyDll.csproj b/src/AITool.Setup/INNO/Examples/MyDll/C#/MyDll.csproj new file mode 100644 index 00000000..2b8f5de3 --- /dev/null +++ b/src/AITool.Setup/INNO/Examples/MyDll/C#/MyDll.csproj @@ -0,0 +1,63 @@ + + + + + Debug + AnyCPU + {79237A5C-6C62-400A-BBDD-3DA1CA327973} + Library + Properties + MyDll + MyDll + v4.5 + 512 + + + true + full + false + .\ + DEBUG;TRACE + prompt + 4 + x86 + + + pdbonly + true + .\ + TRACE + prompt + 4 + x86 + + + + packages\UnmanagedExports.1.2.7\lib\net\RGiesecke.DllExport.Metadata.dll + False + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/AITool.Setup/INNO/Examples/MyDll/C#/MyDll.sln b/src/AITool.Setup/INNO/Examples/MyDll/C#/MyDll.sln new file mode 100644 index 00000000..0c7aa7d1 --- /dev/null +++ b/src/AITool.Setup/INNO/Examples/MyDll/C#/MyDll.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.40629.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyDll", "MyDll.csproj", "{79237A5C-6C62-400A-BBDD-3DA1CA327973}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {79237A5C-6C62-400A-BBDD-3DA1CA327973}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {79237A5C-6C62-400A-BBDD-3DA1CA327973}.Debug|Any CPU.Build.0 = Debug|Any CPU + {79237A5C-6C62-400A-BBDD-3DA1CA327973}.Release|Any CPU.ActiveCfg = Release|Any CPU + {79237A5C-6C62-400A-BBDD-3DA1CA327973}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/AITool.Setup/INNO/Examples/MyDll/C#/Properties/AssemblyInfo.cs b/src/AITool.Setup/INNO/Examples/MyDll/C#/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..93bbef2e --- /dev/null +++ b/src/AITool.Setup/INNO/Examples/MyDll/C#/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("MyDll")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("MyDll")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("711cc3c2-07db-46ca-b34b-ba06f4edcbcd")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/AITool.Setup/INNO/Examples/MyDll/C#/packages.config b/src/AITool.Setup/INNO/Examples/MyDll/C#/packages.config new file mode 100644 index 00000000..f98ea962 --- /dev/null +++ b/src/AITool.Setup/INNO/Examples/MyDll/C#/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/AITool.Setup/INNO/Examples/MyDll/C/MyDll.c b/src/AITool.Setup/INNO/Examples/MyDll/C/MyDll.c new file mode 100644 index 00000000..c48d8d6f --- /dev/null +++ b/src/AITool.Setup/INNO/Examples/MyDll/C/MyDll.c @@ -0,0 +1,6 @@ +#include + +void __stdcall MyDllFunc(HWND hWnd, char *lpText, char *lpCaption, UINT uType) +{ + MessageBox(hWnd, lpText, lpCaption, uType); +} \ No newline at end of file diff --git a/src/AITool.Setup/INNO/Examples/MyDll/C/MyDll.def b/src/AITool.Setup/INNO/Examples/MyDll/C/MyDll.def new file mode 100644 index 00000000..1dbed903 --- /dev/null +++ b/src/AITool.Setup/INNO/Examples/MyDll/C/MyDll.def @@ -0,0 +1,2 @@ +EXPORTS + MyDllFunc \ No newline at end of file diff --git a/src/AITool.Setup/INNO/Examples/MyDll/C/MyDll.dsp b/src/AITool.Setup/INNO/Examples/MyDll/C/MyDll.dsp new file mode 100644 index 00000000..d5a1a3cb --- /dev/null +++ b/src/AITool.Setup/INNO/Examples/MyDll/C/MyDll.dsp @@ -0,0 +1,76 @@ +# Microsoft Developer Studio Project File - Name="MyDll" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=MyDll - Win32 Release +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "MyDll.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "MyDll.mak" CFG="MyDll - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "MyDll - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "." +# PROP Intermediate_Dir "." +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "MYDLL_EXPORTS" /YX /FD /c +# ADD CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "MYDLL_EXPORTS" /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x413 /d "NDEBUG" +# ADD RSC /l 0x413 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 +# Begin Target + +# Name "MyDll - Win32 Release" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\MyDll.c +# End Source File +# Begin Source File + +SOURCE=.\MyDll.def +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/src/AITool.Setup/INNO/Examples/MyDll/Delphi/MyDll.dpr b/src/AITool.Setup/INNO/Examples/MyDll/Delphi/MyDll.dpr new file mode 100644 index 00000000..c4f72a09 --- /dev/null +++ b/src/AITool.Setup/INNO/Examples/MyDll/Delphi/MyDll.dpr @@ -0,0 +1,14 @@ +library MyDll; + +uses + Windows; + +procedure MyDllFunc(hWnd: Integer; lpText, lpCaption: PAnsiChar; uType: Cardinal); stdcall; +begin + MessageBoxA(hWnd, lpText, lpCaption, uType); +end; + +exports MyDllFunc; + +begin +end. diff --git a/src/AITool.Setup/INNO/Examples/MyProg-ARM64.exe b/src/AITool.Setup/INNO/Examples/MyProg-ARM64.exe new file mode 100644 index 00000000..4ada8b74 Binary files /dev/null and b/src/AITool.Setup/INNO/Examples/MyProg-ARM64.exe differ diff --git a/src/AITool.Setup/INNO/Examples/MyProg-x64.exe b/src/AITool.Setup/INNO/Examples/MyProg-x64.exe new file mode 100644 index 00000000..2db6eaf4 Binary files /dev/null and b/src/AITool.Setup/INNO/Examples/MyProg-x64.exe differ diff --git a/src/AITool.Setup/INNO/Examples/MyProg.chm b/src/AITool.Setup/INNO/Examples/MyProg.chm new file mode 100644 index 00000000..1c885354 Binary files /dev/null and b/src/AITool.Setup/INNO/Examples/MyProg.chm differ diff --git a/src/AITool.Setup/INNO/Examples/MyProg.exe b/src/AITool.Setup/INNO/Examples/MyProg.exe new file mode 100644 index 00000000..1c612db6 Binary files /dev/null and b/src/AITool.Setup/INNO/Examples/MyProg.exe differ diff --git a/src/AITool.Setup/INNO/Examples/Readme-Dutch.txt b/src/AITool.Setup/INNO/Examples/Readme-Dutch.txt new file mode 100644 index 00000000..7f190a69 --- /dev/null +++ b/src/AITool.Setup/INNO/Examples/Readme-Dutch.txt @@ -0,0 +1 @@ +Dit is het Leesmij bestand voor My Program. \ No newline at end of file diff --git a/src/AITool.Setup/INNO/Examples/Readme-German.txt b/src/AITool.Setup/INNO/Examples/Readme-German.txt new file mode 100644 index 00000000..57cf84a8 --- /dev/null +++ b/src/AITool.Setup/INNO/Examples/Readme-German.txt @@ -0,0 +1 @@ +Dies ist die LIESMICH-Datei fr "My Program". \ No newline at end of file diff --git a/src/AITool.Setup/INNO/Examples/Readme.txt b/src/AITool.Setup/INNO/Examples/Readme.txt new file mode 100644 index 00000000..2e7f6b47 --- /dev/null +++ b/src/AITool.Setup/INNO/Examples/Readme.txt @@ -0,0 +1 @@ +This is the README file for My Program. diff --git a/src/AITool.Setup/INNO/Examples/UnicodeExample1.iss b/src/AITool.Setup/INNO/Examples/UnicodeExample1.iss new file mode 100644 index 00000000..5db0a3bc --- /dev/null +++ b/src/AITool.Setup/INNO/Examples/UnicodeExample1.iss @@ -0,0 +1,30 @@ +; -- UnicodeExample1.iss -- +; Demonstrates some Unicode functionality. +; +; SEE THE DOCUMENTATION FOR DETAILS ON CREATING .ISS SCRIPT FILES! + +[Setup] +AppName=ɯɐɹƃoɹd ʎɯ +AppVerName=ɯɐɹƃoɹd ʎɯ version 1.5 +WizardStyle=modern +DefaultDirName={autopf}\ɯɐɹƃoɹd ʎɯ +DefaultGroupName=ɯɐɹƃoɹd ʎɯ +UninstallDisplayIcon={app}\ƃoɹdʎɯ.exe +Compression=lzma2 +SolidCompression=yes +OutputDir=userdocs:Inno Setup Examples Output + +[Files] +Source: "MyProg.exe"; DestDir: "{app}"; DestName: "ƃoɹdʎɯ.exe" +Source: "MyProg.chm"; DestDir: "{app}"; DestName: "ƃoɹdʎɯ.chm" +Source: "Readme.txt"; DestDir: "{app}"; Flags: isreadme + +[Icons] +Name: "{group}\ɯɐɹƃoɹd ʎɯ"; Filename: "{app}\ƃoɹdʎɯ.exe" + +[Code] +function InitializeSetup: Boolean; +begin + MsgBox('ɯɐɹƃoɹd ʎɯ', mbInformation, MB_OK); + Result := True; +end; \ No newline at end of file diff --git a/src/AITool.Setup/INNO/Examples/UninstallCodeExample1.iss b/src/AITool.Setup/INNO/Examples/UninstallCodeExample1.iss new file mode 100644 index 00000000..bfd9e7d9 --- /dev/null +++ b/src/AITool.Setup/INNO/Examples/UninstallCodeExample1.iss @@ -0,0 +1,46 @@ +; -- UninstallCodeExample1.iss -- +; +; This script shows various things you can achieve using a [Code] section for Uninstall. + +[Setup] +AppName=My Program +AppVersion=1.5 +WizardStyle=modern +DefaultDirName={autopf}\My Program +DefaultGroupName=My Program +UninstallDisplayIcon={app}\MyProg.exe +OutputDir=userdocs:Inno Setup Examples Output + +[Files] +Source: "MyProg.exe"; DestDir: "{app}" +Source: "MyProg.chm"; DestDir: "{app}" +Source: "Readme.txt"; DestDir: "{app}"; Flags: isreadme + +[Code] +function InitializeUninstall(): Boolean; +begin + Result := MsgBox('InitializeUninstall:' #13#13 'Uninstall is initializing. Do you really want to start Uninstall?', mbConfirmation, MB_YESNO) = idYes; + if Result = False then + MsgBox('InitializeUninstall:' #13#13 'Ok, bye bye.', mbInformation, MB_OK); +end; + +procedure DeinitializeUninstall(); +begin + MsgBox('DeinitializeUninstall:' #13#13 'Bye bye!', mbInformation, MB_OK); +end; + +procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); +begin + case CurUninstallStep of + usUninstall: + begin + MsgBox('CurUninstallStepChanged:' #13#13 'Uninstall is about to start.', mbInformation, MB_OK) + // ...insert code to perform pre-uninstall tasks here... + end; + usPostUninstall: + begin + MsgBox('CurUninstallStepChanged:' #13#13 'Uninstall just finished.', mbInformation, MB_OK); + // ...insert code to perform post-uninstall tasks here... + end; + end; +end; diff --git a/src/AITool.Setup/INNO/GraphicalInstallerUI.e32 b/src/AITool.Setup/INNO/GraphicalInstallerUI.e32 new file mode 100644 index 00000000..ddcca2be Binary files /dev/null and b/src/AITool.Setup/INNO/GraphicalInstallerUI.e32 differ diff --git a/src/AITool.Setup/INNO/ISCC.exe b/src/AITool.Setup/INNO/ISCC.exe new file mode 100644 index 00000000..31bc94c4 Binary files /dev/null and b/src/AITool.Setup/INNO/ISCC.exe differ diff --git a/src/AITool.Setup/INNO/ISCmplr.dll b/src/AITool.Setup/INNO/ISCmplr.dll new file mode 100644 index 00000000..e2b73edf Binary files /dev/null and b/src/AITool.Setup/INNO/ISCmplr.dll differ diff --git a/src/AITool.Setup/INNO/ISCrypt.dll b/src/AITool.Setup/INNO/ISCrypt.dll new file mode 100644 index 00000000..9d1a9760 Binary files /dev/null and b/src/AITool.Setup/INNO/ISCrypt.dll differ diff --git a/src/AITool.Setup/INNO/ISPP.chm b/src/AITool.Setup/INNO/ISPP.chm new file mode 100644 index 00000000..e61fa793 Binary files /dev/null and b/src/AITool.Setup/INNO/ISPP.chm differ diff --git a/src/AITool.Setup/INNO/ISPP.dll b/src/AITool.Setup/INNO/ISPP.dll new file mode 100644 index 00000000..25b04cbd Binary files /dev/null and b/src/AITool.Setup/INNO/ISPP.dll differ diff --git a/src/AITool.Setup/INNO/ISPPBuiltins.iss b/src/AITool.Setup/INNO/ISPPBuiltins.iss new file mode 100644 index 00000000..3d87d391 --- /dev/null +++ b/src/AITool.Setup/INNO/ISPPBuiltins.iss @@ -0,0 +1,323 @@ +// Inno Setup Preprocessor +// +// Inno Setup (C) 1997-2023 Jordan Russell. All Rights Reserved. +// Portions Copyright (C) 2000-2023 Martijn Laan. All Rights Reserved. +// Portions Copyright (C) 2001-2004 Alex Yackimoff. All Rights Reserved. +// +// See the ISPP help file for more documentation of the functions defined by this file +// +#if defined(ISPP_INVOKED) && !defined(_BUILTINS_ISS_) +// +#if PREPROCVER < 0x01000000 +# error Inno Setup Preprocessor version is outdated +#endif +// +#define _BUILTINS_ISS_ +// +#ifdef __OPT_E__ +# define private EnableOptE +# pragma option -e- +#endif + +#ifndef __POPT_P__ +# define private DisablePOptP +#else +# pragma parseroption -p- +#endif + +#define NewLine "\n" +#define Tab "\t" + +#pragma parseroption -p+ + +#pragma spansymbol "\" + +#define True 1 +#define False 0 +#define Yes True +#define No False + +#define MaxInt 0x7FFFFFFFFFFFFFFFL +#define MinInt 0x8000000000000000L + +#define NULL +#define void + +// TypeOf constants + +#define TYPE_ERROR 0 +#define TYPE_NULL 1 +#define TYPE_INTEGER 2 +#define TYPE_STRING 3 +#define TYPE_MACRO 4 +#define TYPE_FUNC 5 +#define TYPE_ARRAY 6 + +// Helper macro to find out the type of an array element or expression. TypeOf +// standard function only allows identifier as its parameter. Use this macro +// to convert an expression to identifier. + +#define TypeOf2(any Expr) TypeOf(Expr) + +// ReadReg constants + +#define HKEY_CLASSES_ROOT 0x80000000UL +#define HKEY_CURRENT_USER 0x80000001UL +#define HKEY_LOCAL_MACHINE 0x80000002UL +#define HKEY_USERS 0x80000003UL +#define HKEY_CURRENT_CONFIG 0x80000005UL +#define HKEY_CLASSES_ROOT_64 0x82000000UL +#define HKEY_CURRENT_USER_64 0x82000001UL +#define HKEY_LOCAL_MACHINE_64 0x82000002UL +#define HKEY_USERS_64 0x82000003UL +#define HKEY_CURRENT_CONFIG_64 0x82000005UL + +#define HKCR HKEY_CLASSES_ROOT +#define HKCU HKEY_CURRENT_USER +#define HKLM HKEY_LOCAL_MACHINE +#define HKU HKEY_USERS +#define HKCC HKEY_CURRENT_CONFIG +#define HKCR64 HKEY_CLASSES_ROOT_64 +#define HKCU64 HKEY_CURRENT_USER_64 +#define HKLM64 HKEY_LOCAL_MACHINE_64 +#define HKU64 HKEY_USERS_64 +#define HKCC64 HKEY_CURRENT_CONFIG_64 + +// Exec constants + +#define SW_HIDE 0 +#define SW_SHOWNORMAL 1 +#define SW_NORMAL 1 +#define SW_SHOWMINIMIZED 2 +#define SW_SHOWMAXIMIZED 3 +#define SW_MAXIMIZE 3 +#define SW_SHOWNOACTIVATE 4 +#define SW_SHOW 5 +#define SW_MINIMIZE 6 +#define SW_SHOWMINNOACTIVE 7 +#define SW_SHOWNA 8 +#define SW_RESTORE 9 +#define SW_SHOWDEFAULT 10 +#define SW_MAX 10 + +// Find constants + +#define FIND_MATCH 0x00 +#define FIND_BEGINS 0x01 +#define FIND_ENDS 0x02 +#define FIND_CONTAINS 0x03 +#define FIND_CASESENSITIVE 0x04 +#define FIND_SENSITIVE FIND_CASESENSITIVE +#define FIND_AND 0x00 +#define FIND_OR 0x08 +#define FIND_NOT 0x10 +#define FIND_TRIM 0x20 + +// FindFirst constants + +#define faReadOnly 0x00000001 +#define faHidden 0x00000002 +#define faSysFile 0x00000004 +#define faVolumeID 0x00000008 +#define faDirectory 0x00000010 +#define faArchive 0x00000020 +#define faSymLink 0x00000040 +#define faAnyFile 0x0000003F + +// GetStringFileInfo standard names + +#define COMPANY_NAME "CompanyName" +#define FILE_DESCRIPTION "FileDescription" +#define FILE_VERSION "FileVersion" +#define INTERNAL_NAME "InternalName" +#define LEGAL_COPYRIGHT "LegalCopyright" +#define ORIGINAL_FILENAME "OriginalFilename" +#define PRODUCT_NAME "ProductName" +#define PRODUCT_VERSION "ProductVersion" + +// GetStringFileInfo helpers + +#define GetFileCompany(str FileName) GetStringFileInfo(FileName, COMPANY_NAME) +#define GetFileDescription(str FileName) GetStringFileInfo(FileName, FILE_DESCRIPTION) +#define GetFileVersionString(str FileName) GetStringFileInfo(FileName, FILE_VERSION) +#define GetFileCopyright(str FileName) GetStringFileInfo(FileName, LEGAL_COPYRIGHT) +#define GetFileOriginalFilename(str FileName) GetStringFileInfo(FileName, ORIGINAL_FILENAME) +#define GetFileProductVersion(str FileName) GetStringFileInfo(FileName, PRODUCT_VERSION) + +#define DeleteToFirstPeriod(str *S) \ + Local[1] = Copy(S, 1, (Local[0] = Pos(".", S)) - 1), \ + S = Copy(S, Local[0] + 1), \ + Local[1] + +#define GetVersionComponents(str FileName, *Major, *Minor, *Rev, *Build) \ + Local[1] = Local[0] = GetVersionNumbersString(FileName), \ + Local[1] == "" ? "" : ( \ + Major = Int(DeleteToFirstPeriod(Local[1])), \ + Minor = Int(DeleteToFirstPeriod(Local[1])), \ + Rev = Int(DeleteToFirstPeriod(Local[1])), \ + Build = Int(Local[1]), \ + Local[0]) + +#define GetPackedVersion(str FileName, *Version) \ + Local[0] = GetVersionComponents(FileName, Local[1], Local[2], Local[3], Local[4]), \ + Version = PackVersionComponents(Local[1], Local[2], Local[3], Local[4]), \ + Local[0] + +#define GetVersionNumbers(str FileName, *MS, *LS) \ + Local[0] = GetPackedVersion(FileName, Local[1]), \ + UnpackVersionNumbers(Local[1], MS, LS), \ + Local[0] + +#define PackVersionNumbers(int VersionMS, int VersionLS) \ + VersionMS << 32 | (VersionLS & 0xFFFFFFFF) + +#define PackVersionComponents(int Major, int Minor, int Rev, int Build) \ + Major << 48 | (Minor & 0xFFFF) << 32 | (Rev & 0xFFFF) << 16 | (Build & 0xFFFF) + +#define UnpackVersionNumbers(int Version, *VersionMS, *VersionLS) \ + VersionMS = Version >> 32, \ + VersionLS = Version & 0xFFFFFFFF, \ + void + +#define UnpackVersionComponents(int Version, *Major, *Minor, *Rev, *Build) \ + Major = Version >> 48, \ + Minor = (Version >> 32) & 0xFFFF, \ + Rev = (Version >> 16) & 0xFFFF, \ + Build = Version & 0xFFFF, \ + void + +#define VersionToStr(int Version) \ + Str(Version >> 48 & 0xFFFF) + "." + Str(Version >> 32 & 0xFFFF) + "." + \ + Str(Version >> 16 & 0xFFFF) + "." + Str(Version & 0xFFFF) + +#define StrToVersion(str Version) \ + Local[0] = Version, \ + Local[1] = Int(DeleteToFirstPeriod(Local[0])), \ + Local[2] = Int(DeleteToFirstPeriod(Local[0])), \ + Local[3] = Int(DeleteToFirstPeriod(Local[0])), \ + Local[4] = Int(Local[0]), \ + PackVersionComponents(Local[1], Local[2], Local[3], Local[4]) + +#define EncodeVer(int Major, int Minor, int Revision = 0, int Build = -1) \ + (Major & 0xFF) << 24 | (Minor & 0xFF) << 16 | (Revision & 0xFF) << 8 | (Build >= 0 ? Build & 0xFF : 0) + +#define DecodeVer(int Version, int Digits = 3) \ + Str(Version >> 24 & 0xFF) + (Digits > 1 ? "." : "") + \ + (Digits > 1 ? \ + Str(Version >> 16 & 0xFF) + (Digits > 2 ? "." : "") : "") + \ + (Digits > 2 ? \ + Str(Version >> 8 & 0xFF) + (Digits > 3 && (Local = Version & 0xFF) ? "." : "") : "") + \ + (Digits > 3 && Local ? \ + Str(Version & 0xFF) : "") + +#define FindSection(str Section = "Files") \ + Find(0, "[" + Section + "]", FIND_MATCH | FIND_TRIM) + 1 + +#if VER >= 0x03000000 +# define FindNextSection(int Line) \ + Find(Line, "[", FIND_BEGINS | FIND_TRIM, "]", FIND_ENDS | FIND_AND) +# define FindSectionEnd(str Section = "Files") \ + FindNextSection(FindSection(Section)) +#else +# define FindSectionEnd(str Section = "Files") \ + FindSection(Section) + EntryCount(Section) +#endif + +#define FindCode() \ + Local[1] = FindSection("Code"), \ + Local[0] = Find(Local[1] - 1, "program", FIND_BEGINS, ";", FIND_ENDS | FIND_AND), \ + (Local[0] < 0 ? Local[1] : Local[0] + 1) + +#define ExtractFilePath(str PathName) \ + (Local[0] = \ + !(Local[1] = RPos("\", PathName)) ? \ + "" : \ + Copy(PathName, 1, Local[1] - 1)), \ + Local[0] + \ + ((Local[2] = Len(Local[0])) == 2 && Copy(Local[0], Local[2]) == ":" ? \ + "\" : \ + "") + +#define ExtractFileDir(str PathName) \ + RemoveBackslash(ExtractFilePath(PathName)) + +#define ExtractFileExt(str PathName) \ + Local[0] = RPos(".", PathName), \ + Copy(PathName, Local[0] + 1) + +#define ExtractFileName(str PathName) \ + !(Local[0] = RPos("\", PathName)) ? \ + PathName : \ + Copy(PathName, Local[0] + 1) + +#define ChangeFileExt(str FileName, str NewExt) \ + !(Local[0] = RPos(".", FileName)) ? \ + FileName + "." + NewExt : \ + Copy(FileName, 1, Local[0]) + NewExt + +#define RemoveFileExt(str FileName) \ + !(Local[0] = RPos(".", FileName)) ? \ + FileName : \ + Copy(FileName, 1, Local[0] - 1) + +#define AddBackslash(str S) \ + Copy(S, Len(S)) == "\" ? S : S + "\" + +#define RemoveBackslash(str S) \ + Local[0] = Len(S), \ + Local[0] > 0 ? \ + Copy(S, Local[0]) == "\" ? \ + (Local[0] == 3 && Copy(S, 2, 1) == ":" ? \ + S : \ + Copy(S, 1, Local[0] - 1)) : \ + S : \ + "" + +#define Delete(str *S, int Index, int Count = MaxInt) \ + S = Copy(S, 1, Index - 1) + Copy(S, Index + Count) + +#define Insert(str *S, int Index, str Substr) \ + Index > Len(S) + 1 ? \ + S : \ + S = Copy(S, 1, Index - 1) + SubStr + Copy(S, Index) + +#define YesNo(str S) \ + (S = LowerCase(S)) == "yes" || S == "true" || S == "1" + +#define IsDirSet(str SetupDirective) \ + YesNo(SetupSetting(SetupDirective)) + +#define Power(int X, int P = 2) \ + !P ? 1 : X * Power(X, P - 1) + +#define Min(int A, int B, int C = MaxInt) \ + A < B ? A < C ? Int(A) : Int(C) : Int(B) + +#define Max(int A, int B, int C = MinInt) \ + A > B ? A > C ? Int(A) : Int(C) : Int(B) + +#define SameText(str S1, str S2) \ + LowerCase(S1) == LowerCase(S2) + +#define SameStr(str S1, str S2) \ + S1 == S2 + +#define WarnRenamedVersion(str OldName, str NewName) \ + Warning("Function """ + OldName + """ has been renamed. Use """ + NewName + """ instead.") + +#define ParseVersion(str FileName, *Major, *Minor, *Rev, *Build) \ + WarnRenamedVersion("ParseVersion", "GetVersionComponents"), \ + GetVersionComponents(FileName, Major, Minor, Rev, Build) + +#define GetFileVersion(str FileName) \ + WarnRenamedVersion("GetFileVersion", "GetVersionNumbersString"), \ + GetVersionNumbersString(FileName) + +#ifdef DisablePOptP +# pragma parseroption -p- +#endif + +#ifdef EnableOptE +# pragma option -e+ +#endif +#endif \ No newline at end of file diff --git a/src/AITool.Setup/INNO/ISetup.chm b/src/AITool.Setup/INNO/ISetup.chm new file mode 100644 index 00000000..615556ac Binary files /dev/null and b/src/AITool.Setup/INNO/ISetup.chm differ diff --git a/src/AITool.Setup/INNO/Languages/Armenian.isl b/src/AITool.Setup/INNO/Languages/Armenian.isl new file mode 100644 index 00000000..148db955 --- /dev/null +++ b/src/AITool.Setup/INNO/Languages/Armenian.isl @@ -0,0 +1,376 @@ +; *** Inno Setup version 6.1.0+ Armenian messages *** +; +; Armenian translation by Hrant Ohanyan +; E-mail: h.ohanyan@haysoft.org +; Translation home page: http://www.haysoft.org +; Last modification date: 2020-10-06 +; +[LangOptions] +LanguageName=Հայերեն +LanguageID=$042B +LanguageCodePage=0 +; If the language you are translating to requires special font faces or +; sizes, uncomment any of the following entries and change them accordingly. +;DialogFontName= +;DialogFontSize=8 +;WelcomeFontName=Verdana +;WelcomeFontSize=12 +;TitleFontName=Arial +;TitleFontSize=29 +;CopyrightFontName=Arial +;CopyrightFontSize=8 + +[Messages] + +; *** Application titles +SetupAppTitle=Տեղադրում +SetupWindowTitle=%1-ի տեղադրում +UninstallAppTitle=Ապատեղադրում +UninstallAppFullTitle=%1-ի ապատեղադրում + +; *** Misc. common +InformationTitle=Տեղեկություն +ConfirmTitle=Հաստատել +ErrorTitle=Սխալ + +; *** SetupLdr messages +SetupLdrStartupMessage=Այս ծրագիրը կտեղադրի %1-ը Ձեր համակարգչում։ Շարունակե՞լ։ +LdrCannotCreateTemp=Հնարավոր չէ ստեղծել ժամանակավոր ֆայլ։ Տեղադրումը կասեցված է +LdrCannotExecTemp=Հնարավոր չէ կատարել ֆայլը ժամանակավոր պանակից։ Տեղադրումը կասեցված է + +; *** Startup error messages +LastErrorMessage=%1.%n%nՍխալ %2: %3 +SetupFileMissing=%1 ֆայլը բացակայում է տեղադրման պանակից։ Ուղղեք խնդիրը կամ ստացեք ծրագրի նոր տարբերակը։ +SetupFileCorrupt=Տեղադրվող ֆայլերը վնասված են։ +SetupFileCorruptOrWrongVer=Տեղադրվող ֆայլերը վնասված են կամ անհամատեղելի են տեղակայիչի այս տարբերակի հետ։ Ուղղեք խնդիրը կամ ստացեք ծրագրի նոր տարբերակը։ +InvalidParameter=Հրամանատողում նշված է սխալ հրաման.%n%n%1 +SetupAlreadyRunning=Տեղակայիչը արդեն աշխատեցված է։ +WindowsVersionNotSupported=Ծրագիրը չի աջակցում այս համակարգչում աշխատող Windows-ի տարբերակը։ +WindowsServicePackRequired=Ծրագիրը պահանջում է %1-ի Service Pack %2 կամ ավելի նոր։ +NotOnThisPlatform=Այս ծրագիրը չի աշխատի %1-ում։ +OnlyOnThisPlatform=Այս ծրագիրը հնարավոր է բացել միայն %1-ում։ +OnlyOnTheseArchitectures=Այս ծրագրի տեղադրումը հնարավոր է միայն Windows-ի մշակիչի հետևյալ կառուցվածքներում՝ %n%n%1 +WinVersionTooLowError=Այս ծրագիրը պահանջում է %1-ի տարբերակ %2 կամ ավելի նորը։ +WinVersionTooHighError=Ծրագիրը չի կարող տեղադրվել %1-ի տարբերակ %2 կամ ավելի նորում +AdminPrivilegesRequired=Ծրագիրը տեղադրելու համար պահանջվում են Վարիչի իրավունքներ։ +PowerUserPrivilegesRequired=Ծրագիրը տեղադրելու համար պետք է մուտք գործել համակարգ որպես Վարիչ կամ «Փորձառու օգտագործող» (Power Users): +SetupAppRunningError=Տեղակայիչը հայտնաբերել է, որ %1-ը աշխատում է։%n%nՓակեք այն և սեղմեք «Լավ»՝ շարունակելու համար կամ «Չեղարկել»՝ փակելու համար։ +UninstallAppRunningError=Ապատեղադրող ծրագիրը հայտնաբերել է, որ %1-ը աշխատում է։%n%nՓակեք այն և սեղմեք «Լավ»՝ շարունակելու համար կամ «Չեղարկել»՝ փակելու համար։ + +; *** Startup questions +PrivilegesRequiredOverrideTitle=Ընտրեք տեղակայիչի տեղադրման կերպը +PrivilegesRequiredOverrideInstruction=Ընտրեք տեղադրման կերպը +PrivilegesRequiredOverrideText1=%1-ը կարող է տեղադրվել բոլոր օգտվողների համար (պահանջում է վարիչի արտոնություններ) կամ միայն ձեզ համար: +PrivilegesRequiredOverrideText2=%1-ը կարող է տեղադրվել միայն ձեզ համար կամ բոլոր օգտվողների համար (պահանջում է վարիչի արտոնություններ): +PrivilegesRequiredOverrideAllUsers=Տեղադրել &բոլոր օգտվողների համար +PrivilegesRequiredOverrideAllUsersRecommended=Տեղադրել &բոլոր օգտվողների համար (հանձնարարելի) +PrivilegesRequiredOverrideCurrentUser=Տեղադրել միայն &ինձ համար +PrivilegesRequiredOverrideCurrentUserRecommended=Տեղադրել միայն &ինձ համար (հանձնարարելի) + +; *** Misc. errors +ErrorCreatingDir=Հնարավոր չէ ստեղծել "%1" պանակը +ErrorTooManyFilesInDir=Հնարավոր չէ ստեղծել ֆայլ "%1" պանակում, որովհետև նրանում կան չափից ավելի շատ ֆայլեր + +; *** Setup common messages +ExitSetupTitle=Տեղակայման ընդհատում +ExitSetupMessage=Տեղակայումը չի ավարատվել։ Եթե ընդհատեք, ապա ծրագիրը չի տեղադրվի։%n%nԱվարտե՞լ։ +AboutSetupMenuItem=&Ծրագրի մասին... +AboutSetupTitle=Ծրագրի մասին +AboutSetupMessage=%1, տարբերակ՝ %2%n%3%n%nՎեբ կայք՝ %1:%n%4 +AboutSetupNote= +TranslatorNote=Armenian translation by Hrant Ohanyan »»» http://www.haysoft.org + +; *** Buttons +ButtonBack=« &Նախորդ +ButtonNext=&Հաջորդ » +ButtonInstall=&Տեղադրել +ButtonOK=Լավ +ButtonCancel=Չեղարկել +ButtonYes=&Այո +ButtonYesToAll=Այո բոլորի &համար +ButtonNo=&Ոչ +ButtonNoToAll=Ո&չ բոլորի համար +ButtonFinish=&Ավարտել +ButtonBrowse=&Ընտրել... +ButtonWizardBrowse=&Ընտրել... +ButtonNewFolder=&Ստեղծել պանակ + +; *** "Select Language" dialog messages +SelectLanguageTitle=Ընտրել տեղակայիչի լեզուն +SelectLanguageLabel=Ընտրեք այն լեզուն, որը օգտագործվելու է տեղադրման ընթացքում: + +; *** Common wizard text +ClickNext=Սեղմեք «Հաջորդ»՝ շարունակելու համար կամ «Չեղարկել»՝ տեղակայիչը փակելու համար։ +BeveledLabel= +BrowseDialogTitle=Ընտրել պանակ +BrowseDialogLabel=Ընտրեք պանակը ցանկից և սեղմեք «Լավ»։ +NewFolderName=Նոր պանակ + +; *** "Welcome" wizard page +WelcomeLabel1=Ձեզ ողջունում է [name]-ի տեղակայման օգնականը +WelcomeLabel2=Ծրագիրը կտեղադրի [name/ver]-ը Ձեր համակարգչում։%n%nՇարունակելուց առաջ խորհուրդ ենք տալիս փակել բոլոր աշխատող ծրագրերը։ + +; *** "Password" wizard page +WizardPassword=Գաղտնաբառ +PasswordLabel1=Ծրագիրը պաշտպանված է գաղտնաբառով։ +PasswordLabel3=Մուտքագրեք գաղտնաբառը և սեղմեք «Հաջորդ»։ +PasswordEditLabel=&Գաղտնաբառ. +IncorrectPassword=Մուտքագրված գաղտնաբառը սխալ է, կրկին փորձեք։ + +; *** "License Agreement" wizard page +WizardLicense=Արտոնագրային համաձայնագիր +LicenseLabel=Խնդրում ենք շարունակելուց առաջ կարդալ հետևյալ տեղեկությունը։ +LicenseLabel3=Կարդացեք արտոնագրային համաձայնագիրը։ Շարունակելուց առաջ պետք է ընդունեք նշված պայմանները։ +LicenseAccepted=&Ընդունում եմ արտոնագրային համաձայնագիրը +LicenseNotAccepted=&Չեմ ընդունում արտոնագրային համաձայնագիրը + +; *** "Information" wizard pages +WizardInfoBefore=Տեղեկություն +InfoBeforeLabel=Շարունակելուց առաջ կարդացեք այս տեղեկությունը։ +InfoBeforeClickLabel=Եթե պատրաստ եք սեղմեք «Հաջորդը»։ +WizardInfoAfter=Տեղեկություն +InfoAfterLabel=Շարունակելուց առաջ կարդացեք այս տեղեկությունը։ +InfoAfterClickLabel=Երբ պատրաստ լինեք շարունակելու՝ սեղմեք «Հաջորդ»։ + +; *** "User Information" wizard page +WizardUserInfo=Տեղեկություն օգտվողի մասին +UserInfoDesc=Գրեք տվյալներ Ձեր մասին +UserInfoName=&Օգտվողի անուն և ազգանուն. +UserInfoOrg=&Կազմակերպություն. +UserInfoSerial=&Հերթական համար. +UserInfoNameRequired=Պետք է գրեք Ձեր անունը։ + +; *** "Select Destination Location" wizard page +WizardSelectDir=Ընտրել տեղակադրման պանակը +SelectDirDesc=Ո՞ր պանակում տեղադրել [name]-ը։ +SelectDirLabel3=Ծրագիրը կտեղադրի [name]-ը հետևյալ պանակում։ +SelectDirBrowseLabel=Սեղմեք «Հաջորդ»՝ շարունակելու համար։ Եթե ցանկանում եք ընտրել այլ պանակ՝ սեղմեք «Ընտրել»։ +DiskSpaceGBLabel=Առնվազն [gb] ԳԲ ազատ տեղ է պահանջվում: +DiskSpaceMBLabel=Առնվազն [mb] ՄԲ ազատ տեղ է պահանջվում: +CannotInstallToNetworkDrive=Հնարավոր չէ տեղադրել Ցանցային հիշասարքում։ +CannotInstallToUNCPath=Հնարավոր չէ տեղադրել UNC ուղիում։ +InvalidPath=Պետք է նշեք ամբողջական ուղին՝ հիշասարքի տառով, օրինակ՝%n%nC:\APP%n%nկամ UNC ուղի՝ %n%n\\սպասարկիչի_անունը\ռեսուրսի_անունը +InvalidDrive=Ընտրված հիշասարքը կամ ցանցային ուղին գոյություն չունեն կամ անհասանելի են։ Ընտրեք այլ ուղի։ +DiskSpaceWarningTitle=Չկա պահանջվող չափով ազատ տեղ +DiskSpaceWarning=Առնվազն %1 ԿԲ ազատ տեղ է պահանջվում, մինչդեռ հասանելի է ընդամենը %2 ԿԲ։%n%nԱյնուհանդերձ, շարունակե՞լ։ +DirNameTooLong=Պանակի անունը կամ ուղին երկար են: +InvalidDirName=Պանակի նշված անունը անընդունելի է։ +BadDirName32=Անվան մեջ չպետք է լինեն հետևյալ գրանշանները՝ %n%n%1 +DirExistsTitle=Թղթապանակը գոյություն ունի +DirExists=%n%n%1%n%n պանակը արդեն գոյություն ունի։ Այնուհանդերձ, տեղադրե՞լ այստեղ։ +DirDoesntExistTitle=Պանակ գոյություն չունի +DirDoesntExist=%n%n%1%n%n պանակը գոյություն չունի։ Ստեղծե՞լ այն։ + +; *** "Select Components" wizard page +WizardSelectComponents=Ընտրել բաղադրիչներ +SelectComponentsDesc=Ո՞ր ֆայլերը պետք է տեղադրվեն։ +SelectComponentsLabel2=Նշեք այն ֆայլերը, որոնք պետք է տեղադրվեն, ապանշեք նրանք, որոնք չպետք է տեղադրվեն։ Սեղմեք «Հաջորդ»՝ շարունակելու համար։ +FullInstallation=Լրիվ տեղադրում +; if possible don't translate 'Compact' as 'Minimal' (I mean 'Minimal' in your language) +CompactInstallation=Սեղմված տեղադրում +CustomInstallation=Ընտրովի տեղադրում +NoUninstallWarningTitle=Տեղակայվող ֆայլերը +NoUninstallWarning=Տեղակայիչ ծրագիրը հայտնաբերել է, որ հետևյալ բաղադրիչները արդեն տեղադրված են Ձեր համակարգչում։ %n%n%1%n%nԱյս բաղադրիչների ընտրության վերակայումը չի ջնջի դրանք։%n%nՇարունակե՞լ։ +ComponentSize1=%1 ԿԲ +ComponentSize2=%1 ՄԲ +ComponentsDiskSpaceGBLabel=Ընթացիկ ընտրումը պահանջում է առնվազն [gb] ԳԲ տեղ հիշասարքում: +ComponentsDiskSpaceMBLabel=Տվյալ ընտրությունը պահանջում է ամենաքիչը [mb] ՄԲ տեղ հիշասարքում: + +; *** "Select Additional Tasks" wizard page +WizardSelectTasks=Լրացուցիչ առաջադրանքներ +SelectTasksDesc=Ի՞նչ լրացուցիչ առաջադրանքներ պետք է կատարվեն։ +SelectTasksLabel2=Ընտրեք լրացուցիչ առաջադրանքներ, որոնք պետք է կատարվեն [name]-ի տեղադրման ընթացքում, ապա սեղմեք «Հաջորդ». + +; *** "Select Start Menu Folder" wizard page +WizardSelectProgramGroup=Ընտրել «Մեկնարկ» ցանկի պանակը +SelectStartMenuFolderDesc=Որտե՞ղ ստեղծել դյուրանցումներ. +SelectStartMenuFolderLabel3=Ծրագիրը կստեղծի դյուրանցումներ «Մեկնարկ» ցանկի հետևյալ պանակում։ +SelectStartMenuFolderBrowseLabel=Սեղմեք «Հաջորդ»՝ շարունակելու համար։ Եթե ցանկանում եք ընտրեք այլ պանակ՝ սեղմեք «Ընտրել»։ +MustEnterGroupName=Պետք է գրել պանակի անունը։ +GroupNameTooLong=Պանակի անունը կամ ուղին շատ երկար են։ +InvalidGroupName=Նշված անունը անընդունելի է։ +BadGroupName=Անվան մեջ չպետք է լինեն հետևյալ գրանշանները՝ %n%n%1 +NoProgramGroupCheck2=&Չստեղծել պանակ «Մեկնարկ» ցանկում + +; *** "Ready to Install" wizard page +WizardReady=Պատրաստ է +ReadyLabel1=Տեղակայիչը պատրաստ է սկսել [name]-ի տեղադրումը։ +ReadyLabel2a=Սեղմեք «Տեղադրել»՝ շարունակելու համար կամ «Նախորդ»՝ եթե ցանկանում եք դիտել կամ փոփոխել տեղադրելու կարգավորումները։ +ReadyLabel2b=Սեղմեք «Տեղադրել»՝ շարունակելու համար։ +ReadyMemoUserInfo=Տեղեկություն օգտվողի մասին. +ReadyMemoDir=Տեղադրելու պանակ. +ReadyMemoType=Տեղադրման ձև. +ReadyMemoComponents=Ընտրված բաղադրիչներ. +ReadyMemoGroup=Թղթապանակ «Մեկնարկ» ցանկում. +ReadyMemoTasks=Լրացուցիչ առաջադրանքներ. +; *** TDownloadWizardPage wizard page and DownloadTemporaryFile +DownloadingLabel=Լրացուցիչ ֆայլերի ներբեռնում... +ButtonStopDownload=&Կանգնեցնել ներբեռնումը +StopDownload=Համոզվա՞ծ եք, որ պետք է կանգնեցնել ներբեռնումը: +ErrorDownloadAborted=Ներբեռնումը կասեցված է +ErrorDownloadFailed=Ներբեռնումը ձախողվեց. %1 %2 +ErrorDownloadSizeFailed=Չափի ստացումը ձախողվեց. %1 %2 +ErrorFileHash1=Ֆահլի հաշվեգումարը ձախողվեց. %1 +ErrorFileHash2=Ֆայլի անվավեր հաշվեգումար. ակընկալվում էր %1, գտնվել է %2 +ErrorProgress=Անվավեր ընթացք. %1-ը %2-ից +ErrorFileSize=Ֆայլի անվավեր աչփ. ակընկալվում էր %1, գտնվել է %2 +; *** "Preparing to Install" wizard page +WizardPreparing=Նախատրաստում է տեղադրումը +PreparingDesc=Տեղակայիչը պատրաստվում է տեղադրել [name]-ը ձեր համակարգչում։ +PreviousInstallNotCompleted=Այլ ծրագրի տեղադրումը կամ ապատեղադրումը չի ավարտվել։ Այն ավարտելու համար պետք է վերամեկնարկեք համակարգիչը։%n%nՎերամեկնարկելուց հետո կրկին բացեք տեղակայման փաթեթը՝ [name]-ի տեղադրումը ավարտելու համար։ +CannotContinue=Հնարավոր չէ շարունակել։ Սեղմեք «Չեղարկել»՝ ծրագիրը փակելու համար։ +ApplicationsFound=Հետևյալ ծրագրերը օգտագործում են ֆայլեր, որոնք պետք է թարմացվեն տեղակայիչի կողմից։ Թույլատրեք տեղակայիչին ինքնաբար փակելու այդ ծրագրերը։ +ApplicationsFound2=Հետևյալ ծրագրերը օգտագործում են ֆայլեր, որոնք պետք է թարմացվեն տեղակայիչի կողմից։ Թույլատրեք տեղակայիչին ինքնաբար փակելու այդ ծրագրերը։ Տեղադրումը ավարտելուց հետո տեղակայիչը կփորձի վերամեկնարկել այդ ծրագրերը։ +CloseApplications=&Ինքնաբար փակել ծրագրերը +DontCloseApplications=&Չփակել ծրագրերը +ErrorCloseApplications=Տեղակայիչը չկարողացավ ինքնաբար փակել բոլոր ծրագրերը: Խորհուրդ ենք տալիս փակել այն բոլոր ծրագրերը, որոնք պետք է թարմացվեն տեղակայիչի կողմից: +PrepareToInstallNeedsRestart=Տեղակայիչը պետք է վերամեկնարկի ձեր համակարգիչը: Դրանից հետո կրկին աշխատեցրեք այն՝ ավարտելու համար [name]-ի տեղադրումը:%n%nՑանկանո՞ւմ եք վերամեկնարկել հիմա: + +; *** "Installing" wizard page +WizardInstalling=Տեղադրում +InstallingLabel=Խնդրում ենք սպասել մինչ [name]-ը կտեղադրվի Ձեր համակարգչում։ + +; *** "Setup Completed" wizard page +FinishedHeadingLabel=[name]-ի տեղադրման ավարտ +FinishedLabelNoIcons=[name] ծրագիրը տեղադրվել է Ձեր համակարգչում։ +FinishedLabel=[name] ծրագիրը տեղադրվել է Ձեր համակարգչում։ +ClickFinish=Սեղմեք «Ավարտել»՝ տեղակայիչը փակելու համար։ +FinishedRestartLabel=[name]-ի տեղադրումը ավարտելու համար պետք է վերամեկնարկել համակարգիչը։ վերամեկնարկե՞լ հիմա։ +FinishedRestartMessage=[name]-ի տեղադրումը ավարտելու համար պետք է վերամեկնարկել համակարգիչը։ %n%վերամեկնարկե՞լ հիմա։ +ShowReadmeCheck=Նայել README ֆայլը։ +YesRadio=&Այո, վերամեկնարկել +NoRadio=&Ոչ, ես հետո վերամեկնարկեմ +; used for example as 'Run MyProg.exe' +RunEntryExec=Աշխատեցնել %1-ը +; used for example as 'View Readme.txt' +RunEntryShellExec=Նայել %1-ը + +; *** "Setup Needs the Next Disk" stuff +ChangeDiskTitle=Տեղակայիչը պահանջում է հաջորդ սկավառակը +SelectDiskLabel2=Զետեղեք %1 սկավառակը և սեղմեք «Լավ»։ %n%nԵթե ֆայլերի պանակը գտնվում է այլ տեղ, ապա ընտրեք ճիշտ ուղին կամ սեղմեք «Ընտրել»։ +PathLabel=&Ուղին. +FileNotInDir2="%1" ֆայլը չի գտնվել "%2"-ում։ Զետեղեք ճիշտ սկավառակ կամ ընտրեք այլ պանակ։ +SelectDirectoryLabel=Խնդրում ենք նշել հաջորդ սկավառակի տեղադրությունը։ + +; *** Installation phase messages +SetupAborted=Տեղակայումը չի ավարտվել։ %n%nՈւղղեք խնդիրը և կրկին փորձեք։ +AbortRetryIgnoreSelectAction=Ընտրեք գործողություն +AbortRetryIgnoreRetry=&Կրկին փորձել +AbortRetryIgnoreIgnore=&Անտեսել սխալը և շարունակել +AbortRetryIgnoreCancel=Չեղարկել տեղադրումը + +; *** Installation status messages +StatusClosingApplications=Փակում է ծրագրերը... +StatusCreateDirs=Պանակների ստեղծում... +StatusExtractFiles=Ֆայլերի դուրս բերում... +StatusCreateIcons=Դյուրանցումների ստեղծում... +StatusCreateIniEntries=INI ֆայլերի ստեղծում... +StatusCreateRegistryEntries=Գրանցամատյանի գրանցումների ստեղծում... +StatusRegisterFiles=Ֆայլերի գրանցում... +StatusSavingUninstall=Ապատեղադրելու տեղեկության պահում... +StatusRunProgram=Տեղադրելու ավարտ... +StatusRestartingApplications=Ծրագրերի վերամեկնարկում... +StatusRollback=Փոփոխությունների հետ բերում... + +; *** Misc. errors +ErrorInternal2=Ներքին սխալ %1 +ErrorFunctionFailedNoCode=%1. վթար +ErrorFunctionFailed=%1. վթար, կոդը՝ %2 +ErrorFunctionFailedWithMessage=%1. վթար, կոդը՝ %2.%n%3 +ErrorExecutingProgram=Հնարավոր չէ կատարել %n%1 ֆայլը + +; *** Registry errors +ErrorRegOpenKey=Գրանցամատյանի բանալին բացելու սխալ՝ %n%1\%2 +ErrorRegCreateKey=Գրանցամատյանի բանալին ստեղծելու սխալ՝ %n%1\%2 +ErrorRegWriteKey=Գրանցամատյանի բանալիում գրանցում կատարելու սխալ՝ %n%1\%2 + +; *** INI errors +ErrorIniEntry=Սխալ՝ "%1" INI ֆայլում գրառում կատարելիս։ + +; *** File copying errors +FileAbortRetryIgnoreSkipNotRecommended=Բաց թողնել այս ֆայլը (խորհուրդ չի տրվում) +FileAbortRetryIgnoreIgnoreNotRecommended=Անտեսել սխալը և շարունակել (խորհուրդ չի տրվում) +SourceIsCorrupted=Սկզբնական ֆայլը վնասված է։ +SourceDoesntExist=Սկզբնական "%1" ֆայլը գոյություն չունի +ExistingFileReadOnly2=Առկա ֆայլը չի կարող փոխարինվել, քանի որ այն նշված է որպես միայն կարդալու: +ExistingFileReadOnlyRetry=&Հեռացրեք միայն կարդալ հատկանիշը և կրկին փորձեք +ExistingFileReadOnlyKeepExisting=&Պահել առկա ֆայլը +ErrorReadingExistingDest=Սխալ՝ ֆայլը կարդալիս. +FileExistsSelectAction=Ընտրեք գործողություն +FileExists2=Ֆայլը գոյություն չունի +FileExistsOverwriteExisting=&Վրագրել առկա ֆայլը +FileExistsKeepExisting=&Պահել առկա ֆայլը +FileExistsOverwriteOrKeepAll=&Անել սա հաջորդ բախման ժամանակ +ExistingFileNewerSelectAction=Ընտրեք գործողություն +ExistingFileNewer2=Առկա ֆայլը ավելի նոր է, քան այն, որ տեղակայիչը փորձում է տեղադրել: +ExistingFileNewerOverwriteExisting=&Վրագրել առկա ֆայլը +ExistingFileNewerKeepExisting=&Պահել առկա ֆայլը (հանձնարարելի) +ExistingFileNewerOverwriteOrKeepAll=&Անել սա հաջորդ բախման ժամանակ +ErrorChangingAttr=Սխալ՝ ընթացիկ ֆայլի հատկանիշները փոխելիս. +ErrorCreatingTemp=Սխալ՝ նշված պանակում ֆայլ ստեղծելիս. +ErrorReadingSource=Սխալ՝ ֆայլը կարդալիս. +ErrorCopying=Սխալ՝ ֆայլը պատճենելիս. +ErrorReplacingExistingFile=Սխալ՝ գոյություն ունեցող ֆայլը փոխարինելիս. +ErrorRestartReplace=RestartReplace ձախողում. +ErrorRenamingTemp=Սխալ՝ նպատակակետ պանակում՝ ֆայլը վերանվանելիս. +ErrorRegisterServer=Հնարավոր չէ գրանցել DLL/OCX-ը. %1 +ErrorRegSvr32Failed=RegSvr32-ի ձախողում, կոդ՝ %1 +ErrorRegisterTypeLib=Հնարավոր չէ գրանցել դարանները՝ %1 +; *** Uninstall display name markings +; used for example as 'My Program (32-bit)' +UninstallDisplayNameMark=%1 (%2) +; used for example as 'My Program (32-bit, All users)' +UninstallDisplayNameMarks=%1 (%2, %3) +UninstallDisplayNameMark32Bit=32 բիթային +UninstallDisplayNameMark64Bit=64 բիթային +UninstallDisplayNameMarkAllUsers=Բոլոր օգտվողները +UninstallDisplayNameMarkCurrentUser=Ընթացիկ օգտվողը + +; *** Post-installation errors +ErrorOpeningReadme=Սխալ՝ README ֆայլը բացելիս։ +ErrorRestartingComputer=Հնարավոր չեղավ վերամեկնարկել համակարգիչը։ Ինքներդ փորձեք։ + +; *** Uninstaller messages +UninstallNotFound="%1" ֆայլը գոյություն չունի։ Հնարավոր չէ ապատեղադրել։ +UninstallOpenError="%1" ֆայլը հնարավոր չէ բացել: Հնարավոր չէ ապատեղադրել +UninstallUnsupportedVer=Ապատեղադրելու "%1" մատյանի ֆայլը անճանաչելի է ապատեղադրող ծրագրի այս տարբերակի համար։ Հնարավոր չէ ապատեղադրել +UninstallUnknownEntry=Անհայտ գրառում է (%1)՝ հայնաբերվել ապատեղադրելու մատյանում +ConfirmUninstall=Ապատեղադրե՞լ %1-ը և նրա բոլոր բաղադրիչները։ +UninstallOnlyOnWin64=Հնարավոր է ապատեղադրել միայն 64 բիթանոց Windows-ում։ +OnlyAdminCanUninstall=Հնարավոր է ապատեղադրել միայն Ադմինի իրավունքներով։ +UninstallStatusLabel=Խնդրում ենք սպասել, մինչև %1-ը ապատեղադրվում է Ձեր համակարգչից։ +UninstalledAll=%1 ծրագիրը ապատեղադրվել է համակարգչից։ +UninstalledMost=%1-ը ապատեղադրվեց Ձեր համակարգչից։%n%nՈրոշ ֆայլեր հնարավոր չեղավ հեռացնել։ Ինքներդ հեռացրեք դրանք։ +UninstalledAndNeedsRestart=%1-ի ապատեղադրումը ավարտելու համար պետք է վերամեկնարկել համակարգիչը։%n%nՎերամեկնարկե՞լ։ +UninstallDataCorrupted="%1" ֆայլը վնասված է։ Հնարավոր չէ ապատեղադրել + +; *** Uninstallation phase messages +ConfirmDeleteSharedFileTitle=Հեռացնե՞լ համատեղ օգտագործվող ֆայլը։ +ConfirmDeleteSharedFile2=Համակարգը նշում է, որ հետևյալ համատեղ օգտագործվող ֆայլը այլևս չի օգտագործվում այլ ծրագրի կողմից։ Ապատեղադրե՞լ այն։ %n%nԵթե համոզված չեք սեղմեք «Ոչ»։ +SharedFileNameLabel=Ֆայլի անուն. +SharedFileLocationLabel=Տեղադրություն. +WizardUninstalling=Ապատեղադրելու վիճակ +StatusUninstalling=%1-ի ապատեղադրում... + +; *** Shutdown block reasons +ShutdownBlockReasonInstallingApp=%1-ի տեղադրում։ +ShutdownBlockReasonUninstallingApp=%1-ի ապատեղադրում։ + +; The custom messages below aren't used by Setup itself, but if you make +; use of them in your scripts, you'll want to translate them. + +[CustomMessages] + +NameAndVersion=%1 տարբերակ՝ %2 +AdditionalIcons=Լրացուցիչ դյուրանցումներ +CreateDesktopIcon=Ստեղծել դյուրանցում &Աշխատասեղանին +CreateQuickLaunchIcon=Ստեղծել դյուրանցում &Արագ թողարկման գոտում +ProgramOnTheWeb=%1-ի վեբ կայքը +UninstallProgram=%1-ի ապատեղադրում +LaunchProgram=Բացել %1-ը +AssocFileExtension=Հա&մակցել %1-ը %2 ֆայլերի հետ։ +AssocingFileExtension=%1-ը համակցվում է %2 ընդլայնումով ֆայլերի հետ... +AutoStartProgramGroupDescription=Ինքնամեկնարկ. +AutoStartProgram=Ինքնաբար մեկնարկել %1-ը +AddonHostProgramNotFound=%1 չի կարող տեղադրվել Ձեր ընտրած պանակում։%n%nՇարունակե՞լ։ + diff --git a/src/AITool.Setup/INNO/Languages/BrazilianPortuguese.isl b/src/AITool.Setup/INNO/Languages/BrazilianPortuguese.isl new file mode 100644 index 00000000..69edb37f --- /dev/null +++ b/src/AITool.Setup/INNO/Languages/BrazilianPortuguese.isl @@ -0,0 +1,384 @@ +; *** Inno Setup version 6.1.0+ Brazilian Portuguese messages made by Cesar82 cesar.zanetti.82@gmail.com *** +; +; To download user-contributed translations of this file, go to: +; https://jrsoftware.org/files/istrans/ +; +; Note: When translating this text, do not add periods (.) to the end of +; messages that didn't have them already, because on those messages Inno +; Setup adds the periods automatically (appending a period would result in +; two periods being displayed). + +[LangOptions] +; The following three entries are very important. Be sure to read and +; understand the '[LangOptions] section' topic in the help file. +LanguageName=Portugus Brasileiro +LanguageID=$0416 +LanguageCodePage=1252 +; If the language you are translating to requires special font faces or +; sizes, uncomment any of the following entries and change them accordingly. +;DialogFontName= +;DialogFontSize=8 +;WelcomeFontName=Verdana +;WelcomeFontSize=12 +;TitleFontName=Arial +;TitleFontSize=29 +;CopyrightFontName=Arial +;CopyrightFontSize=8 + +[Messages] + +; *** Application titles +SetupAppTitle=Instalador +SetupWindowTitle=%1 - Instalador +UninstallAppTitle=Desinstalar +UninstallAppFullTitle=Desinstalar %1 + +; *** Misc. common +InformationTitle=Informao +ConfirmTitle=Confirmar +ErrorTitle=Erro + +; *** SetupLdr messages +SetupLdrStartupMessage=Isto instalar o %1. Voc deseja continuar? +LdrCannotCreateTemp=Incapaz de criar um arquivo temporrio. Instalao abortada +LdrCannotExecTemp=Incapaz de executar o arquivo no diretrio temporrio. Instalao abortada +HelpTextNote= + +; *** Startup error messages +LastErrorMessage=%1.%n%nErro %2: %3 +SetupFileMissing=Est faltando o arquivo %1 do diretrio de instalao. Por favor corrija o problema ou obtenha uma nova cpia do programa. +SetupFileCorrupt=Os arquivos de instalao esto corrompidos. Por favor obtenha uma nova cpia do programa. +SetupFileCorruptOrWrongVer=Os arquivos de instalao esto corrompidos ou so incompatveis com esta verso do instalador. Por favor corrija o problema ou obtenha uma nova cpia do programa. +InvalidParameter=Um parmetro invlido foi passado na linha de comando:%n%n%1 +SetupAlreadyRunning=O instalador j est em execuo. +WindowsVersionNotSupported=Este programa no suporta a verso do Windows que seu computador est executando. +WindowsServicePackRequired=Este programa requer o %1 Service Pack %2 ou superior. +NotOnThisPlatform=Este programa no executar no %1. +OnlyOnThisPlatform=Este programa deve ser executado no %1. +OnlyOnTheseArchitectures=Este programa s pode ser instalado em verses do Windows projetadas para as seguintes arquiteturas de processadores:%n%n% 1 +WinVersionTooLowError=Este programa requer a %1 verso %2 ou superior. +WinVersionTooHighError=Este programa no pode ser instalado na %1 verso %2 ou superior. +AdminPrivilegesRequired=Voc deve estar logado como administrador quando instalar este programa. +PowerUserPrivilegesRequired=Voc deve estar logado como administrador ou como um membro do grupo de Usurios Power quando instalar este programa. +SetupAppRunningError=O instalador detectou que o %1 est atualmente em execuo.%n%nPor favor feche todas as instncias dele agora, ento clique em OK pra continuar ou em Cancelar pra sair. +UninstallAppRunningError=O Desinstalador detectou que o %1 est atualmente em execuo.%n%nPor favor feche todas as instncias dele agora, ento clique em OK pra continuar ou em Cancelar pra sair. + +; *** Startup questions +PrivilegesRequiredOverrideTitle=Selecione o Modo de Instalao do Instalador +PrivilegesRequiredOverrideInstruction=Selecione o modo de instalao +PrivilegesRequiredOverrideText1=O %1 pode ser instalado pra todos os usurios (requer privilgios administrativos) ou s pra voc. +PrivilegesRequiredOverrideText2=O %1 pode ser instalado s pra voc ou pra todos os usurios (requer privilgios administrativos). +PrivilegesRequiredOverrideAllUsers=Instalar pra &todos os usurios +PrivilegesRequiredOverrideAllUsersRecommended=Instalar pra &todos os usurios (recomendado) +PrivilegesRequiredOverrideCurrentUser=Instalar s &pra mim +PrivilegesRequiredOverrideCurrentUserRecommended=Instalar s &pra mim (recomendado) + +; *** Misc. errors +ErrorCreatingDir=O instalador foi incapaz de criar o diretrio "%1" +ErrorTooManyFilesInDir=Incapaz de criar um arquivo no diretrio "%1" porque ele contm arquivos demais + +; *** Setup common messages +ExitSetupTitle=Sair do Instalador +ExitSetupMessage=A Instalao no est completa. Se voc sair agora o programa no ser instalado.%n%nVoc pode executar o instalador de novo outra hora pra completar a instalao.%n%nSair do instalador? +AboutSetupMenuItem=&Sobre o Instalador... +AboutSetupTitle=Sobre o Instalador +AboutSetupMessage=%1 verso %2%n%3%n%n%1 home page:%n%4 +AboutSetupNote= +TranslatorNote= + +; *** Buttons +ButtonBack=< &Voltar +ButtonNext=&Avanar > +ButtonInstall=&Instalar +ButtonOK=OK +ButtonCancel=Cancelar +ButtonYes=&Sim +ButtonYesToAll=Sim pra &Todos +ButtonNo=&No +ButtonNoToAll=N&o pra Todos +ButtonFinish=&Concluir +ButtonBrowse=&Procurar... +ButtonWizardBrowse=P&rocurar... +ButtonNewFolder=&Criar Nova Pasta + +; *** "Select Language" dialog messages +SelectLanguageTitle=Selecione o Idioma do Instalador +SelectLanguageLabel=Selecione o idioma pra usar durante a instalao: + +; *** Common wizard text +ClickNext=Clique em Avanar pra continuar ou em Cancelar pra sair do instalador. +BeveledLabel= +BrowseDialogTitle=Procurar Pasta +BrowseDialogLabel=Selecione uma pasta na lista abaixo, ento clique em OK. +NewFolderName=Nova Pasta + +; *** "Welcome" wizard page +WelcomeLabel1=Bem-vindo ao Assistente do Instalador do [name] +WelcomeLabel2=Isto instalar o [name/ver] no seu computador.%n%n recomendado que voc feche todos os outros aplicativos antes de continuar. + +; *** "Password" wizard page +WizardPassword=Senha +PasswordLabel1=Esta instalao est protegida por senha. +PasswordLabel3=Por favor fornea a senha, ento clique em Avanar pra continuar. As senhas so caso-sensitivo. +PasswordEditLabel=&Senha: +IncorrectPassword=A senha que voc inseriu no est correta. Por favor tente de novo. + +; *** "License Agreement" wizard page +WizardLicense=Acordo de Licena +LicenseLabel=Por favor leia as seguintes informaes importantes antes de continuar. +LicenseLabel3=Por favor leia o seguinte Acordo de Licena. Voc deve aceitar os termos deste acordo antes de continuar com a instalao. +LicenseAccepted=Eu &aceito o acordo +LicenseNotAccepted=Eu &no aceito o acordo + +; *** "Information" wizard pages +WizardInfoBefore=Informao +InfoBeforeLabel=Por favor leia as seguintes informaes importantes antes de continuar. +InfoBeforeClickLabel=Quando voc estiver pronto pra continuar com o instalador, clique em Avanar. +WizardInfoAfter=Informao +InfoAfterLabel=Por favor leia as seguintes informaes importantes antes de continuar. +InfoAfterClickLabel=Quando voc estiver pronto pra continuar com o instalador, clique em Avanar. + +; *** "User Information" wizard page +WizardUserInfo=Informao do Usurio +UserInfoDesc=Por favor insira suas informaes. +UserInfoName=&Nome do Usurio: +UserInfoOrg=&Organizao: +UserInfoSerial=&Nmero de Srie: +UserInfoNameRequired=Voc deve inserir um nome. + +; *** "Select Destination Location" wizard page +WizardSelectDir=Selecione o Local de Destino +SelectDirDesc=Aonde o [name] deve ser instalado? +SelectDirLabel3=O instalador instalar o [name] na seguinte pasta. +SelectDirBrowseLabel=Pra continuar clique em Avanar. Se voc gostaria de selecionar uma pasta diferente, clique em Procurar. +DiskSpaceGBLabel=Pelo menos [gb] MBs de espao livre em disco so requeridos. +DiskSpaceMBLabel=Pelo menos [mb] MBs de espao livre em disco so requeridos. +CannotInstallToNetworkDrive=O instalador no pode instalar em um drive de rede. +CannotInstallToUNCPath=O instalador no pode instalar em um caminho UNC. +InvalidPath=Voc deve inserir um caminho completo com a letra do drive; por exemplo:%n%nC:\APP%n%no um caminho UNC no formulrio:%n%n\\server\share +InvalidDrive=O drive ou compartilhamento UNC que voc selecionou no existe ou no est acessvel. Por favor selecione outro. +DiskSpaceWarningTitle=Sem Espao em Disco o Bastante +DiskSpaceWarning=O instalador requer pelo menos %1 KBs de espao livre pra instalar mas o drive selecionado s tem %2 KBs disponveis.%n%nVoc quer continuar de qualquer maneira? +DirNameTooLong=O nome ou caminho da pasta muito longo. +InvalidDirName=O nome da pasta no vlido. +BadDirName32=Os nomes das pastas no pode incluir quaisquer dos seguintes caracteres:%n%n%1 +DirExistsTitle=A Pasta Existe +DirExists=A pasta:%n%n%1%n%nj existe. Voc gostaria de instalar nesta pasta de qualquer maneira? +DirDoesntExistTitle=A Pasta No Existe +DirDoesntExist=A pasta:%n%n%1%n%nno existe. Voc gostaria quer a pasta fosse criada? + +; *** "Select Components" wizard page +WizardSelectComponents=Selecionar Componentes +SelectComponentsDesc=Quais componentes devem ser instalados? +SelectComponentsLabel2=Selecione os componentes que voc quer instalar; desmarque os componentes que voc no quer instalar. Clique em Avanar quando voc estiver pronto pra continuar. +FullInstallation=Instalao completa +; if possible don't translate 'Compact' as 'Minimal' (I mean 'Minimal' in your language) +CompactInstallation=Instalao compacta +CustomInstallation=Instalao personalizada +NoUninstallWarningTitle=O Componente Existe +NoUninstallWarning=O instalador detectou que os seguintes componentes j esto instalados no seu computador:%n%n%1%n%nNo selecionar estes componentes no desinstalar eles.%n%nVoc gostaria de continuar de qualquer maneira? +ComponentSize1=%1 KBs +ComponentSize2=%1 MBs +ComponentsDiskSpaceGBLabel=A seleo atual requer pelo menos [gb] MBs de espao em disco. +ComponentsDiskSpaceMBLabel=A seleo atual requer pelo menos [mb] MBs de espao em disco. + +; *** "Select Additional Tasks" wizard page +WizardSelectTasks=Selecionar Tarefas Adicionais +SelectTasksDesc=Quais tarefas adicionais devem ser executadas? +SelectTasksLabel2=Selecione as tarefas adicionais que voc gostaria que o instalador executasse enquanto instala o [name], ento clique em Avanar. + +; *** "Select Start Menu Folder" wizard page +WizardSelectProgramGroup=Selecionar a Pasta do Menu Iniciar +SelectStartMenuFolderDesc=Aonde o instalador deve colocar os atalhos do programa? +SelectStartMenuFolderLabel3=O instalador criar os atalhos do programa na seguinte pasta do Menu Iniciar. +SelectStartMenuFolderBrowseLabel=Pra continuar clique em Avanar. Se voc gostaria de selecionar uma pasta diferente, clique em Procurar. +MustEnterGroupName=Voc deve inserir um nome de pasta. +GroupNameTooLong=O nome ou caminho da pasta muito longo. +InvalidGroupName=O nome da pasta no vlido. +BadGroupName=O nome da pasta no pode incluir quaisquer dos seguintes caracteres:%n%n%1 +NoProgramGroupCheck2=&No criar uma pasta no Menu Iniciar + +; *** "Ready to Install" wizard page +WizardReady=Pronto pra Instalar +ReadyLabel1=O instalador est agora pronto pra comear a instalar o [name] no seu computador. +ReadyLabel2a=Clique em Instalar pra continuar com a instalao ou clique em Voltar se voc quer revisar ou mudar quaisquer configuraes. +ReadyLabel2b=Clique em Instalar pra continuar com a instalao. +ReadyMemoUserInfo=Informao do usurio: +ReadyMemoDir=Local de destino: +ReadyMemoType=Tipo de instalao: +ReadyMemoComponents=Componentes selecionados: +ReadyMemoGroup=Pasta do Menu Iniciar: +ReadyMemoTasks=Tarefas adicionais: + +; *** TDownloadWizardPage wizard page and DownloadTemporaryFile +DownloadingLabel=Baixando arquivos adicionais... +ButtonStopDownload=&Parar download +StopDownload=Tem certeza que deseja parar o download? +ErrorDownloadAborted=Download abortado +ErrorDownloadFailed=Download falhou: %1 %2 +ErrorDownloadSizeFailed=Falha ao obter o tamanho: %1 %2 +ErrorFileHash1=Falha no hash do arquivo: %1 +ErrorFileHash2=Hash de arquivo invlido: esperado %1, encontrado %2 +ErrorProgress=Progresso invlido: %1 de %2 +ErrorFileSize=Tamanho de arquivo invlido: esperado %1, encontrado %2 + +; *** "Preparing to Install" wizard page +WizardPreparing=Preparando pra Instalar +PreparingDesc=O instalador est se preparando pra instalar o [name] no seu computador. +PreviousInstallNotCompleted=A instalao/remoo de um programa anterior no foi completada. Voc precisar reiniciar o computador pra completar essa instalao.%n%nAps reiniciar seu computador execute o instalador de novo pra completar a instalao do [name]. +CannotContinue=O instalador no pode continuar. Por favor clique em Cancelar pra sair. +ApplicationsFound=Os aplicativos a seguir esto usando arquivos que precisam ser atualizados pelo instalador. recomendados que voc permita ao instalador fechar automaticamente estes aplicativos. +ApplicationsFound2=Os aplicativos a seguir esto usando arquivos que precisam ser atualizados pelo instalador. recomendados que voc permita ao instalador fechar automaticamente estes aplicativos. Aps a instalao ter completado, o instalador tentar reiniciar os aplicativos. +CloseApplications=&Fechar os aplicativos automaticamente +DontCloseApplications=&No fechar os aplicativos +ErrorCloseApplications=O instalador foi incapaz de fechar automaticamente todos os aplicativos. recomendado que voc feche todos os aplicativos usando os arquivos que precisam ser atualizados pelo instalador antes de continuar. +PrepareToInstallNeedsRestart=A instalao deve reiniciar seu computador. Depois de reiniciar o computador, execute a Instalao novamente para concluir a instalao de [name].%n%nDeseja reiniciar agora? + +; *** "Installing" wizard page +WizardInstalling=Instalando +InstallingLabel=Por favor espere enquanto o instalador instala o [name] no seu computador. + +; *** "Setup Completed" wizard page +FinishedHeadingLabel=Completando o Assistente do Instalador do [name] +FinishedLabelNoIcons=O instalador terminou de instalar o [name] no seu computador. +FinishedLabel=O instalador terminou de instalar o [name] no seu computador. O aplicativo pode ser iniciado selecionando os atalhos instalados. +ClickFinish=Clique em Concluir pra sair do Instalador. +FinishedRestartLabel=Pra completar a instalao do [name], o instalador deve reiniciar seu computador. Voc gostaria de reiniciar agora? +FinishedRestartMessage=Pra completar a instalao do [name], o instalador deve reiniciar seu computador.%n%nVoc gostaria de reiniciar agora? +ShowReadmeCheck=Sim, eu gostaria de visualizar o arquivo README +YesRadio=&Sim, reiniciar o computador agora +NoRadio=&No, eu reiniciarei o computador depois +; used for example as 'Run MyProg.exe' +RunEntryExec=Executar %1 +; used for example as 'View Readme.txt' +RunEntryShellExec=Visualizar %1 + +; *** "Setup Needs the Next Disk" stuff +ChangeDiskTitle=O Instalador Precisa do Prximo Disco +SelectDiskLabel2=Por favor insira o Disco %1 e clique em OK.%n%nSe os arquivos neste disco podem ser achados numa pasta diferente do que a exibida abaixo, insira o caminho correto ou clique em Procurar. +PathLabel=&Caminho: +FileNotInDir2=O arquivo "%1" no pde ser localizado em "%2". Por favor insira o disco correto ou selecione outra pasta. +SelectDirectoryLabel=Por favor especifique o local do prximo disco. + +; *** Installation phase messages +SetupAborted=A instalao no foi completada.%n%nPor favor corrija o problema e execute o instalador de novo. +AbortRetryIgnoreSelectAction=Selecionar ao +AbortRetryIgnoreRetry=&Tentar de novo +AbortRetryIgnoreIgnore=&Ignorar o erro e continuar +AbortRetryIgnoreCancel=Cancelar instalao + +; *** Installation status messages +StatusClosingApplications=Fechando aplicativos... +StatusCreateDirs=Criando diretrios... +StatusExtractFiles=Extraindo arquivos... +StatusCreateIcons=Criando atalhos... +StatusCreateIniEntries=Criando entradas INI... +StatusCreateRegistryEntries=Criando entradas do registro... +StatusRegisterFiles=Registrando arquivos... +StatusSavingUninstall=Salvando informaes de desinstalao... +StatusRunProgram=Concluindo a instalao... +StatusRestartingApplications=Reiniciando os aplicativos... +StatusRollback=Desfazendo as mudanas... + +; *** Misc. errors +ErrorInternal2=Erro interno: %1 +ErrorFunctionFailedNoCode=%1 falhou +ErrorFunctionFailed=%1 falhou; cdigo %2 +ErrorFunctionFailedWithMessage=%1 falhou; cdigo %2.%n%3 +ErrorExecutingProgram=Incapaz de executar o arquivo:%n%1 + +; *** Registry errors +ErrorRegOpenKey=Erro ao abrir a chave do registro:%n%1\%2 +ErrorRegCreateKey=Erro ao criar a chave do registro:%n%1\%2 +ErrorRegWriteKey=Erro ao gravar a chave do registro:%n%1\%2 + +; *** INI errors +ErrorIniEntry=Erro ao criar a entrada INI no arquivo "%1". + +; *** File copying errors +FileAbortRetryIgnoreSkipNotRecommended=&Ignorar este arquivo (no recomendado) +FileAbortRetryIgnoreIgnoreNotRecommended=&Ignorar o erro e continuar (no recomendado) +SourceIsCorrupted=O arquivo de origem est corrompido +SourceDoesntExist=O arquivo de origem "%1" no existe +ExistingFileReadOnly2=O arquivo existente no pde ser substitudo porque est marcado como somente-leitura. +ExistingFileReadOnlyRetry=&Remover o atributo somente-leitura e tentar de novo +ExistingFileReadOnlyKeepExisting=&Manter o arquivo existente +ErrorReadingExistingDest=Um erro ocorreu enquanto tentava ler o arquivo existente: +FileExistsSelectAction=Selecione a ao +FileExists2=O arquivo j existe. +FileExistsOverwriteExisting=&Sobrescrever o arquivo existente +FileExistsKeepExisting=&Mantenha o arquivo existente +FileExistsOverwriteOrKeepAll=&Faa isso para os prximos conflitos +ExistingFileNewerSelectAction=Selecione a ao +ExistingFileNewer2=O arquivo existente mais recente do que aquele que o Setup est tentando instalar. +ExistingFileNewerOverwriteExisting=&Sobrescrever o arquivo existente +ExistingFileNewerKeepExisting=&Mantenha o arquivo existente (recomendado) +ExistingFileNewerOverwriteOrKeepAll=&Faa isso para os prximos conflitos +ErrorChangingAttr=Um erro ocorreu enquanto tentava mudar os atributos do arquivo existente: +ErrorCreatingTemp=Um erro ocorreu enquanto tentava criar um arquivo no diretrio destino: +ErrorReadingSource=Um erro ocorreu enquanto tentava ler o arquivo de origem: +ErrorCopying=Um erro ocorreu enquanto tentava copiar um arquivo: +ErrorReplacingExistingFile=Um erro ocorreu enquanto tentava substituir o arquivo existente: +ErrorRestartReplace=ReiniciarSubstituir falhou: +ErrorRenamingTemp=Um erro ocorreu enquanto tentava renomear um arquivo no diretrio destino: +ErrorRegisterServer=Incapaz de registrar a DLL/OCX: %1 +ErrorRegSvr32Failed=O RegSvr32 falhou com o cdigo de sada %1 +ErrorRegisterTypeLib=Incapaz de registrar a biblioteca de tipos: %1 + +; *** Uninstall display name markings +; used for example as 'My Program (32-bit)' +UninstallDisplayNameMark=%1 (%2) +; used for example as 'My Program (32-bit, All users)' +UninstallDisplayNameMarks=%1 (%2, %3) +UninstallDisplayNameMark32Bit=32 bits +UninstallDisplayNameMark64Bit=64 bits +UninstallDisplayNameMarkAllUsers=Todos os usurios +UninstallDisplayNameMarkCurrentUser=Usurio atual + +; *** Post-installation errors +ErrorOpeningReadme=Um erro ocorreu enquanto tentava abrir o arquivo README. +ErrorRestartingComputer=O instalador foi incapaz de reiniciar o computador. Por favor faa isto manualmente. + +; *** Uninstaller messages +UninstallNotFound=O arquivo "%1" no existe. No consegue desinstalar. +UninstallOpenError=O arquivo "%1" no pde ser aberto. No consegue desinstalar +UninstallUnsupportedVer=O arquivo do log da desinstalao "%1" est num formato no reconhecido por esta verso do desinstalador. No consegue desinstalar +UninstallUnknownEntry=Uma entrada desconhecida (%1) foi encontrada no log da desinstalao +ConfirmUninstall=Voc tem certeza que voc quer remover completamente o %1 e todos os seus componentes? +UninstallOnlyOnWin64=Esta instalao s pode ser desinstalada em Windows 64 bits. +OnlyAdminCanUninstall=Esta instalao s pode ser desinstalada por um usurio com privilgios administrativos. +UninstallStatusLabel=Por favor espere enquanto o %1 removido do seu computador. +UninstalledAll=O %1 foi removido com sucesso do seu computador. +UninstalledMost=Desinstalao do %1 completa.%n%nAlguns elementos no puderam ser removidos. Estes podem ser removidos manualmente. +UninstalledAndNeedsRestart=Pra completar a desinstalao do %1, seu computador deve ser reiniciado.%n%nVoc gostaria de reiniciar agora? +UninstallDataCorrupted=O arquivo "%1" est corrompido. No consegue desinstalar + +; *** Uninstallation phase messages +ConfirmDeleteSharedFileTitle=Remover Arquivo Compartilhado? +ConfirmDeleteSharedFile2=O sistema indica que o seguinte arquivo compartilhado no est mais em uso por quaisquer programas. Voc gostaria que a Desinstalao removesse este arquivo compartilhado?%n%nSe quaisquer programas ainda esto usando este arquivo e ele removido, esses programas podem no funcionar apropriadamente. Se voc no tiver certeza escolha No. Deixar o arquivo no seu sistema no causar qualquer dano. +SharedFileNameLabel=Nome do arquivo: +SharedFileLocationLabel=Local: +WizardUninstalling=Status da Desinstalao +StatusUninstalling=Desinstalando o %1... + +; *** Shutdown block reasons +ShutdownBlockReasonInstallingApp=Instalando o %1. +ShutdownBlockReasonUninstallingApp=Desinstalando o %1. + +; The custom messages below aren't used by Setup itself, but if you make +; use of them in your scripts, you'll want to translate them. + +[CustomMessages] + +NameAndVersion=%1 verso %2 +AdditionalIcons=Atalhos adicionais: +CreateDesktopIcon=Criar um atalho &na rea de trabalho +CreateQuickLaunchIcon=Criar um atalho na &barra de inicializao rpida +ProgramOnTheWeb=%1 na Web +UninstallProgram=Desinstalar o %1 +LaunchProgram=Iniciar o %1 +AssocFileExtension=&Associar o %1 com a extenso do arquivo %2 +AssocingFileExtension=Associando o %1 com a extenso do arquivo %2... +AutoStartProgramGroupDescription=Inicializao: +AutoStartProgram=Iniciar o %1 automaticamente +AddonHostProgramNotFound=O %1 no pde ser localizado na pasta que voc selecionou.%n%nVoc quer continuar de qualquer maneira? diff --git a/src/AITool.Setup/INNO/Languages/Bulgarian.isl b/src/AITool.Setup/INNO/Languages/Bulgarian.isl new file mode 100644 index 00000000..d67871f8 --- /dev/null +++ b/src/AITool.Setup/INNO/Languages/Bulgarian.isl @@ -0,0 +1,382 @@ +; *** Inno Setup version 6.1.0+ Bulgarian messages *** +; Ventsislav Dimitrov +; +; За да изтеглите преводи на този файл, предоставени от потребители, посетете: +; http://www.jrsoftware.org/files/istrans/ +; +; Забележка: когато превеждате, не добавяйте точка (.) в края на съобщения, +; които нямат, защото Inno Setup им добавя автоматично (прибавянето на точка +; ще доведе до показване на две точки). + +[LangOptions] +; Следните три записа са много важни. Уверете се, че сте прочел и разбирате +; раздела "[LangOptions]" на помощния файл. +LanguageName=Български +LanguageID=$0402 +LanguageCodePage=1251 +; Ако езикът, на който превеждате, изисква специална гарнитура или размер на +; шрифта, извадете от коментар съответните записи по-долу и ги променете +; според вашите нужди. +;DialogFontName= +;DialogFontSize=8 +;WelcomeFontName=Verdana +;WelcomeFontSize=12 +;TitleFontName=Arial +;TitleFontSize=29 +;CopyrightFontName=Arial +;CopyrightFontSize=8 + +[Messages] + +; *** Заглавия на приложенията +SetupAppTitle=Инсталиране +SetupWindowTitle=Инсталиране на %1 +UninstallAppTitle=Деинсталиране +UninstallAppFullTitle=Деинсталиране на %1 + +; *** Заглавия от общ тип +InformationTitle=Информация +ConfirmTitle=Потвърждение +ErrorTitle=Грешка + +; *** Съобщения на зареждащия модул +SetupLdrStartupMessage=Ще се инсталира %1. Желаете ли да продължите? +LdrCannotCreateTemp=Не е възможно да се създаде временен файл. Инсталирането бе прекратено +LdrCannotExecTemp=Не е възможно да се стартира файл от временната директория. Инсталирането бе прекратено + +; *** Съобщения за грешка при стартиране +LastErrorMessage=%1.%n%nГрешка %2: %3 +SetupFileMissing=Файлът %1 липсва от инсталационната директория. Моля, отстранете проблема или се снабдете с ново копие на програмата. +SetupFileCorrupt=Инсталационните файлове са повредени. Моля, снабдете се с ново копие на програмата. +SetupFileCorruptOrWrongVer=Инсталационните файлове са повредени или несъвместими с тази версия на инсталатора. Моля, отстранете проблема или се снабдете с ново копие на програмата. +InvalidParameter=В командния ред е подаден невалиден параметър:%n%n%1 +SetupAlreadyRunning=Инсталаторът вече се изпълнява. +WindowsVersionNotSupported=Програмата не поддържа версията на Windows, с която работи компютърът ви. +WindowsServicePackRequired=Програмата изисква %1 Service Pack %2 или по-нов. +NotOnThisPlatform=Програмата не може да се изпълнява под %1. +OnlyOnThisPlatform=Програмата трябва да се изпълнява под %1. +OnlyOnTheseArchitectures=Програмата може да се инсталира само под версии на Windows за следните процесорни архитектури:%n%n%1 +WinVersionTooLowError=Програмата изисква %1 версия %2 или по-нова. +WinVersionTooHighError=Програмата не може да бъде инсталирана в %1 версия %2 или по-нова. +AdminPrivilegesRequired=За да инсталирате програмата, трябва да влезете като администратор. +PowerUserPrivilegesRequired=За да инсталирате програмата, трябва да влезете като администратор или потребител с разширени права. +SetupAppRunningError=Инсталаторът установи, че %1 се изпълнява в момента.%n%nМоля, затворете всички копия на програмата и натиснете "OK", за да продължите, или "Cancel" за изход. +UninstallAppRunningError=Деинсталаторът установи, че %1 се изпълнява в момента.%n%nМоля, затворете всички копия на програмата и натиснете "OK", за да продължите, или "Cancel" за изход. + +; *** Въпроси при стартиране +PrivilegesRequiredOverrideTitle=Избор на режим на инсталация +PrivilegesRequiredOverrideInstruction=Изберете режим на инсталация +PrivilegesRequiredOverrideText1=%1 може да бъде инсталирана за всички потребители (изисква администраторски привилегии) или само за Вас. +PrivilegesRequiredOverrideText2=%1 може да бъде инсталирана само за Вас или за всички потребители (изисква администраторски привилегии). +PrivilegesRequiredOverrideAllUsers=Инсталирай за &всички потребители +PrivilegesRequiredOverrideAllUsersRecommended=Инсталирай за &всички потребители (препоръчва се) +PrivilegesRequiredOverrideCurrentUser=Инсталирай само за &мен +PrivilegesRequiredOverrideCurrentUserRecommended=Инсталирай само за &мен (препоръчва се) + +; *** Други грешки +ErrorCreatingDir=Не е възможно да се създаде директория "%1" +ErrorTooManyFilesInDir=Не е възможно да се създаде файл в директорията "%1", тъй като тя съдържа твърде много файлове + +; *** Съобщения от общ тип на инсталатора +ExitSetupTitle=Затваряне на инсталатора +ExitSetupMessage=Инсталирането не е завършено. Ако затворите сега, програмата няма да бъде инсталирана.%n%nПо-късно можете отново да стартирате инсталатора, за да завършите инсталирането.%n%nЗатваряте ли инсталатора? +AboutSetupMenuItem=&За инсталатора... +AboutSetupTitle=За инсталатора +AboutSetupMessage=%1 версия %2%n%3%n%nУебстраница:%n%4 +AboutSetupNote= +TranslatorNote=Превод на български: Михаил Балабанов + +; *** Бутони +ButtonBack=< На&зад +ButtonNext=На&пред > +ButtonInstall=&Инсталиране +ButtonOK=OK +ButtonCancel=Отказ +ButtonYes=&Да +ButtonYesToAll=Да за &всички +ButtonNo=&Не +ButtonNoToAll=Не за в&сички +ButtonFinish=&Готово +ButtonBrowse=Пре&глед... +ButtonWizardBrowse=Пре&глед... +ButtonNewFolder=&Нова папка + +; *** Съобщения в диалоговия прозорец за избор на език +SelectLanguageTitle=Избор на език за инсталатора +SelectLanguageLabel=Изберете кой език ще ползвате с инсталатора. + +; *** Текстове от общ тип на съветника +ClickNext=Натиснете "Напред", за да продължите, или "Отказ" за затваряне на инсталатора. +BeveledLabel= +BrowseDialogTitle=Преглед за папка +BrowseDialogLabel=Изберете папка от долния списък и натиснете "OK". +NewFolderName=Нова папка + +; *** Страница "Добре дошли" на съветника +WelcomeLabel1=Добре дошли при Съветника за инсталиране на [name] +WelcomeLabel2=Съветникът ще инсталира [name/ver] във Вашия компютър.%n%nПрепоръчва се да затворите всички останали приложения, преди да продължите. + +; *** Страница "Парола" на съветника +WizardPassword=Парола +PasswordLabel1=Инсталацията е защитена с парола. +PasswordLabel3=Моля, въведете паролата и натиснете "Напред", за да продължите. Главни и малки букви са от значение. +PasswordEditLabel=&Парола: +IncorrectPassword=Въведената от вас парола е неправилна. Моля, опитайте отново. + +; *** Страница "Лицензионно споразумение" на съветника +WizardLicense=Лицензионно споразумение +LicenseLabel=Моля, прочетете следната важна информация, преди да продължите. +LicenseLabel3=Моля, прочетете следното Лицензионно споразумение. Преди инсталирането да продължи, трябва да приемете условията на споразумението. +LicenseAccepted=П&риемам споразумението +LicenseNotAccepted=&Не приемам споразумението + +; *** Страници "Информация" на съветника +WizardInfoBefore=Информация +InfoBeforeLabel=Моля, прочетете следната важна информация, преди да продължите. +InfoBeforeClickLabel=Когато сте готов да продължите, натиснете "Напред". +WizardInfoAfter=Информация +InfoAfterLabel=Моля, прочетете следната важна информация, преди да продължите. +InfoAfterClickLabel=Когато сте готов да продължите, натиснете "Напред". + +; *** Страница "Данни за потребител" на съветника +WizardUserInfo=Данни за потребител +UserInfoDesc=Моля, въведете вашите данни. +UserInfoName=&Име: +UserInfoOrg=&Организация: +UserInfoSerial=&Сериен номер: +UserInfoNameRequired=Трябва да въведете име. + +; *** Страница "Избор на местоназначение" на съветника +WizardSelectDir=Избор на местоназначение +SelectDirDesc=Къде да се инсталира [name]? +SelectDirLabel3=[name] ще се инсталира в следната папка. +SelectDirBrowseLabel=Натиснете "Напред", за да продължите. За да изберете друга папка, натиснете "Преглед". +DiskSpaceGBLabel=Изискват се поне [gb] ГБ свободно дисково пространство. +DiskSpaceMBLabel=Изискват се поне [mb] МБ свободно дисково пространство. +CannotInstallToNetworkDrive=Инсталаторът не може да инсталира на мрежово устройство. +CannotInstallToUNCPath=Инсталаторът не може да инсталира в UNC път. +InvalidPath=Трябва да въведете пълен път с буква на устройство, например:%n%nC:\APP%n%nили UNC път във вида:%n%n\\сървър\споделено място +InvalidDrive=Избраното от вас устройство или споделено UNC място не съществува или не е достъпно. Моля, изберете друго. +DiskSpaceWarningTitle=Недостиг на дисково пространство +DiskSpaceWarning=Инсталирането изисква %1 кБ свободно място, но на избраното устройство има само %2 кБ.%n%nЖелаете ли все пак да продължите? +DirNameTooLong=Твърде дълго име на папка или път. +InvalidDirName=Името на папка е невалидно. +BadDirName32=Имената на папки не могат да съдържат следните знаци:%n%n%1 +DirExistsTitle=Папката съществува +DirExists=Папката:%n%n%1%n%nвече съществува. Желаете ли все пак да инсталирате в нея? +DirDoesntExistTitle=Папката не съществува +DirDoesntExist=Папката:%n%n%1%n%nне съществува. Желаете ли да бъде създадена? + +; *** Страница "Избор на компоненти" на съветника +WizardSelectComponents=Избор на компоненти +SelectComponentsDesc=Кои компоненти да бъдат инсталирани? +SelectComponentsLabel2=Изберете компонентите, които желаете да инсталирате, и откажете нежеланите. Натиснете "Напред", когато сте готов да продължите. +FullInstallation=Пълна инсталация +; По възможност не превеждайте "Compact" като "Minimal" (има се предвид "Minimal" на Вашия език) +CompactInstallation=Компактна инсталация +CustomInstallation=Инсталация по избор +NoUninstallWarningTitle=Компонентите съществуват +NoUninstallWarning=Инсталаторът установи, че следните компоненти са вече инсталирани в компютърa:%n%n%1%n%nОтказването на тези компоненти няма да ги деинсталира.%n%nЖелаете ли все пак да продължите? +ComponentSize1=%1 кБ +ComponentSize2=%1 МБ +ComponentsDiskSpaceGBLabel=Направеният избор изисква поне [gb] ГБ дисково пространство. +ComponentsDiskSpaceMBLabel=Направеният избор изисква поне [mb] МБ дисково пространство. + +; *** Страница "Избор на допълнителни задачи" на съветника +WizardSelectTasks=Избор на допълнителни задачи +SelectTasksDesc=Кои допълнителни задачи да бъдат изпълнени? +SelectTasksLabel2=Изберете кои допълнителни задачи желаете да се изпълнят при инсталиране на [name], след което натиснете "Напред". + +; *** Страница "Избор на папка в менюто "Старт" на съветника +WizardSelectProgramGroup=Избор на папка в менюто "Старт" +SelectStartMenuFolderDesc=Къде да бъдат поставени преките пътища на програмата? +SelectStartMenuFolderLabel3=Инсталаторът ще създаде преки пътища в следната папка от менюто "Старт". +SelectStartMenuFolderBrowseLabel=Натиснете "Напред", за да продължите. За да изберете друга папка, натиснете "Преглед". +MustEnterGroupName=Трябва да въведете име на папка. +GroupNameTooLong=Твърде дълго име на папка или път. +InvalidGroupName=Името на папка е невалидно. +BadGroupName=Името на папка не може да съдържа следните знаци:%n%n%1 +NoProgramGroupCheck2=И&нсталиране без папка в менюто "Старт" + +; *** Страница "Готовност за инсталиране" на съветника +WizardReady=Готовност за инсталиране +ReadyLabel1=Инсталаторът е готов да инсталира [name] във Вашия компютър. +ReadyLabel2a=Натиснете "Инсталиране", за да продължите, или "Назад" за преглед или промяна на някои настройки. +ReadyLabel2b=Натиснете "Инсталиране", за да продължите с инсталирането. +ReadyMemoUserInfo=Данни за потребител: +ReadyMemoDir=Местоназначение: +ReadyMemoType=Тип инсталация: +ReadyMemoComponents=Избрани компоненти: +ReadyMemoGroup=Папка в менюто "Старт": +ReadyMemoTasks=Допълнителни задачи: + +; *** Страница "TDownloadWizardPage" на съветника и DownloadTemporaryFile +DownloadingLabel=Изтегляне на допълнителни файлове... +ButtonStopDownload=&Спри изтеглянето +StopDownload=Сигурни ли сте, че искате да спрете изтеглянето? +ErrorDownloadAborted=Изтеглянето беше прекъснато +ErrorDownloadFailed=Изтеглянето беше неуспешно: %1 %2 +ErrorDownloadSizeFailed=Неуспешно получаване на размер: %1 %2 +ErrorFileHash1=Неуспешна контролна сума на файл: %1 +ErrorFileHash2=Невалидна контролна сума на файл: очаквана %1, открита %2 +ErrorProgress=Невалиден напредък: %1 of %2 +ErrorFileSize=Невалиден размер на файл: очакван %1, открит %2 + +; *** Страница "Подготовка за инсталиране" на съветника +WizardPreparing=Подготовка за инсталиране +PreparingDesc=Инсталаторът се подготвя да инсталира [name] във Вашия компютър. +PreviousInstallNotCompleted=Инсталиране или премахване на предишна програма не е завършило. Рестартирайте компютъра, за да може процесът да завърши.%n%nСлед като рестартирате, стартирайте инсталатора отново, за да довършите инсталирането на [name]. +CannotContinue=Инсталирането не може да продължи. Моля, натиснете "Отказ" за изход. +ApplicationsFound=Следните приложения използват файлове, които трябва да бъдат обновени от инсталатора. Препоръчва се да разрешите на инсталатора автоматично да затвори приложенията. +ApplicationsFound2=Следните приложения използват файлове, които трябва да бъдат обновени от инсталатора. Препоръчва се да разрешите на инсталатора автоматично да затвори приложенията. След края на инсталирането ще бъде направен опит за рестартирането им. +CloseApplications=Приложенията да се затворят &автоматично +DontCloseApplications=Приложенията да &не се затварят +ErrorCloseApplications=Не бе възможно да се затворят автоматично всички приложения. Препоръчва се преди да продължите, да затворите всички приложения, използващи файлове, които инсталаторът трябва да обнови. +PrepareToInstallNeedsRestart=Инсталаторът трябва да ресартира Вашия компютър. След рестартирането, стартирайте инсталатора отново, за да завършите инсталацията на [name].%n%nЖелаете ли да рестартирате сега? + +; *** Страница "Инсталиране" на съветника +WizardInstalling=Инсталиране +InstallingLabel=Моля, изчакайте докато [name] се инсталира във Вашия компютър. + +; *** Страница "Инсталирането завърши" на съветника +FinishedHeadingLabel=Съветникът за инсталиране на [name] завърши +FinishedLabelNoIcons=Инсталирането на [name] във Вашия компютър завърши. +FinishedLabel=Инсталирането на [name] във Вашия компютър завърши. Можете да стартирате приложението чрез инсталираните икони. +ClickFinish=Натиснете "Готово", за да затворите инсталатора. +FinishedRestartLabel=Инсталаторът трябва да рестартира компютъра, за да завърши инсталирането на [name]. Желаете ли да рестартирате сега? +FinishedRestartMessage=Инсталаторът трябва да рестартира компютъра, за да завърши инсталирането на [name].%n%nЖелаете ли да рестартирате сега? +ShowReadmeCheck=Да, желая да прегледам файла README +YesRadio=&Да, нека компютърът се рестартира сега +NoRadio=&Не, ще рестартирам компютъра по-късно +; Използва се например в "Стартиране на MyProg.exe" +RunEntryExec=Стартиране на %1 +; Използва се например в "Преглеждане на Readme.txt" +RunEntryShellExec=Преглеждане на %1 + +; *** Текстове от рода на "Инсталаторът изисква следващ носител" +ChangeDiskTitle=Инсталаторът изисква следващ носител +SelectDiskLabel2=Моля, поставете носител %1 и натиснете "ОК".%n%nАко файловете от носителя се намират в различна от показаната по-долу папка, въведете правилния път до тях или натиснете "Преглед". +PathLabel=П&ът: +FileNotInDir2=Файлът "%1" не бе намерен в "%2". Моля, поставете правилния носител или изберете друга папка. +SelectDirectoryLabel=Моля, посочете местоположението на следващия носител. + +; *** Съобщения от фаза "Инсталиране" +SetupAborted=Инсталирането не е завършено.%n%nМоля, отстранете проблема и стартирайте инсталатора отново. +AbortRetryIgnoreSelectAction=Изберете действие +AbortRetryIgnoreRetry=Повторен &опит +AbortRetryIgnoreIgnore=&Пренебрегни грешката и продължи +AbortRetryIgnoreCancel=Прекрати инсталацията + +; *** Съобщения за хода на инсталирането +StatusClosingApplications=Затварят се приложения... +StatusCreateDirs=Създават се директории... +StatusExtractFiles=Извличат се файлове... +StatusCreateIcons=Създават се преки пътища... +StatusCreateIniEntries=Създават се записи в INI файл... +StatusCreateRegistryEntries=Създават се записи в регистъра... +StatusRegisterFiles=Регистрират се файлове... +StatusSavingUninstall=Записват се данни за деинсталиране... +StatusRunProgram=Инсталацията приключва... +StatusRestartingApplications=Рестартират се приложения... +StatusRollback=Заличават се промени... + +; *** Грешки от общ тип +ErrorInternal2=Вътрешна грешка: %1 +ErrorFunctionFailedNoCode=Неуспешно изпълнение на %1 +ErrorFunctionFailed=Неуспешно изпълнение на %1; код на грешката: %2 +ErrorFunctionFailedWithMessage=Неуспешно изпълнение на %1; код на грешката: %2.%n%3 +ErrorExecutingProgram=Не е възможно да се стартира файл:%n%1 + +; *** Грешки, свързани с регистъра +ErrorRegOpenKey=Грешка при отваряне на ключ в регистъра:%n%1\%2 +ErrorRegCreateKey=Грешка при създаване на ключ в регистъра:%n%1\%2 +ErrorRegWriteKey=Грешка при писане в ключ от регистъра:%n%1\%2 + +; *** Грешки, свързани с INI файлове +ErrorIniEntry=Грешка при създаване на INI запис във файла "%1". + +; *** Грешки при копиране на файлове +FileAbortRetryIgnoreSkipNotRecommended=Прескочи този &файл (не се препоръчва) +FileAbortRetryIgnoreIgnoreNotRecommended=&Пренебрегни грешката и продължи (не се препоръчва) +SourceIsCorrupted=Файлът - източник е повреден +SourceDoesntExist=Файлът - източник "%1" не съществува +ExistingFileReadOnly2=Съществуващият файл не беше заменен, защото е маркиран само за четене. +ExistingFileReadOnlyRetry=&Премахни атрибута „само за четене“ и опитай отново +ExistingFileReadOnlyKeepExisting=&Запази съществуващия файл +ErrorReadingExistingDest=Грешка при опит за четене на съществуващ файл: +FileExistsSelectAction=Изберете действие +FileExists2=Файлът вече съществува. +FileExistsOverwriteExisting=&Презапиши съществуващия файл +FileExistsKeepExisting=&Запази съществуващия файл +FileExistsOverwriteOrKeepAll=&Извършвай същото за останалите конфликти +ExistingFileNewerSelectAction=Изберете действие +ExistingFileNewer2=Съществуващият файл е по-нов от този, който инсталаторът се опитва да инсталира. +ExistingFileNewerOverwriteExisting=&Презапиши съществуващия файл +ExistingFileNewerKeepExisting=&Запази съществуващия файл (препоръчително) +ExistingFileNewerOverwriteOrKeepAll=&Извършвай същото за останалите конфликти +ErrorChangingAttr=Грешка при опит за смяна на атрибути на съществуващ файл: +ErrorCreatingTemp=Грешка при опит за създаване на файл в целевата директория: +ErrorReadingSource=Грешка при опит за четене на файл - източник: +ErrorCopying=Грешка при опит за копиране на файл: +ErrorReplacingExistingFile=Грешка при опит за заместване на съществуващ файл: +ErrorRestartReplace=Неуспешно отложено заместване: +ErrorRenamingTemp=Грешка при опит за преименуване на файл в целевата директория: +ErrorRegisterServer=Не е възможно да се регистрира библиотека от тип DLL/OCX: %1 +ErrorRegSvr32Failed=Неуспешно изпълнение на RegSvr32 с код на изход %1 +ErrorRegisterTypeLib=Не е възможно да се регистрира библиотека от типове: %1 + +; *** Обозначаване на показваните имена на програми за деинсталиране +UninstallDisplayNameMark=%1 (%2) +UninstallDisplayNameMarks=%1 (%2, %3) +UninstallDisplayNameMark32Bit=32-битова +UninstallDisplayNameMark64Bit=64-битова +UninstallDisplayNameMarkAllUsers=Всички потребители +UninstallDisplayNameMarkCurrentUser=Текущ потребител + +; *** Грешки след инсталиране +ErrorOpeningReadme=Възникна грешка при опит за отваряне на файла README. +ErrorRestartingComputer=Инсталаторът не е в състояние да рестартира компютъра. Моля, направете го ръчно. + +; *** Съобщения на деинсталатора +UninstallNotFound=Файлът "%1" не съществува. Деинсталирането е невъзможно. +UninstallOpenError=Файлът "%1" не може да се отвори. Деинсталирането е невъзможно +UninstallUnsupportedVer=Форматът на регистрационния файл за деинсталиране "%1" не се разпознава от тази версия на деинсталатора. Деинсталирането е невъзможно +UninstallUnknownEntry=Открит бе непознат запис (%1) в регистрационния файл за деинсталиране +ConfirmUninstall=Наистина ли желаете да премахнете напълно %1 и всички прилежащи компоненти? +UninstallOnlyOnWin64=Програмата може да бъде деинсталирана само под 64-битов Windows. +OnlyAdminCanUninstall=Програмата може да бъде премахната само от потребител с администраторски права. +UninstallStatusLabel=Моля, изчакайте премахването на %1 от Вашия компютър да приключи. +UninstalledAll=%1 беше премахната успешно от Вашия компютър. +UninstalledMost=Деинсталирането на %1 завърши.%n%nПремахването на някои елементи не бе възможно. Можете да ги отстраните ръчно. +UninstalledAndNeedsRestart=За да приключи деинсталирането на %1, трябва да рестартирате Вашия компютър.%n%nЖелаете ли да рестартирате сега? +UninstallDataCorrupted=Файлът "%1" е повреден. Деинсталирането е невъзможно + +; *** Съобщения от фаза "Деинсталиране" +ConfirmDeleteSharedFileTitle=Премахване на споделен файл? +ConfirmDeleteSharedFile2=Системата отчита, че следният споделен файл вече не се ползва от никоя програма. Желаете ли деинсталаторът да го премахне?%n%nАко някоя програма все пак ползва файла и той бъде изтрит, програмата може да спре да работи правилно. Ако се колебаете, изберете "Не". Оставянето на файла в системата е безвредно. +SharedFileNameLabel=Име на файла: +SharedFileLocationLabel=Местоположение: +WizardUninstalling=Ход на деинсталирането +StatusUninstalling=%1 се деинсталира... + +; *** Обяснения за блокирано спиране на системата +ShutdownBlockReasonInstallingApp=Инсталира се %1. +ShutdownBlockReasonUninstallingApp=Деинсталира се %1. + +; Потребителските съобщения по-долу не се ползват от самия инсталатор, но +; ако ползвате такива в скриптовете си, вероятно бихте искали да ги преведете. + +[CustomMessages] + +NameAndVersion=%1, версия %2 +AdditionalIcons=Допълнителни икони: +CreateDesktopIcon=Икона на &работния плот +CreateQuickLaunchIcon=Икона в лентата "&Бързо стартиране" +ProgramOnTheWeb=%1 в Интернет +UninstallProgram=Деинсталиране на %1 +LaunchProgram=Стартиране на %1 +AssocFileExtension=&Свързване на %1 с файловото разширение %2 +AssocingFileExtension=%1 се свързва с файловото разширение %2... +AutoStartProgramGroupDescription=Стартиране: +AutoStartProgram=Автоматично стартиране на %1 +AddonHostProgramNotFound=%1 не бе намерена в избраната от вас папка.%n%nЖелаете ли все пак да продължите? diff --git a/src/AITool.Setup/INNO/Languages/Catalan.isl b/src/AITool.Setup/INNO/Languages/Catalan.isl new file mode 100644 index 00000000..5fc86822 --- /dev/null +++ b/src/AITool.Setup/INNO/Languages/Catalan.isl @@ -0,0 +1,371 @@ +; *** Inno Setup version 6.1.0+ Catalan messages *** +; +; Translated by Carles Millan (email: carles@carlesmillan.cat) +; +; To download user-contributed translations of this file, go to: +; https://jrsoftware.org/files/istrans/ +; +; Note: When translating this text, do not add periods (.) to the end of +; messages that didn't have them already, because on those messages Inno + +[LangOptions] + +LanguageName=Catal<00E0> +LanguageID=$0403 +LanguageCodePage=1252 + +[Messages] + +; *** Application titles +SetupAppTitle=Installaci +SetupWindowTitle=Installaci - %1 +UninstallAppTitle=Desinstallaci +UninstallAppFullTitle=Desinstalla %1 + +; *** Misc. common +InformationTitle=Informaci +ConfirmTitle=Confirmaci +ErrorTitle=Error + +; *** SetupLdr messages +SetupLdrStartupMessage=Aquest programa installar %1. Voleu continuar? +LdrCannotCreateTemp=No s'ha pogut crear un fitxer temporal. Installaci cancellada +LdrCannotExecTemp=No s'ha pogut executar el fitxer a la carpeta temporal. Installaci cancellada +HelpTextNote= + +; *** Startup error messages +LastErrorMessage=%1.%n%nError %2: %3 +SetupFileMissing=El fitxer %1 no es troba a la carpeta d'installaci. Resoleu el problema o obteniu una nova cpia del programa. +SetupFileCorrupt=Els fitxers d'installaci estan corromputs. Obteniu una nova cpia del programa. +SetupFileCorruptOrWrongVer=Els fitxers d'installaci estan espatllats, o sn incompatibles amb aquesta versi del programa. Resoleu el problema o obteniu una nova cpia del programa. +InvalidParameter=Un parmetre invlid ha estat passat a la lnia de comanda:%n%n%1 +SetupAlreadyRunning=La installaci ja est en curs. +WindowsVersionNotSupported=Aquest programa no suporta la versi de Windows installada al vostre ordinador. +WindowsServicePackRequired=Aquest programa necessita %1 Service Pack %2 o posterior. +NotOnThisPlatform=Aquest programa no funcionar sota %1. +OnlyOnThisPlatform=Aquest programa noms pot ser executat sota %1. +OnlyOnTheseArchitectures=Aquest programa noms pot ser installat en versions de Windows dissenyades per a les segents arquitectures de processador:%n%n%1 +WinVersionTooLowError=Aquest programa requereix %1 versi %2 o posterior. +WinVersionTooHighError=Aquest programa no pot ser installat sota %1 versi %2 o posterior. +AdminPrivilegesRequired=Cal que tingueu privilegis d'administrador per poder installar aquest programa. +PowerUserPrivilegesRequired=Cal que accediu com a administrador o com a membre del grup Power Users en installar aquest programa. +SetupAppRunningError=El programa d'installaci ha detectat que %1 s'est executant actualment.%n%nTanqueu el programa i premeu Accepta per a continuar o Cancella per a sortir. +UninstallAppRunningError=El programa de desinstallaci ha detectat que %1 s'est executant en aquest moment.%n%nTanqueu el programa i premeu Accepta per a continuar o Cancella per a sortir. + +; *** Startup questions +PrivilegesRequiredOverrideTitle=Selecci del Mode d'Installaci +PrivilegesRequiredOverrideInstruction=Trieu mode d'installaci +PrivilegesRequiredOverrideText1=%1 pot ser installat per a tots els usuaris (cal tenir privilegis d'administrador), o noms per a vs. +PrivilegesRequiredOverrideText2=%1 pot ser installat noms per a vs, o per a tots els usuaris (cal tenir privilegis d'administrador). +PrivilegesRequiredOverrideAllUsers=Installaci per a &tots els usuaris +PrivilegesRequiredOverrideAllUsersRecommended=Installaci per a &tots els usuaris (recomanat) +PrivilegesRequiredOverrideCurrentUser=Installaci noms per a &mi +PrivilegesRequiredOverrideCurrentUserRecommended=Installaci noms per a &mi (recomanat) + +; *** Misc. errors +ErrorCreatingDir=El programa d'installaci no ha pogut crear la carpeta "%1" +ErrorTooManyFilesInDir=No s'ha pogut crear un fitxer a la carpeta "%1" perqu cont massa fitxers + +; *** Setup common messages +ExitSetupTitle=Surt +ExitSetupMessage=La installaci no s'ha completat. Si sortiu ara, el programa no ser installat.%n%nPer a completar-la podreu tornar a executar el programa d'installaci quan vulgueu.%n%nVoleu sortir-ne? +AboutSetupMenuItem=&Sobre la installaci... +AboutSetupTitle=Sobre la installaci +AboutSetupMessage=%1 versi %2%n%3%n%nPgina web de %1:%n%4 +AboutSetupNote= +TranslatorNote=Catalan translation by Carles Millan (carles at carlesmillan.cat) + +; *** Buttons +ButtonBack=< &Enrere +ButtonNext=&Segent > +ButtonInstall=&Installa +ButtonOK=Accepta +ButtonCancel=Cancella +ButtonYes=&S +ButtonYesToAll=S a &tot +ButtonNo=&No +ButtonNoToAll=N&o a tot +ButtonFinish=&Finalitza +ButtonBrowse=&Explora... +ButtonWizardBrowse=&Cerca... +ButtonNewFolder=Crea &nova carpeta + +; *** "Select Language" dialog messages +SelectLanguageTitle=Trieu idioma +SelectLanguageLabel=Trieu idioma a emprar durant la installaci. + +; *** Common wizard text +ClickNext=Premeu Segent per a continuar o Cancella per a abandonar la installaci. +BeveledLabel= +BrowseDialogTitle=Trieu una carpeta +BrowseDialogLabel=Trieu la carpeta de destinaci i premeu Accepta. +NewFolderName=Nova carpeta + +; *** "Welcome" wizard page +WelcomeLabel1=Benvingut a l'assistent d'installaci de [name] +WelcomeLabel2=Aquest programa installar [name/ver] al vostre ordinador.%n%ns molt recomanable que abans de continuar tanqueu tots els altres programes oberts, per tal d'evitar conflictes durant el procs d'installaci. + +; *** "Password" wizard page +WizardPassword=Contrasenya +PasswordLabel1=Aquesta installaci est protegida amb una contrasenya. +PasswordLabel3=Indiqueu la contrasenya i premeu Segent per a continuar. Aquesta contrasenya distingeix entre majscules i minscules. +PasswordEditLabel=&Contrasenya: +IncorrectPassword=La contrasenya introduda no s correcta. Torneu-ho a intentar. + +; *** "License Agreement" wizard page +WizardLicense=Acord de Llicncia +LicenseLabel=Cal que llegiu aquesta informaci abans de continuar. +LicenseLabel3=Cal que llegiu l'Acord de Llicncia segent. Cal que n'accepteu els termes abans de continuar amb la installaci. +LicenseAccepted=&Accepto l'acord +LicenseNotAccepted=&No accepto l'acord + +; *** "Information" wizard pages +WizardInfoBefore=Informaci +InfoBeforeLabel=Llegiu la informaci segent abans de continuar. +InfoBeforeClickLabel=Quan estigueu preparat per a continuar, premeu Segent. +WizardInfoAfter=Informaci +InfoAfterLabel=Llegiu la informaci segent abans de continuar. +InfoAfterClickLabel=Quan estigueu preparat per a continuar, premeu Segent + +; *** "User Information" wizard page +WizardUserInfo=Informaci sobre l'usuari +UserInfoDesc=Introduu la vostra informaci. +UserInfoName=&Nom de l'usuari: +UserInfoOrg=&Organitzaci +UserInfoSerial=&Nmero de srie: +UserInfoNameRequired=Cal que hi introduu un nom + +; *** "Select Destination Location" wizard page +WizardSelectDir=Trieu Carpeta de Destinaci +SelectDirDesc=On s'ha d'installar [name]? +SelectDirLabel3=El programa d'installaci installar [name] a la carpeta segent. +SelectDirBrowseLabel=Per a continuar, premeu Segent. Si desitgeu triar una altra capeta, premeu Cerca. +DiskSpaceGBLabel=Aquest programa necessita un mnim de [gb] GB d'espai a disc. +DiskSpaceMBLabel=Aquest programa necessita un mnim de [mb] MB d'espai a disc. +CannotInstallToNetworkDrive=La installaci no es pot fer en un disc de xarxa. +CannotInstallToUNCPath=La installaci no es pot fer a una ruta UNC. +InvalidPath=Cal donar una ruta completa amb lletra d'unitat, per exemple:%n%nC:\Aplicaci%n%no b una ruta UNC en la forma:%n%n\\servidor\compartit +InvalidDrive=El disc o ruta de xarxa seleccionat no existeix, trieu-ne un altre. +DiskSpaceWarningTitle=No hi ha prou espai al disc +DiskSpaceWarning=El programa d'installaci necessita com a mnim %1 KB d'espai lliure, per el disc seleccionat noms t %2 KB disponibles.%n%nTot i amb aix, desitgeu continuar? +DirNameTooLong=El nom de la carpeta o de la ruta s massa llarg. +InvalidDirName=El nom de la carpeta no s vlid. +BadDirName32=Un nom de carpeta no pot contenir cap dels carcters segents:%n%n%1 +DirExistsTitle=La carpeta existeix +DirExists=La carpeta:%n%n%1%n%nja existeix. Voleu installar igualment el programa en aquesta carpeta? +DirDoesntExistTitle=La Carpeta No Existeix +DirDoesntExist=La carpeta:%n%n%1%n%nno existeix. Voleu que sigui creada? + +; *** "Select Program Group" wizard page +WizardSelectComponents=Trieu Components +SelectComponentsDesc=Quins components cal installar? +SelectComponentsLabel2=Trieu els components que voleu installar; elimineu els components que no voleu installar. Premeu Segent per a continuar. +FullInstallation=Installaci completa +; if possible don't translate 'Compact' as 'Minimal' (I mean 'Minimal' in your language) +CompactInstallation=Installaci compacta +CustomInstallation=Installaci personalitzada +NoUninstallWarningTitle=Els components Existeixen +NoUninstallWarning=El programa d'installaci ha detectat que els components segents ja es troben al vostre ordinador:%n%n%1%n%nSi no estan seleccionats no seran desinstallats.%n%nVoleu continuar igualment? +ComponentSize1=%1 Kb +ComponentSize2=%1 Mb +ComponentsDiskSpaceGBLabel=Aquesta selecci requereix un mnim de [gb] GB d'espai al disc. +ComponentsDiskSpaceMBLabel=Aquesta selecci requereix un mnim de [mb] Mb d'espai al disc. + +; *** "Select Additional Tasks" wizard page +WizardSelectTasks=Trieu tasques addicionals +SelectTasksDesc=Quines tasques addicionals cal executar? +SelectTasksLabel2=Trieu les tasques addicionals que voleu que siguin executades mentre s'installa [name], i desprs premeu Segent. + +; *** "Select Start Menu Folder" wizard page +WizardSelectProgramGroup=Trieu la carpeta del Men Inici +SelectStartMenuFolderDesc=On cal situar els enllaos del programa? +SelectStartMenuFolderLabel3=El programa d'installaci crear l'accs directe al programa a la segent carpeta del men d'Inici. +SelectStartMenuFolderBrowseLabel=Per a continuar, premeu Segent. Si desitgeu triar una altra carpeta, premeu Cerca. +MustEnterGroupName=Cal que hi introduu un nom de carpeta. +GroupNameTooLong=El nom de la carpeta o de la ruta s massa llarg. +InvalidGroupName=El nom de la carpeta no s vlid. +BadGroupName=El nom del grup no pot contenir cap dels carcters segents:%n%n%1 +NoProgramGroupCheck2=&No cres una carpeta al Men Inici + +; *** "Ready to Install" wizard page +WizardReady=Preparat per a installar +ReadyLabel1=El programa d'installaci est preparat per a iniciar la installaci de [name] al vostre ordinador. +ReadyLabel2a=Premeu Installa per a continuar amb la installaci, o Enrere si voleu revisar o modificar les opcions d'installaci. +ReadyLabel2b=Premeu Installa per a continuar amb la installaci. +ReadyMemoUserInfo=Informaci de l'usuari: +ReadyMemoDir=Carpeta de destinaci: +ReadyMemoType=Tipus d'installaci: +ReadyMemoComponents=Components seleccionats: +ReadyMemoGroup=Carpeta del Men Inici: +ReadyMemoTasks=Tasques addicionals: + +; *** TDownloadWizardPage wizard page and DownloadTemporaryFile +DownloadingLabel=Descarregant els fitxers addicionals... +ButtonStopDownload=&Atura la descrrega +StopDownload=Esteu segur que voleu aturar la descrrega? +ErrorDownloadAborted=Descrrega cancellada +ErrorDownloadFailed=La descrrega ha fallat: %1 %2 +ErrorDownloadSizeFailed=La mesura de la descrrega ha fallat: %1 %2 +ErrorFileHash1=El hash del fitxer ha fallat: %1 +ErrorFileHash2=El hash del fitxer s invlid: s'esperava %1, s'ha trobat %2 +ErrorProgress=Progrs invlid: %1 de %2 +ErrorFileSize=Mida del fitxer invlida: s'esperava %1, s'ha trobat %2 + +; *** "Preparing to Install" wizard page +WizardPreparing=Preparant la installaci +PreparingDesc=Preparant la installaci de [name] al vostre ordinador. +PreviousInstallNotCompleted=La installaci o desinstallaci anterior no s'ha dut a terme. Caldr que reinicieu l'ordinador per a finalitzar aquesta installaci.%n%nDesprs de reiniciar l'ordinador, executeu aquest programa de nou per completar la installaci de [name]. +CannotContinue=La installaci no pot continuar. Premeu Cancella per a sortir. +ApplicationsFound=Les segents aplicacions estan fent servir fitxers que necessiten ser actualitzats per la installaci. Es recomana que permeteu a la installaci tancar automticament aquestes aplicacions. +ApplicationsFound2=Les segents aplicacions estan fent servir fitxers que necessiten ser actualitzats per la installaci. Es recomana que permeteu a la installaci tancar automticament aquestes aplicacions. Desprs de completar la installaci s'intentar reiniciar les aplicacions. +CloseApplications=&Tanca automticament les aplicacions +DontCloseApplications=&No tanquis les aplicacions +ErrorCloseApplications=El programa d'installaci no ha pogut tancar automticament totes les aplicacions. Es recomana que abans de continuar tanqueu totes les aplicacions que estan usant fitxers que han de ser actualitzats pel programa d'installaci. +PrepareToInstallNeedsRestart=El programa d'installaci ha de reiniciar l'ordinador. Desprs del reinici, executeu de nou l'installador per tal de completar la installaci de [name].%n%nVoleu reiniciar-lo ara? + +; *** "Installing" wizard page +WizardInstalling=Installant +InstallingLabel=Espereu mentre s'installa [name] al vostre ordinador. + +; *** "Setup Completed" wizard page +FinishedHeadingLabel=Completant l'assistent d'installaci de [name] +FinishedLabelNoIcons=El programa ha finalitzat la installaci de [name] al vostre ordinador. +FinishedLabel=El programa ha finalitzat la installaci de [name] al vostre ordinador. L'aplicaci pot ser iniciada seleccionant les icones installades. +ClickFinish=Premeu Finalitza per a sortir de la installaci. +FinishedRestartLabel=Per a completar la installaci de [name] cal reiniciar l'ordinador. Voleu fer-ho ara? +FinishedRestartMessage=Per a completar la installaci de [name] cal reiniciar l'ordinador. Voleu fer-ho ara? +ShowReadmeCheck=S, vull visualitzar el fitxer LLEGIUME.TXT +YesRadio=&S, reiniciar l'ordinador ara +NoRadio=&No, reiniciar l'ordinador ms tard +; used for example as 'Run MyProg.exe' +RunEntryExec=Executa %1 +; used for example as 'View Readme.txt' +RunEntryShellExec=Visualitza %1 + +; *** "Setup Needs the Next Disk" stuff +ChangeDiskTitle=El programa d'installaci necessita el disc segent +SelectDiskLabel2=Introduiu el disc %1 i premeu Continua.%n%nSi els fitxers d'aquest disc es poden trobar en una carpeta diferent de la indicada tot seguit, introduu-ne la ruta correcta o b premeu Explora. +PathLabel=&Ruta: +FileNotInDir2=El fitxer "%1" no s'ha pogut trobar a "%2". Introduu el disc correcte o trieu una altra carpeta. +SelectDirectoryLabel=Indiqueu on es troba el disc segent. + +; *** Installation phase messages +SetupAborted=La installaci no s'ha completat.%n%n%Resoleu el problema i executeu de nou el programa d'installaci. +AbortRetryIgnoreSelectAction=Trieu acci +AbortRetryIgnoreRetry=&Torna-ho a intentar +AbortRetryIgnoreIgnore=&Ignora l'error i continua +AbortRetryIgnoreCancel=Cancella la installaci + +; *** Installation status messages +StatusClosingApplications=Tancant aplicacions... +StatusCreateDirs=Creant carpetes... +StatusExtractFiles=Extraient fitxers... +StatusCreateIcons=Creant enllaos del programa... +StatusCreateIniEntries=Creant entrades al fitxer INI... +StatusCreateRegistryEntries=Creant entrades de registre... +StatusRegisterFiles=Registrant fitxers... +StatusSavingUninstall=Desant informaci de desinstallaci... +StatusRunProgram=Finalitzant la installaci... +StatusRestartingApplications=Reiniciant aplicacions... +StatusRollback=Desfent els canvis... + +; *** Misc. errors +ErrorInternal2=Error intern: %1 +ErrorFunctionFailedNoCode=%1 ha fallat +ErrorFunctionFailed=%1 ha fallat; codi %2 +ErrorFunctionFailedWithMessage=%1 ha fallat; codi %2.%n%3 +ErrorExecutingProgram=No es pot executar el fitxer:%n%1 + +; *** Registry errors +ErrorRegOpenKey=Error en obrir la clau de registre:%n%1\%2 +ErrorRegCreateKey=Error en crear la clau de registre:%n%1\%2 +ErrorRegWriteKey=Error en escriure a la clau de registre:%n%1\%2 + +; *** INI errors +ErrorIniEntry=Error en crear l'entrada INI al fitxer "%1". + +; *** File copying errors +FileAbortRetryIgnoreSkipNotRecommended=&Salta't aquest fitxer (no recomanat) +FileAbortRetryIgnoreIgnoreNotRecommended=&Ignora l'error i continua (no recomanat) +SourceIsCorrupted=El fitxer d'origen est corromput +SourceDoesntExist=El fitxer d'origen "%1" no existeix +ExistingFileReadOnly2=El fitxer existent no ha pogut ser substitut perqu est marcat com a noms lectura. +ExistingFileReadOnlyRetry=&Lleveu-li l'atribut de noms lectura i torneu-ho a intentar +ExistingFileReadOnlyKeepExisting=&Mant el fitxer existent +ErrorReadingExistingDest=S'ha produt un error en llegir el fitxer: +FileExistsSelectAction=Trieu acci +FileExists2=El fitxer ja existeix. +FileExistsOverwriteExisting=&Sobreescriu el fitxer existent +FileExistsKeepExisting=&Mant el fitxer existent +FileExistsOverwriteOrKeepAll=&Fes-ho tamb per als propers conflictes +ExistingFileNewerSelectAction=Trieu acci +ExistingFileNewer2=El fitxer existent s ms nou que el que s'intenta installar. +ExistingFileNewerOverwriteExisting=&Sobreescriu el fitxer existent +ExistingFileNewerKeepExisting=&Mant el fitxer existent (recomanat) +ExistingFileNewerOverwriteOrKeepAll=&Fes-ho tamb per als propers conflictes +ErrorChangingAttr=Hi ha hagut un error en canviar els atributs del fitxer: +ErrorCreatingTemp=Hi ha hagut un error en crear un fitxer a la carpeta de destinaci: +ErrorReadingSource=Hi ha hagut un error en llegir el fitxer d'origen: +ErrorCopying=Hi ha hagut un error en copiar un fitxer: +ErrorReplacingExistingFile=Hi ha hagut un error en reemplaar el fitxer existent: +ErrorRestartReplace=Ha fallat reemplaar: +ErrorRenamingTemp=Hi ha hagut un error en reanomenar un fitxer a la carpeta de destinaci: +ErrorRegisterServer=No s'ha pogut registrar el DLL/OCX: %1 +ErrorRegSvr32Failed=Ha fallat RegSvr32 amb el codi de sortida %1 +ErrorRegisterTypeLib=No s'ha pogut registrar la biblioteca de tipus: %1 + +; *** Uninstall display name markings +UninstallDisplayNameMark=%1 (%2) +UninstallDisplayNameMarks=%1 (%2, %3) +UninstallDisplayNameMark32Bit=32-bit +UninstallDisplayNameMark64Bit=64-bit +UninstallDisplayNameMarkAllUsers=Tots els usuaris +UninstallDisplayNameMarkCurrentUser=Usuari actual + +; *** Post-installation errors +ErrorOpeningReadme=Hi ha hagut un error en obrir el fitxer LLEGIUME.TXT. +ErrorRestartingComputer=El programa d'installaci no ha pogut reiniciar l'ordinador. Cal que ho feu manualment. + +; *** Uninstaller messages +UninstallNotFound=El fitxer "%1" no existeix. No es pot desinstallar. +UninstallOpenError=El fitxer "%1" no pot ser obert. No es pot desinstallar +UninstallUnsupportedVer=El fitxer de desinstallaci "%1" est en un format no reconegut per aquesta versi del desinstallador. No es pot desinstallar +UninstallUnknownEntry=S'ha trobat una entrada desconeguda (%1) al fitxer de desinstallaci. +ConfirmUninstall=Esteu segur de voler eliminar completament %1 i tots els seus components? +UninstallOnlyOnWin64=Aquest programa noms pot ser desinstallat en Windows de 64 bits. +OnlyAdminCanUninstall=Aquest programa noms pot ser desinstallat per un usuari amb privilegis d'administrador. +UninstallStatusLabel=Espereu mentre s'elimina %1 del vostre ordinador. +UninstalledAll=%1 ha estat desinstallat correctament del vostre ordinador. +UninstalledMost=Desinstallaci de %1 completada.%n%nAlguns elements no s'han pogut eliminar. Poden ser eliminats manualment. +UninstalledAndNeedsRestart=Per completar la installaci de %1, cal reiniciar el vostre ordinador.%n%nVoleu fer-ho ara? +UninstallDataCorrupted=El fitxer "%1" est corromput. No es pot desinstallar. + +; *** Uninstallation phase messages +ConfirmDeleteSharedFileTitle=Eliminar fitxer compartit? +ConfirmDeleteSharedFile2=El sistema indica que el fitxer compartit segent ja no s emprat per cap altre programa. Voleu que la desinstallaci elimini aquest fitxer?%n%nSi algun programa encara el fa servir i s eliminat, podria no funcionar correctament. Si no n'esteu segur, trieu No. Deixar el fitxer al sistema no far cap mal. +SharedFileNameLabel=Nom del fitxer: +SharedFileLocationLabel=Localitzaci: +WizardUninstalling=Estat de la desinstallaci +StatusUninstalling=Desinstallant %1... + +; *** Shutdown block reasons +ShutdownBlockReasonInstallingApp=Installant %1. +ShutdownBlockReasonUninstallingApp=Desinstallant %1. + +; The custom messages below aren't used by Setup itself, but if you make +; use of them in your scripts, you'll want to translate them. + +[CustomMessages] + +NameAndVersion=%1 versi %2 +AdditionalIcons=Icones addicionals: +CreateDesktopIcon=Crea una icona a l'&Escriptori +CreateQuickLaunchIcon=Crea una icona a la &Barra de tasques +ProgramOnTheWeb=%1 a Internet +UninstallProgram=Desinstalla %1 +LaunchProgram=Obre %1 +AssocFileExtension=&Associa %1 amb l'extensi de fitxer %2 +AssocingFileExtension=Associant %1 amb l'extensi de fitxer %2... +AutoStartProgramGroupDescription=Inici: +AutoStartProgram=Inicia automticament %1 +AddonHostProgramNotFound=%1 no ha pogut ser trobat a la carpeta seleccionada.%n%nVoleu continuar igualment? diff --git a/src/AITool.Setup/INNO/Languages/Corsican.isl b/src/AITool.Setup/INNO/Languages/Corsican.isl new file mode 100644 index 00000000..6f03bcaf --- /dev/null +++ b/src/AITool.Setup/INNO/Languages/Corsican.isl @@ -0,0 +1,399 @@ +; *** Inno Setup version 6.1.0+ Corsican messages *** +; +; To download user-contributed translations of this file, go to: +; https://jrsoftware.org/files/istrans/ +; +; Note: When translating this text, do not add periods (.) to the end of +; messages that didn't have them already, because on those messages Inno +; Setup adds the periods automatically (appending a period would result in +; two periods being displayed). + +; Created and maintained by Patriccollu di Santa Maria è Sichè +; Schedariu di traduzzione in lingua corsa da Patriccollu +; E-mail: Patrick.Santa-Maria[at]LaPoste.Net +; +; Changes: +; November 14th, 2020 - Changes to current version 6.1.0+ +; July 25th, 2020 - Update to version 6.1.0+ +; July 1st, 2020 - Update to version 6.0.6+ +; October 6th, 2019 - Update to version 6.0.3+ +; January 20th, 2019 - Update to version 6.0.0+ +; April 9th, 2016 - Changes to current version 5.5.3+ +; January 3rd, 2013 - Update to version 5.5.3+ +; August 8th, 2012 - Update to version 5.5.0+ +; September 17th, 2011 - Creation for version 5.1.11 + +[LangOptions] +; The following three entries are very important. Be sure to read and +; understand the '[LangOptions] section' topic in the help file. +LanguageName=Corsu +LanguageID=$0483 +LanguageCodePage=1252 +; If the language you are translating to requires special font faces or +; sizes, uncomment any of the following entries and change them accordingly. +;DialogFontName= +;DialogFontSize=8 +;WelcomeFontName=Verdana +;WelcomeFontSize=12 +;TitleFontName=Arial +;TitleFontSize=29 +;CopyrightFontName=Arial +;CopyrightFontSize=8 + +[Messages] + +; *** Application titles +SetupAppTitle=Assistente d’installazione +SetupWindowTitle=Assistente d’installazione - %1 +UninstallAppTitle=Disinstallà +UninstallAppFullTitle=Disinstallazione di %1 + +; *** Misc. common +InformationTitle=Infurmazione +ConfirmTitle=Cunfirmà +ErrorTitle=Sbagliu + +; *** SetupLdr messages +SetupLdrStartupMessage=St’assistente hà da installà %1. Vulete cuntinuà ? +LdrCannotCreateTemp=Impussibule di creà un cartulare timpurariu. Assistente d’installazione interrottu +LdrCannotExecTemp=Impussibule d’eseguisce u schedariu in u cartulare timpurariu. Assistente d’installazione interrottu +HelpTextNote= + +; *** Startup error messages +LastErrorMessage=%1.%n%nSbagliu %2 : %3 +SetupFileMissing=U schedariu %1 manca in u cartulare d’installazione. Ci vole à currege u penseru o ottene una nova copia di u prugramma. +SetupFileCorrupt=I schedarii d’installazione sò alterati. Ci vole à ottene una nova copia di u prugramma. +SetupFileCorruptOrWrongVer=I schedarii d’installazione sò alterati, o sò incumpatibule cù sta versione di l’assistente. Ci vole à currege u penseru o ottene una nova copia di u prugramma. +InvalidParameter=Un parametru micca accettevule hè statu passatu in a linea di cumanda :%n%n%1 +SetupAlreadyRunning=L’assistente d’installazione hè dighjà in corsu. +WindowsVersionNotSupported=Stu prugramma ùn pò micca funziunà cù a versione di Windows installata nant’à st’urdinatore. +WindowsServicePackRequired=Stu prugramma richiede %1 Service Pack %2 o più recente. +NotOnThisPlatform=Stu prugramma ùn funzionerà micca cù %1. +OnlyOnThisPlatform=Stu prugramma deve funzionà cù %1. +OnlyOnTheseArchitectures=Stu prugramma pò solu esse installatu nant’à e versioni di Windows fatte apposta per st’architetture di prucessore :%n%n%1 +WinVersionTooLowError=Stu prugramma richiede %1 versione %2 o più recente. +WinVersionTooHighError=Stu prugramma ùn pò micca esse installatu nant’à %1 version %2 o più recente. +AdminPrivilegesRequired=Ci vole à esse cunnettu cum’è un amministratore quandu voi installate stu prugramma. +PowerUserPrivilegesRequired=Ci vole à esse cunnettu cum’è un amministratore o fà parte di u gruppu « Utilizatori cù putere » quandu voi installate stu prugramma. +SetupAppRunningError=L’assistente hà vistu chì %1 era dighjà in corsu.%n%nCi vole à chjode tutte e so finestre avà, eppò sceglie Vai per cuntinuà, o Abbandunà per compie. +UninstallAppRunningError=A disinstallazione hà vistu chì %1 era dighjà in corsu.%n%nCi vole à chjode tutte e so finestre avà, eppò sceglie Vai per cuntinuà, o Abbandunà per compie. + +; *** Startup questions +PrivilegesRequiredOverrideTitle=Selezziunà u modu d’installazione di l’assistente +PrivilegesRequiredOverrideInstruction=Selezziunà u modu d’installazione +PrivilegesRequiredOverrideText1=%1 pò esse installatu per tutti l’utilizatore (richiede i diritti d’amministratore), o solu per voi. +PrivilegesRequiredOverrideText2=%1 pò esse installatu solu per voi, o per tutti l’utilizatore (richiede i diritti d’amministratore). +PrivilegesRequiredOverrideAllUsers=Installazione per &tutti l’utilizatori +PrivilegesRequiredOverrideAllUsersRecommended=Installazione per &tutti l’utilizatori (ricumandatu) +PrivilegesRequiredOverrideCurrentUser=Installazione solu per &mè +PrivilegesRequiredOverrideCurrentUserRecommended=Installazione solu per &mè (ricumandatu) + +; *** Misc. errors +ErrorCreatingDir=L’assistente ùn hà micca pussutu creà u cartulare « %1 » +ErrorTooManyFilesInDir=Impussibule di creà un schedariu in u cartulare « %1 » perchè ellu ne cuntene troppu + +; *** Setup common messages +ExitSetupTitle=Compie l’assistente +ExitSetupMessage=L’assistente ùn hè micca compiu bè. S’è voi escite avà, u prugramma ùn serà micca installatu.%n%nPudete impiegà l’assistente torna un altra volta per compie l’installazione.%n%nCompie l’assistente ? +AboutSetupMenuItem=&Apprupositu di l’assistente… +AboutSetupTitle=Apprupositu di l’assistente +AboutSetupMessage=%1 versione %2%n%3%n%n%1 pagina d’accolta :%n%4 +AboutSetupNote= +TranslatorNote=Traduzzione in lingua corsa da Patriccollu di Santa Maria è Sichè + +; *** Buttons +ButtonBack=< &Precedente +ButtonNext=&Seguente > +ButtonInstall=&Installà +ButtonOK=Vai +ButtonCancel=Abbandunà +ButtonYes=&Iè +ButtonYesToAll=Iè per &tutti +ButtonNo=I&nnò +ButtonNoToAll=Innò per t&utti +ButtonFinish=&Piantà +ButtonBrowse=&Sfuglià… +ButtonWizardBrowse=&Sfuglià… +ButtonNewFolder=&Creà un novu cartulare + +; *** "Select Language" dialog messages +SelectLanguageTitle=Definisce a lingua di l’assistente +SelectLanguageLabel=Selezziunà a lingua à impiegà per l’installazione. + +; *** Common wizard text +ClickNext=Sceglie Seguente per cuntinuà, o Abbandunà per compie l’assistente. +BeveledLabel= +BrowseDialogTitle=Sfuglià u cartulare +BrowseDialogLabel=Selezziunà un cartulare in a lista inghjò, eppò sceglie Vai. +NewFolderName=Novu cartulare + +; *** "Welcome" wizard page +WelcomeLabel1=Benvenuta in l’assistente d’installazione di [name] +WelcomeLabel2=Quessu installerà [name/ver] nant’à l’urdinatore.%n%nHè ricumandatu di chjode tutte l’altre appiecazioni nanzu di cuntinuà. + +; *** "Password" wizard page +WizardPassword=Parolla d’entrata +PasswordLabel1=L’installazione hè prutetta da una parolla d’entrata. +PasswordLabel3=Ci vole à pruvede a parolla d’entrata, eppò sceglie Seguente per cuntinuà. E parolle d’entrata ponu cuntene maiuscule è minuscule. +PasswordEditLabel=&Parolla d’entrata : +IncorrectPassword=A parolla d’entrata pruvista ùn hè micca curretta. Ci vole à pruvà torna. + +; *** "License Agreement" wizard page +WizardLicense=Cuntrattu di licenza +LicenseLabel=Ci vole à leghje l’infurmazione impurtante chì seguiteghja nanzu di cuntinuà. +LicenseLabel3=Ci vole à leghje u cuntrattu di licenza chì seguiteghja. Duvete accettà i termini di stu cuntrattu nanzu di cuntinuà l’installazione. +LicenseAccepted=Sò d’&accunsentu cù u cuntrattu +LicenseNotAccepted=Ùn sò &micca d’accunsentu cù u cuntrattu + +; *** "Information" wizard pages +WizardInfoBefore=Infurmazione +InfoBeforeLabel=Ci vole à leghje l’infurmazione impurtante chì seguiteghja nanzu di cuntinuà. +InfoBeforeClickLabel=Quandu site prontu à cuntinuà cù l’assistente, sciglite Seguente. +WizardInfoAfter=Infurmazione +InfoAfterLabel=Ci vole à leghje l’infurmazione impurtante chì seguiteghja nanzu di cuntinuà. +InfoAfterClickLabel=Quandu site prontu à cuntinuà cù l’assistente, sciglite Seguente. + +; *** "User Information" wizard page +WizardUserInfo=Infurmazioni di l’utilizatore +UserInfoDesc=Ci vole à scrive e vostre infurmazioni. +UserInfoName=&Nome d’utilizatore : +UserInfoOrg=&Urganismu : +UserInfoSerial=&Numeru di Seria : +UserInfoNameRequired=Ci vole à scrive un nome. + +; *** "Select Destination Location" wizard page +WizardSelectDir=Selezziunà u locu di destinazione +SelectDirDesc=Induve [name] deve esse installatu ? +SelectDirLabel3=L’assistente installerà [name] in stu cartulare. +SelectDirBrowseLabel=Per cuntinuà, sceglie Seguente. S’è voi preferisce selezziunà un altru cartulare, sciglite Sfuglià. +DiskSpaceGBLabel=Hè richiestu omancu [gb] Go di spaziu liberu di discu. +DiskSpaceMBLabel=Hè richiestu omancu [mb] Mo di spaziu liberu di discu. +CannotInstallToNetworkDrive=L’assistente ùn pò micca installà nant’à un discu di a reta. +CannotInstallToUNCPath=L’assistente ùn pò micca installà in un chjassu UNC. +InvalidPath=Ci vole à scrive un chjassu cumplettu cù a lettera di u lettore ; per indettu :%n%nC:\APP%n%no un chjassu UNC in a forma :%n%n\\servitore\spartu +InvalidDrive=U lettore o u chjassu UNC spartu ùn esiste micca o ùn hè micca accessibule. Ci vole à selezziunane un altru. +DiskSpaceWarningTitle=Ùn basta u spaziu discu +DiskSpaceWarning=L’assistente richiede omancu %1 Ko di spaziu liberu per installà, ma u lettore selezziunatu hà solu %2 Ko dispunibule.%n%nVulete cuntinuà quantunque ? +DirNameTooLong=U nome di cartulare o u chjassu hè troppu longu. +InvalidDirName=U nome di cartulare ùn hè micca accettevule. +BadDirName32=I nomi di cartulare ùn ponu micca cuntene sti caratteri :%n%n%1 +DirExistsTitle=Cartulare esistente +DirExists=U cartulare :%n%n%1%n%nesiste dighjà. Vulete installà in stu cartulare quantunque ? +DirDoesntExistTitle=Cartulare inesistente +DirDoesntExist=U cartulare :%n%n%1%n%nùn esiste micca. Vulete chì stu cartulare sia creatu ? + +; *** "Select Components" wizard page +WizardSelectComponents=Selezzione di cumpunenti +SelectComponentsDesc=Chì cumpunenti devenu esse installati ? +SelectComponentsLabel2=Selezziunà i cumpunenti à installà ; deselezziunà quelli ch’ùn devenu micca esse installati. Sceglie Seguente quandu site prontu à cuntinuà. +FullInstallation=Installazione sana +; if possible don't translate 'Compact' as 'Minimal' (I mean 'Minimal' in your language) +CompactInstallation=Installazione cumpatta +CustomInstallation=Installazione persunalizata +NoUninstallWarningTitle=Cumpunenti esistenti +NoUninstallWarning=L’assistente hà vistu chì sti cumpunenti sò dighjà installati nant’à l’urdinatore :%n%n%1%n%nDeselezziunà sti cumpunenti ùn i disinstallerà micca.%n%nVulete cuntinuà quantunque ? +ComponentSize1=%1 Ko +ComponentSize2=%1 Mo +ComponentsDiskSpaceGBLabel=A selezzione attuale richiede omancu [gb] Go di spaziu liberu nant’à u discu. +ComponentsDiskSpaceMBLabel=A selezzione attuale richiede omancu [mb] Mo di spaziu liberu nant’à u discu. + +; *** "Select Additional Tasks" wizard page +WizardSelectTasks=Selezziunà trattamenti addizziunali +SelectTasksDesc=Chì trattamenti addizziunali vulete fà ? +SelectTasksLabel2=Selezziunà i trattamenti addizziunali chì l’assistente deve fà durante l’installazione di [name], eppò sceglie Seguente. + +; *** "Select Start Menu Folder" wizard page +WizardSelectProgramGroup=Selezzione di u cartulare di u listinu « Démarrer » +SelectStartMenuFolderDesc=Induve l’assistente deve piazzà l’accurtatoghji di u prugramma ? +SelectStartMenuFolderLabel3=L’assistente piazzerà l’accurtatoghji di u prugramma in stu cartulare di u listinu « Démarrer ». +SelectStartMenuFolderBrowseLabel=Per cuntinuà, sceglie Seguente. S’è voi preferisce selezziunà un altru cartulare, sciglite Sfuglià. +MustEnterGroupName=Ci vole à scrive un nome di cartulare. +GroupNameTooLong=U nome di cartulare o u chjassu hè troppu longu. +InvalidGroupName=U nome di cartulare ùn hè micca accettevule. +BadGroupName=U nome di u cartulare ùn pò micca cuntene alcunu di sti caratteri :%n%n%1 +NoProgramGroupCheck2=Ùn creà &micca di cartulare in u listinu « Démarrer » + +; *** "Ready to Install" wizard page +WizardReady=Prontu à Installà +ReadyLabel1=Avà l’assistente hè prontu à principià l’installazione di [name] nant’à l’urdinatore. +ReadyLabel2a=Sceglie Installà per cuntinuà l’installazione, o nant’à Precedente per rivede o cambià qualchì preferenza. +ReadyLabel2b=Sceglie Installà per cuntinuà l’installazione. +ReadyMemoUserInfo=Infurmazioni di l’utilizatore : +ReadyMemoDir=Cartulare d’installazione : +ReadyMemoType=Tipu d’installazione : +ReadyMemoComponents=Cumpunenti selezziunati : +ReadyMemoGroup=Cartulare di u listinu « Démarrer » : +ReadyMemoTasks=Trattamenti addizziunali : + +; *** TDownloadWizardPage wizard page and DownloadTemporaryFile +DownloadingLabel=Scaricamentu di i schedarii addiziunali… +ButtonStopDownload=&Piantà u scaricamentu +StopDownload=Site sicuru di vulè piantà u scaricamentu ? +ErrorDownloadAborted=Scaricamentu interrottu +ErrorDownloadFailed=Scaricamentu fiascu : %1 %2 +ErrorDownloadSizeFailed=Fiascu per ottene a dimensione : %1 %2 +ErrorFileHash1=Fiascu di u tazzeghju di u schedariu : %1 +ErrorFileHash2=Tazzeghju di u schedariu inaccettevule : aspettatu %1, trovu %2 +ErrorProgress=Prugressione inaccettevule : %1 di %2 +ErrorFileSize=Dimensione di u schedariu inaccettevule : aspettatu %1, trovu %2 + +; *** "Preparing to Install" wizard page +WizardPreparing=Preparazione di l’installazione +PreparingDesc=L’assistente appronta l’installazione di [name] nant’à l’urdinatore. +PreviousInstallNotCompleted=L’installazione o a cacciatura di un prugramma precedente ùn s’hè micca compia bè. Ci vulerà à ridimarrà l’urdinatore per compie st’installazione.%n%nDopu, ci vulerà à rilancià l’assistente per compie l’installazione di [name]. +CannotContinue=L’assistente ùn pò micca cuntinuà. Sceglie Abbandunà per esce. +ApplicationsFound=St’appiecazioni impieganu schedarii chì devenu esse mudificati da l’assistente. Hè ricumandatu di permette à l’assistente di chjode autumaticamente st’appiecazioni. +ApplicationsFound2=St’appiecazioni impieganu schedarii chì devenu esse mudificati da l’assistente. Hè ricumandatu di permette à l’assistente di chjode autumaticamente st’appiecazioni. S’è l’installazione si compie bè, l’assistente pruverà di rilancià l’appiecazioni. +CloseApplications=Chjode &autumaticamente l’appiecazioni +DontCloseApplications=Ùn chjode &micca l’appiecazioni +ErrorCloseApplications=L’assistente ùn hà micca pussutu chjode autumaticamente tutti l’appiecazioni. Nanzu di cuntinuà, hè ricumandatu di chjode tutti l’appiecazioni chì impieganu schedarii chì devenu esse mudificati da l’assistente durante l’installazione. +PrepareToInstallNeedsRestart=L’assistente deve ridimarrà l’urdinatore. Dopu, ci vulerà à rilancià l’assistente per compie l’installazione di [name].%n%nVulete ridimarrà l’urdinatore subitu ? + +; *** "Installing" wizard page +WizardInstalling=Installazione in corsu +InstallingLabel=Ci vole à aspettà durante l’installazione di [name] nant’à l’urdinatore. + +; *** "Setup Completed" wizard page +FinishedHeadingLabel=Fine di l’installazione di [name] +FinishedLabelNoIcons=L’assistente hà compiu l’installazione di [name] nant’à l’urdinatore. +FinishedLabel=L’assistente hà compiu l’installazione di [name] nant’à l’urdinatore. L’appiecazione pò esse lanciata selezziunendu l’accurtatoghji installati. +ClickFinish=Sceglie Piantà per compie l’assistente. +FinishedRestartLabel=Per compie l’installazione di [name], l’assistente deve ridimarrà l’urdinatore. Vulete ridimarrà l’urdinatore subitu ? +FinishedRestartMessage=Per compie l’installazione di [name], l’assistente deve ridimarrà l’urdinatore.%n%nVulete ridimarrà l’urdinatore subitu ? +ShowReadmeCheck=Iè, vogliu leghje u schedariu LISEZMOI o README +YesRadio=&Iè, ridimarrà l’urdinatore subitu +NoRadio=I&nnò, preferiscu ridimarrà l’urdinatore dopu +; used for example as 'Run MyProg.exe' +RunEntryExec=Eseguisce %1 +; used for example as 'View Readme.txt' +RunEntryShellExec=Fighjà %1 + +; *** "Setup Needs the Next Disk" stuff +ChangeDiskTitle=L’assistente hà bisogniu di u discu seguente +SelectDiskLabel2=Mette u discu %1 è sceglie Vai.%n%nS’è i schedarii di stu discu si trovanu in un’altru cartulare chì quellu indicatu inghjò, scrive u chjassu currettu o sceglie Sfuglià. +PathLabel=&Chjassu : +FileNotInDir2=U schedariu « %1 » ùn si truva micca in « %2 ». Mette u discu curretu o sceglie un’altru cartulare. +SelectDirectoryLabel=Ci vole à specificà induve si trova u discu seguente. + +; *** Installation phase messages +SetupAborted=L’installazione ùn s’hè micca compia bè.%n%nCi vole à currege u penseru è eseguisce l’assistente torna. +AbortRetryIgnoreSelectAction=Selezziunate un’azzione +AbortRetryIgnoreRetry=&Pruvà torna +AbortRetryIgnoreIgnore=&Ignurà u sbagliu è cuntinuà +AbortRetryIgnoreCancel=Abbandunà l’installazione + +; *** Installation status messages +StatusClosingApplications=Chjusura di l’appiecazioni… +StatusCreateDirs=Creazione di i cartulari… +StatusExtractFiles=Estrazzione di i schedarii… +StatusCreateIcons=Creazione di l’accurtatoghji… +StatusCreateIniEntries=Creazione di l’elementi INI… +StatusCreateRegistryEntries=Creazione di l’elementi di u registru… +StatusRegisterFiles=Arregistramentu di i schedarii… +StatusSavingUninstall=Cunservazione di l’informazioni di disinstallazione… +StatusRunProgram=Cumpiera di l’installazione… +StatusRestartingApplications=Relanciu di l’appiecazioni… +StatusRollback=Annulazione di i mudificazioni… + +; *** Misc. errors +ErrorInternal2=Sbagliu internu : %1 +ErrorFunctionFailedNoCode=Fiascu di %1 +ErrorFunctionFailed=Fiascu di %1 ; codice %2 +ErrorFunctionFailedWithMessage=Fiascu di %1 ; codice %2.%n%3 +ErrorExecutingProgram=Impussibule d’eseguisce u schedariu :%n%1 + +; *** Registry errors +ErrorRegOpenKey=Sbagliu durante l’apertura di a chjave di registru :%n%1\%2 +ErrorRegCreateKey=Sbagliu durante a creazione di a chjave di registru :%n%1\%2 +ErrorRegWriteKey=Sbagliu durante a scrittura di a chjave di registru :%n%1\%2 + +; *** INI errors +ErrorIniEntry=Sbagliu durante a creazione di l’elementu INI in u schedariu « %1 ». + +; *** File copying errors +FileAbortRetryIgnoreSkipNotRecommended=Ignurà stu &schedariu (micca ricumandatu) +FileAbortRetryIgnoreIgnoreNotRecommended=&Ignurà u sbagliu è cuntinuà (micca ricumandatu) +SourceIsCorrupted=U schedariu d’urigine hè alteratu +SourceDoesntExist=U schedariu d’urigine « %1 » ùn esiste micca +ExistingFileReadOnly2=U schedariu esistente hà un attributu di lettura-sola è ùn pò micca esse rimpiazzatu. +ExistingFileReadOnlyRetry=&Caccià l’attributu di lettura-sola è pruvà torna +ExistingFileReadOnlyKeepExisting=Cunservà u schedariu &esistente +ErrorReadingExistingDest=Un sbagliu hè accadutu pruvendu di leghje u schedariu esistente : +FileExistsSelectAction=Selezziunate un’azzione +FileExists2=U schedariu esiste dighjà. +FileExistsOverwriteExisting=&Rimpiazzà u schedariu chì esiste +FileExistsKeepExisting=Cunservà u schedariu &esistente +FileExistsOverwriteOrKeepAll=&Fà què per l’altri cunflitti +ExistingFileNewerSelectAction=Selezziunate un’azzione +ExistingFileNewer2=U schedariu esistente hè più recente chì quellu chì l’assistente prova d’installà. +ExistingFileNewerOverwriteExisting=&Rimpiazzà u schedariu chì esiste +ExistingFileNewerKeepExisting=Cunservà u schedariu &esistente (ricumandatu) +ExistingFileNewerOverwriteOrKeepAll=&Fà què per l’altri cunflitti +ErrorChangingAttr=Un sbagliu hè accadutu pruvendu di cambià l’attributi di u schedariu esistente : +ErrorCreatingTemp=Un sbagliu hè accadutu pruvendu di creà un schedariu in u cartulare di destinazione : +ErrorReadingSource=Un sbagliu hè accadutu pruvendu di leghje u schedariu d’urigine : +ErrorCopying=Un sbagliu hè accadutu pruvendu di cupià un schedariu : +ErrorReplacingExistingFile=Un sbagliu hè accadutu pruvendu di rimpiazzà u schedariu esistente : +ErrorRestartReplace=Fiascu di Rimpiazzamentu di schedariu à u riavviu di l’urdinatore : +ErrorRenamingTemp=Un sbagliu hè accadutu pruvendu di rinuminà un schedariu in u cartulare di destinazione : +ErrorRegisterServer=Impussibule d’arregistrà a bibliuteca DLL/OCX : %1 +ErrorRegSvr32Failed=Fiascu di RegSvr32 cù codice d’esciuta %1 +ErrorRegisterTypeLib=Impussibule d’arregistrà a bibliuteca di tipu : %1 + +; *** Uninstall display name markings +; used for example as 'My Program (32-bit)' +UninstallDisplayNameMark=%1 (%2) +; used for example as 'My Program (32-bit, All users)' +UninstallDisplayNameMarks=%1 (%2, %3) +UninstallDisplayNameMark32Bit=32-bit +UninstallDisplayNameMark64Bit=64-bit +UninstallDisplayNameMarkAllUsers=Tutti l’utilizatori +UninstallDisplayNameMarkCurrentUser=L’utilizatore attuale + +; *** Post-installation errors +ErrorOpeningReadme=Un sbagliu hè accadutu pruvendu d’apre u schedariu LISEZMOI o README. +ErrorRestartingComputer=L’assistente ùn hà micca pussutu ridimarrà l’urdinatore. Ci vole à fallu manualmente. + +; *** Uninstaller messages +UninstallNotFound=U schedariu « %1 » ùn esiste micca. Impussibule di disinstallà. +UninstallOpenError=U schedariu« %1 » ùn pò micca esse apertu. Impussibule di disinstallà +UninstallUnsupportedVer=U ghjurnale di disinstallazione « %1 » hè in una forma scunnisciuta da sta versione di l’assistente di disinstallazione. Impussibule di disinstallà +UninstallUnknownEntry=Un elementu scunisciutu (%1) hè statu trovu in u ghjurnale di disinstallazione +ConfirmUninstall=Site sicuru di vulè caccià cumpletamente %1 è tutti i so cumpunenti ? +UninstallOnlyOnWin64=St’appiecazione pò esse disinstallata solu cù una versione 64-bit di Windows. +OnlyAdminCanUninstall=St’appiecazione pò esse disinstallata solu da un utilizatore di u gruppu d’amministratori. +UninstallStatusLabel=Ci vole à aspettà chì %1 sia cacciatu di l’urdinatore. +UninstalledAll=%1 hè statu cacciatu bè da l’urdinatore. +UninstalledMost=A disinstallazione di %1 hè compia.%n%nQualchì elementu ùn pò micca esse cacciatu. Ci vole à cacciallu manualmente. +UninstalledAndNeedsRestart=Per compie a disinstallazione di %1, l’urdinatore deve esse ridimarratu.%n%nVulete ridimarrà l’urdinatore subitu ? +UninstallDataCorrupted=U schedariu « %1 » hè alteratu. Impussibule di disinstallà + +; *** Uninstallation phase messages +ConfirmDeleteSharedFileTitle=Caccià i schedarii sparti ? +ConfirmDeleteSharedFile2=U sistema indicheghja chì u schedariu spartu ùn hè più impiegatu da nisunu prugramma. Vulete chì a disinstallazione cacci stu schedariu spartu ?%n%nS’è qualchì prugramma impiegheghja sempre stu schedariu è ch’ellu hè cacciatu, quellu prugramma ùn puderà funziunà currettamente. S’è ùn site micca sicuru, sceglie Innò. Lascià stu schedariu nant’à u sistema ùn pò micca pruduce danni. +SharedFileNameLabel=Nome di schedariu : +SharedFileLocationLabel=Lucalizazione : +WizardUninstalling=Statu di disinstallazione +StatusUninstalling=Disinstallazione di %1… + +; *** Shutdown block reasons +ShutdownBlockReasonInstallingApp=Installazione di %1. +ShutdownBlockReasonUninstallingApp=Disinstallazione di %1. + +; The custom messages below aren't used by Setup itself, but if you make +; use of them in your scripts, you'll want to translate them. + +[CustomMessages] + +NameAndVersion=%1 versione %2 +AdditionalIcons=Accurtatoghji addizziunali : +CreateDesktopIcon=Creà un accurtatoghju nant’à u &scagnu +CreateQuickLaunchIcon=Creà un accurtatoghju nant’à a barra di &lanciu prontu +ProgramOnTheWeb=%1 nant’à u Web +UninstallProgram=Disinstallà %1 +LaunchProgram=Lancià %1 +AssocFileExtension=&Assucià %1 cù l’estensione di schedariu %2 +AssocingFileExtension=Associu di %1 cù l’estensione di schedariu %2… +AutoStartProgramGroupDescription=Lanciu autumaticu : +AutoStartProgram=Lanciu autumaticu di %1 +AddonHostProgramNotFound=Impussibule di truvà %1 in u cartulare selezziunatu.%n%nVulete cuntinuà l’installazione quantunque ? diff --git a/src/AITool.Setup/INNO/Languages/Czech.isl b/src/AITool.Setup/INNO/Languages/Czech.isl new file mode 100644 index 00000000..9e37db5e --- /dev/null +++ b/src/AITool.Setup/INNO/Languages/Czech.isl @@ -0,0 +1,378 @@ +; ******************************************************* +; *** *** +; *** Inno Setup version 6.1.0+ Czech messages *** +; *** *** +; *** Original Author: *** +; *** *** +; *** Ivo Bauer (bauer@ozm.cz) *** +; *** *** +; *** Contributors: *** +; *** *** +; *** Lubos Stanek (lubek@users.sourceforge.net) *** +; *** Vitezslav Svejdar (vitezslav.svejdar@cuni.cz) *** +; *** Jiri Fenz (jirifenz@gmail.com) *** +; *** *** +; ******************************************************* + +[LangOptions] +LanguageName=<010C>e<0161>tina +LanguageID=$0405 +LanguageCodePage=1250 + +[Messages] + +; *** Application titles +SetupAppTitle=Prvodce instalac +SetupWindowTitle=Prvodce instalac - %1 +UninstallAppTitle=Prvodce odinstalac +UninstallAppFullTitle=Prvodce odinstalac - %1 + +; *** Misc. common +InformationTitle=Informace +ConfirmTitle=Potvrzen +ErrorTitle=Chyba + +; *** SetupLdr messages +SetupLdrStartupMessage=Vt Vs prvodce instalac produktu %1. Chcete pokraovat? +LdrCannotCreateTemp=Nelze vytvoit doasn soubor. Prvodce instalac bude ukonen +LdrCannotExecTemp=Nelze spustit soubor v doasn sloce. Prvodce instalac bude ukonen +HelpTextNote= + +; *** Startup error messages +LastErrorMessage=%1.%n%nChyba %2: %3 +SetupFileMissing=Instalan sloka neobsahuje soubor %1. Opravte prosm tuto chybu nebo si opatete novou kopii tohoto produktu. +SetupFileCorrupt=Soubory prvodce instalac jsou pokozeny. Opatete si prosm novou kopii tohoto produktu. +SetupFileCorruptOrWrongVer=Soubory prvodce instalac jsou pokozeny nebo se nesluuj s touto verz prvodce instalac. Opravte prosm tuto chybu nebo si opatete novou kopii tohoto produktu. +InvalidParameter=Pkazov dek obsahuje neplatn parametr:%n%n%1 +SetupAlreadyRunning=Prvodce instalac je ji sputn. +WindowsVersionNotSupported=Tento produkt nepodporuje verzi MS Windows, kter b na Vaem potai. +WindowsServicePackRequired=Tento produkt vyaduje %1 Service Pack %2 nebo vy. +NotOnThisPlatform=Tento produkt nelze spustit ve %1. +OnlyOnThisPlatform=Tento produkt mus bt sputn ve %1. +OnlyOnTheseArchitectures=Tento produkt lze nainstalovat pouze ve verzch MS Windows s podporou architektury procesor:%n%n%1 +WinVersionTooLowError=Tento produkt vyaduje %1 verzi %2 nebo vy. +WinVersionTooHighError=Tento produkt nelze nainstalovat ve %1 verzi %2 nebo vy. +AdminPrivilegesRequired=K instalaci tohoto produktu muste bt pihleni s oprvnnmi sprvce. +PowerUserPrivilegesRequired=K instalaci tohoto produktu muste bt pihleni s oprvnnmi sprvce nebo lena skupiny Power Users. +SetupAppRunningError=Prvodce instalac zjistil, e produkt %1 je nyn sputn.%n%nZavete prosm vechny instance tohoto produktu a pak pokraujte klepnutm na tlatko OK, nebo ukonete instalaci tlatkem Zruit. +UninstallAppRunningError=Prvodce odinstalac zjistil, e produkt %1 je nyn sputn.%n%nZavete prosm vechny instance tohoto produktu a pak pokraujte klepnutm na tlatko OK, nebo ukonete odinstalaci tlatkem Zruit. + +; *** Startup questions +PrivilegesRequiredOverrideTitle=Vbr reimu prvodce instalac +PrivilegesRequiredOverrideInstruction=Zvolte reim instalace +PrivilegesRequiredOverrideText1=Produkt %1 lze nainstalovat pro vechny uivatele (muste bt pihleni s oprvnnmi sprvce), nebo pouze pro Vs. +PrivilegesRequiredOverrideText2=Produkt %1 lze nainstalovat pouze pro Vs, nebo pro vechny uivatele (muste bt pihleni s oprvnnmi sprvce). +PrivilegesRequiredOverrideAllUsers=Nainstalovat pro &vechny uivatele +PrivilegesRequiredOverrideAllUsersRecommended=Nainstalovat pro &vechny uivatele (doporuuje se) +PrivilegesRequiredOverrideCurrentUser=Nainstalovat pouze pro &m +PrivilegesRequiredOverrideCurrentUserRecommended=Nainstalovat pouze pro &m (doporuuje se) + +; *** Misc. errors +ErrorCreatingDir=Prvodci instalac se nepodailo vytvoit sloku "%1" +ErrorTooManyFilesInDir=Nelze vytvoit soubor ve sloce "%1", protoe tato sloka ji obsahuje pli mnoho soubor + +; *** Setup common messages +ExitSetupTitle=Ukonit prvodce instalac +ExitSetupMessage=Instalace nebyla zcela dokonena. Jestlie nyn prvodce instalac ukonte, produkt nebude nainstalovn.%n%nPrvodce instalac mete znovu spustit kdykoliv jindy a instalaci dokonit.%n%nChcete prvodce instalac ukonit? +AboutSetupMenuItem=&O prvodci instalac... +AboutSetupTitle=O prvodci instalac +AboutSetupMessage=%1 verze %2%n%3%n%n%1 domovsk strnka:%n%4 +AboutSetupNote= +TranslatorNote=Czech translation maintained by Ivo Bauer (bauer@ozm.cz), Lubos Stanek (lubek@users.sourceforge.net), Vitezslav Svejdar (vitezslav.svejdar@cuni.cz) and Jiri Fenz (jirifenz@gmail.com) + +; *** Buttons +ButtonBack=< &Zpt +ButtonNext=&Dal > +ButtonInstall=&Instalovat +ButtonOK=OK +ButtonCancel=Zruit +ButtonYes=&Ano +ButtonYesToAll=Ano &vem +ButtonNo=&Ne +ButtonNoToAll=N&e vem +ButtonFinish=&Dokonit +ButtonBrowse=&Prochzet... +ButtonWizardBrowse=&Prochzet... +ButtonNewFolder=&Vytvoit novou sloku + +; *** "Select Language" dialog messages +SelectLanguageTitle=Vbr jazyka prvodce instalac +SelectLanguageLabel=Zvolte jazyk, kter se m pout bhem instalace. + +; *** Common wizard text +ClickNext=Pokraujte klepnutm na tlatko Dal, nebo ukonete prvodce instalac tlatkem Zruit. +BeveledLabel= +BrowseDialogTitle=Vyhledat sloku +BrowseDialogLabel=Z ne uvedenho seznamu vyberte sloku a klepnte na tlatko OK. +NewFolderName=Nov sloka + +; *** "Welcome" wizard page +WelcomeLabel1=Vt Vs prvodce instalac produktu [name]. +WelcomeLabel2=Produkt [name/ver] bude nainstalovn na V pota.%n%nDve ne budete pokraovat, doporuuje se zavt veker sputn aplikace. + +; *** "Password" wizard page +WizardPassword=Heslo +PasswordLabel1=Tato instalace je chrnna heslem. +PasswordLabel3=Zadejte prosm heslo a pokraujte klepnutm na tlatko Dal. Pi zadvn hesla rozliujte mal a velk psmena. +PasswordEditLabel=&Heslo: +IncorrectPassword=Zadan heslo nen sprvn. Zkuste to prosm znovu. + +; *** "License Agreement" wizard page +WizardLicense=Licenn smlouva +LicenseLabel=Dve ne budete pokraovat, pette si prosm pozorn nsledujc dleit informace. +LicenseLabel3=Pette si prosm nsledujc licenn smlouvu. Aby instalace mohla pokraovat, muste souhlasit s podmnkami tto smlouvy. +LicenseAccepted=&Souhlasm s podmnkami licenn smlouvy +LicenseNotAccepted=&Nesouhlasm s podmnkami licenn smlouvy + +; *** "Information" wizard pages +WizardInfoBefore=Informace +InfoBeforeLabel=Dve ne budete pokraovat, pette si prosm pozorn nsledujc dleit informace. +InfoBeforeClickLabel=Pokraujte v instalaci klepnutm na tlatko Dal. +WizardInfoAfter=Informace +InfoAfterLabel=Dve ne budete pokraovat, pette si prosm pozorn nsledujc dleit informace. +InfoAfterClickLabel=Pokraujte v instalaci klepnutm na tlatko Dal. + +; *** "User Information" wizard page +WizardUserInfo=Informace o uivateli +UserInfoDesc=Zadejte prosm poadovan daje. +UserInfoName=&Uivatelsk jmno: +UserInfoOrg=&Spolenost: +UserInfoSerial=S&riov slo: +UserInfoNameRequired=Muste zadat uivatelsk jmno. + +; *** "Select Destination Location" wizard page +WizardSelectDir=Zvolte clov umstn +SelectDirDesc=Kam m bt produkt [name] nainstalovn? +SelectDirLabel3=Prvodce nainstaluje produkt [name] do nsledujc sloky. +SelectDirBrowseLabel=Pokraujte klepnutm na tlatko Dal. Chcete-li zvolit jinou sloku, klepnte na tlatko Prochzet. +DiskSpaceGBLabel=Instalace vyaduje nejmn [gb] GB volnho msta na disku. +DiskSpaceMBLabel=Instalace vyaduje nejmn [mb] MB volnho msta na disku. +CannotInstallToNetworkDrive=Prvodce instalac neme instalovat do sov jednotky. +CannotInstallToUNCPath=Prvodce instalac neme instalovat do cesty UNC. +InvalidPath=Muste zadat plnou cestu vetn psmene jednotky; napklad:%n%nC:\Aplikace%n%nnebo cestu UNC ve tvaru:%n%n\\server\sdlen sloka +InvalidDrive=Vmi zvolen jednotka nebo cesta UNC neexistuje nebo nen dostupn. Zvolte prosm jin umstn. +DiskSpaceWarningTitle=Nedostatek msta na disku +DiskSpaceWarning=Prvodce instalac vyaduje nejmn %1 KB volnho msta pro instalaci produktu, ale na zvolen jednotce je dostupnch pouze %2 KB.%n%nChcete pesto pokraovat? +DirNameTooLong=Nzev sloky nebo cesta jsou pli dlouh. +InvalidDirName=Nzev sloky nen platn. +BadDirName32=Nzev sloky neme obsahovat dn z nsledujcch znak:%n%n%1 +DirExistsTitle=Sloka existuje +DirExists=Sloka:%n%n%1%n%nji existuje. M se pesto instalovat do tto sloky? +DirDoesntExistTitle=Sloka neexistuje +DirDoesntExist=Sloka:%n%n%1%n%nneexistuje. M bt tato sloka vytvoena? + +; *** "Select Components" wizard page +WizardSelectComponents=Zvolte sousti +SelectComponentsDesc=Jak sousti maj bt nainstalovny? +SelectComponentsLabel2=Zakrtnte sousti, kter maj bt nainstalovny; sousti, kter se nemaj instalovat, ponechte nezakrtnut. Pokraujte klepnutm na tlatko Dal. +FullInstallation=pln instalace +; if possible don't translate 'Compact' as 'Minimal' (I mean 'Minimal' in your language) +CompactInstallation=Kompaktn instalace +CustomInstallation=Voliteln instalace +NoUninstallWarningTitle=Sousti existuj +NoUninstallWarning=Prvodce instalac zjistil, e nsledujc sousti jsou ji na Vaem potai nainstalovny:%n%n%1%n%nNezahrnete-li tyto sousti do vbru, nebudou nyn odinstalovny.%n%nChcete pesto pokraovat? +ComponentSize1=%1 KB +ComponentSize2=%1 MB +ComponentsDiskSpaceGBLabel=Vybran sousti vyaduj nejmn [gb] GB msta na disku. +ComponentsDiskSpaceMBLabel=Vybran sousti vyaduj nejmn [mb] MB msta na disku. + +; *** "Select Additional Tasks" wizard page +WizardSelectTasks=Zvolte dal lohy +SelectTasksDesc=Kter dal lohy maj bt provedeny? +SelectTasksLabel2=Zvolte dal lohy, kter maj bt provedeny v prbhu instalace produktu [name], a pak pokraujte klepnutm na tlatko Dal. + +; *** "Select Start Menu Folder" wizard page +WizardSelectProgramGroup=Vyberte sloku v nabdce Start +SelectStartMenuFolderDesc=Kam m prvodce instalac umstit zstupce aplikace? +SelectStartMenuFolderLabel3=Prvodce instalac vytvo zstupce aplikace v nsledujc sloce nabdky Start. +SelectStartMenuFolderBrowseLabel=Pokraujte klepnutm na tlatko Dal. Chcete-li zvolit jinou sloku, klepnte na tlatko Prochzet. +MustEnterGroupName=Muste zadat nzev sloky. +GroupNameTooLong=Nzev sloky nebo cesta jsou pli dlouh. +InvalidGroupName=Nzev sloky nen platn. +BadGroupName=Nzev sloky neme obsahovat dn z nsledujcch znak:%n%n%1 +NoProgramGroupCheck2=&Nevytvet sloku v nabdce Start + +; *** "Ready to Install" wizard page +WizardReady=Instalace je pipravena +ReadyLabel1=Prvodce instalac je nyn pipraven nainstalovat produkt [name] na V pota. +ReadyLabel2a=Pokraujte v instalaci klepnutm na tlatko Instalovat. Pejete-li si zmnit nkter nastaven instalace, klepnte na tlatko Zpt. +ReadyLabel2b=Pokraujte v instalaci klepnutm na tlatko Instalovat. +ReadyMemoUserInfo=Informace o uivateli: +ReadyMemoDir=Clov umstn: +ReadyMemoType=Typ instalace: +ReadyMemoComponents=Vybran sousti: +ReadyMemoGroup=Sloka v nabdce Start: +ReadyMemoTasks=Dal lohy: + +; *** TDownloadWizardPage wizard page and DownloadTemporaryFile +DownloadingLabel=Stahuj se dal soubory... +ButtonStopDownload=&Zastavit stahovn +StopDownload=Urit chcete stahovn zastavit? +ErrorDownloadAborted=Stahovn perueno +ErrorDownloadFailed=Stahovn selhalo: %1 %2 +ErrorDownloadSizeFailed=Nepodailo se zjistit velikost: %1 %2 +ErrorFileHash1=Nepodailo se urit kontroln souet souboru: %1 +ErrorFileHash2=Neplatn kontroln souet souboru: oekvno %1, nalezeno %2 +ErrorProgress=Neplatn prbh: %1 of %2 +ErrorFileSize=Neplatn velikost souboru: oekvno %1, nalezeno %2 + +; *** "Preparing to Install" wizard page +WizardPreparing=Pprava k instalaci +PreparingDesc=Prvodce instalac pipravuje instalaci produktu [name] na V pota. +PreviousInstallNotCompleted=Instalace/odinstalace pedchozho produktu nebyla zcela dokonena. Aby mohla bt dokonena, muste restartovat V pota.%n%nPo restartovn Vaeho potae spuste znovu prvodce instalac, aby bylo mon dokonit instalaci produktu [name]. +CannotContinue=Prvodce instalac neme pokraovat. Ukonete prosm prvodce instalac klepnutm na tlatko Zruit. +ApplicationsFound=Nsledujc aplikace pistupuj k souborm, kter je teba bhem instalace aktualizovat. Doporuuje se povolit prvodci instalac, aby tyto aplikace automaticky zavel. +ApplicationsFound2=Nsledujc aplikace pistupuj k souborm, kter je teba bhem instalace aktualizovat. Doporuuje se povolit prvodci instalac, aby tyto aplikace automaticky zavel. Po dokonen instalace se prvodce instalac pokus aplikace restartovat. +CloseApplications=&Zavt aplikace automaticky +DontCloseApplications=&Nezavrat aplikace +ErrorCloseApplications=Prvodci instalac se nepodailo automaticky zavt vechny aplikace. Dve ne budete pokraovat, doporuuje se zavt veker aplikace pistupujc k souborm, kter je teba bhem instalace aktualizovat. +PrepareToInstallNeedsRestart=Prvodce instalac mus restartovat V pota. Po restartovn Vaeho potae spuste prvodce instalac znovu, aby bylo mon dokonit instalaci produktu [name].%n%nChcete jej restartovat nyn? + +; *** "Installing" wizard page +WizardInstalling=Instalovn +InstallingLabel=ekejte prosm, dokud prvodce instalac nedokon instalaci produktu [name] na V pota. + +; *** "Setup Completed" wizard page +FinishedHeadingLabel=Dokonuje se instalace produktu [name] +FinishedLabelNoIcons=Prvodce instalac dokonil instalaci produktu [name] na V pota. +FinishedLabel=Prvodce instalac dokonil instalaci produktu [name] na V pota. Produkt lze spustit pomoc nainstalovanch zstupc. +ClickFinish=Ukonete prvodce instalac klepnutm na tlatko Dokonit. +FinishedRestartLabel=K dokonen instalace produktu [name] je nezbytn, aby prvodce instalac restartoval V pota. Chcete jej restartovat nyn? +FinishedRestartMessage=K dokonen instalace produktu [name] je nezbytn, aby prvodce instalac restartoval V pota.%n%nChcete jej restartovat nyn? +ShowReadmeCheck=Ano, chci zobrazit dokument "TIMNE" +YesRadio=&Ano, chci nyn restartovat pota +NoRadio=&Ne, pota restartuji pozdji +; used for example as 'Run MyProg.exe' +RunEntryExec=Spustit %1 +; used for example as 'View Readme.txt' +RunEntryShellExec=Zobrazit %1 + +; *** "Setup Needs the Next Disk" stuff +ChangeDiskTitle=Prvodce instalac vyaduje dal disk +SelectDiskLabel2=Vlote prosm disk %1 a klepnte na tlatko OK.%n%nPokud se soubory na tomto disku nachzej v jin sloce ne v t, kter je zobrazena ne, pak zadejte sprvnou cestu nebo ji zvolte klepnutm na tlatko Prochzet. +PathLabel=&Cesta: +FileNotInDir2=Soubor "%1" nelze najt v "%2". Vlote prosm sprvn disk nebo zvolte jinou sloku. +SelectDirectoryLabel=Specifikujte prosm umstn dalho disku. + +; *** Installation phase messages +SetupAborted=Instalace nebyla zcela dokonena.%n%nOpravte prosm chybu a spuste prvodce instalac znovu. +AbortRetryIgnoreSelectAction=Zvolte akci +AbortRetryIgnoreRetry=&Zopakovat akci +AbortRetryIgnoreIgnore=&Ignorovat chybu a pokraovat +AbortRetryIgnoreCancel=Zruit instalaci + +; *** Installation status messages +StatusClosingApplications=Zavraj se aplikace... +StatusCreateDirs=Vytvej se sloky... +StatusExtractFiles=Extrahuj se soubory... +StatusCreateIcons=Vytvej se zstupci... +StatusCreateIniEntries=Vytvej se zznamy v inicializanch souborech... +StatusCreateRegistryEntries=Vytvej se zznamy v systmovm registru... +StatusRegisterFiles=Registruj se soubory... +StatusSavingUninstall=Ukldaj se informace pro odinstalaci produktu... +StatusRunProgram=Dokonuje se instalace... +StatusRestartingApplications=Restartuj se aplikace... +StatusRollback=Proveden zmny se vracej zpt... + +; *** Misc. errors +ErrorInternal2=Intern chyba: %1 +ErrorFunctionFailedNoCode=Funkce %1 selhala +ErrorFunctionFailed=Funkce %1 selhala; kd %2 +ErrorFunctionFailedWithMessage=Funkce %1 selhala; kd %2.%n%3 +ErrorExecutingProgram=Nelze spustit soubor:%n%1 + +; *** Registry errors +ErrorRegOpenKey=Dolo k chyb pi otevrn kle systmovho registru:%n%1\%2 +ErrorRegCreateKey=Dolo k chyb pi vytven kle systmovho registru:%n%1\%2 +ErrorRegWriteKey=Dolo k chyb pi zpisu do kle systmovho registru:%n%1\%2 + +; *** INI errors +ErrorIniEntry=Dolo k chyb pi vytven zznamu v inicializanm souboru "%1". + +; *** File copying errors +FileAbortRetryIgnoreSkipNotRecommended=&Peskoit tento soubor (nedoporuuje se) +FileAbortRetryIgnoreIgnoreNotRecommended=&Ignorovat chybu a pokraovat (nedoporuuje se) +SourceIsCorrupted=Zdrojov soubor je pokozen +SourceDoesntExist=Zdrojov soubor "%1" neexistuje +ExistingFileReadOnly2=Nelze nahradit existujc soubor, protoe je uren pouze pro ten. +ExistingFileReadOnlyRetry=&Odstranit atribut "pouze pro ten" a zopakovat akci +ExistingFileReadOnlyKeepExisting=&Ponechat existujc soubor +ErrorReadingExistingDest=Dolo k chyb pi pokusu o ten existujcho souboru: +FileExistsSelectAction=Zvolte akci +FileExists2=Soubor ji existuje. +FileExistsOverwriteExisting=&Nahradit existujc soubor +FileExistsKeepExisting=&Ponechat existujc soubor +FileExistsOverwriteOrKeepAll=&Zachovat se stejn u dalch konflikt +ExistingFileNewerSelectAction=Zvolte akci +ExistingFileNewer2=Existujc soubor je novj ne ten, kter se prvodce instalac pokou instalovat. +ExistingFileNewerOverwriteExisting=&Nahradit existujc soubor +ExistingFileNewerKeepExisting=&Ponechat existujc soubor (doporuuje se) +ExistingFileNewerOverwriteOrKeepAll=&Zachovat se stejn u dalch konflikt +ErrorChangingAttr=Dolo k chyb pi pokusu o zmnu atribut existujcho souboru: +ErrorCreatingTemp=Dolo k chyb pi pokusu o vytvoen souboru v clov sloce: +ErrorReadingSource=Dolo k chyb pi pokusu o ten zdrojovho souboru: +ErrorCopying=Dolo k chyb pi pokusu o zkoprovn souboru: +ErrorReplacingExistingFile=Dolo k chyb pi pokusu o nahrazen existujcho souboru: +ErrorRestartReplace=Funkce "RestartReplace" prvodce instalac selhala: +ErrorRenamingTemp=Dolo k chyb pi pokusu o pejmenovn souboru v clov sloce: +ErrorRegisterServer=Nelze zaregistrovat DLL/OCX: %1 +ErrorRegSvr32Failed=Voln RegSvr32 selhalo s nvratovm kdem %1 +ErrorRegisterTypeLib=Nelze zaregistrovat typovou knihovnu: %1 + +; *** Uninstall display name markings +; used for example as 'My Program (32-bit)' +UninstallDisplayNameMark=%1 (%2) +; used for example as 'My Program (32-bit, All users)' +UninstallDisplayNameMarks=%1 (%2, %3) +UninstallDisplayNameMark32Bit=32bitov +UninstallDisplayNameMark64Bit=64bitov +UninstallDisplayNameMarkAllUsers=Vichni uivatel +UninstallDisplayNameMarkCurrentUser=Aktuln uivatel + +; *** Post-installation errors +ErrorOpeningReadme=Dolo k chyb pi pokusu o oteven dokumentu "TIMNE". +ErrorRestartingComputer=Prvodci instalac se nepodailo restartovat V pota. Restartujte jej prosm run. + +; *** Uninstaller messages +UninstallNotFound=Soubor "%1" neexistuje. Produkt nelze odinstalovat. +UninstallOpenError=Soubor "%1" nelze otevt. Produkt nelze odinstalovat. +UninstallUnsupportedVer=Formt souboru se zznamy k odinstalaci produktu "%1" nebyl touto verz prvodce odinstalac rozpoznn. Produkt nelze odinstalovat +UninstallUnknownEntry=V souboru obsahujcm informace k odinstalaci produktu byla zjitna neznm poloka (%1) +ConfirmUninstall=Urit chcete produkt %1 a vechny jeho sousti odinstalovat? +UninstallOnlyOnWin64=Tento produkt lze odinstalovat pouze v 64-bitovch verzch MS Windows. +OnlyAdminCanUninstall=K odinstalaci tohoto produktu muste bt pihleni s oprvnnmi sprvce. +UninstallStatusLabel=ekejte prosm, dokud produkt %1 nebude odinstalovn z Vaeho potae. +UninstalledAll=Produkt %1 byl z Vaeho potae spn odinstalovn. +UninstalledMost=Produkt %1 byl odinstalovn.%n%nNkter jeho sousti se odinstalovat nepodailo. Mete je vak odstranit run. +UninstalledAndNeedsRestart=K dokonen odinstalace produktu %1 je nezbytn, aby prvodce odinstalac restartoval V pota.%n%nChcete jej restartovat nyn? +UninstallDataCorrupted=Soubor "%1" je pokozen. Produkt nelze odinstalovat + +; *** Uninstallation phase messages +ConfirmDeleteSharedFileTitle=Odebrat sdlen soubor? +ConfirmDeleteSharedFile2=Systm indikuje, e nsledujc sdlen soubor nen pouvn dnmi jinmi aplikacemi. M bt tento sdlen soubor prvodcem odinstalac odstrann?%n%nPokud nkter aplikace tento soubor pouvaj, pak po jeho odstrann nemusej pracovat sprvn. Pokud si nejste jisti, zvolte Ne. Ponechn tohoto souboru ve Vaem systmu nezpsob dnou kodu. +SharedFileNameLabel=Nzev souboru: +SharedFileLocationLabel=Umstn: +WizardUninstalling=Stav odinstalace +StatusUninstalling=Probh odinstalace produktu %1... + +; *** Shutdown block reasons +ShutdownBlockReasonInstallingApp=Probh instalace produktu %1. +ShutdownBlockReasonUninstallingApp=Probh odinstalace produktu %1. + +; The custom messages below aren't used by Setup itself, but if you make +; use of them in your scripts, you'll want to translate them. + +[CustomMessages] + +NameAndVersion=%1 verze %2 +AdditionalIcons=Dal zstupci: +CreateDesktopIcon=Vytvoit zstupce na &ploe +CreateQuickLaunchIcon=Vytvoit zstupce na panelu &Snadn sputn +ProgramOnTheWeb=Aplikace %1 na internetu +UninstallProgram=Odinstalovat aplikaci %1 +LaunchProgram=Spustit aplikaci %1 +AssocFileExtension=Vytvoit &asociaci mezi soubory typu %2 a aplikac %1 +AssocingFileExtension=Vytv se asociace mezi soubory typu %2 a aplikac %1... +AutoStartProgramGroupDescription=Po sputn: +AutoStartProgram=Spoutt aplikaci %1 automaticky +AddonHostProgramNotFound=Aplikace %1 nebyla ve Vmi zvolen sloce nalezena.%n%nChcete pesto pokraovat? diff --git a/src/AITool.Setup/INNO/Languages/Danish.isl b/src/AITool.Setup/INNO/Languages/Danish.isl new file mode 100644 index 00000000..3939de26 --- /dev/null +++ b/src/AITool.Setup/INNO/Languages/Danish.isl @@ -0,0 +1,379 @@ +; *** Inno Setup version 6.1.0+ Danish messages *** +; +; To download user-contributed translations of this file, go to: +; https://jrsoftware.org/files/istrans/ +; +; Note: When translating this text, do not add periods (.) to the end of +; messages that didn't have them already, because on those messages Inno +; Setup adds the periods automatically (appending a period would result in +; two periods being displayed). +; +; ID: Danish.isl,v 6.0.3+ 2020/07/26 Thomas Vedel, thomas@veco.dk +; Parts by scootergrisen, 2015 + +[LangOptions] +LanguageName=Dansk +LanguageID=$0406 +LanguageCodePage=1252 + +; If the language you are translating to requires special font faces or +; sizes, uncomment any of the following entries and change them accordingly. +;DialogFontName= +;DialogFontSize=8 +;WelcomeFontName=Verdana +;WelcomeFontSize=12 +;TitleFontName=Arial +;TitleFontSize=29 +;CopyrightFontName=Arial +;CopyrightFontSize=8 + +[Messages] +; *** Application titles +SetupAppTitle=Installationsguide +SetupWindowTitle=Installationsguide - %1 +UninstallAppTitle=Afinstallr +UninstallAppFullTitle=Afinstallerer %1 + +; *** Misc. common +InformationTitle=Information +ConfirmTitle=Bekrft +ErrorTitle=Fejl + +; *** SetupLdr messages +SetupLdrStartupMessage=Denne guide installerer %1. Vil du fortstte? +LdrCannotCreateTemp=Kan ikke oprette en midlertidig fil. Installationen afbrydes +LdrCannotExecTemp=Kan ikke kre et program i den midlertidige mappe. Installationen afbrydes +HelpTextNote= + +; *** Startup error messages +LastErrorMessage=%1.%n%nFejl %2: %3 +SetupFileMissing=Filen %1 mangler i installationsmappen. Ret venligst problemet eller f en ny kopi af programmet. +SetupFileCorrupt=Installationsfilerne er beskadiget. F venligst en ny kopi af installationsprogrammet. +SetupFileCorruptOrWrongVer=Installationsfilerne er beskadiget, eller ogs er de ikke kompatible med denne version af installationsprogrammet. Ret venligst problemet eller f en ny kopi af installationsprogrammet. +InvalidParameter=En ugyldig parameter blev angivet p kommandolinjen:%n%n%1 +SetupAlreadyRunning=Installationsprogrammet krer allerede. +WindowsVersionNotSupported=Programmet understtter ikke den version af Windows, som denne computer krer. +WindowsServicePackRequired=Programmet krver %1 med Service Pack %2 eller senere. +NotOnThisPlatform=Programmet kan ikke anvendes p %1. +OnlyOnThisPlatform=Programmet kan kun anvendes p %1. +OnlyOnTheseArchitectures=Programmet kan kun installeres p versioner af Windows der anvender disse processor-arkitekturer:%n%n%1 +WinVersionTooLowError=Programmet krver %1 version %2 eller senere. +WinVersionTooHighError=Programmet kan ikke installeres p %1 version %2 eller senere. +AdminPrivilegesRequired=Du skal vre logget p som administrator imens programmet installeres. +PowerUserPrivilegesRequired=Du skal vre logget p som administrator eller vre medlem af gruppen Superbrugere imens programmet installeres. +SetupAppRunningError=Installationsprogrammet har registreret at %1 krer.%n%nLuk venligst alle forekomster af programmet, og klik s OK for at fortstte, eller Annuller for at afbryde. +UninstallAppRunningError=Afinstallationsprogrammet har registreret at %1 krer.%n%nLuk venligst alle forekomster af programmet, og klik s OK for at fortstte, eller Annuller for at afbryde. + +; *** Startup questions +PrivilegesRequiredOverrideTitle=Vlg guidens installationsmde +PrivilegesRequiredOverrideInstruction=Vlg installationsmde +PrivilegesRequiredOverrideText1=%1 kan installeres for alle brugere (krver administrator-rettigheder), eller for dig alene. +PrivilegesRequiredOverrideText2=%1 kan installeres for dig alene, eller for alle brugere p computeren (sidstnvnte krver administrator-rettigheder). +PrivilegesRequiredOverrideAllUsers=Installer for &alle brugere +PrivilegesRequiredOverrideAllUsersRecommended=Installer for &alle brugere (anbefales) +PrivilegesRequiredOverrideCurrentUser=Installer for &mig alene +PrivilegesRequiredOverrideCurrentUserRecommended=Installer for &mig alene (anbefales) + +; *** Misc. errors +ErrorCreatingDir=Installationsprogrammet kan ikke oprette mappen "%1" +ErrorTooManyFilesInDir=Kan ikke oprette en fil i mappen "%1". Mappen indeholder for mange filer + +; *** Setup common messages +ExitSetupTitle=Afbryd installationen +ExitSetupMessage=Installationen er ikke fuldfrt. Programmet installeres ikke, hvis du afbryder nu.%n%nDu kan kre installationsprogrammet igen p et andet tidspunkt for at udfre installationen.%n%nSkal installationen afbrydes? +AboutSetupMenuItem=&Om installationsprogrammet... +AboutSetupTitle=Om installationsprogrammet +AboutSetupMessage=%1 version %2%n%3%n%n%1 hjemmeside:%n%4 +AboutSetupNote= +TranslatorNote=Danish translation maintained by Thomas Vedel (thomas@veco.dk). Parts by scootergrisen. + +; *** Buttons +ButtonBack=< &Tilbage +ButtonNext=N&ste > +ButtonInstall=&Installer +ButtonOK=&OK +ButtonCancel=&Annuller +ButtonYes=&Ja +ButtonYesToAll=Ja til a&lle +ButtonNo=&Nej +ButtonNoToAll=Nej t&il alle +ButtonFinish=&Frdig +ButtonBrowse=&Gennemse... +ButtonWizardBrowse=G&ennemse... +ButtonNewFolder=&Opret ny mappe + +; *** "Select Language" dialog messages +SelectLanguageTitle=Vlg installationssprog +SelectLanguageLabel=Vlg det sprog der skal vises under installationen. + +; *** Common wizard text +ClickNext=Klik p Nste for at fortstte, eller Annuller for at afbryde installationen. +BeveledLabel= +BrowseDialogTitle=Vlg mappe +BrowseDialogLabel=Vlg en mappe fra nedenstende liste og klik p OK. +NewFolderName=Ny mappe + +; *** "Welcome" wizard page +WelcomeLabel1=Velkommen til installationsguiden for [name] +WelcomeLabel2=Guiden installerer [name/ver] p computeren.%n%nDet anbefales at lukke alle andre programmer inden du fortstter. + +; *** "Password" wizard page +WizardPassword=Adgangskode +PasswordLabel1=Installationen er beskyttet med adgangskode. +PasswordLabel3=Indtast venligst adgangskoden og klik p Nste for at fortstte. Der skelnes mellem store og sm bogstaver. +PasswordEditLabel=&Adgangskode: +IncorrectPassword=Den indtastede kode er forkert. Prv venligst igen. + +; *** "License Agreement" wizard page +WizardLicense=Licensaftale +LicenseLabel=Ls venligst flgende vigtige oplysninger inden du fortstter. +LicenseLabel3=Ls venligst licensaftalen. Du skal acceptere betingelserne i aftalen for at fortstte installationen. +LicenseAccepted=Jeg &accepterer aftalen +LicenseNotAccepted=Jeg accepterer &ikke aftalen + +; *** "Information" wizard pages +WizardInfoBefore=Information +InfoBeforeLabel=Ls venligst flgende information inden du fortstter. +InfoBeforeClickLabel=Klik p Nste, nr du er klar til at fortstte installationen. +WizardInfoAfter=Information +InfoAfterLabel=Ls venligst flgende information inden du fortstter. +InfoAfterClickLabel=Klik p Nste, nr du er klar til at fortstte installationen. + +; *** "User Information" wizard page +WizardUserInfo=Brugerinformation +UserInfoDesc=Indtast venligst dine oplysninger. +UserInfoName=&Brugernavn: +UserInfoOrg=&Organisation: +UserInfoSerial=&Serienummer: +UserInfoNameRequired=Du skal indtaste et navn. + +; *** "Select Destination Directory" wizard page +WizardSelectDir=Vlg installationsmappe +SelectDirDesc=Hvor skal [name] installeres? +SelectDirLabel3=Installationsprogrammet installerer [name] i flgende mappe. +SelectDirBrowseLabel=Klik p Nste for at fortstte. Klik p Gennemse, hvis du vil vlge en anden mappe. +DiskSpaceGBLabel=Der skal vre mindst [gb] GB fri diskplads. +DiskSpaceMBLabel=Der skal vre mindst [mb] MB fri diskplads. +CannotInstallToNetworkDrive=Guiden kan ikke installere programmet p et netvrksdrev. +CannotInstallToUNCPath=Guiden kan ikke installere programmet til en UNC-sti. +InvalidPath=Du skal indtaste en komplet sti med drevbogstav, f.eks.:%n%nC:\Program%n%neller et UNC-stinavn i formatet:%n%n\\server\share +InvalidDrive=Drevet eller UNC-stien du valgte findes ikke, eller der er ikke adgang til det lige nu. Vlg venligst en anden placering. +DiskSpaceWarningTitle=Ikke nok ledig diskplads. +DiskSpaceWarning=Guiden krver mindst %1 KB ledig diskplads for at kunne installere programmet, men det valgte drev har kun %2 KB ledig diskplads.%n%nVil du alligevel fortstte installationen? +DirNameTooLong=Navnet p mappen eller stien er for langt. +InvalidDirName=Navnet p mappen er ikke tilladt. +BadDirName32=Mappenavne m ikke indeholde flgende tegn:%n%n%1 +DirExistsTitle=Mappen findes +DirExists=Mappen:%n%n%1%n%nfindes allerede. Vil du alligevel installere i denne mappe? +DirDoesntExistTitle=Mappen findes ikke. +DirDoesntExist=Mappen:%n%n%1%n%nfindes ikke. Vil du oprette mappen? + +; *** "Select Components" wizard page +WizardSelectComponents=Vlg Komponenter +SelectComponentsDesc=Hvilke komponenter skal installeres? +SelectComponentsLabel2=Vlg de komponenter der skal installeres, og fjern markering fra dem der ikke skal installeres. Klik s p Nste for at fortstte. +FullInstallation=Fuld installation +; if possible don't translate 'Compact' as 'Minimal' (I mean 'Minimal' in your language) +CompactInstallation=Kompakt installation +CustomInstallation=Tilpasset installation +NoUninstallWarningTitle=Komponenterne er installeret +NoUninstallWarning=Installationsprogrammet har registreret at flgende komponenter allerede er installeret p computeren:%n%n%1%n%nKomponenterne bliver ikke afinstalleret hvis de fravlges.%n%nFortst alligevel? +ComponentSize1=%1 KB +ComponentSize2=%1 MB +ComponentsDiskSpaceGBLabel=De nuvrende valg krver mindst [gb] GB ledig diskplads. +ComponentsDiskSpaceMBLabel=De nuvrende valg krver mindst [mb] MB ledig diskplads. + +; *** "Select Additional Tasks" wizard page +WizardSelectTasks=Vlg supplerende opgaver +SelectTasksDesc=Hvilke supplerende opgaver skal udfres? +SelectTasksLabel2=Vlg de supplerende opgaver du vil have guiden til at udfre under installationen af [name] og klik p Nste. + +; *** "Select Start Menu Folder" wizard page +WizardSelectProgramGroup=Vlg mappe i menuen Start +SelectStartMenuFolderDesc=Hvor skal installationsprogrammet oprette genveje til programmet? +SelectStartMenuFolderLabel3=Installationsprogrammet opretter genveje til programmet i flgende mappe i menuen Start. +SelectStartMenuFolderBrowseLabel=Klik p Nste for at fortstte. Klik p Gennemse, hvis du vil vlge en anden mappe. +MustEnterGroupName=Du skal indtaste et mappenavn. +GroupNameTooLong=Mappens eller stiens navn er for langt. +InvalidGroupName=Mappenavnet er ugyldigt. +BadGroupName=Navnet p en programgruppe m ikke indeholde flgende tegn: %1. Angiv andet navn. +NoProgramGroupCheck2=Opret &ingen programgruppe i menuen Start + +; *** "Ready to Install" wizard page +WizardReady=Klar til at installere +ReadyLabel1=Installationsprogrammet er nu klar til at installere [name] p computeren. +ReadyLabel2a=Klik p Installer for at fortstte med installationen, eller klik p Tilbage hvis du vil se eller ndre indstillingerne. +ReadyLabel2b=Klik p Installer for at fortstte med installationen. +ReadyMemoUserInfo=Brugerinformation: +ReadyMemoDir=Installationsmappe: +ReadyMemoType=Installationstype: +ReadyMemoComponents=Valgte komponenter: +ReadyMemoGroup=Mappe i menuen Start: +ReadyMemoTasks=Valgte supplerende opgaver: + +; *** TDownloadWizardPage wizard page and DownloadTemporaryFile +DownloadingLabel=Downloader yderligere filer... +ButtonStopDownload=&Stop download +StopDownload=Er du sikker p at du nsker at afbryde download? +ErrorDownloadAborted=Download afbrudt +ErrorDownloadFailed=Fejl under download: %1 %2 +ErrorDownloadSizeFailed=Fejl ved lsning af filstrrelse: %1 %2 +ErrorFileHash1=Fejl i hash: %1 +ErrorFileHash2=Fejl i fil hash vrdi: forventet %1, fundet %2 +ErrorProgress=Fejl i trin: %1 af %2 +ErrorFileSize=Fejl i filstrrelse: forventet %1, fundet %2 + +; *** "Preparing to Install" wizard page +WizardPreparing=Klargring af installationen +PreparingDesc=Installationsprogrammet gr klar til at installere [name] p din computer. +PreviousInstallNotCompleted=Installation eller afinstallation af et program er ikke afsluttet. Du skal genstarte computeren for at afslutte den foregende installation.%n%nNr computeren er genstartet skal du kre installationsprogrammet til [name] igen. +CannotContinue=Installationsprogrammet kan ikke fortstte. Klik venligst p Fortryd for at afslutte. +ApplicationsFound=Flgende programmer bruger filer som skal opdateres. Det anbefales at du giver installationsprogrammet tilladelse til automatisk at lukke programmerne. +ApplicationsFound2=Flgende programmer bruger filer som skal opdateres. Det anbefales at du giver installationsprogrammet tilladelse til automatisk at lukke programmerne. Installationsguiden vil forsge at genstarte programmerne nr installationen er fuldfrt. +CloseApplications=&Luk programmerne automatisk +DontCloseApplications=Luk &ikke programmerne +ErrorCloseApplications=Installationsprogrammet kunne ikke lukke alle programmerne automatisk. Det anbefales at du lukker alle programmer som bruger filer der skal opdateres, inden installationsprogrammet fortstter. +PrepareToInstallNeedsRestart=Installationsprogrammet er ndt til at genstarte computeren. Efter genstarten skal du kre installationsprogrammet igen for at frdiggre installation af [name].%n%nVil du at genstarte nu? + +; *** "Installing" wizard page +WizardInstalling=Installerer +InstallingLabel=Vent venligst mens installationsprogrammet installerer [name] p computeren. + +; *** "Setup Completed" wizard page +FinishedHeadingLabel=Fuldfrer installation af [name] +FinishedLabelNoIcons=Installationsguiden har fuldfrt installation af [name] p computeren. +FinishedLabel=Installationsguiden har fuldfrt installation af [name] p computeren. Programmet kan startes ved at vlge de oprettede ikoner. +ClickFinish=Klik p Frdig for at afslutte installationsprogrammet. +FinishedRestartLabel=Computeren skal genstartes for at fuldfre installation af [name]. Vil du genstarte computeren nu? +FinishedRestartMessage=Computeren skal genstartes for at fuldfre installation af [name].%n%nVil du genstarte computeren nu? +ShowReadmeCheck=Ja, jeg vil gerne se README-filen +YesRadio=&Ja, genstart computeren nu +NoRadio=&Nej, jeg genstarter computeren senere +; used for example as 'Run MyProg.exe' +RunEntryExec=Kr %1 +; used for example as 'View Readme.txt' +RunEntryShellExec=Vis %1 + +; *** "Setup Needs the Next Disk" stuff +ChangeDiskTitle=Installationsprogrammet skal bruge den nste disk +SelectDiskLabel2=Indst disk %1 og klik p OK.%n%nHvis filerne findes i en anden mappe end den viste, s indtast stien eller klik Gennemse. +PathLabel=&Sti: +FileNotInDir2=Filen "%1" blev ikke fundet i "%2". Indst venligst den korrekte disk, eller vlg en anden mappe. +SelectDirectoryLabel=Angiv venligst placeringen af den nste disk. + +; *** Installation phase messages +SetupAborted=Installationen blev ikke fuldfrt.%n%nRet venligst de fundne problemer og kr installationsprogrammet igen. +AbortRetryIgnoreSelectAction=Vlg nsket handling +AbortRetryIgnoreRetry=&Forsg igen +AbortRetryIgnoreIgnore=&Ignorer fejlen og fortst +AbortRetryIgnoreCancel=Afbryd installationen + +; *** Installation status messages +StatusClosingApplications=Lukker programmer... +StatusCreateDirs=Opretter mapper... +StatusExtractFiles=Udpakker filer... +StatusCreateIcons=Opretter genveje... +StatusCreateIniEntries=Opretter poster i INI-filer... +StatusCreateRegistryEntries=Opretter poster i registreringsdatabasen... +StatusRegisterFiles=Registrerer filer... +StatusSavingUninstall=Gemmer information om afinstallation... +StatusRunProgram=Fuldfrer installation... +StatusRestartingApplications=Genstarter programmer... +StatusRollback=Fjerner ndringer... + +; *** Misc. errors +ErrorInternal2=Intern fejl: %1 +ErrorFunctionFailedNoCode=%1 fejlede +ErrorFunctionFailed=%1 fejlede; kode %2 +ErrorFunctionFailedWithMessage=%1 fejlede; kode %2.%n%3 +ErrorExecutingProgram=Kan ikke kre programfilen:%n%1 + +; *** Registry errors +ErrorRegOpenKey=Fejl ved bning af ngle i registreringsdatabase:%n%1\%2 +ErrorRegCreateKey=Fejl ved oprettelse af ngle i registreringsdatabase:%n%1\%2 +ErrorRegWriteKey=Fejl ved skrivning til ngle i registreringsdatabase:%n%1\%2 + +; *** INI errors +ErrorIniEntry=Fejl ved oprettelse af post i INI-filen "%1". + +; *** File copying errors +FileAbortRetryIgnoreSkipNotRecommended=&Spring over denne fil (anbefales ikke) +FileAbortRetryIgnoreIgnoreNotRecommended=&Ignorer fejlen og fortst (anbefales ikke) +SourceIsCorrupted=Kildefilen er beskadiget +SourceDoesntExist=Kildefilen "%1" findes ikke +ExistingFileReadOnly2=Den eksisterende fil er skrivebeskyttet og kan derfor ikke overskrives. +ExistingFileReadOnlyRetry=&Fjern skrivebeskyttelsen og forsg igen +ExistingFileReadOnlyKeepExisting=&Behold den eksisterende fil +ErrorReadingExistingDest=Der opstod en fejl ved lsning af den eksisterende fil: +FileExistsSelectAction=Vlg handling +FileExists2=Filen findes allerede. +FileExistsOverwriteExisting=&Overskriv den eksisterende fil +FileExistsKeepExisting=&Behold den eksiterende fil +FileExistsOverwriteOrKeepAll=&Gentag handlingen for de nste konflikter +ExistingFileNewerSelectAction=Vlg handling +ExistingFileNewer2=Den eksisterende fil er nyere end den som forsges installeret. +ExistingFileNewerOverwriteExisting=&Overskriv den eksisterende fil +ExistingFileNewerKeepExisting=&Behold den eksisterende fil (anbefales) +ExistingFileNewerOverwriteOrKeepAll=&Gentag handlingen for de nste konflikter +ErrorChangingAttr=Der opstod en fejl ved ndring af attributter for den eksisterende fil: +ErrorCreatingTemp=Der opstod en fejl ved oprettelse af en fil i mappen: +ErrorReadingSource=Der opstod en fejl ved lsning af kildefilen: +ErrorCopying=Der opstod en fejl ved kopiering af en fil: +ErrorReplacingExistingFile=Der opstod en fejl ved forsg p at erstatte den eksisterende fil: +ErrorRestartReplace=Erstatning af fil ved genstart mislykkedes: +ErrorRenamingTemp=Der opstod en fejl ved forsg p at omdbe en fil i installationsmappen: +ErrorRegisterServer=Kan ikke registrere DLL/OCX: %1 +ErrorRegSvr32Failed=RegSvr32 fejlede med exit kode %1 +ErrorRegisterTypeLib=Kan ikke registrere typebiblioteket: %1 + +; *** Uninstall display name markings +UninstallDisplayNameMark=%1 (%2) +UninstallDisplayNameMarks=%1 (%2, %3) +UninstallDisplayNameMark32Bit=32-bit +UninstallDisplayNameMark64Bit=64-bit +UninstallDisplayNameMarkAllUsers=Alle brugere +UninstallDisplayNameMarkCurrentUser=Nuvrende bruger + +; *** Post-installation errors +ErrorOpeningReadme=Der opstod en fejl ved forsg p at bne README-filen. +ErrorRestartingComputer=Installationsprogrammet kunne ikke genstarte computeren. Genstart venligst computeren manuelt. + +; *** Uninstaller messages +UninstallNotFound=Filen "%1" findes ikke. Kan ikke afinstalleres. +UninstallOpenError=Filen "%1" kunne ikke bnes. Kan ikke afinstalleres +UninstallUnsupportedVer=Afinstallations-logfilen "%1" er i et format der ikke genkendes af denne version af afinstallations-guiden. Afinstallationen afbrydes +UninstallUnknownEntry=Der er en ukendt post (%1) i afinstallerings-logfilen. +ConfirmUninstall=Er du sikker p at du vil fjerne %1 og alle tilhrende komponenter? +UninstallOnlyOnWin64=Denne installation kan kun afinstalleres p 64-bit Windows-versioner +OnlyAdminCanUninstall=Programmet kan kun afinstalleres af en bruger med administratorrettigheder. +UninstallStatusLabel=Vent venligst imens %1 afinstalleres fra computeren. +UninstalledAll=%1 er nu fjernet fra computeren. +UninstalledMost=%1 afinstallation er fuldfrt.%n%nNogle elementer kunne ikke fjernes. De kan fjernes manuelt. +UninstalledAndNeedsRestart=Computeren skal genstartes for at fuldfre afinstallation af %1.%n%nVil du genstarte nu? +UninstallDataCorrupted=Filen "%1" er beskadiget. Kan ikke afinstallere + +; *** Uninstallation phase messages +ConfirmDeleteSharedFileTitle=Fjern delt fil? +ConfirmDeleteSharedFile2=Systemet indikerer at flgende delte fil ikke lngere er i brug. Skal den/de delte fil(er) fjernes af guiden?%n%nHvis du er usikker s vlg Nej. Beholdes filen p maskinen, vil den ikke gre nogen skade, men hvis filen fjernes, selv om den stadig anvendes, bliver de programmer, der anvender filen, ustabile +SharedFileNameLabel=Filnavn: +SharedFileLocationLabel=Placering: +WizardUninstalling=Status for afinstallation +StatusUninstalling=Afinstallerer %1... + +; *** Shutdown block reasons +ShutdownBlockReasonInstallingApp=Installerer %1. +ShutdownBlockReasonUninstallingApp=Afinstallerer %1. + +[CustomMessages] +NameAndVersion=%1 version %2 +AdditionalIcons=Supplerende ikoner: +CreateDesktopIcon=Opret ikon p skrive&bordet +CreateQuickLaunchIcon=Opret &hurtigstart-ikon +ProgramOnTheWeb=%1 p internettet +UninstallProgram=Afinstaller (fjern) %1 +LaunchProgram=&Start %1 +AssocFileExtension=Sammen&kd %1 med filtypen %2 +AssocingFileExtension=Sammenkder %1 med filtypen %2... +AutoStartProgramGroupDescription=Start: +AutoStartProgram=Start automatisk %1 +AddonHostProgramNotFound=%1 blev ikke fundet i den valgte mappe.%n%nVil du alligevel fortstte? diff --git a/src/AITool.Setup/INNO/Languages/Dutch.isl b/src/AITool.Setup/INNO/Languages/Dutch.isl new file mode 100644 index 00000000..761528b2 --- /dev/null +++ b/src/AITool.Setup/INNO/Languages/Dutch.isl @@ -0,0 +1,359 @@ +; *** Inno Setup version 6.1.0+ Dutch messages *** +; +; This file is based on user-contributed translations by various authors +; +; Maintained by Martijn Laan (mlaan@jrsoftware.org) + +[LangOptions] +LanguageName=Nederlands +LanguageID=$0413 +LanguageCodePage=1252 + +[Messages] + +; *** Application titles +SetupAppTitle=Setup +SetupWindowTitle=Setup - %1 +UninstallAppTitle=Verwijderen +UninstallAppFullTitle=%1 verwijderen + +; *** Misc. common +InformationTitle=Informatie +ConfirmTitle=Bevestigen +ErrorTitle=Fout + +; *** SetupLdr messages +SetupLdrStartupMessage=Hiermee wordt %1 geïnstalleerd. Wilt u doorgaan? +LdrCannotCreateTemp=Kan geen tijdelijk bestand maken. Setup wordt afgesloten +LdrCannotExecTemp=Kan een bestand in de tijdelijke map niet uitvoeren. Setup wordt afgesloten + +; *** Startup error messages +LastErrorMessage=%1.%n%nFout %2: %3 +SetupFileMissing=Het bestand %1 ontbreekt in de installatiemap. Corrigeer dit probleem of gebruik een andere kopie van het programma. +SetupFileCorrupt=De installatiebestanden zijn beschadigd. Gebruik een andere kopie van het programma. +SetupFileCorruptOrWrongVer=De installatiebestanden zijn beschadigd, of zijn niet compatibel met deze versie van Setup. Corrigeer dit probleem of gebruik een andere kopie van het programma. +InvalidParameter=Er werd een ongeldige schakeloptie opgegeven op de opdrachtregel:%n%n%1 +SetupAlreadyRunning=Setup is al gestart. +WindowsVersionNotSupported=Dit programma ondersteunt de versie van Windows die u gebruikt niet. +WindowsServicePackRequired=Dit programma vereist %1 Service Pack %2 of hoger. +NotOnThisPlatform=Dit programma kan niet worden uitgevoerd onder %1. +OnlyOnThisPlatform=Dit programma moet worden uitgevoerd onder %1. +OnlyOnTheseArchitectures=Dit programma kan alleen geïnstalleerd worden onder versies van Windows ontworpen voor de volgende processor architecturen:%n%n%1 +WinVersionTooLowError=Dit programma vereist %1 versie %2 of hoger. +WinVersionTooHighError=Dit programma kan niet worden geïnstalleerd onder %1 versie %2 of hoger. +AdminPrivilegesRequired=U moet aangemeld zijn als een systeembeheerder om dit programma te kunnen installeren. +PowerUserPrivilegesRequired=U moet ingelogd zijn als systeembeheerder of als gebruiker met systeembeheerders rechten om dit programma te kunnen installeren. +SetupAppRunningError=Setup heeft vastgesteld dat %1 op dit moment actief is.%n%nSluit alle vensters hiervan, en klik daarna op OK om verder te gaan, of op Annuleren om Setup af te sluiten. +UninstallAppRunningError=Het verwijderprogramma heeft vastgesteld dat %1 op dit moment actief is.%n%nSluit alle vensters hiervan, en klik daarna op OK om verder te gaan, of op Annuleren om het verwijderen af te breken. + +; *** Startup questions +PrivilegesRequiredOverrideTitle=Selecteer installatie modus voor Setup +PrivilegesRequiredOverrideInstruction=Selecteer installatie modus +PrivilegesRequiredOverrideText1=%1 kan geïnstalleerd worden voor alle gebruikers (vereist aanmelding als een systeembeheerder), of voor u alleen. +PrivilegesRequiredOverrideText2=%1 kan geïnstalleerd worden voor u alleen, of voor alle gebruikers (vereist aanmelding als een systeembeheerder). +PrivilegesRequiredOverrideAllUsers=Installeer voor &alle gebruikers +PrivilegesRequiredOverrideAllUsersRecommended=Installeer voor &alle gebruikers (aanbevolen) +PrivilegesRequiredOverrideCurrentUser=Installeer voor &mij alleen +PrivilegesRequiredOverrideCurrentUserRecommended=Installeer voor &mij alleen (aanbevolen) + +; *** Misc. errors +ErrorCreatingDir=Setup kan de map "%1" niet maken +ErrorTooManyFilesInDir=Kan geen bestand maken in de map "%1" omdat deze te veel bestanden bevat + +; *** Setup common messages +ExitSetupTitle=Setup afsluiten +ExitSetupMessage=Setup is niet voltooid. Als u nu afsluit, wordt het programma niet geïnstalleerd.%n%nU kunt Setup later opnieuw uitvoeren om de installatie te voltooien.%n%nSetup afsluiten? +AboutSetupMenuItem=&Over Setup... +AboutSetupTitle=Over Setup +AboutSetupMessage=%1 versie %2%n%3%n%n%1-homepage:%n%4 +AboutSetupNote= +TranslatorNote=Dutch translation maintained by Martijn Laan (mlaan@jrsoftware.org) + +; *** Buttons +ButtonBack=< Vo&rige +ButtonNext=&Volgende > +ButtonInstall=&Installeren +ButtonOK=OK +ButtonCancel=Annuleren +ButtonYes=&Ja +ButtonYesToAll=Ja op &alles +ButtonNo=&Nee +ButtonNoToAll=N&ee op alles +ButtonFinish=&Voltooien +ButtonBrowse=&Bladeren... +ButtonWizardBrowse=B&laderen... +ButtonNewFolder=&Nieuwe map maken + +; *** "Select Language" dialog messages +SelectLanguageTitle=Selecteer taal voor Setup +SelectLanguageLabel=Selecteer de taal die Setup gebruikt tijdens de installatie. + +; *** Common wizard text +ClickNext=Klik op Volgende om verder te gaan of op Annuleren om Setup af te sluiten. +BeveledLabel= +BrowseDialogTitle=Map Selecteren +BrowseDialogLabel=Selecteer een map in onderstaande lijst en klik daarna op OK. +NewFolderName=Nieuwe map + +; *** "Welcome" wizard page +WelcomeLabel1=Welkom bij het installatieprogramma van [name]. +WelcomeLabel2=Hiermee wordt [name/ver] geïnstalleerd op deze computer.%n%nU wordt aanbevolen alle actieve programma's af te sluiten voordat u verder gaat. + +; *** "Password" wizard page +WizardPassword=Wachtwoord +PasswordLabel1=Deze installatie is beveiligd met een wachtwoord. +PasswordLabel3=Voer het wachtwoord in en klik op Volgende om verder te gaan. Wachtwoorden zijn hoofdlettergevoelig. +PasswordEditLabel=&Wachtwoord: +IncorrectPassword=Het ingevoerde wachtwoord is niet correct. Probeer het opnieuw. + +; *** "License Agreement" wizard page +WizardLicense=Licentieovereenkomst +LicenseLabel=Lees de volgende belangrijke informatie voordat u verder gaat. +LicenseLabel3=Lees de volgende licentieovereenkomst. Gebruik de schuifbalk of druk op de knop Page Down om de rest van de overeenkomst te zien. +LicenseAccepted=Ik &accepteer de licentieovereenkomst +LicenseNotAccepted=Ik accepteer de licentieovereenkomst &niet + +; *** "Information" wizard pages +WizardInfoBefore=Informatie +InfoBeforeLabel=Lees de volgende belangrijke informatie voordat u verder gaat. +InfoBeforeClickLabel=Klik op Volgende als u gereed bent om verder te gaan met Setup. +WizardInfoAfter=Informatie +InfoAfterLabel=Lees de volgende belangrijke informatie voordat u verder gaat. +InfoAfterClickLabel=Klik op Volgende als u gereed bent om verder te gaan met Setup. + +; *** "User Information" wizard page +WizardUserInfo=Gebruikersinformatie +UserInfoDesc=Vul hier uw informatie in. +UserInfoName=&Gebruikersnaam: +UserInfoOrg=&Organisatie: +UserInfoSerial=&Serienummer: +UserInfoNameRequired=U moet een naam invullen. + +; *** "Select Destination Location" wizard page +WizardSelectDir=Kies de doelmap +SelectDirDesc=Waar moet [name] geïnstalleerd worden? +SelectDirLabel3=Setup zal [name] in de volgende map installeren. +SelectDirBrowseLabel=Klik op Volgende om door te gaan. Klik op Bladeren om een andere map te kiezen. +DiskSpaceGBLabel=Er is ten minste [gb] GB vrije schijfruimte vereist. +DiskSpaceMBLabel=Er is ten minste [mb] MB vrije schijfruimte vereist. +CannotInstallToNetworkDrive=Setup kan niet installeren naar een netwerkstation. +CannotInstallToUNCPath=Setup kan niet installeren naar een UNC-pad. +InvalidPath=U moet een volledig pad met stationsletter invoeren; bijvoorbeeld:%nC:\APP%n%nof een UNC-pad zoals:%n%n\\server\share +InvalidDrive=Het geselecteerde station bestaat niet. Kies een ander station. +DiskSpaceWarningTitle=Onvoldoende schijfruimte +DiskSpaceWarning=Setup vereist ten minste %1 kB vrije schijfruimte voor het installeren, maar het geselecteerde station heeft slechts %2 kB beschikbaar.%n%nWilt u toch doorgaan? +DirNameTooLong=De mapnaam of het pad is te lang. +InvalidDirName=De mapnaam is ongeldig. +BadDirName32=Mapnamen mogen geen van de volgende tekens bevatten:%n%n%1 +DirExistsTitle=Map bestaat al +DirExists=De map:%n%n%1%n%nbestaat al. Wilt u toch naar die map installeren? +DirDoesntExistTitle=Map bestaat niet +DirDoesntExist=De map:%n%n%1%n%nbestaat niet. Wilt u de map aanmaken? + +; *** "Select Components" wizard page +WizardSelectComponents=Selecteer componenten +SelectComponentsDesc=Welke componenten moeten geïnstalleerd worden? +SelectComponentsLabel2=Selecteer de componenten die u wilt installeren. Klik op Volgende als u klaar bent om verder te gaan. +FullInstallation=Volledige installatie +CompactInstallation=Compacte installatie +CustomInstallation=Aangepaste installatie +NoUninstallWarningTitle=Component bestaat +NoUninstallWarning=Setup heeft gedetecteerd dat de volgende componenten al geïnstalleerd zijn op uw computer:%n%n%1%n%nAls u de selectie van deze componenten ongedaan maakt, worden ze niet verwijderd.%n%nWilt u toch doorgaan? +ComponentSize1=%1 KB +ComponentSize2=%1 MB +ComponentsDiskSpaceGBLabel=De huidige selectie vereist ten minste [gb] GB vrije schijfruimte. +ComponentsDiskSpaceMBLabel=De huidige selectie vereist ten minste [mb] MB vrije schijfruimte. + +; *** "Select Additional Tasks" wizard page +WizardSelectTasks=Selecteer extra taken +SelectTasksDesc=Welke extra taken moeten uitgevoerd worden? +SelectTasksLabel2=Selecteer de extra taken die u door Setup wilt laten uitvoeren bij het installeren van [name], en klik vervolgens op Volgende. + +; *** "Select Start Menu Folder" wizard page +WizardSelectProgramGroup=Selecteer menu Start map +SelectStartMenuFolderDesc=Waar moeten de snelkoppelingen van het programma geplaatst worden? +SelectStartMenuFolderLabel3=Setup plaatst de snelkoppelingen van het programma in de volgende menu Start map. +SelectStartMenuFolderBrowseLabel=Klik op Volgende om door te gaan. Klik op Bladeren om een andere map te kiezen. +MustEnterGroupName=U moet een mapnaam invoeren. +GroupNameTooLong=De mapnaam of het pad is te lang. +InvalidGroupName=De mapnaam is ongeldig. +BadGroupName=De mapnaam mag geen van de volgende tekens bevatten:%n%n%1 +NoProgramGroupCheck2=&Geen menu Start map maken + +; *** "Ready to Install" wizard page +WizardReady=Het voorbereiden van de installatie is gereed +ReadyLabel1=Setup is nu gereed om te beginnen met het installeren van [name] op deze computer. +ReadyLabel2a=Klik op Installeren om verder te gaan met installeren, of klik op Vorige als u instellingen wilt terugzien of veranderen. +ReadyLabel2b=Klik op Installeren om verder te gaan met installeren. +ReadyMemoUserInfo=Gebruikersinformatie: +ReadyMemoDir=Doelmap: +ReadyMemoType=Installatietype: +ReadyMemoComponents=Geselecteerde componenten: +ReadyMemoGroup=Menu Start map: +ReadyMemoTasks=Extra taken: + +; *** TDownloadWizardPage wizard page and DownloadTemporaryFile +DownloadingLabel=Bezig met het downloaden van extra bestanden... +ButtonStopDownload=&Stop download +StopDownload=Weet u zeker dat u de download wilt stoppen? +ErrorDownloadAborted=Download afgebroken +ErrorDownloadFailed=Download mislukt: %1 %2 +ErrorDownloadSizeFailed=Ophalen grootte mislukt: %1 %2 +ErrorFileHash1=Bestand hashing mislukt: %1 +ErrorFileHash2=Ongeldige bestandshash: %1 verwacht, %2 gevonden +ErrorProgress=Ongeldige voortgang: %1 van %2 +ErrorFileSize=Ongeldige bestandsgrootte: %1 verwacht, %2 gevonden + +; *** "Preparing to Install" wizard page +WizardPreparing=Bezig met het voorbereiden van de installatie +PreparingDesc=Setup is bezig met het voorbereiden van de installatie van [name]. +PreviousInstallNotCompleted=De installatie/verwijdering van een vorig programma is niet voltooid. U moet uw computer opnieuw opstarten om die installatie te voltooien.%n%nStart Setup nogmaals nadat uw computer opnieuw is opgestart om de installatie van [name] te voltooien. +CannotContinue=Setup kan niet doorgaan. Klik op annuleren om af te sluiten. +ApplicationsFound=De volgende programma's gebruiken bestanden die moeten worden bijgewerkt door Setup. U wordt aanbevolen Setup toe te staan om automatisch deze programma's af te sluiten. +ApplicationsFound2=De volgende programma's gebruiken bestanden die moeten worden bijgewerkt door Setup. U wordt aanbevolen Setup toe te staan om automatisch deze programma's af te sluiten. Nadat de installatie is voltooid zal Setup proberen de applicaties opnieuw op te starten. +CloseApplications=&Programma's automatisch afsluiten +DontCloseApplications=Programma's &niet afsluiten +ErrorCloseApplications=Setup kon niet alle programma's automatisch afsluiten. U wordt aanbevolen alle programma's die bestanden gebruiken die moeten worden bijgewerkt door Setup af te sluiten voordat u verder gaat. +PrepareToInstallNeedsRestart=Setup moet uw computer opnieuw opstarten. Start Setup nogmaals nadat uw computer opnieuw is opgestart om de installatie van [name] te voltooien.%n%nWilt u nu opnieuw opstarten? + +; *** "Installing" wizard page +WizardInstalling=Bezig met installeren +InstallingLabel=Setup installeert [name] op uw computer. Een ogenblik geduld... + +; *** "Setup Completed" wizard page +FinishedHeadingLabel=Setup heeft het installeren van [name] op deze computer voltooid. +FinishedLabelNoIcons=Setup heeft het installeren van [name] op deze computer voltooid. +FinishedLabel=Setup heeft het installeren van [name] op deze computer voltooid. U kunt het programma uitvoeren met de geïnstalleerde snelkoppelingen. +ClickFinish=Klik op Voltooien om Setup te beëindigen. +FinishedRestartLabel=Setup moet de computer opnieuw opstarten om de installatie van [name] te voltooien. Wilt u nu opnieuw opstarten? +FinishedRestartMessage=Setup moet uw computer opnieuw opstarten om de installatie van [name] te voltooien.%n%nWilt u nu opnieuw opstarten? +ShowReadmeCheck=Ja, ik wil het bestand Leesmij zien +YesRadio=&Ja, start de computer nu opnieuw op +NoRadio=&Nee, ik start de computer later opnieuw op +RunEntryExec=Start %1 +RunEntryShellExec=Bekijk %1 + +; *** "Setup Needs the Next Disk" stuff +ChangeDiskTitle=Setup heeft de volgende diskette nodig +SelectDiskLabel2=Voer diskette %1 in en klik op OK.%n%nAls de bestanden op deze diskette in een andere map gevonden kunnen worden dan die hieronder wordt getoond, voer dan het juiste pad in of klik op Bladeren. +PathLabel=&Pad: +FileNotInDir2=Kan het bestand "%1" niet vinden in "%2". Voer de juiste diskette in of kies een andere map. +SelectDirectoryLabel=Geef de locatie van de volgende diskette. + +; *** Installation phase messages +SetupAborted=Setup is niet voltooid.%n%nCorrigeer het probleem en voer Setup opnieuw uit. +AbortRetryIgnoreSelectAction=Selecteer actie +AbortRetryIgnoreRetry=&Probeer opnieuw +AbortRetryIgnoreIgnore=&Negeer de fout en ga door +AbortRetryIgnoreCancel=Breek installatie af + +; *** Installation status messages +StatusClosingApplications=Programma's afsluiten... +StatusCreateDirs=Mappen maken... +StatusExtractFiles=Bestanden uitpakken... +StatusCreateIcons=Snelkoppelingen maken... +StatusCreateIniEntries=INI-gegevens instellen... +StatusCreateRegistryEntries=Registergegevens instellen... +StatusRegisterFiles=Bestanden registreren... +StatusSavingUninstall=Verwijderingsinformatie opslaan... +StatusRunProgram=Installatie voltooien... +StatusRestartingApplications=Programma's opnieuw starten... +StatusRollback=Veranderingen ongedaan maken... + +; *** Misc. errors +ErrorInternal2=Interne fout: %1 +ErrorFunctionFailedNoCode=%1 mislukt +ErrorFunctionFailed=%1 mislukt; code %2 +ErrorFunctionFailedWithMessage=%1 mislukt; code %2.%n%3 +ErrorExecutingProgram=Kan bestand niet uitvoeren:%n%1 + +; *** Registry errors +ErrorRegOpenKey=Fout bij het openen van registersleutel:%n%1\%2 +ErrorRegCreateKey=Fout bij het maken van registersleutel:%n%1\%2 +ErrorRegWriteKey=Fout bij het schrijven naar registersleutel:%n%1\%2 + +; *** INI errors +ErrorIniEntry=Fout bij het maken van een INI-instelling in bestand "%1". + +; *** File copying errors +FileAbortRetryIgnoreSkipNotRecommended=&Sla dit bestand over (niet aanbevolen) +FileAbortRetryIgnoreIgnoreNotRecommended=&Negeer de fout en ga door (niet aanbevolen) +SourceIsCorrupted=Het bronbestand is beschadigd +SourceDoesntExist=Het bronbestand "%1" bestaat niet +ExistingFileReadOnly2=Het bestaande bestand kon niet vervangen worden omdat het een alleen-lezen markering heeft. +ExistingFileReadOnlyRetry=&Verwijder de alleen-lezen markering en probeer het opnieuw +ExistingFileReadOnlyKeepExisting=&Behoud het bestaande bestand +ErrorReadingExistingDest=Er is een fout opgetreden bij het lezen van het bestaande bestand: +FileExistsSelectAction=Selecteer actie +FileExists2=Het bestand bestaat al. +FileExistsOverwriteExisting=&Overschrijf het bestaande bestand +FileExistsKeepExisting=&Behoud het bestaande bestand +FileExistsOverwriteOrKeepAll=&Dit voor de volgende conflicten uitvoeren +ExistingFileNewerSelectAction=Selecteer actie +ExistingFileNewer2=Het bestaande bestand is nieuwer dan het bestand dat Setup probeert te installeren. +ExistingFileNewerOverwriteExisting=&Overschrijf het bestaande bestand +ExistingFileNewerKeepExisting=&Behoud het bestaande bestand (aanbevolen) +ExistingFileNewerOverwriteOrKeepAll=&Dit voor de volgende conflicten uitvoeren +ErrorChangingAttr=Er is een fout opgetreden bij het wijzigen van de kenmerken van het bestaande bestand: +ErrorCreatingTemp=Er is een fout opgetreden bij het maken van een bestand in de doelmap: +ErrorReadingSource=Er is een fout opgetreden bij het lezen van het bronbestand: +ErrorCopying=Er is een fout opgetreden bij het kopiëren van een bestand: +ErrorReplacingExistingFile=Er is een fout opgetreden bij het vervangen van het bestaande bestand: +ErrorRestartReplace=Vervangen na opnieuw starten is mislukt: +ErrorRenamingTemp=Er is een fout opgetreden bij het hernoemen van een bestand in de doelmap: +ErrorRegisterServer=Kan de DLL/OCX niet registreren: %1 +ErrorRegSvr32Failed=RegSvr32 mislukt met afsluitcode %1 +ErrorRegisterTypeLib=Kan de type library niet registreren: %1 + +; *** Uninstall display name markings +UninstallDisplayNameMark=%1 (%2) +UninstallDisplayNameMarks=%1 (%2, %3) +UninstallDisplayNameMark32Bit=32-bit +UninstallDisplayNameMark64Bit=64-bit +UninstallDisplayNameMarkAllUsers=Alle gebruikers +UninstallDisplayNameMarkCurrentUser=Huidige gebruiker + +; *** Post-installation errors +ErrorOpeningReadme=Er is een fout opgetreden bij het openen van het Leesmij-bestand. +ErrorRestartingComputer=Setup kan de computer niet opnieuw opstarten. Doe dit handmatig. + +; *** Uninstaller messages +UninstallNotFound=Bestand "%1" bestaat niet. Kan het programma niet verwijderen. +UninstallUnsupportedVer=Het installatie-logbestand "%1" heeft een formaat dat niet herkend wordt door deze versie van het verwijderprogramma. Kan het programma niet verwijderen +UninstallUnknownEntry=Er is een onbekend gegeven (%1) aangetroffen in het installatie-logbestand +ConfirmUninstall=Weet u zeker dat u %1 en alle bijbehorende componenten wilt verwijderen? +UninstallOnlyOnWin64=Deze installatie kan alleen worden verwijderd onder 64-bit Windows. +OnlyAdminCanUninstall=Deze installatie kan alleen worden verwijderd door een gebruiker met administratieve rechten. +UninstallStatusLabel=%1 wordt verwijderd van uw computer. Een ogenblik geduld. +UninstallOpenError=Bestand "%1" kon niet worden geopend. Kan het verwijderen niet voltooien. +UninstalledAll=%1 is met succes van deze computer verwijderd. +UninstalledMost=Het verwijderen van %1 is voltooid.%n%nEnkele elementen konden niet verwijderd worden. Deze kunnen handmatig verwijderd worden. +UninstalledAndNeedsRestart=Om het verwijderen van %1 te voltooien, moet uw computer opnieuw worden opgestart.%n%nWilt u nu opnieuw opstarten? +UninstallDataCorrupted="%1" bestand is beschadigd. Kan verwijderen niet voltooien + +; *** Uninstallation phase messages +ConfirmDeleteSharedFileTitle=Gedeeld bestand verwijderen? +ConfirmDeleteSharedFile2=Het systeem geeft aan dat het volgende gedeelde bestand niet langer gebruikt wordt door enig programma. Wilt u dat dit gedeelde bestand verwijderd wordt?%n%nAls dit bestand toch nog gebruikt wordt door een programma en het verwijderd wordt, werkt dat programma misschien niet meer correct. Als u het niet zeker weet, kies dan Nee. Bewaren van het bestand op dit systeem is niet schadelijk. +SharedFileNameLabel=Bestandsnaam: +SharedFileLocationLabel=Locatie: +WizardUninstalling=Verwijderingsstatus +StatusUninstalling=Verwijderen van %1... + +; *** Shutdown block reasons +ShutdownBlockReasonInstallingApp=Installeren van %1. +ShutdownBlockReasonUninstallingApp=Verwijderen van %1. + +[CustomMessages] + +NameAndVersion=%1 versie %2 +AdditionalIcons=Extra snelkoppelingen: +CreateDesktopIcon=Maak een snelkoppeling op het &bureaublad +CreateQuickLaunchIcon=Maak een snelkoppeling op de &Snel starten werkbalk +ProgramOnTheWeb=%1 op het Web +UninstallProgram=Verwijder %1 +LaunchProgram=&Start %1 +AssocFileExtension=&Koppel %1 aan de %2 bestandsextensie +AssocingFileExtension=Bezig met koppelen van %1 aan de %2 bestandsextensie... +AutoStartProgramGroupDescription=Opstarten: +AutoStartProgram=%1 automatisch starten +AddonHostProgramNotFound=%1 kon niet worden gevonden in de geselecteerde map.%n%nWilt u toch doorgaan? diff --git a/src/AITool.Setup/INNO/Languages/Finnish.isl b/src/AITool.Setup/INNO/Languages/Finnish.isl new file mode 100644 index 00000000..17a5f258 --- /dev/null +++ b/src/AITool.Setup/INNO/Languages/Finnish.isl @@ -0,0 +1,359 @@ +; *** Inno Setup version 6.1.0+ Finnish messages *** +; +; Finnish translation by Antti Karttunen +; E-mail: antti.j.karttunen@iki.fi +; Last modification date: 2020-08-02 + +[LangOptions] +LanguageName=Suomi +LanguageID=$040B +LanguageCodePage=1252 + +[Messages] + +; *** Application titles +SetupAppTitle=Asennus +SetupWindowTitle=%1 - Asennus +UninstallAppTitle=Asennuksen poisto +UninstallAppFullTitle=%1 - Asennuksen poisto + +; *** Misc. common +InformationTitle=Ilmoitus +ConfirmTitle=Varmistus +ErrorTitle=Virhe + +; *** SetupLdr messages +SetupLdrStartupMessage=Tll asennusohjelmalla asennetaan %1. Haluatko jatkaa? +LdrCannotCreateTemp=Vliaikaistiedostoa ei voitu luoda. Asennus keskeytettiin +LdrCannotExecTemp=Vliaikaisessa hakemistossa olevaa tiedostoa ei voitu suorittaa. Asennus keskeytettiin + +; *** Startup error messages +LastErrorMessage=%1.%n%nVirhe %2: %3 +SetupFileMissing=Tiedostoa %1 ei lydy asennushakemistosta. Korjaa ongelma tai hanki uusi kopio ohjelmasta. +SetupFileCorrupt=Asennustiedostot ovat vaurioituneet. Hanki uusi kopio ohjelmasta. +SetupFileCorruptOrWrongVer=Asennustiedostot ovat vaurioituneet tai ovat epyhteensopivia tmn Asennuksen version kanssa. Korjaa ongelma tai hanki uusi kopio ohjelmasta. +InvalidParameter=Virheellinen komentoriviparametri:%n%n%1 +SetupAlreadyRunning=Asennus on jo kynniss. +WindowsVersionNotSupported=Tm ohjelma ei tue kytss olevaa Windowsin versiota. +WindowsServicePackRequired=Tm ohjelma vaatii %1 Service Pack %2 -pivityksen tai myhemmn. +NotOnThisPlatform=Tm ohjelma ei toimi %1-kyttjrjestelmss. +OnlyOnThisPlatform=Tm ohjelma toimii vain %1-kyttjrjestelmss. +OnlyOnTheseArchitectures=Tm ohjelma voidaan asentaa vain niihin Windowsin versioihin, jotka on suunniteltu seuraaville prosessorityypeille:%n%n%1 +WinVersionTooLowError=Tm ohjelma vaatii version %2 tai myhemmn %1-kyttjrjestelmst. +WinVersionTooHighError=Tt ohjelmaa ei voi asentaa %1-kyttjrjestelmn versioon %2 tai myhempn. +AdminPrivilegesRequired=Sinun tytyy kirjautua sisn jrjestelmnvalvojana asentaaksesi tmn ohjelman. +PowerUserPrivilegesRequired=Sinun tytyy kirjautua sisn jrjestelmnvalvojana tai tehokyttjn asentaaksesi tmn ohjelman. +SetupAppRunningError=Asennus lysi kynniss olevan kopion ohjelmasta %1.%n%nSulje kaikki kynniss olevat kopiot ohjelmasta ja valitse OK jatkaaksesi, tai valitse Peruuta poistuaksesi. +UninstallAppRunningError=Asennuksen poisto lysi kynniss olevan kopion ohjelmasta %1.%n%nSulje kaikki kynniss olevat kopiot ohjelmasta ja valitse OK jatkaaksesi, tai valitse Peruuta poistuaksesi. + +; *** Startup questions +PrivilegesRequiredOverrideTitle=Valitse asennustapa +PrivilegesRequiredOverrideInstruction=Valitse, kenen kyttn ohjelma asennetaan +PrivilegesRequiredOverrideText1=%1 voidaan asentaa kaikille kyttjille (vaatii jrjestelmnvalvojan oikeudet) tai vain sinun kyttsi. +PrivilegesRequiredOverrideText2=%1 voidaan asentaa vain sinun kyttsi tai kaikille kyttjille (vaatii jrjestelmnvalvojan oikeudet). +PrivilegesRequiredOverrideAllUsers=Asenna &kaikille kyttjille +PrivilegesRequiredOverrideAllUsersRecommended=Asenna &kaikille kyttjille (suositus) +PrivilegesRequiredOverrideCurrentUser=Asenna vain &minun kyttni +PrivilegesRequiredOverrideCurrentUserRecommended=Asenna vain &minun kyttni (suositus) + +; *** Misc. errors +ErrorCreatingDir=Asennus ei voinut luoda hakemistoa "%1" +ErrorTooManyFilesInDir=Tiedoston luominen hakemistoon "%1" eponnistui, koska se sislt liian monta tiedostoa + +; *** Setup common messages +ExitSetupTitle=Poistu Asennuksesta +ExitSetupMessage=Asennus ei ole valmis. Jos lopetat nyt, ohjelmaa ei asenneta.%n%nVoit ajaa Asennuksen toiste asentaaksesi ohjelman.%n%nLopetetaanko Asennus? +AboutSetupMenuItem=&Tietoja Asennuksesta... +AboutSetupTitle=Tietoja Asennuksesta +AboutSetupMessage=%1 versio %2%n%3%n%n%1 -ohjelman kotisivu:%n%4 +AboutSetupNote= +TranslatorNote=Suomenkielinen knns: Antti Karttunen (antti.j.karttunen@iki.fi) + +; *** Buttons +ButtonBack=< &Takaisin +ButtonNext=&Seuraava > +ButtonInstall=&Asenna +ButtonOK=OK +ButtonCancel=Peruuta +ButtonYes=&Kyll +ButtonYesToAll=Kyll k&aikkiin +ButtonNo=&Ei +ButtonNoToAll=E&i kaikkiin +ButtonFinish=&Lopeta +ButtonBrowse=S&elaa... +ButtonWizardBrowse=S&elaa... +ButtonNewFolder=&Luo uusi kansio + +; *** "Select Language" dialog messages +SelectLanguageTitle=Valitse Asennuksen kieli +SelectLanguageLabel=Valitse asentamisen aikana kytettv kieli. + +; *** Common wizard text +ClickNext=Valitse Seuraava jatkaaksesi tai Peruuta poistuaksesi. +BeveledLabel= +BrowseDialogTitle=Selaa kansioita +BrowseDialogLabel=Valitse kansio allaolevasta listasta ja valitse sitten OK jatkaaksesi. +NewFolderName=Uusi kansio + +; *** "Welcome" wizard page +WelcomeLabel1=Tervetuloa [name] -asennusohjelmaan. +WelcomeLabel2=Tll asennusohjelmalla koneellesi asennetaan [name/ver]. %n%nOn suositeltavaa, ett suljet kaikki muut kynniss olevat sovellukset ennen jatkamista. Tm auttaa vlttmn ristiriitatilanteita asennuksen aikana. + +; *** "Password" wizard page +WizardPassword=Salasana +PasswordLabel1=Tm asennusohjelma on suojattu salasanalla. +PasswordLabel3=Anna salasana ja valitse sitten Seuraava jatkaaksesi.%n%nIsot ja pienet kirjaimet ovat eriarvoisia. +PasswordEditLabel=&Salasana: +IncorrectPassword=Antamasi salasana oli virheellinen. Anna salasana uudelleen. + +; *** "License Agreement" wizard page +WizardLicense=Kyttoikeussopimus +LicenseLabel=Lue seuraava trke tiedotus ennen kuin jatkat. +LicenseLabel3=Lue seuraava kyttoikeussopimus tarkasti. Sinun tytyy hyvksy sopimus, jos haluat jatkaa asentamista. +LicenseAccepted=&Hyvksyn sopimuksen +LicenseNotAccepted=&En hyvksy sopimusta + +; *** "Information" wizard pages +WizardInfoBefore=Tiedotus +InfoBeforeLabel=Lue seuraava trke tiedotus ennen kuin jatkat. +InfoBeforeClickLabel=Kun olet valmis jatkamaan asentamista, valitse Seuraava. +WizardInfoAfter=Tiedotus +InfoAfterLabel=Lue seuraava trke tiedotus ennen kuin jatkat. +InfoAfterClickLabel=Kun olet valmis jatkamaan asentamista, valitse Seuraava. + +; *** "Select Destination Directory" wizard page +WizardUserInfo=Kyttjtiedot +UserInfoDesc=Anna pyydetyt tiedot. +UserInfoName=Kyttjn &nimi: +UserInfoOrg=&Yritys: +UserInfoSerial=&Tunnuskoodi: +UserInfoNameRequired=Sinun tytyy antaa nimi. + +; *** "Select Destination Location" wizard page +WizardSelectDir=Valitse kohdekansio +SelectDirDesc=Mihin [name] asennetaan? +SelectDirLabel3=[name] asennetaan thn kansioon. +SelectDirBrowseLabel=Valitse Seuraava jatkaaksesi. Jos haluat vaihtaa kansiota, valitse Selaa. +DiskSpaceGBLabel=Vapaata levytilaa tarvitaan vhintn [gb] Gt. +DiskSpaceMBLabel=Vapaata levytilaa tarvitaan vhintn [mb] Mt. +CannotInstallToNetworkDrive=Asennus ei voi asentaa ohjelmaa verkkoasemalle. +CannotInstallToUNCPath=Asennus ei voi asentaa ohjelmaa UNC-polun alle. +InvalidPath=Anna tydellinen polku levyaseman kirjaimen kanssa. Esimerkiksi %nC:\OHJELMA%n%ntai UNC-polku muodossa %n%n\\palvelin\resurssi +InvalidDrive=Valitsemaasi asemaa tai UNC-polkua ei ole olemassa tai sit ei voi kytt. Valitse toinen asema tai UNC-polku. +DiskSpaceWarningTitle=Ei tarpeeksi vapaata levytilaa +DiskSpaceWarning=Asennus vaatii vhintn %1 kt vapaata levytilaa, mutta valitulla levyasemalla on vain %2 kt vapaata levytilaa.%n%nHaluatko jatkaa tst huolimatta? +DirNameTooLong=Kansion nimi tai polku on liian pitk. +InvalidDirName=Virheellinen kansion nimi. +BadDirName32=Kansion nimess ei saa olla seuraavia merkkej:%n%n%1 +DirExistsTitle=Kansio on olemassa +DirExists=Kansio:%n%n%1%n%non jo olemassa. Haluatko kuitenkin suorittaa asennuksen thn kansioon? +DirDoesntExistTitle=Kansiota ei ole olemassa +DirDoesntExist=Kansiota%n%n%1%n%nei ole olemassa. Luodaanko kansio? + +; *** "Select Components" wizard page +WizardSelectComponents=Valitse asennettavat osat +SelectComponentsDesc=Mitk osat asennetaan? +SelectComponentsLabel2=Valitse ne osat, jotka haluat asentaa, ja poista niiden osien valinta, joita et halua asentaa. Valitse Seuraava, kun olet valmis. +FullInstallation=Normaali asennus +CompactInstallation=Suppea asennus +CustomInstallation=Mukautettu asennus +NoUninstallWarningTitle=Asennettuja osia lydettiin +NoUninstallWarning=Seuraavat osat on jo asennettu koneelle:%n%n%1%n%nNiden osien valinnan poistaminen ei poista niit koneelta.%n%nHaluatko jatkaa tst huolimatta? +ComponentSize1=%1 kt +ComponentSize2=%1 Mt +ComponentsDiskSpaceGBLabel=Nykyiset valinnat vaativat vhintn [gb] Gt levytilaa. +ComponentsDiskSpaceMBLabel=Nykyiset valinnat vaativat vhintn [mb] Mt levytilaa. + +; *** "Select Additional Tasks" wizard page +WizardSelectTasks=Valitse muut toiminnot +SelectTasksDesc=Mit muita toimintoja suoritetaan? +SelectTasksLabel2=Valitse muut toiminnot, jotka haluat Asennuksen suorittavan samalla kun [name] asennetaan. Valitse Seuraava, kun olet valmis. + +; *** "Select Start Menu Folder" wizard page +WizardSelectProgramGroup=Valitse Kynnist-valikon kansio +SelectStartMenuFolderDesc=Mihin ohjelman pikakuvakkeet sijoitetaan? +SelectStartMenuFolderLabel3=Ohjelman pikakuvakkeet luodaan thn Kynnist-valikon kansioon. +SelectStartMenuFolderBrowseLabel=Valitse Seuraava jatkaaksesi. Jos haluat vaihtaa kansiota, valitse Selaa. +MustEnterGroupName=Kansiolle pit antaa nimi. +GroupNameTooLong=Kansion nimi tai polku on liian pitk. +InvalidGroupName=Virheellinen kansion nimi. +BadGroupName=Kansion nimess ei saa olla seuraavia merkkej:%n%n%1 +NoProgramGroupCheck2=l luo k&ansiota Kynnist-valikkoon + +; *** "Ready to Install" wizard page +WizardReady=Valmiina asennukseen +ReadyLabel1=[name] on nyt valmis asennettavaksi. +ReadyLabel2a=Valitse Asenna jatkaaksesi asentamista tai valitse Takaisin, jos haluat tarkastella tekemisi asetuksia tai muuttaa niit. +ReadyLabel2b=Valitse Asenna jatkaaksesi asentamista. +ReadyMemoUserInfo=Kyttjtiedot: +ReadyMemoDir=Kohdekansio: +ReadyMemoType=Asennustyyppi: +ReadyMemoComponents=Asennettavaksi valitut osat: +ReadyMemoGroup=Kynnist-valikon kansio: +ReadyMemoTasks=Muut toiminnot: + +; *** TDownloadWizardPage wizard page and DownloadTemporaryFile +DownloadingLabel=Ladataan tarvittavia tiedostoja... +ButtonStopDownload=&Pysyt lataus +StopDownload=Oletko varma, ett haluat pysytt tiedostojen latauksen? +ErrorDownloadAborted=Tiedostojen lataaminen keskeytettiin +ErrorDownloadFailed=Tiedoston lataaminen eponnistui: %1 %2 +ErrorDownloadSizeFailed=Latauksen koon noutaminen eponnistui: %1 %2 +ErrorFileHash1=Tiedoston tiivisteen luominen eponnistui: %1 +ErrorFileHash2=Tiedoston tiiviste on virheellinen: odotettu %1, lydetty %2 +ErrorProgress=Virheellinen edistyminen: %1 / %2 +ErrorFileSize=Virheellinen tiedoston koko: odotettu %1, lydetty %2 + +; *** "Preparing to Install" wizard page +WizardPreparing=Valmistellaan asennusta +PreparingDesc=Valmistaudutaan asentamaan [name] koneellesi. +PreviousInstallNotCompleted=Edellisen ohjelman asennus tai asennuksen poisto ei ole valmis. Sinun tytyy kynnist kone uudelleen viimeistellksesi edellisen asennuksen.%n%nAja [name] -asennusohjelma uudestaan, kun olet kynnistnyt koneen uudelleen. +CannotContinue=Asennusta ei voida jatkaa. Valitse Peruuta poistuaksesi. +ApplicationsFound=Seuraavat sovellukset kyttvt tiedostoja, joita Asennuksen pit pivitt. On suositeltavaa, ett annat Asennuksen sulkea nm sovellukset automaattisesti. +ApplicationsFound2=Seuraavat sovellukset kyttvt tiedostoja, joita Asennuksen pit pivitt. On suositeltavaa, ett annat Asennuksen sulkea nm sovellukset automaattisesti. Valmistumisen jlkeen Asennus yritt uudelleenkynnist sovellukset. +CloseApplications=&Sulje sovellukset automaattisesti +DontCloseApplications=&l sulje sovelluksia +ErrorCloseApplications=Asennus ei pystynyt sulkemaan tarvittavia sovelluksia automaattisesti. On suositeltavaa, ett ennen jatkamista suljet sovellukset, jotka kyttvt asennuksen aikana pivitettvi tiedostoja. +PrepareToInstallNeedsRestart=Asennuksen tytyy kynnist tietokone uudelleen. Aja Asennus uudelleenkynnistyksen jlkeen, jotta [name] voidaan asentaa.%n%nHaluatko kynnist tietokoneen uudelleen nyt? + +; *** "Installing" wizard page +WizardInstalling=Asennus kynniss +InstallingLabel=Odota, kun [name] asennetaan koneellesi. + +; *** "Setup Completed" wizard page +FinishedHeadingLabel=[name] - Asennuksen viimeistely +FinishedLabelNoIcons=[name] on nyt asennettu koneellesi. +FinishedLabel=[name] on nyt asennettu. Sovellus voidaan kynnist valitsemalla jokin asennetuista kuvakkeista. +ClickFinish=Valitse Lopeta poistuaksesi Asennuksesta. +FinishedRestartLabel=Jotta [name] saataisiin asennettua loppuun, pit kone kynnist uudelleen. Haluatko kynnist koneen uudelleen nyt? +FinishedRestartMessage=Jotta [name] saataisiin asennettua loppuun, pit kone kynnist uudelleen.%n%nHaluatko kynnist koneen uudelleen nyt? +ShowReadmeCheck=Kyll, haluan nhd LUEMINUT-tiedoston +YesRadio=&Kyll, kynnist kone uudelleen +NoRadio=&Ei, kynnistn koneen uudelleen myhemmin +RunEntryExec=Kynnist %1 +RunEntryShellExec=Nyt %1 + +; *** "Setup Needs the Next Disk" stuff +ChangeDiskTitle=Asennus tarvitsee seuraavan levykkeen +SelectDiskLabel2=Aseta levyke %1 asemaan ja valitse OK. %n%nJos joku toinen kansio sislt levykkeen tiedostot, anna oikea polku tai valitse Selaa. +PathLabel=&Polku: +FileNotInDir2=Tiedostoa "%1" ei lytynyt lhteest "%2". Aseta oikea levyke asemaan tai valitse toinen kansio. +SelectDirectoryLabel=Mrit seuraavan levykkeen sislln sijainti. + +; *** Installation phase messages +SetupAborted=Asennusta ei suoritettu loppuun.%n%nKorjaa ongelma ja suorita Asennus uudelleen. +AbortRetryIgnoreSelectAction=Valitse toiminto +AbortRetryIgnoreRetry=&Yrit uudelleen +AbortRetryIgnoreIgnore=&Jatka virheest huolimatta +AbortRetryIgnoreCancel=Peruuta asennus + +; *** Installation status messages +StatusClosingApplications=Suljetaan sovellukset... +StatusCreateDirs=Luodaan hakemistoja... +StatusExtractFiles=Puretaan tiedostoja... +StatusCreateIcons=Luodaan pikakuvakkeita... +StatusCreateIniEntries=Luodaan INI-merkintj... +StatusCreateRegistryEntries=Luodaan rekisterimerkintj... +StatusRegisterFiles=Rekisteridn tiedostoja... +StatusSavingUninstall=Tallennetaan Asennuksen poiston tietoja... +StatusRunProgram=Viimeistelln asennusta... +StatusRestartingApplications=Uudelleenkynnistetn sovellukset... +StatusRollback=Peruutetaan tehdyt muutokset... + +; *** Misc. errors +ErrorInternal2=Sisinen virhe: %1 +ErrorFunctionFailedNoCode=%1 eponnistui +ErrorFunctionFailed=%1 eponnistui; virhekoodi %2 +ErrorFunctionFailedWithMessage=%1 eponnistui; virhekoodi %2.%n%3 +ErrorExecutingProgram=Virhe suoritettaessa tiedostoa%n%1 + +; *** Shutdown block reasons +ShutdownBlockReasonInstallingApp=Asennetaan %1. +ShutdownBlockReasonUninstallingApp=Poistetaan %1. + +; *** Registry errors +ErrorRegOpenKey=Virhe avattaessa rekisteriavainta%n%1\%2 +ErrorRegCreateKey=Virhe luotaessa rekisteriavainta%n%1\%2 +ErrorRegWriteKey=Virhe kirjoitettaessa rekisteriavaimeen%n%1\%2 + +; *** INI errors +ErrorIniEntry=Virhe luotaessa INI-merkint tiedostoon "%1". + +; *** File copying errors +FileAbortRetryIgnoreSkipNotRecommended=&Ohita tm tiedosto (ei suositeltavaa) +FileAbortRetryIgnoreIgnoreNotRecommended=&Jatka virheest huolimatta (ei suositeltavaa) +SourceIsCorrupted=Lhdetiedosto on vaurioitunut +SourceDoesntExist=Lhdetiedostoa "%1" ei ole olemassa +ExistingFileReadOnly2=Nykyist tiedostoa ei voitu korvata, koska se on Vain luku -tiedosto. +ExistingFileReadOnlyRetry=&Poista Vain luku -asetus ja yrit uudelleen +ExistingFileReadOnlyKeepExisting=&Silyt nykyinen tiedosto +ErrorReadingExistingDest=Virhe luettaessa nykyist tiedostoa: +FileExistsSelectAction=Valitse toiminto +FileExists2=Tiedosto on jo olemassa. +FileExistsOverwriteExisting=Korvaa &olemassa oleva tiedosto +FileExistsKeepExisting=&Silyt olemassa oleva tiedosto +FileExistsOverwriteOrKeepAll=&Hoida muut vastaavat tilanteet samalla tavalla +ExistingFileNewerSelectAction=Valitse toiminto +ExistingFileNewer2=Olemassa oleva tiedosto on uudempi kuin Asennuksen sisltm tiedosto. +ExistingFileNewerOverwriteExisting=Korvaa &olemassa oleva tiedosto +ExistingFileNewerKeepExisting=&Silyt olemassa oleva tiedosto (suositeltavaa) +ExistingFileNewerOverwriteOrKeepAll=&Hoida muut vastaavat tilanteet samalla tavalla +ErrorChangingAttr=Virhe vaihdettaessa nykyisen tiedoston mritteit: +ErrorCreatingTemp=Virhe luotaessa tiedostoa kohdehakemistoon: +ErrorReadingSource=Virhe luettaessa lhdetiedostoa: +ErrorCopying=Virhe kopioitaessa tiedostoa: +ErrorReplacingExistingFile=Virhe korvattaessa nykyist tiedostoa: +ErrorRestartReplace=RestartReplace-komento eponnistui: +ErrorRenamingTemp=Virhe uudelleennimettess tiedostoa kohdehakemistossa: +ErrorRegisterServer=DLL/OCX -laajennuksen rekisterinti eponnistui: %1 +ErrorRegSvr32Failed=RegSvr32-toiminto eponnistui. Virhekoodi: %1 +ErrorRegisterTypeLib=Tyyppikirjaston rekisteriminen eponnistui: %1 + +; *** Uninstall display name markings +UninstallDisplayNameMark=%1 (%2) +UninstallDisplayNameMarks=%1 (%2, %3) +UninstallDisplayNameMark32Bit=32-bittinen +UninstallDisplayNameMark64Bit=64-bittinen +UninstallDisplayNameMarkAllUsers=Kaikki kyttjt +UninstallDisplayNameMarkCurrentUser=Tmnhetkinen kyttj + +; *** Post-installation errors +ErrorOpeningReadme=Virhe avattaessa LUEMINUT-tiedostoa. +ErrorRestartingComputer=Koneen uudelleenkynnistminen ei onnistunut. Suorita uudelleenkynnistys itse. + +; *** Uninstaller messages +UninstallNotFound=Tiedostoa "%1" ei lytynyt. Asennuksen poisto ei onnistu. +UninstallOpenError=Tiedostoa "%1" ei voitu avata. Asennuksen poisto ei onnistu. +UninstallUnsupportedVer=Tm versio Asennuksen poisto-ohjelmasta ei pysty lukemaan lokitiedostoa "%1". Asennuksen poisto ei onnistu +UninstallUnknownEntry=Asennuksen poisto-ohjelman lokitiedostosta lytyi tuntematon merkint (%1) +ConfirmUninstall=Poistetaanko %1 ja kaikki sen osat? +UninstallOnlyOnWin64=Tm ohjelma voidaan poistaa vain 64-bittisest Windowsista ksin. +OnlyAdminCanUninstall=Tmn asennuksen poistaminen vaatii jrjestelmnvalvojan oikeudet. +UninstallStatusLabel=Odota, kun %1 poistetaan koneeltasi. +UninstalledAll=%1 poistettiin onnistuneesti. +UninstalledMost=%1 poistettiin koneelta.%n%nJoitakin osia ei voitu poistaa. Voit poistaa osat itse. +UninstalledAndNeedsRestart=Kone tytyy kynnist uudelleen, jotta %1 voidaan poistaa kokonaan.%n%nHaluatko kynnist koneen uudeelleen nyt? +UninstallDataCorrupted=Tiedosto "%1" on vaurioitunut. Asennuksen poisto ei onnistu. + +; *** Uninstallation phase messages +ConfirmDeleteSharedFileTitle=Poistetaanko jaettu tiedosto? +ConfirmDeleteSharedFile2=Jrjestelmn mukaan seuraava jaettu tiedosto ei ole en minkn muun sovelluksen kytss. Poistetaanko tiedosto?%n%nJos jotkut sovellukset kyttvt viel tt tiedostoa ja se poistetaan, ne eivt vlttmtt toimi en kunnolla. Jos olet epvarma, valitse Ei. Tiedoston jttminen koneelle ei aiheuta ongelmia. +SharedFileNameLabel=Tiedoston nimi: +SharedFileLocationLabel=Sijainti: +WizardUninstalling=Asennuksen poiston tila +StatusUninstalling=Poistetaan %1... + +[CustomMessages] + +NameAndVersion=%1 versio %2 +AdditionalIcons=Liskuvakkeet: +CreateDesktopIcon=Lu&o kuvake typydlle +CreateQuickLaunchIcon=Luo kuvake &pikakynnistyspalkkiin +ProgramOnTheWeb=%1 Internetiss +UninstallProgram=Poista %1 +LaunchProgram=&Kynnist %1 +AssocFileExtension=&Yhdist %1 tiedostoptteeseen %2 +AssocingFileExtension=Yhdistetn %1 tiedostoptteeseen %2 ... +AutoStartProgramGroupDescription=Kynnistys: +AutoStartProgram=Kynnist %1 automaattisesti +AddonHostProgramNotFound=%1 ei ole valitsemassasi kansiossa.%n%nHaluatko jatkaa tst huolimatta? diff --git a/src/AITool.Setup/INNO/Languages/French.isl b/src/AITool.Setup/INNO/Languages/French.isl new file mode 100644 index 00000000..7c8db923 --- /dev/null +++ b/src/AITool.Setup/INNO/Languages/French.isl @@ -0,0 +1,404 @@ +; *** Inno Setup version 6.1.0+ French messages *** +; +; To download user-contributed translations of this file, go to: +; https://jrsoftware.org/files/istrans/ +; +; Note: When translating this text, do not add periods (.) to the end of +; messages that didn't have them already, because on those messages Inno +; Setup adds the periods automatically (appending a period would result in +; two periods being displayed). +; +; Maintained by Pierre Yager (pierre@levosgien.net) +; +; Contributors : Frédéric Bonduelle, Francis Pallini, Lumina, Pascal Peyrot +; +; Changes : +; + Accents on uppercase letters +; http://www.academie-francaise.fr/langue/questions.html#accentuation (lumina) +; + Typography quotes [see ISBN: 978-2-7433-0482-9] +; http://fr.wikipedia.org/wiki/Guillemet (lumina) +; + Binary units (Kio, Mio) [IEC 80000-13:2008] +; http://fr.wikipedia.org/wiki/Octet (lumina) +; + Reverted to standard units (Ko, Mo) to follow Windows Explorer Standard +; http://blogs.msdn.com/b/oldnewthing/archive/2009/06/11/9725386.aspx +; + Use more standard verbs for click and retry +; "click": "Clicker" instead of "Appuyer" +; "retry": "Recommencer" au lieu de "Réessayer" +; + Added new 6.0.0 messages +; + Added new 6.1.0 messages + +[LangOptions] +; The following three entries are very important. Be sure to read and +; understand the '[LangOptions] section' topic in the help file. +LanguageName=Français +LanguageID=$040C +LanguageCodePage=1252 +; If the language you are translating to requires special font faces or +; sizes, uncomment any of the following entries and change them accordingly. +;DialogFontName= +;DialogFontSize=8 +;WelcomeFontName=Verdana +;WelcomeFontSize=12 +;TitleFontName=Arial +;TitleFontSize=29 +;CopyrightFontName=Arial +;CopyrightFontSize=8 + +[Messages] + +; *** Application titles +SetupAppTitle=Installation +SetupWindowTitle=Installation - %1 +UninstallAppTitle=Désinstallation +UninstallAppFullTitle=Désinstallation - %1 + +; *** Misc. common +InformationTitle=Information +ConfirmTitle=Confirmation +ErrorTitle=Erreur + +; *** SetupLdr messages +SetupLdrStartupMessage=Cet assistant va installer %1. Voulez-vous continuer ? +LdrCannotCreateTemp=Impossible de créer un fichier temporaire. Abandon de l'installation +LdrCannotExecTemp=Impossible d'exécuter un fichier depuis le dossier temporaire. Abandon de l'installation +HelpTextNote= + +; *** Startup error messages +LastErrorMessage=%1.%n%nErreur %2 : %3 +SetupFileMissing=Le fichier %1 est absent du dossier d'installation. Veuillez corriger le problème ou vous procurer une nouvelle copie du programme. +SetupFileCorrupt=Les fichiers d'installation sont altérés. Veuillez vous procurer une nouvelle copie du programme. +SetupFileCorruptOrWrongVer=Les fichiers d'installation sont altérés ou ne sont pas compatibles avec cette version de l'assistant d'installation. Veuillez corriger le problème ou vous procurer une nouvelle copie du programme. +InvalidParameter=Un paramètre non valide a été passé à la ligne de commande :%n%n%1 +SetupAlreadyRunning=L'assistant d'installation est déjà en cours d'exécution. +WindowsVersionNotSupported=Ce programme n'est pas prévu pour fonctionner avec la version de Windows utilisée sur votre ordinateur. +WindowsServicePackRequired=Ce programme a besoin de %1 Service Pack %2 ou d'une version plus récente. +NotOnThisPlatform=Ce programme ne fonctionne pas sous %1. +OnlyOnThisPlatform=Ce programme ne peut fonctionner que sous %1. +OnlyOnTheseArchitectures=Ce programme ne peut être installé que sur des versions de Windows qui supportent ces architectures : %n%n%1 +WinVersionTooLowError=Ce programme requiert la version %2 ou supérieure de %1. +WinVersionTooHighError=Ce programme ne peut pas être installé sous %1 version %2 ou supérieure. +AdminPrivilegesRequired=Vous devez disposer des droits d'administration de cet ordinateur pour installer ce programme. +PowerUserPrivilegesRequired=Vous devez disposer des droits d'administration ou faire partie du groupe « Utilisateurs avec pouvoir » de cet ordinateur pour installer ce programme. +SetupAppRunningError=L'assistant d'installation a détecté que %1 est actuellement en cours d'exécution.%n%nVeuillez fermer toutes les instances de cette application puis cliquer sur OK pour continuer, ou bien cliquer sur Annuler pour abandonner l'installation. +UninstallAppRunningError=La procédure de désinstallation a détecté que %1 est actuellement en cours d'exécution.%n%nVeuillez fermer toutes les instances de cette application puis cliquer sur OK pour continuer, ou bien cliquer sur Annuler pour abandonner la désinstallation. + +; *** Startup questions +PrivilegesRequiredOverrideTitle=Choix du Mode d'Installation +PrivilegesRequiredOverrideInstruction=Choisissez le mode d'installation +PrivilegesRequiredOverrideText1=%1 peut être installé pour tous les utilisateurs (nécessite des privilèges administrateur), ou seulement pour vous. +PrivilegesRequiredOverrideText2=%1 peut-être installé seulement pour vous, ou pour tous les utilisateurs (nécessite des privilèges administrateur). +PrivilegesRequiredOverrideAllUsers=Installer pour &tous les utilisateurs +PrivilegesRequiredOverrideAllUsersRecommended=Installer pour &tous les utilisateurs (recommandé) +PrivilegesRequiredOverrideCurrentUser=Installer seulement pour &moi +PrivilegesRequiredOverrideCurrentUserRecommended=Installer seulement pour &moi (recommandé) + +; *** Misc. errors +ErrorCreatingDir=L'assistant d'installation n'a pas pu créer le dossier "%1" +ErrorTooManyFilesInDir=L'assistant d'installation n'a pas pu créer un fichier dans le dossier "%1" car celui-ci contient trop de fichiers + +; *** Setup common messages +ExitSetupTitle=Quitter l'installation +ExitSetupMessage=L'installation n'est pas terminée. Si vous abandonnez maintenant, le programme ne sera pas installé.%n%nVous devrez relancer cet assistant pour finir l'installation.%n%nVoulez-vous quand même quitter l'assistant d'installation ? +AboutSetupMenuItem=À &propos... +AboutSetupTitle=À Propos de l'assistant d'installation +AboutSetupMessage=%1 version %2%n%3%n%nPage d'accueil de %1 :%n%4 +AboutSetupNote= +TranslatorNote=Traduction française maintenue par Pierre Yager (pierre@levosgien.net) + +; *** Buttons +ButtonBack=< &Précédent +ButtonNext=&Suivant > +ButtonInstall=&Installer +ButtonOK=OK +ButtonCancel=Annuler +ButtonYes=&Oui +ButtonYesToAll=Oui pour &tout +ButtonNo=&Non +ButtonNoToAll=N&on pour tout +ButtonFinish=&Terminer +ButtonBrowse=Pa&rcourir... +ButtonWizardBrowse=Pa&rcourir... +ButtonNewFolder=Nouveau &dossier + +; *** "Select Language" dialog messages +SelectLanguageTitle=Langue de l'assistant d'installation +SelectLanguageLabel=Veuillez sélectionner la langue qui sera utilisée par l'assistant d'installation. + +; *** Common wizard text +ClickNext=Cliquez sur Suivant pour continuer ou sur Annuler pour abandonner l'installation. +BeveledLabel= +BrowseDialogTitle=Parcourir les dossiers +BrowseDialogLabel=Veuillez choisir un dossier de destination, puis cliquez sur OK. +NewFolderName=Nouveau dossier + +; *** "Welcome" wizard page +WelcomeLabel1=Bienvenue dans l'assistant d'installation de [name] +WelcomeLabel2=Cet assistant va vous guider dans l'installation de [name/ver] sur votre ordinateur.%n%nIl est recommandé de fermer toutes les applications actives avant de continuer. + +; *** "Password" wizard page +WizardPassword=Mot de passe +PasswordLabel1=Cette installation est protégée par un mot de passe. +PasswordLabel3=Veuillez saisir le mot de passe (attention à la distinction entre majuscules et minuscules) puis cliquez sur Suivant pour continuer. +PasswordEditLabel=&Mot de passe : +IncorrectPassword=Le mot de passe saisi n'est pas valide. Veuillez essayer à nouveau. + +; *** "License Agreement" wizard page +WizardLicense=Accord de licence +LicenseLabel=Les informations suivantes sont importantes. Veuillez les lire avant de continuer. +LicenseLabel3=Veuillez lire le contrat de licence suivant. Vous devez en accepter tous les termes avant de continuer l'installation. +LicenseAccepted=Je comprends et j'&accepte les termes du contrat de licence +LicenseNotAccepted=Je &refuse les termes du contrat de licence + +; *** "Information" wizard pages +WizardInfoBefore=Information +InfoBeforeLabel=Les informations suivantes sont importantes. Veuillez les lire avant de continuer. +InfoBeforeClickLabel=Lorsque vous êtes prêt à continuer, cliquez sur Suivant. +WizardInfoAfter=Information +InfoAfterLabel=Les informations suivantes sont importantes. Veuillez les lire avant de continuer. +InfoAfterClickLabel=Lorsque vous êtes prêt à continuer, cliquez sur Suivant. + +; *** "User Information" wizard page +WizardUserInfo=Informations sur l'Utilisateur +UserInfoDesc=Veuillez saisir les informations qui vous concernent. +UserInfoName=&Nom d'utilisateur : +UserInfoOrg=&Organisation : +UserInfoSerial=Numéro de &série : +UserInfoNameRequired=Vous devez au moins saisir un nom. + +; *** "Select Destination Location" wizard page +WizardSelectDir=Dossier de destination +SelectDirDesc=Où [name] doit-il être installé ? +SelectDirLabel3=L'assistant va installer [name] dans le dossier suivant. +SelectDirBrowseLabel=Pour continuer, cliquez sur Suivant. Si vous souhaitez choisir un dossier différent, cliquez sur Parcourir. +DiskSpaceGBLabel=Le programme requiert au moins [gb] Go d'espace disque disponible. +DiskSpaceMBLabel=Le programme requiert au moins [mb] Mo d'espace disque disponible. +CannotInstallToNetworkDrive=L'assistant ne peut pas installer sur un disque réseau. +CannotInstallToUNCPath=L'assistant ne peut pas installer sur un chemin UNC. +InvalidPath=Vous devez saisir un chemin complet avec sa lettre de lecteur ; par exemple :%n%nC:\APP%n%nou un chemin réseau de la forme :%n%n\\serveur\partage +InvalidDrive=L'unité ou l'emplacement réseau que vous avez sélectionné n'existe pas ou n'est pas accessible. Veuillez choisir une autre destination. +DiskSpaceWarningTitle=Espace disponible insuffisant +DiskSpaceWarning=L'assistant a besoin d'au moins %1 Ko d'espace disponible pour effectuer l'installation, mais l'unité que vous avez sélectionnée ne dispose que de %2 Ko d'espace disponible.%n%nSouhaitez-vous continuer malgré tout ? +DirNameTooLong=Le nom ou le chemin du dossier est trop long. +InvalidDirName=Le nom du dossier est invalide. +BadDirName32=Le nom du dossier ne doit contenir aucun des caractères suivants :%n%n%1 +DirExistsTitle=Dossier existant +DirExists=Le dossier :%n%n%1%n%nexiste déjà. Souhaitez-vous installer dans ce dossier malgré tout ? +DirDoesntExistTitle=Le dossier n'existe pas +DirDoesntExist=Le dossier %n%n%1%n%nn'existe pas. Souhaitez-vous que ce dossier soit créé ? + +; *** "Select Components" wizard page +WizardSelectComponents=Composants à installer +SelectComponentsDesc=Quels composants de l'application souhaitez-vous installer ? +SelectComponentsLabel2=Sélectionnez les composants que vous désirez installer ; décochez les composants que vous ne désirez pas installer. Cliquez ensuite sur Suivant pour continuer l'installation. +FullInstallation=Installation complète +; if possible don't translate 'Compact' as 'Minimal' (I mean 'Minimal' in your language) +CompactInstallation=Installation compacte +CustomInstallation=Installation personnalisée +NoUninstallWarningTitle=Composants existants +NoUninstallWarning=L'assistant d'installation a détecté que les composants suivants sont déjà installés sur votre système :%n%n%1%n%nDésélectionner ces composants ne les désinstallera pas pour autant.%n%nVoulez-vous continuer malgré tout ? +ComponentSize1=%1 Ko +ComponentSize2=%1 Mo +ComponentsDiskSpaceGBLabel=Les composants sélectionnés nécessitent au moins [gb] Go d'espace disponible. +ComponentsDiskSpaceMBLabel=Les composants sélectionnés nécessitent au moins [mb] Mo d'espace disponible. + +; *** "Select Additional Tasks" wizard page +WizardSelectTasks=Tâches supplémentaires +SelectTasksDesc=Quelles sont les tâches supplémentaires qui doivent être effectuées ? +SelectTasksLabel2=Sélectionnez les tâches supplémentaires que l'assistant d'installation doit effectuer pendant l'installation de [name], puis cliquez sur Suivant. + +; *** "Select Start Menu Folder" wizard page +WizardSelectProgramGroup=Sélection du dossier du menu Démarrer +SelectStartMenuFolderDesc=Où l'assistant d'installation doit-il placer les raccourcis du programme ? +SelectStartMenuFolderLabel3=L'assistant va créer les raccourcis du programme dans le dossier du menu Démarrer indiqué ci-dessous. +SelectStartMenuFolderBrowseLabel=Cliquez sur Suivant pour continuer. Cliquez sur Parcourir si vous souhaitez sélectionner un autre dossier du menu Démarrer. +MustEnterGroupName=Vous devez saisir un nom de dossier du menu Démarrer. +GroupNameTooLong=Le nom ou le chemin du dossier est trop long. +InvalidGroupName=Le nom du dossier n'est pas valide. +BadGroupName=Le nom du dossier ne doit contenir aucun des caractères suivants :%n%n%1 +NoProgramGroupCheck2=Ne pas créer de &dossier dans le menu Démarrer + +; *** "Ready to Install" wizard page +WizardReady=Prêt à installer +ReadyLabel1=L'assistant dispose à présent de toutes les informations pour installer [name] sur votre ordinateur. +ReadyLabel2a=Cliquez sur Installer pour procéder à l'installation ou sur Précédent pour revoir ou modifier une option d'installation. +ReadyLabel2b=Cliquez sur Installer pour procéder à l'installation. +ReadyMemoUserInfo=Informations sur l'utilisateur : +ReadyMemoDir=Dossier de destination : +ReadyMemoType=Type d'installation : +ReadyMemoComponents=Composants sélectionnés : +ReadyMemoGroup=Dossier du menu Démarrer : +ReadyMemoTasks=Tâches supplémentaires : + +; *** TDownloadWizardPage wizard page and DownloadTemporaryFile +DownloadingLabel=Téléchargement de fichiers supplémentaires... +ButtonStopDownload=&Arrêter le téléchargement +StopDownload=Êtes-vous sûr de vouloir arrêter le téléchargement ? +ErrorDownloadAborted=Téléchargement annulé +ErrorDownloadFailed=Le téléchargement a échoué : %1 %2 +ErrorDownloadSizeFailed=La récupération de la taille du fichier a échouée : %1 %2 +ErrorFileHash1=Le calcul de l'empreinte du fichier a échoué : %1 +ErrorFileHash2=Empreinte du fichier invalide : attendue %1, trouvée %2 +ErrorProgress=Progression invalide : %1 sur %2 +ErrorFileSize=Taille du fichier invalide : attendue %1, trouvée %2 + +; *** "Preparing to Install" wizard page +WizardPreparing=Préparation de l'installation +PreparingDesc=L'assistant d'installation prépare l'installation de [name] sur votre ordinateur. +PreviousInstallNotCompleted=L'installation ou la suppression d'un programme précédent n'est pas totalement achevée. Veuillez redémarrer votre ordinateur pour achever cette installation ou suppression.%n%nUne fois votre ordinateur redémarré, veuillez relancer cet assistant pour reprendre l'installation de [name]. +CannotContinue=L'assistant ne peut pas continuer. Veuillez cliquer sur Annuler pour abandonner l'installation. +ApplicationsFound=Les applications suivantes utilisent des fichiers qui doivent être mis à jour par l'assistant. Il est recommandé d'autoriser l'assistant à fermer ces applications automatiquement. +ApplicationsFound2=Les applications suivantes utilisent des fichiers qui doivent être mis à jour par l'assistant. Il est recommandé d'autoriser l'assistant à fermer ces applications automatiquement. Une fois l'installation terminée, l'assistant essaiera de relancer ces applications. +CloseApplications=&Arrêter les applications automatiquement +DontCloseApplications=&Ne pas arrêter les applications +ErrorCloseApplications=L'assistant d'installation n'a pas pu arrêter toutes les applications automatiquement. Nous vous recommandons de fermer toutes les applications qui utilisent des fichiers devant être mis à jour par l'assistant d'installation avant de continuer. +PrepareToInstallNeedsRestart=L'assistant d'installation doit redémarrer votre ordinateur. Une fois votre ordinateur redémarré, veuillez relancer cet assistant d'installation pour terminer l'installation de [name].%n%nVoulez-vous redémarrer votre ordinateur maintenant ? + +; *** "Installing" wizard page +WizardInstalling=Installation en cours +InstallingLabel=Veuillez patienter pendant que l'assistant installe [name] sur votre ordinateur. + +; *** "Setup Completed" wizard page +FinishedHeadingLabel=Fin de l'installation de [name] +FinishedLabelNoIcons=L'assistant a terminé l'installation de [name] sur votre ordinateur. +FinishedLabel=L'assistant a terminé l'installation de [name] sur votre ordinateur. L'application peut être lancée à l'aide des icônes créées sur le Bureau par l'installation. +ClickFinish=Veuillez cliquer sur Terminer pour quitter l'assistant d'installation. +FinishedRestartLabel=L'assistant doit redémarrer votre ordinateur pour terminer l'installation de [name].%n%nVoulez-vous redémarrer maintenant ? +FinishedRestartMessage=L'assistant doit redémarrer votre ordinateur pour terminer l'installation de [name].%n%nVoulez-vous redémarrer maintenant ? +ShowReadmeCheck=Oui, je souhaite lire le fichier LISEZMOI +YesRadio=&Oui, redémarrer mon ordinateur maintenant +NoRadio=&Non, je préfère redémarrer mon ordinateur plus tard +; used for example as 'Run MyProg.exe' +RunEntryExec=Exécuter %1 +; used for example as 'View Readme.txt' +RunEntryShellExec=Voir %1 + +; *** "Setup Needs the Next Disk" stuff +ChangeDiskTitle=L'assistant a besoin du disque suivant +SelectDiskLabel2=Veuillez insérer le disque %1 et cliquer sur OK.%n%nSi les fichiers de ce disque se trouvent à un emplacement différent de celui indiqué ci-dessous, veuillez saisir le chemin correspondant ou cliquez sur Parcourir. +PathLabel=&Chemin : +FileNotInDir2=Le fichier "%1" ne peut pas être trouvé dans "%2". Veuillez insérer le bon disque ou sélectionner un autre dossier. +SelectDirectoryLabel=Veuillez indiquer l'emplacement du disque suivant. + +; *** Installation phase messages +SetupAborted=L'installation n'est pas terminée.%n%nVeuillez corriger le problème et relancer l'installation. +AbortRetryIgnoreSelectAction=Choisissez une action +AbortRetryIgnoreRetry=&Recommencer +AbortRetryIgnoreIgnore=&Ignorer l'erreur et continuer +AbortRetryIgnoreCancel=Annuler l'installation + +; *** Installation status messages +StatusClosingApplications=Ferme les applications... +StatusCreateDirs=Création des dossiers... +StatusExtractFiles=Extraction des fichiers... +StatusCreateIcons=Création des raccourcis... +StatusCreateIniEntries=Création des entrées du fichier INI... +StatusCreateRegistryEntries=Création des entrées de registre... +StatusRegisterFiles=Enregistrement des fichiers... +StatusSavingUninstall=Sauvegarde des informations de désinstallation... +StatusRunProgram=Finalisation de l'installation... +StatusRestartingApplications=Relance les applications... +StatusRollback=Annulation des modifications... + +; *** Misc. errors +ErrorInternal2=Erreur interne : %1 +ErrorFunctionFailedNoCode=%1 a échoué +ErrorFunctionFailed=%1 a échoué ; code %2 +ErrorFunctionFailedWithMessage=%1 a échoué ; code %2.%n%3 +ErrorExecutingProgram=Impossible d'exécuter le fichier :%n%1 + +; *** Registry errors +ErrorRegOpenKey=Erreur lors de l'ouverture de la clé de registre :%n%1\%2 +ErrorRegCreateKey=Erreur lors de la création de la clé de registre :%n%1\%2 +ErrorRegWriteKey=Erreur lors de l'écriture de la clé de registre :%n%1\%2 + +; *** INI errors +ErrorIniEntry=Erreur d'écriture d'une entrée dans le fichier INI "%1". + +; *** File copying errors +FileAbortRetryIgnoreSkipNotRecommended=&Ignorer ce fichier (non recommandé) +FileAbortRetryIgnoreIgnoreNotRecommended=&Ignorer l'erreur et continuer (non recommandé) +SourceIsCorrupted=Le fichier source est altéré +SourceDoesntExist=Le fichier source "%1" n'existe pas +ExistingFileReadOnly2=Le fichier existant ne peut pas être remplacé parce qu'il est protégé par l'attribut lecture seule. +ExistingFileReadOnlyRetry=&Supprimer l'attribut lecture seule et réessayer +ExistingFileReadOnlyKeepExisting=&Conserver le fichier existant +ErrorReadingExistingDest=Une erreur s'est produite lors de la tentative de lecture du fichier existant : +FileExistsSelectAction=Choisissez une action +FileExists2=Le fichier existe déjà. +FileExistsOverwriteExisting=&Ecraser le fichier existant +FileExistsKeepExisting=&Conserver le fichier existant +FileExistsOverwriteOrKeepAll=&Faire ceci pour les conflits à venir +ExistingFileNewerSelectAction=Choisissez une action +ExistingFileNewer2=Le fichier existant est plus récent que celui que l'assistant d'installation est en train d'installer. +ExistingFileNewerOverwriteExisting=&Ecraser le fichier existant +ExistingFileNewerKeepExisting=&Conserver le fichier existant (recommandé) +ExistingFileNewerOverwriteOrKeepAll=&Faire ceci pour les conflits à venir +ErrorChangingAttr=Une erreur est survenue en essayant de modifier les attributs du fichier existant : +ErrorCreatingTemp=Une erreur est survenue en essayant de créer un fichier dans le dossier de destination : +ErrorReadingSource=Une erreur est survenue lors de la lecture du fichier source : +ErrorCopying=Une erreur est survenue lors de la copie d'un fichier : +ErrorReplacingExistingFile=Une erreur est survenue lors du remplacement d'un fichier existant : +ErrorRestartReplace=Le marquage d'un fichier pour remplacement au redémarrage de l'ordinateur a échoué : +ErrorRenamingTemp=Une erreur est survenue en essayant de renommer un fichier dans le dossier de destination : +ErrorRegisterServer=Impossible d'enregistrer la bibliothèque DLL/OCX : %1 +ErrorRegSvr32Failed=RegSvr32 a échoué et a retourné le code d'erreur %1 +ErrorRegisterTypeLib=Impossible d'enregistrer la bibliothèque de type : %1 + +; *** Nom d'affichage pour la désinstallaton +; par exemple 'Mon Programme (32-bit)' +UninstallDisplayNameMark=%1 (%2) +; ou par exemple 'Mon Programme (32-bit, Tous les utilisateurs)' +UninstallDisplayNameMarks=%1 (%2, %3) +UninstallDisplayNameMark32Bit=32-bit +UninstallDisplayNameMark64Bit=64-bit +UninstallDisplayNameMarkAllUsers=Tous les utilisateurs +UninstallDisplayNameMarkCurrentUser=Utilisateur courant + +; *** Post-installation errors +ErrorOpeningReadme=Une erreur est survenue à l'ouverture du fichier LISEZMOI. +ErrorRestartingComputer=L'installation n'a pas pu redémarrer l'ordinateur. Merci de bien vouloir le faire vous-même. + +; *** Uninstaller messages +UninstallNotFound=Le fichier "%1" n'existe pas. Impossible de désinstaller. +UninstallOpenError=Le fichier "%1" n'a pas pu être ouvert. Impossible de désinstaller +UninstallUnsupportedVer=Le format du fichier journal de désinstallation "%1" n'est pas reconnu par cette version de la procédure de désinstallation. Impossible de désinstaller +UninstallUnknownEntry=Une entrée inconnue (%1) a été rencontrée dans le fichier journal de désinstallation +ConfirmUninstall=Voulez-vous vraiment désinstaller complètement %1 ainsi que tous ses composants ? +UninstallOnlyOnWin64=La désinstallation de ce programme ne fonctionne qu'avec une version 64 bits de Windows. +OnlyAdminCanUninstall=Ce programme ne peut être désinstallé que par un utilisateur disposant des droits d'administration. +UninstallStatusLabel=Veuillez patienter pendant que %1 est retiré de votre ordinateur. +UninstalledAll=%1 a été correctement désinstallé de cet ordinateur. +UninstalledMost=La désinstallation de %1 est terminée.%n%nCertains éléments n'ont pas pu être supprimés automatiquement. Vous pouvez les supprimer manuellement. +UninstalledAndNeedsRestart=Vous devez redémarrer l'ordinateur pour terminer la désinstallation de %1.%n%nVoulez-vous redémarrer maintenant ? +UninstallDataCorrupted=Le ficher "%1" est altéré. Impossible de désinstaller + +; *** Uninstallation phase messages +ConfirmDeleteSharedFileTitle=Supprimer les fichiers partagés ? +ConfirmDeleteSharedFile2=Le système indique que le fichier partagé suivant n'est plus utilisé par aucun programme. Souhaitez-vous que la désinstallation supprime ce fichier partagé ?%n%nSi des programmes utilisent encore ce fichier et qu'il est supprimé, ces programmes ne pourront plus fonctionner correctement. Si vous n'êtes pas sûr, choisissez Non. Laisser ce fichier dans votre système ne posera pas de problème. +SharedFileNameLabel=Nom du fichier : +SharedFileLocationLabel=Emplacement : +WizardUninstalling=État de la désinstallation +StatusUninstalling=Désinstallation de %1... + +; *** Shutdown block reasons +ShutdownBlockReasonInstallingApp=Installe %1. +ShutdownBlockReasonUninstallingApp=Désinstalle %1. + +; Les messages personnalisés suivants ne sont pas utilisé par l'installation +; elle-même, mais si vous les utilisez dans vos scripts, vous devez les +; traduire + +[CustomMessages] + +NameAndVersion=%1 version %2 +AdditionalIcons=Icônes supplémentaires : +CreateDesktopIcon=Créer une icône sur le &Bureau +CreateQuickLaunchIcon=Créer une icône dans la barre de &Lancement rapide +ProgramOnTheWeb=Page d'accueil de %1 +UninstallProgram=Désinstaller %1 +LaunchProgram=Exécuter %1 +AssocFileExtension=&Associer %1 avec l'extension de fichier %2 +AssocingFileExtension=Associe %1 avec l'extension de fichier %2... +AutoStartProgramGroupDescription=Démarrage : +AutoStartProgram=Démarrer automatiquement %1 +AddonHostProgramNotFound=%1 n'a pas été trouvé dans le dossier que vous avez choisi.%n%nVoulez-vous continuer malgré tout ? diff --git a/src/AITool.Setup/INNO/Languages/German.isl b/src/AITool.Setup/INNO/Languages/German.isl new file mode 100644 index 00000000..9877b2b9 --- /dev/null +++ b/src/AITool.Setup/INNO/Languages/German.isl @@ -0,0 +1,406 @@ +; ****************************************************** +; *** *** +; *** Inno Setup version 6.1.0+ German messages *** +; *** *** +; *** Changes 6.0.0+ Author: *** +; *** *** +; *** Jens Brand (jens.brand@wolf-software.de) *** +; *** *** +; *** Original Authors: *** +; *** *** +; *** Peter Stadler (Peter.Stadler@univie.ac.at) *** +; *** Michael Reitz (innosetup@assimilate.de) *** +; *** *** +; *** Contributors: *** +; *** *** +; *** Roland Ruder (info@rr4u.de) *** +; *** Hans Sperber (Hans.Sperber@de.bosch.com) *** +; *** LaughingMan (puma.d@web.de) *** +; *** *** +; ****************************************************** +; +; Diese Übersetzung hält sich an die neue deutsche Rechtschreibung. + +; To download user-contributed translations of this file, go to: +; https://jrsoftware.org/files/istrans/ + +; Note: When translating this text, do not add periods (.) to the end of +; messages that didn't have them already, because on those messages Inno +; Setup adds the periods automatically (appending a period would result in +; two periods being displayed). + +[LangOptions] +; The following three entries are very important. Be sure to read and +; understand the '[LangOptions] section' topic in the help file. +LanguageName=Deutsch +LanguageID=$0407 +LanguageCodePage=1252 +; If the language you are translating to requires special font faces or +; sizes, uncomment any of the following entries and change them accordingly. +;DialogFontName= +;DialogFontSize=8 +;WelcomeFontName=Verdana +;WelcomeFontSize=12 +;TitleFontName=Arial +;TitleFontSize=29 +;CopyrightFontName=Arial +;CopyrightFontSize=8 + +[Messages] + +; *** Application titles +SetupAppTitle=Setup +SetupWindowTitle=Setup - %1 +UninstallAppTitle=Entfernen +UninstallAppFullTitle=%1 entfernen + +; *** Misc. common +InformationTitle=Information +ConfirmTitle=Bestätigen +ErrorTitle=Fehler + +; *** SetupLdr messages +SetupLdrStartupMessage=%1 wird jetzt installiert. Möchten Sie fortfahren? +LdrCannotCreateTemp=Es konnte keine temporäre Datei erstellt werden. Das Setup wurde abgebrochen +LdrCannotExecTemp=Die Datei konnte nicht im temporären Ordner ausgeführt werden. Das Setup wurde abgebrochen +HelpTextNote= + +; *** Startup error messages +LastErrorMessage=%1.%n%nFehler %2: %3 +SetupFileMissing=Die Datei %1 fehlt im Installationsordner. Bitte beheben Sie das Problem oder besorgen Sie sich eine neue Kopie des Programms. +SetupFileCorrupt=Die Setup-Dateien sind beschädigt. Besorgen Sie sich bitte eine neue Kopie des Programms. +SetupFileCorruptOrWrongVer=Die Setup-Dateien sind beschädigt oder inkompatibel zu dieser Version des Setups. Bitte beheben Sie das Problem oder besorgen Sie sich eine neue Kopie des Programms. +InvalidParameter=Ein ungültiger Parameter wurde auf der Kommandozeile übergeben:%n%n%1 +SetupAlreadyRunning=Setup läuft bereits. +WindowsVersionNotSupported=Dieses Programm unterstützt die auf Ihrem Computer installierte Windows-Version nicht. +WindowsServicePackRequired=Dieses Programm benötigt %1 Service Pack %2 oder höher. +NotOnThisPlatform=Dieses Programm kann nicht unter %1 ausgeführt werden. +OnlyOnThisPlatform=Dieses Programm muss unter %1 ausgeführt werden. +OnlyOnTheseArchitectures=Dieses Programm kann nur auf Windows-Versionen installiert werden, die folgende Prozessor-Architekturen unterstützen:%n%n%1 +WinVersionTooLowError=Dieses Programm benötigt %1 Version %2 oder höher. +WinVersionTooHighError=Dieses Programm kann nicht unter %1 Version %2 oder höher installiert werden. +AdminPrivilegesRequired=Sie müssen als Administrator angemeldet sein, um dieses Programm installieren zu können. +PowerUserPrivilegesRequired=Sie müssen als Administrator oder als Mitglied der Hauptbenutzer-Gruppe angemeldet sein, um dieses Programm installieren zu können. +SetupAppRunningError=Das Setup hat entdeckt, dass %1 zurzeit ausgeführt wird.%n%nBitte schließen Sie jetzt alle laufenden Instanzen und klicken Sie auf "OK", um fortzufahren, oder auf "Abbrechen", um zu beenden. +UninstallAppRunningError=Die Deinstallation hat entdeckt, dass %1 zurzeit ausgeführt wird.%n%nBitte schließen Sie jetzt alle laufenden Instanzen und klicken Sie auf "OK", um fortzufahren, oder auf "Abbrechen", um zu beenden. + +; *** Startup questions +PrivilegesRequiredOverrideTitle=Installationsmodus auswählen +PrivilegesRequiredOverrideInstruction=Bitte wählen Sie den Installationsmodus +PrivilegesRequiredOverrideText1=%1 kann für alle Benutzer (erfordert Administrationsrechte) oder nur für Sie installiert werden. +PrivilegesRequiredOverrideText2=%1 kann nur für Sie oder für alle Benutzer (erfordert Administrationsrechte) installiert werden. +PrivilegesRequiredOverrideAllUsers=Installation für &alle Benutzer +PrivilegesRequiredOverrideAllUsersRecommended=Installation für &alle Benutzer (empfohlen) +PrivilegesRequiredOverrideCurrentUser=Installation nur für &Sie +PrivilegesRequiredOverrideCurrentUserRecommended=Installation nur für &Sie (empfohlen) + +; *** Misc. errors +ErrorCreatingDir=Das Setup konnte den Ordner "%1" nicht erstellen. +ErrorTooManyFilesInDir=Das Setup konnte eine Datei im Ordner "%1" nicht erstellen, weil er zu viele Dateien enthält. + +; *** Setup common messages +ExitSetupTitle=Setup verlassen +ExitSetupMessage=Das Setup ist noch nicht abgeschlossen. Wenn Sie jetzt beenden, wird das Programm nicht installiert.%n%nSie können das Setup zu einem späteren Zeitpunkt nochmals ausführen, um die Installation zu vervollständigen.%n%nSetup verlassen? +AboutSetupMenuItem=&Über das Setup ... +AboutSetupTitle=Über das Setup +AboutSetupMessage=%1 Version %2%n%3%n%n%1 Webseite:%n%4 +AboutSetupNote= +TranslatorNote=German translation maintained by Jens Brand (jens.brand@wolf-software.de) + +; *** Buttons +ButtonBack=< &Zurück +ButtonNext=&Weiter > +ButtonInstall=&Installieren +ButtonOK=OK +ButtonCancel=Abbrechen +ButtonYes=&Ja +ButtonYesToAll=J&a für Alle +ButtonNo=&Nein +ButtonNoToAll=N&ein für Alle +ButtonFinish=&Fertigstellen +ButtonBrowse=&Durchsuchen ... +ButtonWizardBrowse=Du&rchsuchen ... +ButtonNewFolder=&Neuen Ordner erstellen + +; *** "Select Language" dialog messages +SelectLanguageTitle=Setup-Sprache auswählen +SelectLanguageLabel=Wählen Sie die Sprache aus, die während der Installation benutzt werden soll: + +; *** Common wizard text +ClickNext="Weiter" zum Fortfahren, "Abbrechen" zum Verlassen. +BeveledLabel= +BrowseDialogTitle=Ordner suchen +BrowseDialogLabel=Wählen Sie einen Ordner aus und klicken Sie danach auf "OK". +NewFolderName=Neuer Ordner + +; *** "Welcome" wizard page +WelcomeLabel1=Willkommen zum [name] Setup-Assistenten +WelcomeLabel2=Dieser Assistent wird jetzt [name/ver] auf Ihrem Computer installieren.%n%nSie sollten alle anderen Anwendungen beenden, bevor Sie mit dem Setup fortfahren. + +; *** "Password" wizard page +WizardPassword=Passwort +PasswordLabel1=Diese Installation wird durch ein Passwort geschützt. +PasswordLabel3=Bitte geben Sie das Passwort ein und klicken Sie danach auf "Weiter". Achten Sie auf korrekte Groß-/Kleinschreibung. +PasswordEditLabel=&Passwort: +IncorrectPassword=Das eingegebene Passwort ist nicht korrekt. Bitte versuchen Sie es noch einmal. + +; *** "License Agreement" wizard page +WizardLicense=Lizenzvereinbarung +LicenseLabel=Lesen Sie bitte folgende wichtige Informationen, bevor Sie fortfahren. +LicenseLabel3=Lesen Sie bitte die folgenden Lizenzvereinbarungen. Benutzen Sie bei Bedarf die Bildlaufleiste oder drücken Sie die "Bild Ab"-Taste. +LicenseAccepted=Ich &akzeptiere die Vereinbarung +LicenseNotAccepted=Ich &lehne die Vereinbarung ab + +; *** "Information" wizard pages +WizardInfoBefore=Information +InfoBeforeLabel=Lesen Sie bitte folgende wichtige Informationen, bevor Sie fortfahren. +InfoBeforeClickLabel=Klicken Sie auf "Weiter", sobald Sie bereit sind, mit dem Setup fortzufahren. +WizardInfoAfter=Information +InfoAfterLabel=Lesen Sie bitte folgende wichtige Informationen, bevor Sie fortfahren. +InfoAfterClickLabel=Klicken Sie auf "Weiter", sobald Sie bereit sind, mit dem Setup fortzufahren. + +; *** "User Information" wizard page +WizardUserInfo=Benutzerinformationen +UserInfoDesc=Bitte tragen Sie Ihre Daten ein. +UserInfoName=&Name: +UserInfoOrg=&Organisation: +UserInfoSerial=&Seriennummer: +UserInfoNameRequired=Sie müssen einen Namen eintragen. + +; *** "Select Destination Location" wizard page +WizardSelectDir=Ziel-Ordner wählen +SelectDirDesc=Wohin soll [name] installiert werden? +SelectDirLabel3=Das Setup wird [name] in den folgenden Ordner installieren. +SelectDirBrowseLabel=Klicken Sie auf "Weiter", um fortzufahren. Klicken Sie auf "Durchsuchen", falls Sie einen anderen Ordner auswählen möchten. +DiskSpaceGBLabel=Mindestens [gb] GB freier Speicherplatz ist erforderlich. +DiskSpaceMBLabel=Mindestens [mb] MB freier Speicherplatz ist erforderlich. +CannotInstallToNetworkDrive=Das Setup kann nicht in einen Netzwerk-Pfad installieren. +CannotInstallToUNCPath=Das Setup kann nicht in einen UNC-Pfad installieren. Wenn Sie auf ein Netzlaufwerk installieren möchten, müssen Sie dem Netzwerkpfad einen Laufwerksbuchstaben zuordnen. +InvalidPath=Sie müssen einen vollständigen Pfad mit einem Laufwerksbuchstaben angeben, z. B.:%n%nC:\Beispiel%n%noder einen UNC-Pfad in der Form:%n%n\\Server\Freigabe +InvalidDrive=Das angegebene Laufwerk bzw. der UNC-Pfad existiert nicht oder es kann nicht darauf zugegriffen werden. Wählen Sie bitte einen anderen Ordner. +DiskSpaceWarningTitle=Nicht genug freier Speicherplatz +DiskSpaceWarning=Das Setup benötigt mindestens %1 KB freien Speicherplatz zum Installieren, aber auf dem ausgewählten Laufwerk sind nur %2 KB verfügbar.%n%nMöchten Sie trotzdem fortfahren? +DirNameTooLong=Der Ordnername/Pfad ist zu lang. +InvalidDirName=Der Ordnername ist nicht gültig. +BadDirName32=Ordnernamen dürfen keine der folgenden Zeichen enthalten:%n%n%1 +DirExistsTitle=Ordner existiert bereits +DirExists=Der Ordner:%n%n%1%n%n existiert bereits. Möchten Sie trotzdem in diesen Ordner installieren? +DirDoesntExistTitle=Ordner ist nicht vorhanden +DirDoesntExist=Der Ordner:%n%n%1%n%nist nicht vorhanden. Soll der Ordner erstellt werden? + +; *** "Select Components" wizard page +WizardSelectComponents=Komponenten auswählen +SelectComponentsDesc=Welche Komponenten sollen installiert werden? +SelectComponentsLabel2=Wählen Sie die Komponenten aus, die Sie installieren möchten. Klicken Sie auf "Weiter", wenn Sie bereit sind, fortzufahren. +FullInstallation=Vollständige Installation +; if possible don't translate 'Compact' as 'Minimal' (I mean 'Minimal' in your language) +CompactInstallation=Kompakte Installation +CustomInstallation=Benutzerdefinierte Installation +NoUninstallWarningTitle=Komponenten vorhanden +NoUninstallWarning=Das Setup hat festgestellt, dass die folgenden Komponenten bereits auf Ihrem Computer installiert sind:%n%n%1%n%nDiese nicht mehr ausgewählten Komponenten werden nicht vom Computer entfernt.%n%nMöchten Sie trotzdem fortfahren? +ComponentSize1=%1 KB +ComponentSize2=%1 MB +ComponentsDiskSpaceGBLabel=Die aktuelle Auswahl erfordert mindestens [gb] GB Speicherplatz. +ComponentsDiskSpaceMBLabel=Die aktuelle Auswahl erfordert mindestens [mb] MB Speicherplatz. + +; *** "Select Additional Tasks" wizard page +WizardSelectTasks=Zusätzliche Aufgaben auswählen +SelectTasksDesc=Welche zusätzlichen Aufgaben sollen ausgeführt werden? +SelectTasksLabel2=Wählen Sie die zusätzlichen Aufgaben aus, die das Setup während der Installation von [name] ausführen soll, und klicken Sie danach auf "Weiter". + +; *** "Select Start Menu Folder" wizard page +WizardSelectProgramGroup=Startmenü-Ordner auswählen +SelectStartMenuFolderDesc=Wo soll das Setup die Programm-Verknüpfungen erstellen? +SelectStartMenuFolderLabel3=Das Setup wird die Programm-Verknüpfungen im folgenden Startmenü-Ordner erstellen. +SelectStartMenuFolderBrowseLabel=Klicken Sie auf "Weiter", um fortzufahren. Klicken Sie auf "Durchsuchen", falls Sie einen anderen Ordner auswählen möchten. +MustEnterGroupName=Sie müssen einen Ordnernamen eingeben. +GroupNameTooLong=Der Ordnername/Pfad ist zu lang. +InvalidGroupName=Der Ordnername ist nicht gültig. +BadGroupName=Der Ordnername darf keine der folgenden Zeichen enthalten:%n%n%1 +NoProgramGroupCheck2=&Keinen Ordner im Startmenü erstellen + +; *** "Ready to Install" wizard page +WizardReady=Bereit zur Installation. +ReadyLabel1=Das Setup ist jetzt bereit, [name] auf Ihrem Computer zu installieren. +ReadyLabel2a=Klicken Sie auf "Installieren", um mit der Installation zu beginnen, oder auf "Zurück", um Ihre Einstellungen zu überprüfen oder zu ändern. +ReadyLabel2b=Klicken Sie auf "Installieren", um mit der Installation zu beginnen. +ReadyMemoUserInfo=Benutzerinformationen: +ReadyMemoDir=Ziel-Ordner: +ReadyMemoType=Setup-Typ: +ReadyMemoComponents=Ausgewählte Komponenten: +ReadyMemoGroup=Startmenü-Ordner: +ReadyMemoTasks=Zusätzliche Aufgaben: + +; *** TDownloadWizardPage wizard page and DownloadTemporaryFile +DownloadingLabel=Lade zusätzliche Dateien herunter... +ButtonStopDownload=Download &abbrechen +StopDownload=Sind Sie sicher, dass Sie den Download abbrechen wollen? +ErrorDownloadAborted=Download abgebrochen +ErrorDownloadFailed=Download fehlgeschlagen: %1 %2 +ErrorDownloadSizeFailed=Fehler beim Ermitteln der Größe: %1 %2 +ErrorFileHash1=Fehler beim Ermitteln der Datei-Prüfsumme: %1 +ErrorFileHash2=Ungültige Datei-Prüfsumme: erwartet %1, gefunden %2 +ErrorProgress=Ungültiger Fortschritt: %1 von %2 +ErrorFileSize=Ungültige Dateigröße: erwartet %1, gefunden %2 + +; *** "Preparing to Install" wizard page +WizardPreparing=Vorbereitung der Installation +PreparingDesc=Das Setup bereitet die Installation von [name] auf diesem Computer vor. +PreviousInstallNotCompleted=Eine vorherige Installation/Deinstallation eines Programms wurde nicht abgeschlossen. Der Computer muss neu gestartet werden, um die Installation/Deinstallation zu beenden.%n%nStarten Sie das Setup nach dem Neustart Ihres Computers erneut, um die Installation von [name] durchzuführen. +CannotContinue=Das Setup kann nicht fortfahren. Bitte klicken Sie auf "Abbrechen" zum Verlassen. +ApplicationsFound=Die folgenden Anwendungen benutzen Dateien, die aktualisiert werden müssen. Es wird empfohlen, Setup zu erlauben, diese Anwendungen zu schließen. +ApplicationsFound2=Die folgenden Anwendungen benutzen Dateien, die aktualisiert werden müssen. Es wird empfohlen, Setup zu erlauben, diese Anwendungen zu schließen. Nachdem die Installation fertiggestellt wurde, versucht Setup, diese Anwendungen wieder zu starten. +CloseApplications=&Schließe die Anwendungen automatisch +DontCloseApplications=Schließe die A&nwendungen nicht +ErrorCloseApplications=Das Setup konnte nicht alle Anwendungen automatisch schließen. Es wird empfohlen, alle Anwendungen zu schließen, die Dateien benutzen, die vom Setup vor einer Fortsetzung aktualisiert werden müssen. +PrepareToInstallNeedsRestart=Das Setup muss Ihren Computer neu starten. Führen Sie nach dem Neustart Setup erneut aus, um die Installation von [name] abzuschließen.%n%nWollen Sie jetzt neu starten? + +; *** "Installing" wizard page +WizardInstalling=Installiere ... +InstallingLabel=Warten Sie bitte, während [name] auf Ihrem Computer installiert wird. + +; *** "Setup Completed" wizard page +FinishedHeadingLabel=Beenden des [name] Setup-Assistenten +FinishedLabelNoIcons=Das Setup hat die Installation von [name] auf Ihrem Computer abgeschlossen. +FinishedLabel=Das Setup hat die Installation von [name] auf Ihrem Computer abgeschlossen. Die Anwendung kann über die installierten Programm-Verknüpfungen gestartet werden. +ClickFinish=Klicken Sie auf "Fertigstellen", um das Setup zu beenden. +FinishedRestartLabel=Um die Installation von [name] abzuschließen, muss das Setup Ihren Computer neu starten. Möchten Sie jetzt neu starten? +FinishedRestartMessage=Um die Installation von [name] abzuschließen, muss das Setup Ihren Computer neu starten.%n%nMöchten Sie jetzt neu starten? +ShowReadmeCheck=Ja, ich möchte die LIESMICH-Datei sehen +YesRadio=&Ja, Computer jetzt neu starten +NoRadio=&Nein, ich werde den Computer später neu starten +; used for example as 'Run MyProg.exe' +RunEntryExec=%1 starten +; used for example as 'View Readme.txt' +RunEntryShellExec=%1 anzeigen + +; *** "Setup Needs the Next Disk" stuff +ChangeDiskTitle=Nächsten Datenträger einlegen +SelectDiskLabel2=Legen Sie bitte Datenträger %1 ein und klicken Sie auf "OK".%n%nWenn sich die Dateien von diesem Datenträger in einem anderen als dem angezeigten Ordner befinden, dann geben Sie bitte den korrekten Pfad ein oder klicken auf "Durchsuchen". +PathLabel=&Pfad: +FileNotInDir2=Die Datei "%1" befindet sich nicht in "%2". Bitte Ordner ändern oder richtigen Datenträger einlegen. +SelectDirectoryLabel=Geben Sie bitte an, wo der nächste Datenträger eingelegt wird. + +; *** Installation phase messages +SetupAborted=Das Setup konnte nicht abgeschlossen werden.%n%nBeheben Sie bitte das Problem und starten Sie das Setup erneut. +AbortRetryIgnoreSelectAction=Bitte auswählen +AbortRetryIgnoreRetry=&Nochmals versuchen +AbortRetryIgnoreIgnore=&Den Fehler ignorieren und fortfahren +AbortRetryIgnoreCancel=Installation abbrechen + +; *** Installation status messages +StatusClosingApplications=Anwendungen werden geschlossen ... +StatusCreateDirs=Ordner werden erstellt ... +StatusExtractFiles=Dateien werden entpackt ... +StatusCreateIcons=Verknüpfungen werden erstellt ... +StatusCreateIniEntries=INI-Einträge werden erstellt ... +StatusCreateRegistryEntries=Registry-Einträge werden erstellt ... +StatusRegisterFiles=Dateien werden registriert ... +StatusSavingUninstall=Deinstallationsinformationen werden gespeichert ... +StatusRunProgram=Installation wird beendet ... +StatusRestartingApplications=Neustart der Anwendungen ... +StatusRollback=Änderungen werden rückgängig gemacht ... + +; *** Misc. errors +ErrorInternal2=Interner Fehler: %1 +ErrorFunctionFailedNoCode=%1 schlug fehl +ErrorFunctionFailed=%1 schlug fehl; Code %2 +ErrorFunctionFailedWithMessage=%1 schlug fehl; Code %2.%n%3 +ErrorExecutingProgram=Datei kann nicht ausgeführt werden:%n%1 + +; *** Registry errors +ErrorRegOpenKey=Registry-Schlüssel konnte nicht geöffnet werden:%n%1\%2 +ErrorRegCreateKey=Registry-Schlüssel konnte nicht erstellt werden:%n%1\%2 +ErrorRegWriteKey=Fehler beim Schreiben des Registry-Schlüssels:%n%1\%2 + +; *** INI errors +ErrorIniEntry=Fehler beim Erstellen eines INI-Eintrages in der Datei "%1". + +; *** File copying errors +FileAbortRetryIgnoreSkipNotRecommended=Diese Datei &überspringen (nicht empfohlen) +FileAbortRetryIgnoreIgnoreNotRecommended=Den Fehler &ignorieren und fortfahren (nicht empfohlen) +SourceIsCorrupted=Die Quelldatei ist beschädigt +SourceDoesntExist=Die Quelldatei "%1" existiert nicht +ExistingFileReadOnly2=Die vorhandene Datei kann nicht ersetzt werden, da sie schreibgeschützt ist. +ExistingFileReadOnlyRetry=&Den Schreibschutz entfernen und noch einmal versuchen +ExistingFileReadOnlyKeepExisting=Die &vorhandene Datei behalten +ErrorReadingExistingDest=Lesefehler in Datei: +FileExistsSelectAction=Aktion auswählen +FileExists2=Die Datei ist bereits vorhanden. +FileExistsOverwriteExisting=Vorhandene Datei &überschreiben +FileExistsKeepExisting=Vorhandene Datei &behalten +FileExistsOverwriteOrKeepAll=&Dies auch für die nächsten Konflikte ausführen +ExistingFileNewerSelectAction=Aktion auswählen +ExistingFileNewer2=Die vorhandene Datei ist neuer als die Datei, die installiert werden soll. +ExistingFileNewerOverwriteExisting=Vorhandene Datei &überschreiben +ExistingFileNewerKeepExisting=Vorhandene Datei &behalten (empfohlen) +ExistingFileNewerOverwriteOrKeepAll=&Dies auch für die nächsten Konflikte ausführen +ErrorChangingAttr=Fehler beim Ändern der Datei-Attribute: +ErrorCreatingTemp=Fehler beim Erstellen einer Datei im Ziel-Ordner: +ErrorReadingSource=Fehler beim Lesen der Quelldatei: +ErrorCopying=Fehler beim Kopieren einer Datei: +ErrorReplacingExistingFile=Fehler beim Ersetzen einer vorhandenen Datei: +ErrorRestartReplace="Ersetzen nach Neustart" fehlgeschlagen: +ErrorRenamingTemp=Fehler beim Umbenennen einer Datei im Ziel-Ordner: +ErrorRegisterServer=DLL/OCX konnte nicht registriert werden: %1 +ErrorRegSvr32Failed=RegSvr32-Aufruf scheiterte mit Exit-Code %1 +ErrorRegisterTypeLib=Typen-Bibliothek konnte nicht registriert werden: %1 + +; *** Uninstall display name markings +; used for example as 'Mein Programm (32 Bit)' +UninstallDisplayNameMark=%1 (%2) +; used for example as 'Mein Programm (32 Bit, Alle Benutzer)' +UninstallDisplayNameMarks=%1 (%2, %3) +UninstallDisplayNameMark32Bit=32 Bit +UninstallDisplayNameMark64Bit=64 Bit +UninstallDisplayNameMarkAllUsers=Alle Benutzer +UninstallDisplayNameMarkCurrentUser=Aktueller Benutzer + +; *** Post-installation errors +ErrorOpeningReadme=Fehler beim Öffnen der LIESMICH-Datei. +ErrorRestartingComputer=Das Setup konnte den Computer nicht neu starten. Bitte führen Sie den Neustart manuell durch. + +; *** Uninstaller messages +UninstallNotFound=Die Datei "%1" existiert nicht. Entfernen der Anwendung fehlgeschlagen. +UninstallOpenError=Die Datei "%1" konnte nicht geöffnet werden. Entfernen der Anwendung fehlgeschlagen. +UninstallUnsupportedVer=Das Format der Deinstallationsdatei "%1" konnte nicht erkannt werden. Entfernen der Anwendung fehlgeschlagen. +UninstallUnknownEntry=In der Deinstallationsdatei wurde ein unbekannter Eintrag (%1) gefunden. +ConfirmUninstall=Sind Sie sicher, dass Sie %1 und alle zugehörigen Komponenten entfernen möchten? +UninstallOnlyOnWin64=Diese Installation kann nur unter 64-Bit-Windows-Versionen entfernt werden. +OnlyAdminCanUninstall=Diese Installation kann nur von einem Benutzer mit Administrator-Rechten entfernt werden. +UninstallStatusLabel=Warten Sie bitte, während %1 von Ihrem Computer entfernt wird. +UninstalledAll=%1 wurde erfolgreich von Ihrem Computer entfernt. +UninstalledMost=Entfernen von %1 beendet.%n%nEinige Komponenten konnten nicht entfernt werden. Diese können von Ihnen manuell gelöscht werden. +UninstalledAndNeedsRestart=Um die Deinstallation von %1 abzuschließen, muss Ihr Computer neu gestartet werden.%n%nMöchten Sie jetzt neu starten? +UninstallDataCorrupted="%1"-Datei ist beschädigt. Entfernen der Anwendung fehlgeschlagen. + +; *** Uninstallation phase messages +ConfirmDeleteSharedFileTitle=Gemeinsame Datei entfernen? +ConfirmDeleteSharedFile2=Das System zeigt an, dass die folgende gemeinsame Datei von keinem anderen Programm mehr benutzt wird. Möchten Sie diese Datei entfernen lassen?%nSollte es doch noch Programme geben, die diese Datei benutzen und sie wird entfernt, funktionieren diese Programme vielleicht nicht mehr richtig. Wenn Sie unsicher sind, wählen Sie "Nein", um die Datei im System zu belassen. Es schadet Ihrem System nicht, wenn Sie die Datei behalten. +SharedFileNameLabel=Dateiname: +SharedFileLocationLabel=Ordner: +WizardUninstalling=Entfernen (Status) +StatusUninstalling=Entferne %1 ... + +; *** Shutdown block reasons +ShutdownBlockReasonInstallingApp=Installation von %1. +ShutdownBlockReasonUninstallingApp=Deinstallation von %1. + +; The custom messages below aren't used by Setup itself, but if you make +; use of them in your scripts, you'll want to translate them. + +[CustomMessages] + +NameAndVersion=%1 Version %2 +AdditionalIcons=Zusätzliche Symbole: +CreateDesktopIcon=&Desktop-Symbol erstellen +CreateQuickLaunchIcon=Symbol in der Schnellstartleiste erstellen +ProgramOnTheWeb=%1 im Internet +UninstallProgram=%1 entfernen +LaunchProgram=%1 starten +AssocFileExtension=&Registriere %1 mit der %2-Dateierweiterung +AssocingFileExtension=%1 wird mit der %2-Dateierweiterung registriert... +AutoStartProgramGroupDescription=Beginn des Setups: +AutoStartProgram=Starte automatisch%1 +AddonHostProgramNotFound=%1 konnte im ausgewählten Ordner nicht gefunden werden.%n%nMöchten Sie dennoch fortfahren? + diff --git a/src/AITool.Setup/INNO/Languages/Hebrew.isl b/src/AITool.Setup/INNO/Languages/Hebrew.isl new file mode 100644 index 00000000..98d9b498 --- /dev/null +++ b/src/AITool.Setup/INNO/Languages/Hebrew.isl @@ -0,0 +1,377 @@ +; *** Inno Setup version 6.1.0+ Hebrew messages (s_h(at)enativ.com) *** +; +; https://jrsoftware.org/files/istrans/ +; Translated by s_h (s_h@enativ.com) (c) 2020 +; + + +[LangOptions] +LanguageName=<05E2><05D1><05E8><05D9><05EA> +LanguageID=$040D +LanguageCodePage=1255 +; If the language you are translating to requires special font faces or +; sizes, uncomment any of the following entries and change them accordingly. +;DialogFontName= +;DialogFontSize=8 +;WelcomeFontName=Tahoma +;WelcomeFontSize=11 +;TitleFontName=Arial +;TitleFontSize=29 +;CopyrightFontName=Arial +;CopyrightFontSize=8 +RightToLeft=yes + +[Messages] + +; *** Application titles +SetupAppTitle= +SetupWindowTitle= - %1 +UninstallAppTitle= +UninstallAppFullTitle= %1 + +; *** Misc. common +InformationTitle= +ConfirmTitle= +ErrorTitle= + +; *** SetupLdr messages +SetupLdrStartupMessage= %1 . ? +LdrCannotCreateTemp= . +LdrCannotExecTemp= . + +; *** Startup error messages +LastErrorMessage=%1.%n%n %2: %3 +SetupFileMissing= %1 . . +SetupFileCorrupt= . . +SetupFileCorruptOrWrongVer= , . . +InvalidParameter= :%n%n%1 +SetupAlreadyRunning= . +WindowsVersionNotSupported= . +WindowsServicePackRequired= %1 %2 . +NotOnThisPlatform= %1. +OnlyOnThisPlatform= %1. +OnlyOnTheseArchitectures= '' :%n%n%1 +WinVersionTooLowError= %1 %2. +WinVersionTooHighError= %1 %2 +AdminPrivilegesRequired= . +PowerUserPrivilegesRequired= , ' ' . +SetupAppRunningError= %1 .%n%n , '' , '' . +UninstallAppRunningError= %1 .%n%n , '' , '' . + +; *** Startup questions +PrivilegesRequiredOverrideTitle= +PrivilegesRequiredOverrideInstruction= +PrivilegesRequiredOverrideText1=%1 ( ), . +PrivilegesRequiredOverrideText2=%1 , ( ). +PrivilegesRequiredOverrideAllUsers= & +PrivilegesRequiredOverrideAllUsersRecommended= & () +PrivilegesRequiredOverrideCurrentUser= & +PrivilegesRequiredOverrideCurrentUserRecommended= & () + +; *** Misc. errors +ErrorCreatingDir= "%1" +ErrorTooManyFilesInDir= "%1" + +; *** Setup common messages +ExitSetupTitle= +ExitSetupMessage= . , .%n%n .%n%n ? +AboutSetupMenuItem=& ... +AboutSetupTitle= +AboutSetupMessage=%1 %2%n%3%n%n%1 :%n%4 +AboutSetupNote= +TranslatorNote= + +; *** Buttons +ButtonBack=< & +ButtonNext=& > +ButtonInstall=& +ButtonOK= +ButtonCancel= +ButtonYes=& +ButtonYesToAll= & +ButtonNo=& +ButtonNoToAll=& +ButtonFinish=& +ButtonBrowse=&... +ButtonWizardBrowse=... +ButtonNewFolder=& + +; *** "Select Language" dialog messages +SelectLanguageTitle= +SelectLanguageLabel= . + +; *** Common wizard text +ClickNext= '' , '' . +BeveledLabel= +BrowseDialogTitle= +BrowseDialogLabel= '' +NewFolderName= + +; *** "Welcome" wizard page +WelcomeLabel1= [name] +WelcomeLabel2= [name/ver] .%n%n . + +; *** "Password" wizard page +WizardPassword= +PasswordLabel1= . +PasswordLabel3= , '' . , . +PasswordEditLabel=&: +IncorrectPassword= . . + +; *** "License Agreement" wizard page +WizardLicense= +LicenseLabel= . +LicenseLabel3= . . +LicenseAccepted= & +LicenseNotAccepted= & + +; *** "Information" wizard pages +WizardInfoBefore= +InfoBeforeLabel= . +InfoBeforeClickLabel= , ''. +WizardInfoAfter= +InfoAfterLabel= +InfoAfterClickLabel= , ''. + +; *** "User Information" wizard page +WizardUserInfo= +UserInfoDesc= . +UserInfoName=& : +UserInfoOrg=&: +UserInfoSerial=& : +UserInfoNameRequired= . + +; *** "Select Destination Location" wizard page +WizardSelectDir= +SelectDirDesc= [name]? +SelectDirLabel3= [name] . +SelectDirBrowseLabel=, ''. , ''. +DiskSpaceGBLabel= [gb] GB . +DiskSpaceMBLabel= [mb] MB . +CannotInstallToNetworkDrive= . +CannotInstallToUNCPath= UNC. +InvalidPath= ; :%n%nC:\APP%n%n UNC :%n%n\\server\share +InvalidDrive= -UNC . . +DiskSpaceWarningTitle= +DiskSpaceWarning= %1KB , %2KB . ? +DirNameTooLong= +InvalidDirName= . +BadDirName32= :%n%n%1 +DirExistsTitle= +DirExists=:%n%n%1%n%n . ? +DirDoesntExistTitle= +DirDoesntExist=:%n%n%1%n%n . ? + +; *** "Select Components" wizard page +WizardSelectComponents= +SelectComponentsDesc= ? +SelectComponentsLabel2= ; . '' . +FullInstallation= +; if possible don't translate 'Compact' as 'Minimal' (I mean 'Minimal' in your language) +CompactInstallation= +CustomInstallation= +NoUninstallWarningTitle= +NoUninstallWarning= :%n%n%1%n .%n%n ? +ComponentSize1=%1 KB +ComponentSize2=%1 MB +ComponentsDiskSpaceGBLabel= [gb] GB . +ComponentsDiskSpaceMBLabel= [mb] MB . + +; *** "Select Additional Tasks" wizard page +WizardSelectTasks= +SelectTasksDesc= ? +SelectTasksLabel2= [name], ''. + +; *** "Select Start Menu Folder" wizard page +WizardSelectProgramGroup= '' +SelectStartMenuFolderDesc= ? +SelectStartMenuFolderLabel3= ''. +SelectStartMenuFolderBrowseLabel=, ''. , ''. +MustEnterGroupName= . +GroupNameTooLong= +InvalidGroupName= -. +BadGroupName= :%n%n%1 +NoProgramGroupCheck2=& '' + +; *** "Ready to Install" wizard page +WizardReady= +ReadyLabel1= [name] . +ReadyLabel2a= '' , '' . +ReadyLabel2b= '' +ReadyMemoUserInfo= : +ReadyMemoDir= : +ReadyMemoType= : +ReadyMemoComponents= : +ReadyMemoGroup= '': +ReadyMemoTasks= : + +; *** TDownloadWizardPage wizard page and DownloadTemporaryFile +DownloadingLabel= ... +ButtonStopDownload=& +StopDownload= ? +ErrorDownloadAborted= +ErrorDownloadFailed= : %1 %2 +ErrorDownloadSizeFailed= : %1 %2 +ErrorFileHash1= : %2 +ErrorFileHash2= : %2, %2 +ErrorProgress= : %1 %1 +ErrorFileSize= : %1, %1 + +; *** "Preparing to Install" wizard page +WizardPreparing= +PreparingDesc= [name] . +PreviousInstallNotCompleted=/ . .%n%n , [name]. +CannotContinue= . '' . +ApplicationsFound= . . +ApplicationsFound2= . . , . +CloseApplications=& +DontCloseApplications=& +ErrorCloseApplications= . . +PrepareToInstallNeedsRestart= . , [name].%n%n ? + +; *** "Installing" wizard page +WizardInstalling= +InstallingLabel= [name] . + +; *** "Setup Completed" wizard page +FinishedHeadingLabel= [name] +FinishedLabelNoIcons= [name] . +FinishedLabel= [name] . . +ClickFinish= '' . +FinishedRestartLabel= [name], . ? +FinishedRestartMessage= [name], .%n%n ? +ShowReadmeCheck=, -' ' +YesRadio=&, +NoRadio=&, +; used for example as 'Run MyProg.exe' +RunEntryExec= %1 +; used for example as 'View Readme.txt' +RunEntryShellExec= %1 + +; *** "Setup Needs the Next Disk" stuff +ChangeDiskTitle= +SelectDiskLabel2= ' %1 ''.%n%n , ''. +PathLabel=&: +FileNotInDir2= "%1" "%2". . +SelectDirectoryLabel= . + +; *** Installation phase messages +SetupAborted= .%n%n . +AbortRetryIgnoreSelectAction= +AbortRetryIgnoreRetry=& +AbortRetryIgnoreIgnore=& +AbortRetryIgnoreCancel= + +; *** Installation status messages +StatusClosingApplications= ... +StatusCreateDirs= ... +StatusExtractFiles= ... +StatusCreateIcons= ... +StatusCreateIniEntries= INI... +StatusCreateRegistryEntries= ... +StatusRegisterFiles= ... +StatusSavingUninstall= ... +StatusRunProgram= ... +StatusRestartingApplications= ... +StatusRollback= ... + +; *** Misc. errors +ErrorInternal2= : %1 +ErrorFunctionFailedNoCode=%1 +ErrorFunctionFailed=%1 ; %2 +ErrorFunctionFailedWithMessage=%1 ; %2.%n%3 +ErrorExecutingProgram= :%n%1 + +; *** Registry errors +ErrorRegOpenKey= :%n%1\%2 +ErrorRegCreateKey= :%n%1\%2 +ErrorRegWriteKey= :%n%1\%2 + +; *** INI errors +ErrorIniEntry= INI "%1". + +; *** File copying errors +FileAbortRetryIgnoreSkipNotRecommended=& ( ) +FileAbortRetryIgnoreIgnoreNotRecommended=& ( ) +SourceIsCorrupted= +SourceDoesntExist= "%1" +ExistingFileReadOnly2= . +ExistingFileReadOnlyRetry=& +ExistingFileReadOnlyKeepExisting=& +ErrorReadingExistingDest= : +FileExistsSelectAction= +FileExists2= . +FileExistsOverwriteExisting=& +FileExistsKeepExisting=& +FileExistsOverwriteOrKeepAll=& +ExistingFileNewerSelectAction= +ExistingFileNewer2= +ExistingFileNewerOverwriteExisting=& +ExistingFileNewerKeepExisting=& () +ExistingFileNewerOverwriteOrKeepAll=& +ErrorChangingAttr= : +ErrorCreatingTemp= : +ErrorReadingSource= : +ErrorCopying= : +ErrorReplacingExistingFile= : +ErrorRestartReplace= -RestartReplace: +ErrorRenamingTemp= : +ErrorRegisterServer= DLL/OCX: %1 +ErrorRegSvr32Failed=RegSvr32 %1 +ErrorRegisterTypeLib= : %1 + +; *** Uninstall display name markings +UninstallDisplayNameMark=%1 (%2) +UninstallDisplayNameMarks=%1 (%2, %3) +UninstallDisplayNameMark32Bit=32-bit +UninstallDisplayNameMark64Bit=64-bit +UninstallDisplayNameMarkAllUsers= +UninstallDisplayNameMarkCurrentUser= + +; *** Post-installation errors +ErrorOpeningReadme= ' '. +ErrorRestartingComputer= . . + +; *** Uninstaller messages +UninstallNotFound= "%1" . . +UninstallOpenError= "%1". . +UninstallUnsupportedVer= "%1" " . +UninstallUnknownEntry= (%1) . +ConfirmUninstall= %1 ? +UninstallOnlyOnWin64= '' 64-. +OnlyAdminCanUninstall= . +UninstallStatusLabel= %1 . +UninstalledAll=%1 . +UninstalledMost= %1 .%n%n " , . +UninstalledAndNeedsRestart= %1, .%n%n ? +UninstallDataCorrupted= "%1" . + +; *** Uninstallation phase messages +ConfirmDeleteSharedFileTitle= ? +ConfirmDeleteSharedFile2= . ?%n%n , . , ''. . +SharedFileNameLabel= : +SharedFileLocationLabel=: +WizardUninstalling= +StatusUninstalling= %1... + +; *** Shutdown block reasons +ShutdownBlockReasonInstallingApp= %1. +ShutdownBlockReasonUninstallingApp= %1. + +; The custom messages below aren't used by Setup itself, but if you make +; use of them in your scripts, you'll want to translate them. + +[CustomMessages] + +NameAndVersion=%1 %2 +AdditionalIcons= : +CreateDesktopIcon= & +CreateQuickLaunchIcon= +ProgramOnTheWeb=%1 +UninstallProgram= %1 +LaunchProgram= %1 +AssocFileExtension=& %1 %2 +AssocingFileExtension= %1 %2 +AutoStartProgramGroupDescription= : +AutoStartProgram= %1 +AddonHostProgramNotFound=%1 .%n%n ? \ No newline at end of file diff --git a/src/AITool.Setup/INNO/Languages/Hungarian.isl b/src/AITool.Setup/INNO/Languages/Hungarian.isl new file mode 100644 index 00000000..c36de334 --- /dev/null +++ b/src/AITool.Setup/INNO/Languages/Hungarian.isl @@ -0,0 +1,386 @@ +; *** Inno Setup version 6.1.0+ Hungarian messages *** +; Based on the translation of Kornél Pál, kornelpal@gmail.com +; István Szabó, E-mail: istvanszabo890629@gmail.com +; +; To download user-contributed translations of this file, go to: +; http://www.jrsoftware.org/files/istrans/ +; +; Note: When translating this text, do not add periods (.) to the end of +; messages that didn't have them already, because on those messages Inno +; Setup adds the periods automatically (appending a period would result in +; two periods being displayed). + +[LangOptions] +; The following three entries are very important. Be sure to read and +; understand the '[LangOptions] section' topic in the help file. +LanguageName=Magyar +LanguageID=$040E +LanguageCodePage=1250 +; If the language you are translating to requires special font faces or +; sizes, uncomment any of the following entries and change them accordingly. +;DialogFontName= +;DialogFontSize=8 +;WelcomeFontName=Verdana +;WelcomeFontSize=12 +;TitleFontName=Arial CE +;TitleFontSize=29 +;CopyrightFontName=Arial CE +;CopyrightFontSize=8 + +[Messages] + +; *** Application titles +SetupAppTitle=Telepítő +SetupWindowTitle=%1 - Telepítő +UninstallAppTitle=Eltávolító +UninstallAppFullTitle=%1 - Eltávolító + +; *** Misc. common +InformationTitle=Információk +ConfirmTitle=Megerősít +ErrorTitle=Hiba + +; *** SetupLdr messages +SetupLdrStartupMessage=%1 telepítve lesz. Szeretné folytatni? +LdrCannotCreateTemp=Átmeneti fájl létrehozása nem lehetséges. A telepítés megszakítva +LdrCannotExecTemp=Fájl futattása nem lehetséges az átmeneti könyvtárban. A telepítés megszakítva +HelpTextNote= + +; *** Startup error messages +LastErrorMessage=%1.%n%nHiba %2: %3 +SetupFileMissing=A(z) %1 fájl hiányzik a telepítő könyvtárából. Kérem hárítsa el a problémát, vagy szerezzen be egy másik példányt a programból! +SetupFileCorrupt=A telepítési fájlok sérültek. Kérem, szerezzen be új másolatot a programból! +SetupFileCorruptOrWrongVer=A telepítési fájlok sérültek, vagy inkompatibilisek a telepítő ezen verziójával. Hárítsa el a problémát, vagy szerezzen be egy másik példányt a programból! +InvalidParameter=A parancssorba átadott paraméter érvénytelen:%n%n%1 +SetupAlreadyRunning=A Telepítő már fut. +WindowsVersionNotSupported=A program nem támogatja a Windows ezen verzióját. +WindowsServicePackRequired=A program futtatásához %1 Service Pack %2 vagy újabb szükséges. +NotOnThisPlatform=Ez a program nem futtatható %1 alatt. +OnlyOnThisPlatform=Ezt a programot %1 alatt kell futtatni. +OnlyOnTheseArchitectures=A program kizárólag a következő processzor architektúrákhoz tervezett Windows-on telepíthető:%n%n%1 +WinVersionTooLowError=A program futtatásához %1 %2 verziója vagy későbbi szükséges. +WinVersionTooHighError=Ez a program nem telepíthető %1 %2 vagy későbbire. +AdminPrivilegesRequired=Csak rendszergazdai módban telepíthető ez a program. +PowerUserPrivilegesRequired=Csak rendszergazdaként vagy kiemelt felhasználóként telepíthető ez a program. +SetupAppRunningError=A telepítő úgy észlelte %1 jelenleg fut.%n%nZárja be az összes példányt, majd kattintson az 'OK'-ra a folytatáshoz, vagy a 'Mégse'-re a kilépéshez. +UninstallAppRunningError=Az eltávolító úgy észlelte %1 jelenleg fut.%n%nZárja be az összes példányt, majd kattintson az 'OK'-ra a folytatáshoz, vagy a 'Mégse'-re a kilépéshez. + +; *** Startup questions +PrivilegesRequiredOverrideTitle=Telepítési mód kiválasztása +PrivilegesRequiredOverrideInstruction=Válasszon telepítési módot +PrivilegesRequiredOverrideText1=%1 telepíthető az összes felhasználónak (rendszergazdai jogok szükségesek), vagy csak magának. +PrivilegesRequiredOverrideText2=%1 csak magának telepíthető, vagy az összes felhasználónak (rendszergazdai jogok szükségesek). +PrivilegesRequiredOverrideAllUsers=Telepítés &mindenkinek +PrivilegesRequiredOverrideAllUsersRecommended=Telepítés &mindenkinek (ajánlott) +PrivilegesRequiredOverrideCurrentUser=Telepítés csak &nekem +PrivilegesRequiredOverrideCurrentUserRecommended=Telepítés csak &nekem (ajánlott) + +; *** Misc. errors +ErrorCreatingDir=A Telepítő nem tudta létrehozni a(z) "%1" könyvtárat +ErrorTooManyFilesInDir=Nem hozható létre fájl a(z) "%1" könyvtárban, mert az már túl sok fájlt tartalmaz + +; *** Setup common messages +ExitSetupTitle=Kilépés a telepítőből +ExitSetupMessage=A telepítés még folyamatban van. Ha most kilép, a program nem kerül telepítésre.%n%nMásik alkalommal is futtatható a telepítés befejezéséhez%n%nKilép a telepítőből? +AboutSetupMenuItem=&Névjegy... +AboutSetupTitle=Telepítő névjegye +AboutSetupMessage=%1 %2 verzió%n%3%n%nAz %1 honlapja:%n%4 +AboutSetupNote= +TranslatorNote= + +; *** Buttons +ButtonBack=< &Vissza +ButtonNext=&Tovább > +ButtonInstall=&Telepít +ButtonOK=OK +ButtonCancel=Mégse +ButtonYes=&Igen +ButtonYesToAll=&Mindet +ButtonNo=&Nem +ButtonNoToAll=&Egyiket se +ButtonFinish=&Befejezés +ButtonBrowse=&Tallózás... +ButtonWizardBrowse=T&allózás... +ButtonNewFolder=Új &könyvtár + +; *** "Select Language" dialog messages +SelectLanguageTitle=Telepítő nyelvi beállítás +SelectLanguageLabel=Válassza ki a telepítés alatt használt nyelvet. + +; *** Common wizard text +ClickNext=A folytatáshoz kattintson a 'Tovább'-ra, a kilépéshez a 'Mégse'-re. +BeveledLabel= +BrowseDialogTitle=Válasszon könyvtárt +BrowseDialogLabel=Válasszon egy könyvtárat az alábbi listából, majd kattintson az 'OK'-ra. +NewFolderName=Új könyvtár + +; *** "Welcome" wizard page +WelcomeLabel1=Üdvözli a(z) [name] Telepítővarázslója. +WelcomeLabel2=A(z) [name/ver] telepítésre kerül a számítógépén.%n%nAjánlott minden, egyéb futó alkalmazás bezárása a folytatás előtt. + +; *** "Password" wizard page +WizardPassword=Jelszó +PasswordLabel1=Ez a telepítés jelszóval védett. +PasswordLabel3=Kérem adja meg a jelszót, majd kattintson a 'Tovább'-ra. A jelszavak kis- és nagy betű érzékenyek lehetnek. +PasswordEditLabel=&Jelszó: +IncorrectPassword=Az ön által megadott jelszó helytelen. Próbálja újra. + +; *** "License Agreement" wizard page +WizardLicense=Licencszerződés +LicenseLabel=Olvassa el figyelmesen az információkat folytatás előtt. +LicenseLabel3=Kérem, olvassa el az alábbi licencszerződést. A telepítés folytatásához, el kell fogadnia a szerződést. +LicenseAccepted=&Elfogadom a szerződést +LicenseNotAccepted=&Nem fogadom el a szerződést + +; *** "Information" wizard pages +WizardInfoBefore=Információk +InfoBeforeLabel=Olvassa el a következő fontos információkat a folytatás előtt. +InfoBeforeClickLabel=Ha készen áll, kattintson a 'Tovább'-ra. +WizardInfoAfter=Információk +InfoAfterLabel=Olvassa el a következő fontos információkat a folytatás előtt. +InfoAfterClickLabel=Ha készen áll, kattintson a 'Tovább'-ra. + +; *** "User Information" wizard page +WizardUserInfo=Felhasználó adatai +UserInfoDesc=Kérem, adja meg az adatait! +UserInfoName=&Felhasználónév: +UserInfoOrg=&Szervezet: +UserInfoSerial=&Sorozatszám: +UserInfoNameRequired=Meg kell adnia egy nevet! + +; *** "Select Destination Location" wizard page +WizardSelectDir=Válasszon célkönyvtárat +SelectDirDesc=Hova települjön a(z) [name]? +SelectDirLabel3=A(z) [name] az alábbi könyvtárba lesz telepítve. +SelectDirBrowseLabel=A folytatáshoz, kattintson a 'Tovább'-ra. Ha másik könyvtárat választana, kattintson a 'Tallózás'-ra. +DiskSpaceGBLabel=Legalább [gb] GB szabad területre van szükség. +DiskSpaceMBLabel=Legalább [mb] MB szabad területre van szükség. +CannotInstallToNetworkDrive=A Telepítő nem tud hálózati meghajtóra telepíteni. +CannotInstallToUNCPath=A Telepítő nem tud hálózati UNC elérési útra telepíteni. +InvalidPath=Teljes útvonalat adjon meg, a meghajtó betűjelével; például:%n%nC:\Alkalmazás%n%nvagy egy hálózati útvonalat a következő alakban:%n%n\\kiszolgáló\megosztás +InvalidDrive=A kiválasztott meghajtó vagy hálózati megosztás nem létezik vagy nem elérhető. Válasszon egy másikat. +DiskSpaceWarningTitle=Nincs elég szabad terület +DiskSpaceWarning=A Telepítőnek legalább %1 KB szabad lemezterületre van szüksége, viszont a kiválasztott meghajtón csupán %2 KB áll rendelkezésre.%n%nMindenképpen folytatja? +DirNameTooLong=A könyvtár neve vagy az útvonal túl hosszú. +InvalidDirName=A könyvtár neve érvénytelen. +BadDirName32=A könyvtárak nevei ezen karakterek egyikét sem tartalmazhatják:%n%n%1 +DirExistsTitle=A könyvtár már létezik +DirExists=A könyvtár:%n%n%1%n%nmár létezik. Mindenképp ide akar telepíteni? +DirDoesntExistTitle=A könyvtár nem létezik +DirDoesntExist=A könyvtár:%n%n%1%n%nnem létezik. Szeretné létrehozni? + +; *** "Select Components" wizard page +WizardSelectComponents=Összetevők kiválasztása +SelectComponentsDesc=Mely összetevők kerüljenek telepítésre? +SelectComponentsLabel2=Jelölje ki a telepítendő összetevőket; törölje a telepíteni nem kívánt összetevőket. Kattintson a 'Tovább'-ra, ha készen áll a folytatásra. +FullInstallation=Teljes telepítés +; if possible don't translate 'Compact' as 'Minimal' (I mean 'Minimal' in your language) +CompactInstallation=Szokásos telepítés +CustomInstallation=Egyéni telepítés +NoUninstallWarningTitle=Létező összetevő +NoUninstallWarning=A telepítő úgy találta, hogy a következő összetevők már telepítve vannak a számítógépre:%n%n%1%n%nEzen összetevők kijelölésének törlése, nem távolítja el azokat a számítógépről.%n%nMindenképpen folytatja? +ComponentSize1=%1 KB +ComponentSize2=%1 MB +ComponentsDiskSpaceGBLabel=A jelenlegi kijelölés legalább [gb] GB lemezterületet igényel. +ComponentsDiskSpaceMBLabel=A jelenlegi kijelölés legalább [mb] MB lemezterületet igényel. + +; *** "Select Additional Tasks" wizard page +WizardSelectTasks=További feladatok +SelectTasksDesc=Mely kiegészítő feladatok kerüljenek végrehajtásra? +SelectTasksLabel2=Jelölje ki, mely kiegészítő feladatokat hajtsa végre a Telepítő a(z) [name] telepítése során, majd kattintson a 'Tovább'-ra. + +; *** "Select Start Menu Folder" wizard page +WizardSelectProgramGroup=Start Menü könyvtára +SelectStartMenuFolderDesc=Hova helyezze a Telepítő a program parancsikonjait? +SelectStartMenuFolderLabel3=A Telepítő a program parancsikonjait a Start menü következő mappájában fogja létrehozni. +SelectStartMenuFolderBrowseLabel=A folytatáshoz kattintson a 'Tovább'-ra. Ha másik mappát választana, kattintson a 'Tallózás'-ra. +MustEnterGroupName=Meg kell adnia egy mappanevet. +GroupNameTooLong=A könyvtár neve vagy az útvonal túl hosszú. +InvalidGroupName=A könyvtár neve érvénytelen. +BadGroupName=A könyvtárak nevei ezen karakterek egyikét sem tartalmazhatják:%n%n%1 +NoProgramGroupCheck2=&Ne hozzon létre mappát a Start menüben + +; *** "Ready to Install" wizard page +WizardReady=Készen állunk a telepítésre +ReadyLabel1=A Telepítő készen áll, a(z) [name] számítógépre telepítéshez. +ReadyLabel2a=Kattintson a 'Telepítés'-re a folytatáshoz, vagy a "Vissza"-ra a beállítások áttekintéséhez vagy megváltoztatásához. +ReadyLabel2b=Kattintson a 'Telepítés'-re a folytatáshoz. +ReadyMemoUserInfo=Felhasználó adatai: +ReadyMemoDir=Telepítés célkönyvtára: +ReadyMemoType=Telepítés típusa: +ReadyMemoComponents=Választott összetevők: +ReadyMemoGroup=Start menü mappája: +ReadyMemoTasks=Kiegészítő feladatok: + +; *** TDownloadWizardPage wizard page and DownloadTemporaryFile +DownloadingLabel=További fájlok letöltése... +ButtonStopDownload=&Letöltés megállítása +StopDownload=Biztos, hogy leakarja állítani a letöltést? +ErrorDownloadAborted=Letöltés megszakítva +ErrorDownloadFailed=A letöltés meghiúsult: %1 %2 +ErrorDownloadSizeFailed=Hiba a fájlméret lekérése során: %1 %2 +ErrorFileHash1=Fájl Hash (hasítóérték) hiba: %1 +ErrorFileHash2=Érvénytelen hash fájl, várt érték: %1, számított: %2 +ErrorProgress=Érvénytelen folyamat: %1 : %2 +ErrorFileSize=Érvénytelen fájlméret, várt méret %1, számított: %2 + +; *** "Preparing to Install" wizard page +WizardPreparing=Felkészülés a telepítésre +PreparingDesc=A Telepítő felkészül a(z) [name] számítógépre történő telepítéshez. +PreviousInstallNotCompleted=Egy korábbi program telepítése/eltávolítása nem fejeződött be. Újra kell indítania a számítógépét a másik telepítés befejezéséhez.%n%nA számítógépe újraindítása után ismét futtassa a Telepítőt a(z) [name] telepítésének befejezéséhez. +CannotContinue=A telepítés nem folytatható. A kilépéshez kattintson a 'Mégse'-re. +ApplicationsFound=A következő alkalmazások olyan fájlokat használnak, amelyeket a Telepítőnek frissíteni kell. Ajánlott, hogy engedélyezze a Telepítőnek ezen alkalmazások automatikus bezárását. +ApplicationsFound2=A következő alkalmazások olyan fájlokat használnak, amelyeket a Telepítőnek frissíteni kell. Ajánlott, hogy engedélyezze a Telepítőnek ezen alkalmazások automatikus bezárását. A telepítés befejezése után, a Telepítő megkísérli az alkalmazások újraindítását. +CloseApplications=&Alkalmazások automatikus bezárása +DontCloseApplications=&Ne zárja be az alkalmazásokat +ErrorCloseApplications=A Telepítő nem tudott minden alkalmazást automatikusan bezárni. A folytatás előtt ajánlott minden, a Telepítő által frissítendő fájlokat használó alkalmazást bezárni. +PrepareToInstallNeedsRestart=A telepítőnek most újra kell indítania a számítógépet. Az újraindítás után, futtassa újból ezt a telepítőt, hogy befejezze a [name] telepítését.%n%nÚjra szeretné most indítani a gépet? + +; *** "Installing" wizard page +WizardInstalling=Telepítés +InstallingLabel=Kérem várjon, amíg a(z) [name] telepítése zajlik. + +; *** "Setup Completed" wizard page +FinishedHeadingLabel=A(z) [name] telepítésének befejezése +FinishedLabelNoIcons=A Telepítő végzett a(z) [name] telepítésével. +FinishedLabel=A Telepítő végzett a(z) [name] telepítésével. Az alkalmazást a létrehozott ikonok kiválasztásával indíthatja. +ClickFinish=Kattintson a 'Befejezés'-re a kilépéshez. +FinishedRestartLabel=A(z) [name] telepítésének befejezéséhez újra kell indítani a számítógépet. Újraindítja most? +FinishedRestartMessage=A(z) [name] telepítésének befejezéséhez, a Telepítőnek újra kell indítani a számítógépet.%n%nÚjraindítja most? +ShowReadmeCheck=Igen, szeretném elolvasni a FONTOS fájlt +YesRadio=&Igen, újraindítás most +NoRadio=&Nem, később indítom újra +; used for example as 'Run MyProg.exe' +RunEntryExec=%1 futtatása +; used for example as 'View Readme.txt' +RunEntryShellExec=%1 megtekintése + +; *** "Setup Needs the Next Disk" stuff +ChangeDiskTitle=A Telepítőnek szüksége van a következő lemezre +SelectDiskLabel2=Helyezze be a(z) %1. lemezt és kattintson az 'OK'-ra.%n%nHa a fájlok a lemez egy a megjelenítettől különböző mappájában találhatók, írja be a helyes útvonalat vagy kattintson a 'Tallózás'-ra. +PathLabel=Ú&tvonal: +FileNotInDir2=A(z) "%1" fájl nem található a következő helyen: "%2". Helyezze be a megfelelő lemezt vagy válasszon egy másik mappát. +SelectDirectoryLabel=Adja meg a következő lemez helyét. + +; *** Installation phase messages +SetupAborted=A telepítés nem fejeződött be.%n%nHárítsa el a hibát és futtassa újból a Telepítőt. +AbortRetryIgnoreSelectAction=Válasszon műveletet +AbortRetryIgnoreRetry=&Újra +AbortRetryIgnoreIgnore=&Hiba elvetése és folytatás +AbortRetryIgnoreCancel=Telepítés megszakítása + +; *** Installation status messages +StatusClosingApplications=Alkalmazások bezárása... +StatusCreateDirs=Könyvtárak létrehozása... +StatusExtractFiles=Fájlok kibontása... +StatusCreateIcons=Parancsikonok létrehozása... +StatusCreateIniEntries=INI bejegyzések létrehozása... +StatusCreateRegistryEntries=Rendszerleíró bejegyzések létrehozása... +StatusRegisterFiles=Fájlok regisztrálása... +StatusSavingUninstall=Eltávolító információk mentése... +StatusRunProgram=Telepítés befejezése... +StatusRestartingApplications=Alkalmazások újraindítása... +StatusRollback=Változtatások visszavonása... + +; *** Misc. errors +ErrorInternal2=Belső hiba: %1 +ErrorFunctionFailedNoCode=Sikertelen %1 +ErrorFunctionFailed=Sikertelen %1; kód: %2 +ErrorFunctionFailedWithMessage=Sikertelen %1; kód: %2.%n%3 +ErrorExecutingProgram=Nem hajtható végre a fájl:%n%1 + +; *** Registry errors +ErrorRegOpenKey=Nem nyitható meg a rendszerleíró kulcs:%n%1\%2 +ErrorRegCreateKey=Nem hozható létre a rendszerleíró kulcs:%n%1\%2 +ErrorRegWriteKey=Nem módosítható a rendszerleíró kulcs:%n%1\%2 + +; *** INI errors +ErrorIniEntry=Hiba lépett fel az INI bejegyzés során, ebben a fájlban: "%1". + +; *** File copying errors +FileAbortRetryIgnoreSkipNotRecommended=&Fájl kihagyása (nem ajánlott) +FileAbortRetryIgnoreIgnoreNotRecommended=&Hiba elvetése és folytatás (nem ajánlott) +SourceIsCorrupted=A forrásfájl megsérült +SourceDoesntExist=A(z) "%1" forrásfájl nem létezik +ExistingFileReadOnly2=A fájl csak olvashatóként van jelölve, ezért nem cserélhető le. +ExistingFileReadOnlyRetry=Csak &olvasható tulajdonság eltávolítása és újra próbálkozás +ExistingFileReadOnlyKeepExisting=&Létező fájl megtartása +ErrorReadingExistingDest=Hiba lépett fel a fájl olvasása közben: +FileExistsSelectAction=Mit tegyünk? +FileExists2=A fájl már létezik. +FileExistsOverwriteExisting=A &létező fájl felülírása +FileExistsKeepExisting=A &már létező fájl megtartása +FileExistsOverwriteOrKeepAll=&Tegyük ezt, a következő fájlütközések esetén is +ExistingFileNewerSelectAction=Mit kíván tenni? +ExistingFileNewer2=A létező fájl újabb a telepítésre kerülőnél +ExistingFileNewerOverwriteExisting=A &létező fájl felülírása +ExistingFileNewerKeepExisting=&Tartsuk meg a létező fájlt (ajánlott) +ExistingFileNewerOverwriteOrKeepAll=&Tegyük ezt, a következő fájlütközések esetén is +ErrorChangingAttr=Hiba lépett fel a fájl attribútumának módosítása közben: +ErrorCreatingTemp=Hiba lépett fel a fájl telepítési könyvtárban történő létrehozása közben: +ErrorReadingSource=Hiba lépett fel a forrásfájl olvasása közben: +ErrorCopying=Hiba lépett fel a fájl másolása közben: +ErrorReplacingExistingFile=Hiba lépett fel a létező fájl cseréje közben: +ErrorRestartReplace=A fájl cseréje az újraindítás után sikertelen volt: +ErrorRenamingTemp=Hiba lépett fel fájl telepítési könyvtárban történő átnevezése közben: +ErrorRegisterServer=Nem lehet regisztrálni a DLL-t/OCX-et: %1 +ErrorRegSvr32Failed=Sikertelen RegSvr32. A visszaadott kód: %1 +ErrorRegisterTypeLib=Nem lehet regisztrálni a típustárat: %1 + +; *** Uninstall display name markings +; used for example as 'My Program (32-bit)' +UninstallDisplayNameMark=%1 (%2) +; used for example as 'My Program (32-bit, All users)' +UninstallDisplayNameMarks=%1 (%2, %3) +UninstallDisplayNameMark32Bit=32-bit +UninstallDisplayNameMark64Bit=64-bit +UninstallDisplayNameMarkAllUsers=Minden felhasználó +UninstallDisplayNameMarkCurrentUser=Jelenlegi felhasználó + +; *** Post-installation errors +ErrorOpeningReadme=Hiba lépett fel a FONTOS fájl megnyitása közben. +ErrorRestartingComputer=A Telepítő nem tudta újraindítani a számítógépet. Indítsa újra kézileg. + +; *** Uninstaller messages +UninstallNotFound=A(z) "%1" fájl nem létezik. Nem távolítható el. +UninstallOpenError=A(z) "%1" fájl nem nyitható meg. Nem távolítható el +UninstallUnsupportedVer=A(z) "%1" eltávolítási naplófájl formátumát nem tudja felismerni az eltávolító jelen verziója. Az eltávolítás nem folytatható +UninstallUnknownEntry=Egy ismeretlen bejegyzés (%1) található az eltávolítási naplófájlban +ConfirmUninstall=Biztosan el kívánja távolítani a(z) %1 programot és minden összetevőjét? +UninstallOnlyOnWin64=Ezt a telepítést csak 64-bites Windows operációs rendszerről lehet eltávolítani. +OnlyAdminCanUninstall=Ezt a telepítést csak adminisztrációs jogokkal rendelkező felhasználó távolíthatja el. +UninstallStatusLabel=Legyen türelemmel, amíg a(z) %1 számítógépéről történő eltávolítása befejeződik. +UninstalledAll=A(z) %1 sikeresen el lett távolítva a számítógépről. +UninstalledMost=A(z) %1 eltávolítása befejeződött.%n%nNéhány elemet nem lehetettet eltávolítani. Törölje kézileg. +UninstalledAndNeedsRestart=A(z) %1 eltávolításának befejezéséhez újra kell indítania a számítógépét.%n%nÚjraindítja most? +UninstallDataCorrupted=A(z) "%1" fájl sérült. Nem távolítható el. + +; *** Uninstallation phase messages +ConfirmDeleteSharedFileTitle=Törli a megosztott fájlt? +ConfirmDeleteSharedFile2=A rendszer azt jelzi, hogy a következő megosztott fájlra már nincs szüksége egyetlen programnak sem. Eltávolítja a megosztott fájlt?%n%nHa más programok még mindig használják a megosztott fájlt, akkor az eltávolítása után lehet, hogy nem fognak megfelelően működni. Ha bizonytalan, válassza a Nemet. A fájl megtartása nem okoz problémát a rendszerben. +SharedFileNameLabel=Fájlnév: +SharedFileLocationLabel=Helye: +WizardUninstalling=Eltávolítás állapota +StatusUninstalling=%1 eltávolítása... + +; *** Shutdown block reasons +ShutdownBlockReasonInstallingApp=%1 telepítése. +ShutdownBlockReasonUninstallingApp=%1 eltávolítása. + +; The custom messages below aren't used by Setup itself, but if you make +; use of them in your scripts, you'll want to translate them. + +[CustomMessages] + +NameAndVersion=%1, verzió: %2 +AdditionalIcons=További parancsikonok: +CreateDesktopIcon=&Asztali ikon létrehozása +CreateQuickLaunchIcon=&Gyorsindító parancsikon létrehozása +ProgramOnTheWeb=%1 az interneten +UninstallProgram=Eltávolítás - %1 +LaunchProgram=Indítás %1 +AssocFileExtension=A(z) %1 &társítása a(z) %2 fájlkiterjesztéssel +AssocingFileExtension=A(z) %1 társítása a(z) %2 fájlkiterjesztéssel... +AutoStartProgramGroupDescription=Indítópult: +AutoStartProgram=%1 automatikus indítása +AddonHostProgramNotFound=A(z) %1 nem található a kiválasztott könyvtárban.%n%nMindenképpen folytatja? diff --git a/src/AITool.Setup/INNO/Languages/Icelandic.isl b/src/AITool.Setup/INNO/Languages/Icelandic.isl new file mode 100644 index 00000000..395a7576 --- /dev/null +++ b/src/AITool.Setup/INNO/Languages/Icelandic.isl @@ -0,0 +1,361 @@ +; *** Inno Setup version 6.1.0+ Icelandic messages *** +; +; Translator: Stefán Örvar Sigmundsson, eMedia Intellect +; Contact: emi@emi.is +; Date: 2020-07-25 + +[LangOptions] + +LanguageName=<00CD>slenska +LanguageID=$040F +LanguageCodePage=1252 + +[Messages] + +; *** Application titles +SetupAppTitle=Uppsetning +SetupWindowTitle=Uppsetning - %1 +UninstallAppTitle=Niðurtaka +UninstallAppFullTitle=%1-niðurtaka + +; *** Misc. common +InformationTitle=Upplýsingar +ConfirmTitle=Staðfesta +ErrorTitle=Villa + +; *** SetupLdr messages +SetupLdrStartupMessage=Þetta mun uppsetja %1. Vilt þú halda áfram? +LdrCannotCreateTemp=Ófært um að skapa tímabundna skrá. Uppsetningu hætt +LdrCannotExecTemp=Ófært um að keyra skrá í tímabundna skráasafninu. Uppsetningu hætt +HelpTextNote= + +; *** Startup error messages +LastErrorMessage=%1.%n%nVilla %2: %3 +SetupFileMissing=Skrána %1 vantar úr uppsetningarskráasafninu. Vinsamlega leiðréttu vandamálið eða fáðu nýtt afrita af forritinu. +SetupFileCorrupt=Uppsetningarskrárnar eru spilltar. Vinsamlega fáðu nýtt afrita af forritinu. +SetupFileCorruptOrWrongVer=Uppsetningarskrárnar eru spilltar eða eru ósamrýmanlegar við þessa útgáfu af Uppsetningu. Vinsamlega leiðréttu vandamálið eða fáðu nýtt afrit af forritinu. +InvalidParameter=Ógild færibreyta var afhend á skipanalínunni:%n%n%1 +SetupAlreadyRunning=Uppsetning er nú þegar keyrandi. +WindowsVersionNotSupported=Þetta forrit styður ekki útgáfuna af Windows sem tölvan þín er keyrandi. +WindowsServicePackRequired=Þetta forrit krefst Þjónustupakka %2 eða síðari. +NotOnThisPlatform=Þetta forrit mun ekki keyra á %1. +OnlyOnThisPlatform=Þetta forrit verður að keyra á %1. +OnlyOnTheseArchitectures=Þetta forrit er einungis hægt að uppsetja á útgáfur af Windows hannaðar fyrir eftirfarandi gjörvahannanir:%n%n%1 +WinVersionTooLowError=Þetta forrit krefst %1-útgáfu %2 eða síðari. +WinVersionTooHighError=Þetta forrit er ekki hægt að uppsetja á %1-útgáfu %2 eða síðari. +AdminPrivilegesRequired=Þú verður að vera innskráð(ur) sem stjórnandi meðan þú uppsetur þetta forrit. +PowerUserPrivilegesRequired=Þú verður að vera innskráð(ur) sem stjórnandi eða sem meðlimur Power Users-hópsins meðan þú uppsetur þetta forrit. +SetupAppRunningError=Uppsetning hefur greint að %1 er eins og er keyrandi.%n%nVinsamlega lokaðu öllum tilvikum þess núna, smelltu síðan á Í lagi til að halda áfram eða Hætta við til að hætta. +UninstallAppRunningError=Niðurtaka hefur greint að %1 er eins og er keyrandi.%n%nVinsamlega lokaðu öllum tilvikum þess núna, smelltu síðan á Í lagi til að halda áfram eða Hætta við til að hætta. + +; *** Startup questions +PrivilegesRequiredOverrideTitle=Veldu uppsetningarham +PrivilegesRequiredOverrideInstruction=Veldu uppsetningarham +PrivilegesRequiredOverrideText1=%1 er hægt að setja upp fyrir alla notendur (krefst stjórnandaréttinda) eða fyrir þig einungis. +PrivilegesRequiredOverrideText2=%1 er hægt að setja upp fyrir þig einungis eða fyrir alla notendur (krefst stjórnandaréttinda). +PrivilegesRequiredOverrideAllUsers=Uppsetja fyrir &alla notendur +PrivilegesRequiredOverrideAllUsersRecommended=Uppsetja fyrir &alla notendur (ráðlagt) +PrivilegesRequiredOverrideCurrentUser=Uppsetja fyrir &mig einungis +PrivilegesRequiredOverrideCurrentUserRecommended=Uppsetja fyrir &mig einungis (ráðlagt) + +; *** Misc. errors +ErrorCreatingDir=Uppsetningunni var ófært um að skapa skráasafnið „%1“ +ErrorTooManyFilesInDir=Ófært um að skapa skrá í skráasafninu „%1“ vegna þess það inniheldur of margar skrár + +; *** Setup common messages +ExitSetupTitle=Hætta í Uppsetningu +ExitSetupMessage=Uppsetningu er ekki lokið. Ef þú hættir núna mun forritið ekki vera uppsett.%n%nÞú getur keyrt Uppsetningu aftur síðar til að ljúka uppsetningunni.%n%nHætta í Uppsetningu? +AboutSetupMenuItem=&Um Uppsetningu… +AboutSetupTitle=Um Uppsetningu +AboutSetupMessage=%1 útgáfa %2%n%3%n%n%1 heimasíðu:%n%4 +AboutSetupNote= +TranslatorNote=Stefán Örvar Sigmundsson, eMedia Intellect + +; *** Buttons +ButtonBack=< &Fyrri +ButtonNext=&Næst > +ButtonInstall=&Uppsetja +ButtonOK=Í lagi +ButtonCancel=Hætta við +ButtonYes=&Já +ButtonYesToAll=Já við &öllu +ButtonNo=&Nei +ButtonNoToAll=&Nei við öllu +ButtonFinish=&Ljúka +ButtonBrowse=&Vafra… +ButtonWizardBrowse=&Vafra… +ButtonNewFolder=&Skapa nýja möppu + +; *** "Select Language" dialog messages +SelectLanguageTitle=Veldu tungumál Uppsetningar +SelectLanguageLabel=Veldu tungumálið sem nota á við uppsetninguna. + +; *** Common wizard text +ClickNext=Smelltu á Næst til að halda áfram eða Hætta við til að hætta Uppsetningu. +BeveledLabel= +BrowseDialogTitle=Vafra eftir möppu +BrowseDialogLabel=Veldu möppu í listanum fyrir neðan, smelltu síðan á Í lagi. +NewFolderName=Ný mappa + +; *** "Welcome" wizard page +WelcomeLabel1=Velkomin(n) í [name]-uppsetningaraðstoðarann +WelcomeLabel2=Þetta mun uppsetja [name/ver] á þína tölvu.%n%nÞað er ráðlagt að þú lokir öllum öðrum hugbúnaði áður en haldið er áfram. + +; *** "Password" wizard page +WizardPassword=Aðgangsorð +PasswordLabel1=Þessi uppsetning er aðgangsorðsvarin. +PasswordLabel3=Vinsamlega veitu aðgangsorðið, smelltu síðan á Næst til að halda áfram. Aðgangsorð eru hástafanæm. +PasswordEditLabel=&Aðgangsorð: +IncorrectPassword=Aðgangsorðið sem þú innslóst er ekki rétt. Vinsamlega reyndu aftur. + +; *** "License Agreement" wizard page +WizardLicense=Leyfissamningur +LicenseLabel=Vinsamlega lestu hinar eftirfarandi mikilvægu upplýsingar áður en haldið er áfram. +LicenseLabel3=Vinsamlega lestu eftirfarandi leyfissamning. Þú verður að samþykkja skilmála samningsins áður en haldið er áfram með uppsetninguna. +LicenseAccepted=Ég &samþykki samninginn +LicenseNotAccepted=Ég samþykki &ekki samninginn + +; *** "Information" wizard pages +WizardInfoBefore=Upplýsingar +InfoBeforeLabel=Vinsamlega lestu hinar eftirfarandi mikilvægu upplýsingar áður en haldið er áfram. +InfoBeforeClickLabel=Þegar þú ert tilbúin(n) til að halda áfram með Uppsetninguna, smelltu á Næst. +WizardInfoAfter=Upplýsingar +InfoAfterLabel=Vinsamlega lestu hinar eftirfarandi mikilvægu upplýsingar áður en haldið er áfram. +InfoAfterClickLabel=Þegar þú ert tilbúin(n) til að halda áfram með Uppsetninguna, smelltu á Næst. + +; *** "User Information" wizard page +WizardUserInfo=Notandaupplýsingar +UserInfoDesc=Vinsamlega innsláðu upplýsingarnar þínar. +UserInfoName=&Notandanafn: +UserInfoOrg=&Stofnun: +UserInfoSerial=&Raðnúmer: +UserInfoNameRequired=Þú verður að innslá nafn. + +; *** "Select Destination Location" wizard page +WizardSelectDir=Velja staðsetningu +SelectDirDesc=Hvar ætti [name] að vera uppsettur? +SelectDirLabel3=Uppsetning mun uppsetja [name] í hina eftirfarandi möppu. +SelectDirBrowseLabel=Til að halda áfram, smelltu á Næst. Ef þú vilt velja aðra möppu, smelltu á Vafra. +DiskSpaceGBLabel=Að minnsta kosti [gb] GB af lausu diskplássi er krafist. +DiskSpaceMBLabel=Að minnsta kosti [mb] MB af lausu diskplássi er krafist. +CannotInstallToNetworkDrive=Uppsetning getur ekki uppsett á netdrif. +CannotInstallToUNCPath=Uppsetning getur ekki uppsett á UNC-slóð. +InvalidPath=Þú verður að innslá fulla slóð með drifstaf; til dæmis:%n%nC:\APP%n%neða UNC-slóð samkvæmt sniðinu:%n%n\\server\share +InvalidDrive=Drifið eða UNC-deilingin sem þú valdir er ekki til eða er ekki aðgengileg. Vinsamlega veldu annað. +DiskSpaceWarningTitle=Ekki nóg diskpláss +DiskSpaceWarning=Uppsetning krefst að minnsta kosti %1 KB af lausu plássi til að uppsetja, en hið valda drif hefur einungis %2 KB tiltæk.%n%nVilt þú halda áfram hvort sem er? +DirNameTooLong=Möppunafnið eða slóðin er of löng. +InvalidDirName=Möppunafnið er ekki gilt. +BadDirName32=Möppunöfn geta ekki innihaldið nein af hinum eftirfarandi rittáknum:%n%n%1 +DirExistsTitle=Mappa er til +DirExists=Mappan:%n%n%1%n%ner nú þegar til. Vilt þú uppsetja í þá möppu hvort sem er? +DirDoesntExistTitle=Mappa er ekki til +DirDoesntExist=Mappan:%n%n%1%n%ner ekki til. Vilt þú að mappan sé sköpuð? + +; *** "Select Components" wizard page +WizardSelectComponents=Velja atriði +SelectComponentsDesc=Hvaða atriði ætti að uppsetja? +SelectComponentsLabel2=Veldu atriðin sem þú vilt uppsetja; hreinsaðu atriðin sem þú vilt ekki uppsetja. Smelltu á Næst þegar þú ert tilbúin(n) til að halda áfram. +FullInstallation=Full uppsetning +CompactInstallation=Samanþjöppuð uppsetning +CustomInstallation=Sérsnídd uppsetning +NoUninstallWarningTitle=Atriði eru til +NoUninstallWarning=Uppsetning hefur greint það að eftirfarandi atriði eru nú þegar uppsett á tölvunni þinni:%n%n%1%n%nAð afvelja þessi atriði mun ekki niðurtaka þau.%n%nVilt þú halda áfram hvort sem er? +ComponentSize1=%1 KB +ComponentSize2=%1 MB +ComponentsDiskSpaceGBLabel=Núverandi val krefst að minnsta kosti [gb] GB af diskplássi. +ComponentsDiskSpaceMBLabel=Núverandi val krefst að minnsta kosti [mb] MB af diskplássi. + +; *** "Select Additional Tasks" wizard page +WizardSelectTasks=Veldu aukaleg verk +SelectTasksDesc=Hvaða aukalegu verk ættu að vera framkvæmd? +SelectTasksLabel2=Veldu hin aukalegu verk sem þú vilt að Uppsetning framkvæmi meðan [name] er uppsettur, ýttu síðan á Næst. + +; *** "Select Start Menu Folder" wizard page +WizardSelectProgramGroup=Veldu Upphafsvalmyndarmöppu +SelectStartMenuFolderDesc=Hvert ætti Uppsetning að setja skyndivísa forritsins? +SelectStartMenuFolderLabel3=Uppsetning mun skapa skyndivísa forritsins í hina eftirfarandi Upphafsvalmyndarmöppu. +SelectStartMenuFolderBrowseLabel=Til að halda áfram, smelltu á Næst. Ef þú vilt velja aðra möppu, smelltu á Vafra. +MustEnterGroupName=Þú verður að innslá möppunafn. +GroupNameTooLong=Möppunafnið eða slóðin er of löng. +InvalidGroupName=Möppunafnið er ekki gilt. +BadGroupName=Möppunafnið getur ekki innihaldið neitt af hinum eftirfarandi rittáknum:%n%n%1 +NoProgramGroupCheck2=&Ekki skapa Upphafsvalmyndarmöppu + +; *** "Ready to Install" wizard page +WizardReady=Tilbúin til að uppsetja +ReadyLabel1=Uppsetning er núna tilbúin til að hefja uppsetningu [name] á tölvuna þína. +ReadyLabel2a=Smelltu á Uppsetja til að halda áfram uppsetningunni eða smelltu á Til baka ef þú vilt endurskoða eða breyta einhverjum stillingum. +ReadyLabel2b=Smelltu á Uppsetja til að halda áfram uppsetningunni. +ReadyMemoUserInfo=Notandaupplýsingar: +ReadyMemoDir=Staðsetning: +ReadyMemoType=Uppsetningartegund: +ReadyMemoComponents=Valin atriði: +ReadyMemoGroup=Upphafsvalmyndarmappa: +ReadyMemoTasks=Aukaleg verk: + +; *** TDownloadWizardPage wizard page and DownloadTemporaryFile +DownloadingLabel=Niðurhlaðandi aukalegum skrám… +ButtonStopDownload=&Stöðva niðurhleðslu +StopDownload=Ert þú viss um að þú viljir stöðva niðurhleðsluna? +ErrorDownloadAborted=Niðurhleðslu hætt +ErrorDownloadFailed=Niðurhleðsla mistókst: %1 %2 +ErrorDownloadSizeFailed=Mistókst að sækja stærð: %1 %2 +ErrorFileHash1=Skráarhakk mistókst: %1 +ErrorFileHash2=Ógilt skráarhakk: bjóst við %1, fékk %2 +ErrorProgress=Ógild framvinda: %1 of %2 +ErrorFileSize=Ógild skráarstærð: bjóst við %1, fékk %2 + +; *** "Preparing to Install" wizard page +WizardPreparing=Undirbúandi uppsetningu +PreparingDesc=Uppsetning er undirbúandi uppsetningu [name] á tölvuna þína. +PreviousInstallNotCompleted=Uppsetningu/Fjarlægingu eftirfarandi forrits var ekki lokið. Þú þarft að endurræsa tölvuna þína til að ljúka þeirri uppsetningu.%n%nEftir endurræsingu tölvunnar þinnar, keyrðu Uppsetningu aftur til að ljúka uppsetningu [name]. +CannotContinue=Uppsetning getur ekki haldið áfram. Vinsamlega smelltu á Hætta við til að hætta. +ApplicationsFound=Eftirfarandi hugbúnaður er notandi skrár sem þurfa að vera uppfærðar af Uppsetningu. Það er ráðlagt að þú leyfir Uppsetningu sjálfvirkt að loka þessum hugbúnaði. +ApplicationsFound2=Eftirfarandi hugbúnaður er notandi skrár sem þurfa að vera uppfærðar af Uppsetningu. Það er ráðlagt að þú leyfir Uppsetningu sjálfvirkt að loka þessum hugbúnaði. Eftir að uppsetningunni lýkur mun Uppsetning reyna að endurræsa hugbúnaðinn. +CloseApplications=&Sjálfvirkt loka hugbúnaðinum +DontCloseApplications=&Ekki loka hugbúnaðinum +ErrorCloseApplications=Uppsetningu var ófært um að sjálfvirkt loka öllum hugbúnaði. Það er ráðlagt að þú lokir öllum hugbúnaði notandi skrár sem þurfa að vera uppfærðar af Uppsetningu áður en haldið er áfram. +PrepareToInstallNeedsRestart=Þú verður að endurræsa tölvuna þína. Eftir að hafa endurræst tölvuna þína, keyrðu Uppsetningu aftur til að ljúka uppsetningu [name].%n%nVilt þú endurræsa núna? + +; *** "Installing" wizard page +WizardInstalling=Uppsetjandi +InstallingLabel=Vinsamlega bíddu meðan Uppsetning uppsetur [name] á tölvuna þína. + +; *** "Setup Completed" wizard page +FinishedHeadingLabel=Ljúkandi [name]-uppsetningaraðstoðaranum +FinishedLabelNoIcons=Uppsetning hefur lokið uppsetningu [name] á tölvuna þína. +FinishedLabel=Uppsetning hefur lokið uppsetningu [name] á þinni tölvu. Hugbúnaðurinn getur verið ræstur með því að velja hina uppsettu skyndivísa. +ClickFinish=Smelltu á Ljúka til að hætta í Uppsetningu. +FinishedRestartLabel=Til að ljúka uppsetningu [name] þarft Uppsetning að endurræsa tölvuna þína. Vilt þú endurræsa núna? +FinishedRestartMessage=Til að ljúka uppsetningu [name] þarf Uppsetning að endurræsa tölvuna þína.%n%nVilt þú endurræsa núna? +ShowReadmeCheck=Já, ég vil skoða README-skrána +YesRadio=&Já, endurræsa tölvuna núna +NoRadio=&Nei, ég mun endurræsa tölvuna síðar +RunEntryExec=Keyra %1 +RunEntryShellExec=Skoða %1 + +; *** "Setup Needs the Next Disk" stuff +ChangeDiskTitle=Uppsetning þarfnast næsta disks +SelectDiskLabel2=Vinsamlega settu inn disk %1 og smelltu á Í lagi.%n%nEf skrárnar á þessum disk er hægt að finna í annarri möppu en þeirri sem birt er fyrir neðan, innsláðu réttu slóðina og smelltu á Vafra. +PathLabel=&Slóð: +FileNotInDir2=Skrána „%1“ var ekki hægt að staðsetja í „%2“. Vinsamlega settu inn rétta diskinn eða veldu aðra möppu. +SelectDirectoryLabel=Vinsamlega tilgreindu staðsetningu næsta disks. + +; *** Installation phase messages +SetupAborted=Uppsetningu var ekki lokið.%n%nVinsamlega leiðréttu vandamálið og keyrðu Uppsetningu aftur. +AbortRetryIgnoreSelectAction=Velja aðgerð +AbortRetryIgnoreRetry=&Reyna aftur +AbortRetryIgnoreIgnore=&Hunsa villuna og halda áfram +AbortRetryIgnoreCancel=Hætta við uppsetningu + +; *** Installation status messages +StatusClosingApplications=Lokandi hugbúnaði… +StatusCreateDirs=Skapandi skráasöfn… +StatusExtractFiles=Útdragandi skrár… +StatusCreateIcons=Skapandi skyndivísa… +StatusCreateIniEntries=Skapandi INI-færslur… +StatusCreateRegistryEntries=Skapandi Windows Registry-færslur… +StatusRegisterFiles=Skrásetjandi skrár… +StatusSavingUninstall=Vistandi niðurtekningarupplýsingar… +StatusRunProgram=Ljúkandi uppsetningu… +StatusRestartingApplications=Endurræsandi hugbúnað… +StatusRollback=Rúllandi aftur breytingum… + +; *** Misc. errors +ErrorInternal2=Innri villa: %1 +ErrorFunctionFailedNoCode=%1 mistókst +ErrorFunctionFailed=%1 mistókst; kóði %2 +ErrorFunctionFailedWithMessage=%1 mistókst; kóði %2.%n%3 +ErrorExecutingProgram=Ófært um að keyra skrá:%n%1 + +; *** Registry errors +ErrorRegOpenKey=Villa við opnun Windows Registry-lykils:%n%1\%2 +ErrorRegCreateKey=Villa við sköpun Windows Registry-lykils:%n%1\%2 +ErrorRegWriteKey=Villa við ritun í Windows Registry-lykil:%n%1\%2 + +; *** INI errors +ErrorIniEntry=Villa við sköpun INI-færslu í skrána „%1“. + +; *** File copying errors +FileAbortRetryIgnoreSkipNotRecommended=&Sleppa þessari skrá (ekki ráðlagt) +FileAbortRetryIgnoreIgnoreNotRecommended=&Hunsa villuna og halda áfram (ekki ráðlagt) +SourceIsCorrupted=Upprunaskráin er spillt +SourceDoesntExist=Upprunaskráin „%1“ er ekki til +ExistingFileReadOnly2=Hina gildandi skrá var ekki hægt að yfirrita því hún er merkt sem lesa-einungis. +ExistingFileReadOnlyRetry=&Fjarlægja lesa-einungis eigindi og reyna aftur +ExistingFileReadOnlyKeepExisting=&Halda gildandi skrá +ErrorReadingExistingDest=Villa kom upp meðan reynt var að lesa gildandi skrána: +FileExistsSelectAction=Velja aðgerð +FileExists2=Skráin er nú þegar til. +FileExistsOverwriteExisting=&Yfirrita hina gildandi skrá +FileExistsKeepExisting=&Halda hinni gildandi skrá +FileExistsOverwriteOrKeepAll=&Gera þetta við næstu ósamstæður +ExistingFileNewerSelectAction=Velja aðgerð +ExistingFileNewer2=Hin gildandi skrá er nýrri en sú sem Uppsetning er að reyna að uppsetja. +ExistingFileNewerOverwriteExisting=&Yfirrita hina gildandi skrá +ExistingFileNewerKeepExisting=&Halda hinni gildandi skrá (ráðlagt) +ExistingFileNewerOverwriteOrKeepAll=&Gera þetta við næstu ósamstæður +ErrorChangingAttr=Villa kom upp meðan reynt var að breyta eigindum gildandi skráarinnar: +ErrorCreatingTemp=Villa kom upp meðan reynt var að skapa skrá í staðsetningarskráasafninu: +ErrorReadingSource=Villa kom upp meðan reynt var að lesa upprunaskrána: +ErrorCopying=Villa kom upp meðan reynt var að afrita skrána: +ErrorReplacingExistingFile=Villa kom upp meðan reynt var að yfirrita gildandi skrána: +ErrorRestartReplace=RestartReplace mistókst: +ErrorRenamingTemp=Villa kom upp meðan reynt var að endurnefna skrá í staðsetningarskráasafninu: +ErrorRegisterServer=Ófært um að skrá DLL/OCX: %1 +ErrorRegSvr32Failed=RegSvr32 mistókst með skilakóðann %1 +ErrorRegisterTypeLib=Ófært um að skrá tegundasafnið: $1 + +; *** Uninstall display name markings +UninstallDisplayNameMark=%1 (%2) +UninstallDisplayNameMarks=%1 (%2, %3) +UninstallDisplayNameMark32Bit=32-bita +UninstallDisplayNameMark64Bit=64-bita +UninstallDisplayNameMarkAllUsers=Allir notendur +UninstallDisplayNameMarkCurrentUser=Núverandi notandi + +; *** Post-installation errors +ErrorOpeningReadme=Villa kom upp meðan reynt var að opna README-skrána. +ErrorRestartingComputer=Uppsetningu tókst ekki að endurræsa tölvuna. Vinsamlega gerðu þetta handvirkt. + +; *** Uninstaller messages +UninstallNotFound=Skráin „%1“ er ekki til. Getur ekki niðurtekið. +UninstallOpenError=Skrána „%1“ var ekki hægt að opna. Getur ekki niðurtekið +UninstallUnsupportedVer=Niðurtökuatburðaskráin „%1“ er á sniði sem er ekki þekkt af þessari útgáfu af niðurtakaranum. Getur ekki niðurtekið +UninstallUnknownEntry=Óþekkt færsla (%1) var fundin í niðurtökuatburðaskránni +ConfirmUninstall=Ert þú viss um að þú viljir algjörlega fjarlægja %1 og öll atriði þess? +UninstallOnlyOnWin64=Þessa uppsetningu er einungis hægt að niðurtaka á 64-bita Windows. +OnlyAdminCanUninstall=Þessi uppsetning getur einungis verið niðurtekin af notanda með stjórnandaréttindi. +UninstallStatusLabel=Vinsamlega bíddu meðan %1 er fjarlægt úr tölvunni þinni. +UninstalledAll=%1 var færsællega fjarlægt af tölvunni þinni. +UninstalledMost=%1-niðurtöku lokið.%n%nSuma liði var ekki hægt að fjarlægja. Þá er hægt að fjarlægja handvirkt. +UninstalledAndNeedsRestart=Til að ljúka niðurtöku %1 þarf að endurræsa tölvuna þína.%n%nVilt þú endurræsa núna? +UninstallDataCorrupted=„%1“-skráin er spillt. Getur ekki niðurtekið + +; *** Uninstallation phase messages +ConfirmDeleteSharedFileTitle=Fjarlægja deilda skrá? +ConfirmDeleteSharedFile2=Kerfið gefur til kynna að hin eftirfarandi deilda skrá er ekki lengur í notkun hjá neinu forriti. Vilt þú að Niðurtakari fjarlægi þessa deildu skrá?%n%nEf einhver forrit eru enn notandi þessa skrá og hún er fjarlægð, kann að vera að þau forrit munu ekki virka almennilega. Ef þú ert óviss, veldu Nei. Að skilja skrána eftir á kerfinu þínu mun ekki valda skaða. +SharedFileNameLabel=Skráarnafn: +SharedFileLocationLabel=Staðsetning: +WizardUninstalling=Niðurtökustaða +StatusUninstalling=Niðurtakandi %1… + +; *** Shutdown block reasons +ShutdownBlockReasonInstallingApp=Uppsetjandi %1. +ShutdownBlockReasonUninstallingApp=Niðurtakandi %1. + +[CustomMessages] + +NameAndVersion=%1 útgáfa %2 +AdditionalIcons=Aukalegir skyndivísir: +CreateDesktopIcon=Skapa &skjáborðsskyndivísi +CreateQuickLaunchIcon=Skapa &Skyndiræsitáknmynd +ProgramOnTheWeb=%1 á Vefnum +UninstallProgram=Niðurtaka %1 +LaunchProgram=Ræsa %1 +AssocFileExtension=&Tengja %1 við %2-skráarframlenginguna +AssocingFileExtension=&Tengjandi %1 við %2-skráarframlenginguna… +AutoStartProgramGroupDescription=Ræsing: +AutoStartProgram=Sjálfvikt ræsa %1 +AddonHostProgramNotFound=%1 gat ekki staðsett möppuna sem þú valdir.%n%nVilt þú halda áfram hvort sem er? \ No newline at end of file diff --git a/src/AITool.Setup/INNO/Languages/Italian.isl b/src/AITool.Setup/INNO/Languages/Italian.isl new file mode 100644 index 00000000..cfdf8b5c --- /dev/null +++ b/src/AITool.Setup/INNO/Languages/Italian.isl @@ -0,0 +1,390 @@ +; bovirus@gmail.com +; *** Inno Setup version 6.1.0+ Italian messages *** +; +; To download user-contributed translations of this file, go to: +; https://jrsoftware.org/files/istrans/ +; +; Note: When translating this text, do not add periods (.) to the end of +; messages that didn't have them already, because on those messages Inno +; Setup adds the periods automatically (appending a period would result in +; two periods being displayed). +; +; Italian.isl - Last Update: 25.07.2020 by bovirus (bovirus@gmail.com) +; +; Translator name: bovirus +; Translator e-mail: bovirus@gmail.com +; Based on previous translations of Rinaldo M. aka Whiteshark (based on ale5000 5.1.11+ translation) +; +[LangOptions] +; The following three entries are very important. Be sure to read and +; understand the '[LangOptions] section' topic in the help file. +LanguageName=Italiano +LanguageID=$0410 +LanguageCodePage=1252 +; If the language you are translating to requires special font faces or +; sizes, uncomment any of the following entries and change them accordingly. +;DialogFontName= +;DialogFontSize=8 +;WelcomeFontName=Verdana +;WelcomeFontSize=12 +;TitleFontName=Arial +;TitleFontSize=29 +;CopyrightFontName=Arial +;CopyrightFontSize=8 + +[Messages] + +; *** Application titles +SetupAppTitle=Installazione +SetupWindowTitle=Installazione di %1 +UninstallAppTitle=Disinstallazione +UninstallAppFullTitle=Disinstallazione di %1 + +; *** Misc. common +InformationTitle=Informazioni +ConfirmTitle=Conferma +ErrorTitle=Errore + +; *** SetupLdr messages +SetupLdrStartupMessage=Questa è l'installazione di %1.%n%nVuoi continuare? +LdrCannotCreateTemp=Impossibile creare un file temporaneo.%n%nInstallazione annullata. +LdrCannotExecTemp=Impossibile eseguire un file nella cartella temporanea.%n%nInstallazione annullata. + +; *** Startup error messages +LastErrorMessage=%1.%n%nErrore %2: %3 +SetupFileMissing=File %1 non trovato nella cartella di installazione.%n%nCorreggi il problema o richiedi una nuova copia del programma. +SetupFileCorrupt=I file di installazione sono danneggiati.%n%nRichiedi una nuova copia del programma. +SetupFileCorruptOrWrongVer=I file di installazione sono danneggiati, o sono incompatibili con questa versione del programma di installazione.%n%nCorreggi il problema o richiedi una nuova copia del programma. +InvalidParameter=È stato inserito nella riga di comando un parametro non valido:%n%n%1 +SetupAlreadyRunning=Il processo di installazione è già in funzione. +WindowsVersionNotSupported=Questo programma non supporta la versione di Windows installata nel computer. +WindowsServicePackRequired=Questo programma richiede %1 Service Pack %2 o successivo. +NotOnThisPlatform=Questo programma non è compatibile con %1. +OnlyOnThisPlatform=Questo programma richiede %1. +OnlyOnTheseArchitectures=Questo programma può essere installato solo su versioni di Windows progettate per le seguenti architetture della CPU:%n%n%1 +WinVersionTooLowError=Questo programma richiede %1 versione %2 o successiva. +WinVersionTooHighError=Questo programma non può essere installato su %1 versione %2 o successiva. +AdminPrivilegesRequired=Per installare questo programma sono richiesti privilegi di amministratore. +PowerUserPrivilegesRequired=Per poter installare questo programma sono richiesti i privilegi di amministratore o di Power Users. +SetupAppRunningError=%1 è attualmente in esecuzione.%n%nChiudi adesso tutte le istanze del programma e poi seleziona "OK", o seleziona "Annulla" per uscire. +UninstallAppRunningError=%1 è attualmente in esecuzione.%n%nChiudi adesso tutte le istanze del programma e poi seleziona "OK", o seleziona "Annulla" per uscire. + +; *** Startup questions +PrivilegesRequiredOverrideTitle=Seleziona modo installazione +PrivilegesRequiredOverrideInstruction=Seleziona modo installazione +PrivilegesRequiredOverrideText1=%1 può essere installato per tutti gli utenti (richiede privilegi di amministratore), o solo per l'utente attuale. +PrivilegesRequiredOverrideText2=%1 può essere installato solo per l'utente attuale, o per tutti gli utenti (richiede privilegi di amministratore). +PrivilegesRequiredOverrideAllUsers=Inst&alla per tutti gli utenti +PrivilegesRequiredOverrideAllUsersRecommended=Inst&alla per tutti gli utenti (suggerito) +PrivilegesRequiredOverrideCurrentUser=Installa solo per l'&utente attuale +PrivilegesRequiredOverrideCurrentUserRecommended=Installa solo per l'&utente attuale (suggerito) + +; *** Misc. errors +ErrorCreatingDir=Impossibile creare la cartella "%1" +ErrorTooManyFilesInDir=Impossibile creare i file nella cartella "%1" perché contiene troppi file. + +; *** Setup common messages +ExitSetupTitle=Uscita dall'installazione +ExitSetupMessage=L'installazione non è completa.%n%nUscendo dall'installazione in questo momento, il programma non sarà installato.%n%nÈ possibile eseguire l'installazione in un secondo tempo.%n%nVuoi uscire dall'installazione? +AboutSetupMenuItem=&Informazioni sull'installazione... +AboutSetupTitle=Informazioni sull'installazione +AboutSetupMessage=%1 versione %2%n%3%n%n%1 sito web:%n%4 +AboutSetupNote= +TranslatorNote=Traduzione italiana a cura di Rinaldo M. aka Whiteshark e bovirus (v. 11.09.2018) + +; *** Buttons +ButtonBack=< &Indietro +ButtonNext=&Avanti > +ButtonInstall=Inst&alla +ButtonOK=OK +ButtonCancel=Annulla +ButtonYes=&Si +ButtonYesToAll=Sì a &tutto +ButtonNo=&No +ButtonNoToAll=N&o a tutto +ButtonFinish=&Fine +ButtonBrowse=&Sfoglia... +ButtonWizardBrowse=S&foglia... +ButtonNewFolder=&Crea nuova cartella + +; *** "Select Language" dialog messages +SelectLanguageTitle=Seleziona la lingua dell'installazione +SelectLanguageLabel=Seleziona la lingua da usare durante l'installazione. + +; *** Common wizard text +ClickNext=Seleziona "Avanti" per continuare, o "Annulla" per uscire. +BeveledLabel= +BrowseDialogTitle=Sfoglia cartelle +BrowseDialogLabel=Seleziona una cartella nell'elenco, e quindi seleziona "OK". +NewFolderName=Nuova cartella + +; *** "Welcome" wizard page +WelcomeLabel1=Installazione di [name] +WelcomeLabel2=[name/ver] sarà installato sul computer.%n%nPrima di procedere chiudi tutte le applicazioni attive. + +; *** "Password" wizard page +WizardPassword=Password +PasswordLabel1=Questa installazione è protetta da password. +PasswordLabel3=Inserisci la password, quindi per continuare seleziona "Avanti".%nLe password sono sensibili alle maiuscole/minuscole. +PasswordEditLabel=&Password: +IncorrectPassword=La password inserita non è corretta. Riprova. + +; *** "License Agreement" wizard page +WizardLicense=Contratto di licenza +LicenseLabel=Prima di procedere leggi con attenzione le informazioni che seguono. +LicenseLabel3=Leggi il seguente contratto di licenza.%nPer procedere con l'installazione è necessario accettare tutti i termini del contratto. +LicenseAccepted=Accetto i termini del &contratto di licenza +LicenseNotAccepted=&Non accetto i termini del contratto di licenza + +; *** "Information" wizard pages +WizardInfoBefore=Informazioni +InfoBeforeLabel=Prima di procedere leggi le importanti informazioni che seguono. +InfoBeforeClickLabel=Quando sei pronto per proseguire, seleziona "Avanti". +WizardInfoAfter=Informazioni +InfoAfterLabel=Prima di procedere leggi le importanti informazioni che seguono. +InfoAfterClickLabel=Quando sei pronto per proseguire, seleziona "Avanti". + +; *** "User Information" wizard page +WizardUserInfo=Informazioni utente +UserInfoDesc=Inserisci le seguenti informazioni. +UserInfoName=&Nome: +UserInfoOrg=&Società: +UserInfoSerial=&Numero di serie: +UserInfoNameRequired=È necessario inserire un nome. + +; *** "Select Destination Location" wizard page +WizardSelectDir=Selezione cartella di installazione +SelectDirDesc=Dove vuoi installare [name]? +SelectDirLabel3=[name] sarà installato nella seguente cartella. +SelectDirBrowseLabel=Per continuare seleziona "Avanti".%nPer scegliere un'altra cartella seleziona "Sfoglia". +DiskSpaceGBLabel=Sono richiesti almeno [gb] GB di spazio libero nel disco. +DiskSpaceMBLabel=Sono richiesti almeno [mb] MB di spazio libero nel disco. +CannotInstallToNetworkDrive=Non è possibile effettuare l'installazione in un disco in rete. +CannotInstallToUNCPath=Non è possibile effettuare l'installazione in un percorso UNC. +InvalidPath=Va inserito un percorso completo di lettera di unità; per esempio:%n%nC:\APP%n%no un percorso di rete nella forma:%n%n\\server\condivisione +InvalidDrive=L'unità o il percorso di rete selezionato non esiste o non è accessibile.%n%nSelezionane un altro. +DiskSpaceWarningTitle=Spazio su disco insufficiente +DiskSpaceWarning=L'installazione richiede per eseguire l'installazione almeno %1 KB di spazio libero, ma l'unità selezionata ha solo %2 KB disponibili.%n%nVuoi continuare comunque? +DirNameTooLong=Il nome della cartella o il percorso sono troppo lunghi. +InvalidDirName=Il nome della cartella non è valido. +BadDirName32=Il nome della cartella non può includere nessuno dei seguenti caratteri:%n%n%1 +DirExistsTitle=Cartella già esistente +DirExists=La cartella%n%n %1%n%nesiste già.%n%nVuoi comunque installare l'applicazione in questa cartella? +DirDoesntExistTitle=Cartella inesistente +DirDoesntExist=La cartella%n%n %1%n%nnon esiste. Vuoi creare la cartella? + +; *** "Select Components" wizard page +WizardSelectComponents=Selezione componenti +SelectComponentsDesc=Quali componenti vuoi installare? +SelectComponentsLabel2=Seleziona i componenti da installare, deseleziona quelli che non vuoi installare.%nPer continuare seleziona "Avanti". +FullInstallation=Installazione completa +; if possible don't translate 'Compact' as 'Minimal' (I mean 'Minimal' in your language) +CompactInstallation=Installazione compatta +CustomInstallation=Installazione personalizzata +NoUninstallWarningTitle=Componente esistente +NoUninstallWarning=I seguenti componenti sono già installati nel computer:%n%n%1%n%nDeselezionando questi componenti essi non verranno rimossi.%n%nVuoi continuare comunque? +ComponentSize1=%1 KB +ComponentSize2=%1 MB +ComponentsDiskSpaceGBLabel=La selezione attuale richiede almeno [gb] GB di spazio nel disco. +ComponentsDiskSpaceMBLabel=La selezione attuale richiede almeno [mb] MB di spazio nel disco. + +; *** "Select Additional Tasks" wizard page +WizardSelectTasks=Selezione processi aggiuntivi +SelectTasksDesc=Quali processi aggiuntivi vuoi eseguire? +SelectTasksLabel2=Seleziona i processi aggiuntivi che verranno eseguiti durante l'installazione di [name], quindi seleziona "Avanti". + +; *** "Select Start Menu Folder" wizard page +WizardSelectProgramGroup=Selezione della cartella nel menu Avvio/Start +SelectStartMenuFolderDesc=Dove vuoi inserire i collegamenti al programma? +SelectStartMenuFolderLabel3=Verranno creati i collegamenti al programma nella seguente cartella del menu Avvio/Start. +SelectStartMenuFolderBrowseLabel=Per continuare, seleziona "Avanti".%nPer selezionare un'altra cartella, seleziona "Sfoglia". +MustEnterGroupName=Devi inserire il nome della cartella. +GroupNameTooLong=Il nome della cartella o il percorso sono troppo lunghi. +InvalidGroupName=Il nome della cartella non è valido. +BadGroupName=Il nome della cartella non può includere nessuno dei seguenti caratteri:%n%n%1 +NoProgramGroupCheck2=&Non creare una cartella nel menu Avvio/Start + +; *** "Ready to Install" wizard page +WizardReady=Pronto per l'installazione +ReadyLabel1=Il programma è pronto per iniziare l'installazione di [name] nel computer. +ReadyLabel2a=Seleziona "Installa" per continuare con l'installazione, o "Indietro" per rivedere o modificare le impostazioni. +ReadyLabel2b=Per procedere con l'installazione seleziona "Installa". +ReadyMemoUserInfo=Informazioni utente: +ReadyMemoDir=Cartella di installazione: +ReadyMemoType=Tipo di installazione: +ReadyMemoComponents=Componenti selezionati: +ReadyMemoGroup=Cartella del menu Avvio/Start: +ReadyMemoTasks=Processi aggiuntivi: + +; *** TDownloadWizardPage wizard page and DownloadTemporaryFile +DownloadingLabel=Download file aggiuntivi... +ButtonStopDownload=&Stop download +StopDownload=Sei sicuro di voler interrompere il download? +ErrorDownloadAborted=Download annullato +ErrorDownloadFailed=Download fallito: %1 %2 +ErrorDownloadSizeFailed=Rilevamento dimensione fallito: %1 %2 +ErrorFileHash1=Errore hash file: %1 +ErrorFileHash2=Hash file non valido: atteso %1, trovato %2 +ErrorProgress=Progresso non valido: %1 di %2 +ErrorFileSize=Dimensione file non valida: attesa %1, trovata %2 + +; *** "Preparing to Install" wizard page +WizardPreparing=Preparazione all'installazione +PreparingDesc=Preparazione all'installazione di [name] nel computer. +PreviousInstallNotCompleted=L'installazione/rimozione precedente del programma non è stata completata.%n%nÈ necessario riavviare il sistema per completare l'installazione.%n%nDopo il riavvio del sistema esegui di nuovo l'installazione di [name]. +CannotContinue=L'installazione non può continuare. Seleziona "Annulla" per uscire. +ApplicationsFound=Le seguenti applicazioni stanno usando file che devono essere aggiornati dall'installazione.%n%nTi consigliamo di permettere al processo di chiudere automaticamente queste applicazioni. +ApplicationsFound2=Le seguenti applicazioni stanno usando file che devono essere aggiornati dall'installazione.%n%nTi consigliamo di permettere al processo di chiudere automaticamente queste applicazioni.%n%nAl completamento dell'installazione, il processo tenterà di riavviare le applicazioni. +CloseApplications=Chiudi &automaticamente le applicazioni +DontCloseApplications=&Non chiudere le applicazioni +ErrorCloseApplications=L'installazione non è riuscita a chiudere automaticamente tutte le applicazioni.%n%nPrima di proseguire ti raccomandiamo di chiudere tutte le applicazioni che usano file che devono essere aggiornati durante l'installazione. +PrepareToInstallNeedsRestart=Il programma di installazione deve riavviare il computer.%nDopo aver riavviato il computer esegui di nuovo il programma di installazione per completare l'installazione di [name].%n%nVuoi riavviare il computer ora? + +; *** "Installing" wizard page +WizardInstalling=Installazione in corso +InstallingLabel=Attendi il completamento dell'installazione di [name] nel computer. + +; *** "Setup Completed" wizard page +FinishedHeadingLabel=Installazione di [name] completata +FinishedLabelNoIcons=Installazione di [name] completata. +FinishedLabel=Installazione di [name] completata.%n%nL'applicazione può essere eseguita selezionando le relative icone. +ClickFinish=Seleziona "Fine" per uscire dall'installazione. +FinishedRestartLabel=Per completare l'installazione di [name], è necessario riavviare il sistema.%n%nVuoi riavviare adesso? +FinishedRestartMessage=Per completare l'installazione di [name], è necessario riavviare il sistema.%n%nVuoi riavviare adesso? +ShowReadmeCheck=Si, visualizza ora il file LEGGIMI +YesRadio=&Si, riavvia il sistema adesso +NoRadio=&No, riavvia il sistema più tardi +; used for example as 'Run MyProg.exe' +RunEntryExec=Esegui %1 +; used for example as 'View Readme.txt' +RunEntryShellExec=Visualizza %1 + +; *** "Setup Needs the Next Disk" stuff +ChangeDiskTitle=L'installazione necessita del disco successivo +SelectDiskLabel2=Inserisci il disco %1 e seleziona "OK".%n%nSe i file di questo disco si trovano in una cartella diversa da quella visualizzata sotto, inserisci il percorso corretto o seleziona "Sfoglia". +PathLabel=&Percorso: +FileNotInDir2=Il file "%1" non è stato trovato in "%2".%n%nInserisci il disco corretto o seleziona un'altra cartella. +SelectDirectoryLabel=Specifica il percorso del prossimo disco. + +; *** Installation phase messages +SetupAborted=L'installazione non è stata completata.%n%nCorreggi il problema e riesegui nuovamente l'installazione. +AbortRetryIgnoreSelectAction=Seleziona azione +AbortRetryIgnoreRetry=&Riprova +AbortRetryIgnoreIgnore=&Ignora questo errore e continua +AbortRetryIgnoreCancel=Annulla installazione + +; *** Installation status messages +StatusClosingApplications=Chiusura applicazioni... +StatusCreateDirs=Creazione cartelle... +StatusExtractFiles=Estrazione file... +StatusCreateIcons=Creazione icone... +StatusCreateIniEntries=Creazione voci nei file INI... +StatusCreateRegistryEntries=Creazione voci di registro... +StatusRegisterFiles=Registrazione file... +StatusSavingUninstall=Salvataggio delle informazioni di disinstallazione... +StatusRunProgram=Termine dell'installazione... +StatusRestartingApplications=Riavvio applicazioni... +StatusRollback=Recupero delle modifiche... + +; *** Misc. errors +ErrorInternal2=Errore interno %1 +ErrorFunctionFailedNoCode=%1 fallito +ErrorFunctionFailed=%1 fallito; codice %2 +ErrorFunctionFailedWithMessage=%1 fallito; codice %2.%n%3 +ErrorExecutingProgram=Impossibile eseguire il file:%n%1 + +; *** Registry errors +ErrorRegOpenKey=Errore di apertura della chiave di registro:%n%1\%2 +ErrorRegCreateKey=Errore di creazione della chiave di registro:%n%1\%2 +ErrorRegWriteKey=Errore di scrittura della chiave di registro:%n%1\%2 + +; *** INI errors +ErrorIniEntry=Errore nella creazione delle voci INI nel file "%1". + +; *** File copying errors +FileAbortRetryIgnoreSkipNotRecommended=&Salta questo file (non suggerito) +FileAbortRetryIgnoreIgnoreNotRecommended=&Ignora questo errore e continua (non suggerito) +SourceIsCorrupted=Il file sorgente è danneggiato +SourceDoesntExist=Il file sorgente "%1" non esiste +ExistingFileReadOnly2=Il file esistente non può essere sostituito in quanto segnato come in sola lettura. +ExistingFileReadOnlyRetry=&Rimuovi attributo di sola lettura e riprova +ExistingFileReadOnlyKeepExisting=&Mantieni il file esistente +ErrorReadingExistingDest=Si è verificato un errore durante la lettura del file esistente: +FileExistsSelectAction=Seleziona azione +FileExists2=Il file esiste già. +FileExistsOverwriteExisting=S&ovrascrivi il file esistente +FileExistsKeepExisting=&Mantieni il file esistente +FileExistsOverwriteOrKeepAll=&Applica questa azione per i prossimi conflitti +ExistingFileNewerSelectAction=Seleziona azione +ExistingFileNewer2=Il file esistente è più recente del file che si sta cercando di installare. +ExistingFileNewerOverwriteExisting=S&ovrascrivi il file esistente +ExistingFileNewerKeepExisting=&Mantieni il file esistente (suggerito) +ExistingFileNewerOverwriteOrKeepAll=&Applica questa azione per i prossimi conflitti +ErrorChangingAttr=Si è verificato un errore durante il tentativo di modifica dell'attributo del file esistente: +ErrorCreatingTemp=Si è verificato un errore durante la creazione di un file nella cartella di installazione: +ErrorReadingSource=Si è verificato un errore durante la lettura del file sorgente: +ErrorCopying=Si è verificato un errore durante la copia di un file: +ErrorReplacingExistingFile=Si è verificato un errore durante la sovrascrittura del file esistente: +ErrorRestartReplace=Errore durante riavvio o sostituzione: +ErrorRenamingTemp=Si è verificato un errore durante il tentativo di rinominare un file nella cartella di installazione: +ErrorRegisterServer=Impossibile registrare la DLL/OCX: %1 +ErrorRegSvr32Failed=RegSvr32 è fallito con codice di uscita %1 +ErrorRegisterTypeLib=Impossibile registrare la libreria di tipo: %1 + +; *** Uninstall display name markings +; used for example as 'My Program (32-bit)' +UninstallDisplayNameMark=%1 (%2) +; used for example as 'My Program (32-bit, All users)' +UninstallDisplayNameMarks=%1 (%2, %3) +UninstallDisplayNameMark32Bit=32bit +UninstallDisplayNameMark64Bit=64bit +UninstallDisplayNameMarkAllUsers=Tutti gli utenti +UninstallDisplayNameMarkCurrentUser=Utente attuale + +; *** Post-installation errors +ErrorOpeningReadme=Si è verificato un errore durante l'apertura del file LEGGIMI. +ErrorRestartingComputer=Impossibile riavviare il sistema. Riavvia il sistema manualmente. + +; *** Uninstaller messages +UninstallNotFound=Il file "%1" non esiste.%n%nImpossibile disinstallare. +UninstallOpenError=Il file "%1" non può essere aperto.%n%nImpossibile disinstallare +UninstallUnsupportedVer=Il file registro di disinstallazione "%1" è in un formato non riconosciuto da questa versione del programma di disinstallazione.%n%nImpossibile disinstallare +UninstallUnknownEntry=Trovata una voce sconosciuta (%1) nel file registro di disinstallazione +ConfirmUninstall=Vuoi rimuovere completamente %1 e tutti i suoi componenti? +UninstallOnlyOnWin64=Questa applicazione può essere disinstallata solo in Windows a 64-bit. +OnlyAdminCanUninstall=Questa applicazione può essere disinstallata solo da un utente con privilegi di amministratore. +UninstallStatusLabel=Attendi fino a che %1 è stato rimosso dal computer. +UninstalledAll=Disinstallazione di %1 completata. +UninstalledMost=Disinstallazione di %1 completata.%n%nAlcuni elementi non possono essere rimossi.%n%nDovranno essere rimossi manualmente. +UninstalledAndNeedsRestart=Per completare la disinstallazione di %1, è necessario riavviare il sistema.%n%nVuoi riavviare il sistema adesso? +UninstallDataCorrupted=Il file "%1" è danneggiato. Impossibile disinstallare + +; *** Uninstallation phase messages +ConfirmDeleteSharedFileTitle=Vuoi rimuovere il file condiviso? +ConfirmDeleteSharedFile2=Il sistema indica che il seguente file condiviso non è più usato da nessun programma.%nVuoi rimuovere questo file condiviso?%nSe qualche programma usasse questo file, potrebbe non funzionare più correttamente.%nSe non sei sicuro, seleziona "No".%nLasciare il file nel sistema non può causare danni. +SharedFileNameLabel=Nome del file: +SharedFileLocationLabel=Percorso: +WizardUninstalling=Stato disinstallazione +StatusUninstalling=Disinstallazione di %1... + +; *** Shutdown block reasons +ShutdownBlockReasonInstallingApp=Installazione di %1. +ShutdownBlockReasonUninstallingApp=Disinstallazione di %1. + +; The custom messages below aren't used by Setup itself, but if you make +; use of them in your scripts, you'll want to translate them. + +[CustomMessages] + +NameAndVersion=%1 versione %2 +AdditionalIcons=Icone aggiuntive: +CreateDesktopIcon=Crea un'icona sul &desktop +CreateQuickLaunchIcon=Crea un'icona nella &barra 'Avvio veloce' +ProgramOnTheWeb=Sito web di %1 +UninstallProgram=Disinstalla %1 +LaunchProgram=Avvia %1 +AssocFileExtension=&Associa i file con estensione %2 a %1 +AssocingFileExtension=Associazione dei file con estensione %2 a %1... +AutoStartProgramGroupDescription=Esecuzione automatica: +AutoStartProgram=Esegui automaticamente %1 +AddonHostProgramNotFound=Impossibile individuare %1 nella cartella selezionata.%n%nVuoi continuare ugualmente? diff --git a/src/AITool.Setup/INNO/Languages/Japanese.isl b/src/AITool.Setup/INNO/Languages/Japanese.isl new file mode 100644 index 00000000..a1c150ae --- /dev/null +++ b/src/AITool.Setup/INNO/Languages/Japanese.isl @@ -0,0 +1,367 @@ +; *** Inno Setup version 6.1.0+ Japanese messages *** +; +; Maintained by Koichi Shirasuka (shirasuka@eugrid.co.jp) +; +; Translation based on Ryou Minakami (ryou32jp@yahoo.co.jp) +; +; $jrsoftware: issrc/Files/Languages/Japanese.isl,v 1.6 2010/03/08 07:50:01 mlaan Exp $ + +[LangOptions] +LanguageName=<65E5><672C><8A9E> +LanguageID=$0411 +LanguageCodePage=932 + +[Messages] + +; *** Application titles +SetupAppTitle=ZbgAbv +SetupWindowTitle=%1 ZbgAbv +UninstallAppTitle=ACXg[ +UninstallAppFullTitle=%1 ACXg[ + +; *** Misc. common +InformationTitle= +ConfirmTitle=mF +ErrorTitle=G[ + +; *** SetupLdr messages +SetupLdrStartupMessage=%1 CXg[܂Bs܂H +LdrCannotCreateTemp=ꎞt@C쐬ł܂BZbgAbv𒆎~܂B +LdrCannotExecTemp=ꎞtH_[̃t@Csł܂BZbgAbv𒆎~܂B + +; *** Startup error messages +LastErrorMessage=%1.%n%nG[ %2: %3 +SetupFileMissing=t@C %1 ‚܂B邩VZbgAbvvO肵ĂB +SetupFileCorrupt=ZbgAbvt@CĂ܂BVZbgAbvvO肵ĂB +SetupFileCorruptOrWrongVer=ZbgAbvt@CĂ邩Ão[W̃ZbgAbvƌ݊܂B邩VZbgAbvvO肵ĂB +InvalidParameter=R}hCɕsȃp[^[n܂:%n%n%1 +SetupAlreadyRunning=ZbgAbv͊ɎsłB +WindowsVersionNotSupported=̃vO͂g̃o[W Windows T|[gĂ܂B +WindowsServicePackRequired=̃vO̎sɂ %1 Service Pack %2 ȍ~KvłB +NotOnThisPlatform=̃vO %1 ł͓삵܂B +OnlyOnThisPlatform=̃vO̎sɂ %1 KvłB +OnlyOnTheseArchitectures=̃vO%n%n%1vZbT[ Windows ɂCXg[ł܂B +WinVersionTooLowError=̃vO̎sɂ %1 %2 ȍ~KvłB +WinVersionTooHighError=̃vO %1 %2 ȍ~ł͓삵܂B +AdminPrivilegesRequired=̃vOCXg[邽߂ɂ͊Ǘ҂ƂăOCKv܂B +PowerUserPrivilegesRequired=̃vOCXg[邽߂ɂ͊Ǘ҂܂̓p[[U[ƂăOCKv܂B +SetupAppRunningError=ZbgAbv͎s %1 o܂B%n%nJĂAvP[Vׂĕ‚ĂuOKvNbNĂBuLZvNbNƁAZbgAbvI܂B +UninstallAppRunningError=ACXg[͎s %1 o܂B%n%nJĂAvP[Vׂĕ‚ĂuOKvNbNĂBuLZvNbNƁAZbgAbvI܂B + +; *** Startup questions +PrivilegesRequiredOverrideTitle=CXg[[h̑I +PrivilegesRequiredOverrideInstruction=CXg[[hIĂ +PrivilegesRequiredOverrideText1=%1 ׂ͂Ẵ[U[ (ǗҌKvł) ܂݂͌̃[U[pɃCXg[ł܂B +PrivilegesRequiredOverrideText2=%1 ݂͌̃[U[܂ׂ͂Ẵ[U[p (ǗҌKvł) ɃCXg[ł܂B +PrivilegesRequiredOverrideAllUsers=ׂẴ[U[pɃCXg[(&A) +PrivilegesRequiredOverrideAllUsersRecommended=ׂẴ[U[pɃCXg[(&A) () +PrivilegesRequiredOverrideCurrentUser=݂̃[U[pɃCXg[(&M) +PrivilegesRequiredOverrideCurrentUserRecommended=݂̃[U[pɃCXg[(&M) () + +; *** Misc. errors +ErrorCreatingDir=fBNg %1 쐬ɃG[܂B +ErrorTooManyFilesInDir=fBNg %1 Ƀt@C쐬ɃG[܂Bt@C̐܂B + +; *** Setup common messages +ExitSetupTitle=ZbgAbvI +ExitSetupMessage=ZbgAbvƂ͊Ă܂BŃZbgAbv𒆎~ƃvO̓CXg[܂B%n%n߂ăCXg[ꍇ́AxZbgAbvsĂB%n%nZbgAbvI܂H +AboutSetupMenuItem=ZbgAbvɂ‚(&A)... +AboutSetupTitle=ZbgAbvɂ‚ +AboutSetupMessage=%1 %2%n%3%n%n%1 z[y[W:%n%4 +AboutSetupNote= +TranslatorNote= + +; *** Buttons +ButtonBack=< ߂(&B) +ButtonNext=(&N) > +ButtonInstall=CXg[(&I) +ButtonOK=OK +ButtonCancel=LZ +ButtonYes=͂(&Y) +ButtonYesToAll=ׂĂ͂(&A) +ButtonNo=(&N) +ButtonNoToAll=ׂĂ(&O) +ButtonFinish=(&F) +ButtonBrowse=Q(&B)... +ButtonWizardBrowse=Q(&R) +ButtonNewFolder=VtH_[(&M) + +; *** "Select Language" dialog messages +SelectLanguageTitle=ZbgAbvɎgp錾̑I +SelectLanguageLabel=CXg[ɗp錾IłB + +; *** Common wizard text +ClickNext=sɂ́uցvAZbgAbvIɂ́uLZvNbNĂB +BeveledLabel= +BrowseDialogTitle=tH_[Q +BrowseDialogLabel=XgtH_[I OK ĂB +NewFolderName=VtH_[ + +; *** "Welcome" wizard page +WelcomeLabel1=[name] ZbgAbvEBU[h̊Jn +WelcomeLabel2=̃vO͂gp̃Rs[^[ [name/ver] CXg[܂B%n%nsOɑ̃AvP[VׂďIĂB + +; *** "Password" wizard page +WizardPassword=pX[h +PasswordLabel1=̃CXg[vO̓pX[hɂĕی삳Ă܂B +PasswordLabel3=pX[h͂āuցvNbNĂBpX[h͑啶Əʂ܂B +PasswordEditLabel=pX[h(&P): +IncorrectPassword=͂ꂽpX[h܂Bx͂ȂĂB + +; *** "License Agreement" wizard page +WizardLicense=gp_񏑂̓ +LicenseLabel=sOɈȉ̏dvȏǂ݂B +LicenseLabel3=ȉ̎gp_񏑂ǂ݂BCXg[𑱍sɂ͂̌_񏑂ɓӂKv܂B +LicenseAccepted=ӂ(&A) +LicenseNotAccepted=ӂȂ(&D) + +; *** "Information" wizard pages +WizardInfoBefore= +InfoBeforeLabel=sOɈȉ̏dvȏǂ݂B +InfoBeforeClickLabel=ZbgAbv𑱍sɂ́uցvNbNĂB +WizardInfoAfter= +InfoAfterLabel=sOɈȉ̏dvȏǂ݂B +InfoAfterClickLabel=ZbgAbv𑱍sɂ́uցvNbNĂB + +; *** "User Information" wizard page +WizardUserInfo=[U[ +UserInfoDesc=[U[͂ĂB +UserInfoName=[U[(&U): +UserInfoOrg=gD(&O): +UserInfoSerial=VAԍ(&S): +UserInfoNameRequired=[U[͂ĂB + +; *** "Select Destination Location" wizard page +WizardSelectDir=CXg[̎w +SelectDirDesc=[name] ̃CXg[w肵ĂB +SelectDirLabel3=[name] CXg[tH_w肵āAuցvNbNĂB +SelectDirBrowseLabel=ɂ́uցvNbNĂBʂ̃tH_[Iɂ́uQƁvNbNĂB +DiskSpaceGBLabel=̃vO͍Œ [gb] GB ̃fBXN󂫗̈KvƂ܂B +DiskSpaceMBLabel=̃vO͍Œ [mb] MB ̃fBXN󂫗̈KvƂ܂B +CannotInstallToNetworkDrive=lbg[NhCuɃCXg[邱Ƃ͂ł܂B +CannotInstallToUNCPath=UNC pXɃCXg[邱Ƃ͂ł܂B +InvalidPath=hCu܂ފSȃpX͂ĂB%n%nFC:\APP%n%n܂ UNC `̃pX͂ĂB%n%nF\\server\share +InvalidDrive=w肵hCu܂ UNC pX‚ȂANZXł܂Bʂ̃pXw肵ĂB +DiskSpaceWarningTitle=fBXN󂫗̈̕s +DiskSpaceWarning=CXg[ɂ͍Œ %1 KB ̃fBXN󂫗̈悪KvłAw肳ꂽhCuɂ %2 KB ̋󂫗̈悵܂B%n%n̂܂ܑs܂H +DirNameTooLong=hCu܂̓pX߂܂B +InvalidDirName=tH_[łB +BadDirName32=ȉ̕܂ރtH_[͎wł܂B:%n%n%1 +DirExistsTitle=̃tH_[ +DirExists=tH_[ %n%n%1%n%nɑ݂܂B̂܂܂̃tH_[փCXg[܂H +DirDoesntExistTitle=tH_[‚܂B +DirDoesntExist=tH_[ %n%n%1%n%n‚܂BVtH_[쐬܂H + +; *** "Select Components" wizard page +WizardSelectComponents=R|[lg̑I +SelectComponentsDesc=CXg[R|[lgIĂB +SelectComponentsLabel2=CXg[R|[lgIĂBCXg[Kv̂ȂR|[lg̓`FbNOĂBsɂ́uցvNbNĂB +FullInstallation=tCXg[ +; if possible don't translate 'Compact' as 'Minimal' (I mean 'Minimal' in your language) +CompactInstallation=RpNgCXg[ +CustomInstallation=JX^CXg[ +NoUninstallWarningTitle=̃R|[lg +NoUninstallWarning=ZbgAbv͈ȉ̃R|[lgɃCXg[Ă邱Ƃo܂B%n%n%1%n%ñR|[lg̑IĂACXg[͂܂B%n%n̂܂ܑs܂H +ComponentSize1=%1 KB +ComponentSize2=%1 MB +ComponentsDiskSpaceGBLabel=݂̑I͍Œ [gb] GB ̃fBXN󂫗̈KvƂ܂B +ComponentsDiskSpaceMBLabel=݂̑I͍Œ [mb] MB ̃fBXN󂫗̈KvƂ܂B + +; *** "Select Additional Tasks" wizard page +WizardSelectTasks=lj^XN̑I +SelectTasksDesc=slj^XNIĂB +SelectTasksLabel2=[name] CXg[Ɏslj^XNIāAuցvNbNĂB + +; *** "Select Start Menu Folder" wizard page +WizardSelectProgramGroup=X^[gj[tH_[̎w +SelectStartMenuFolderDesc=vÕV[gJbg쐬ꏊw肵ĂB +SelectStartMenuFolderLabel3=ZbgAbṽ͎X^[gj[tH_[ɃvÕV[gJbg쐬܂B +SelectStartMenuFolderBrowseLabel=ɂ́uցvNbNĂBႤtH_[Iɂ́uQƁvNbNĂB +MustEnterGroupName=tH_[w肵ĂB +GroupNameTooLong=tH_[܂̓pX߂܂B +InvalidGroupName=tH_[łB +BadGroupName=̕܂ރtH_[͎wł܂:%n%n%1 +NoProgramGroupCheck2=X^[gj[tH_[쐬Ȃ(&D) + +; *** "Ready to Install" wizard page +WizardReady=CXg[ +ReadyLabel1=gp̃Rs[^ [name] CXg[鏀ł܂B +ReadyLabel2a=CXg[𑱍sɂ́uCXg[vAݒ̊mFύXsɂ́u߂vNbNĂB +ReadyLabel2b=CXg[𑱍sɂ́uCXg[vNbNĂB +ReadyMemoUserInfo=[U[: +ReadyMemoDir=CXg[: +ReadyMemoType=ZbgAbv̎: +ReadyMemoComponents=IR|[lg: +ReadyMemoGroup=X^[gj[tH_[: +ReadyMemoTasks=lj^XNꗗ: + +; *** TDownloadWizardPage wizard page and DownloadTemporaryFile +DownloadingLabel=lj̃t@C_E[hĂ܂... +ButtonStopDownload=_E[h𒆎~(&S) +StopDownload=_E[h𒆎~Ă낵łH +ErrorDownloadAborted=_E[h𒆎~܂ +ErrorDownloadFailed=_E[hɎs܂: %1 %2 +ErrorDownloadSizeFailed=TCY̎擾Ɏs܂: %1 %2 +ErrorFileHash1=t@C̃nbVɎs܂: %1 +ErrorFileHash2=ȃt@CnbV: \ꂽl %1, ۂ̒l %2 +ErrorProgress=Ȑis: %1 / %2 +ErrorFileSize=ȃt@CTCY: \ꂽl %1, ۂ̒l %2 + +; *** "Preparing to Install" wizard page +WizardPreparing=CXg[ +PreparingDesc=gp̃Rs[^[ [name] CXg[鏀Ă܂B +PreviousInstallNotCompleted=OsAvP[ṼCXg[܂͍폜Ă܂Bɂ̓Rs[^[ċNKv܂B%n%n[name] ̃CXg[邽߂ɂ́AċNɂxZbgAbvsĂB +CannotContinue=ZbgAbv𑱍sł܂BuLZvNbNăZbgAbvIĂB +ApplicationsFound=ȉ̃AvP[VZbgAbvɕKvȃt@CgpĂ܂BZbgAbvɎIɃAvP[VI邱Ƃ𐄏܂B +ApplicationsFound2=ȉ̃AvP[VZbgAbvɕKvȃt@CgpĂ܂BZbgAbvɎIɃAvP[VI邱Ƃ𐄏܂BCXg[̊AZbgAbv̓AvP[V̍ċN݂܂B +CloseApplications=IɃAvP[VI(&A) +DontCloseApplications=AvP[VIȂ(&D) +ErrorCloseApplications=ZbgAbvׂ͂ẴAvP[VIɏI邱Ƃł܂łBZbgAbv𑱍sOɁAXV̕Kvȃt@CgpĂ邷ׂẴAvP[VI邱Ƃ𐄏܂B +PrepareToInstallNeedsRestart=ZbgAbv̓Rs[^[ċNKv܂BRs[^[ċNAZbgAbvēxs [name] ̃CXg[ĂB%n%nɍċN܂H? + +; *** "Installing" wizard page +WizardInstalling=CXg[ +InstallingLabel=gp̃Rs[^[ [name] CXg[Ă܂B΂炭҂B + +; *** "Setup Completed" wizard page +FinishedHeadingLabel=[name] ZbgAbvEBU[h̊ +FinishedLabelNoIcons=gp̃Rs[^[ [name] ZbgAbv܂B +FinishedLabel=gp̃Rs[^[ [name] ZbgAbv܂BAvP[Vsɂ̓CXg[ꂽV[gJbgIĂB +ClickFinish=ZbgAbvIɂ́uvNbNĂB +FinishedRestartLabel=[name] ̃CXg[邽߂ɂ́ARs[^[ċNKv܂BɍċN܂H +FinishedRestartMessage=[name] ̃CXg[邽߂ɂ́ARs[^[ċNKv܂B%n%nɍċN܂H +ShowReadmeCheck=README t@C\B +YesRadio=ɍċN(&Y) +NoRadio=Ŏ蓮ōċN(&N) +; used for example as 'Run MyProg.exe' +RunEntryExec=%1 ̎s +; used for example as 'View Readme.txt' +RunEntryShellExec=%1 ̕\ + +; *** "Setup Needs the Next Disk" stuff +ChangeDiskTitle=fBXN̑} +SelectDiskLabel2=fBXN %1 }AuOKvNbNĂB%n%ñfBXÑt@Cɕ\ĂtH_[ȊȌꏊɂꍇ́ApX͂邩uQƁv{^NbNĂB +PathLabel=pX(&P): +FileNotInDir2=t@C %1 %2 Ɍ‚܂BfBXN}邩Aʂ̃tH_[w肵ĂB +SelectDirectoryLabel=̃fBXN̂ꏊw肵ĂB + +; *** Installation phase messages +SetupAborted=ZbgAbv͊Ă܂B%n%nĂAxZbgAbvsĂB +AbortRetryIgnoreSelectAction=ANVIĂ +AbortRetryIgnoreRetry=Ďs(&T) +AbortRetryIgnoreIgnore=G[𖳎đs(&I) +AbortRetryIgnoreCancel=CXg[LZ + +; *** Installation status messages +StatusClosingApplications=AvP[VIĂ܂... +StatusCreateDirs=tH_[쐬Ă܂... +StatusExtractFiles=t@CWJĂ܂... +StatusCreateIcons=V|gJbg쐬Ă܂... +StatusCreateIniEntries=INIt@Cݒ肵Ă܂... +StatusCreateRegistryEntries=WXgݒ肵Ă܂... +StatusRegisterFiles=t@Co^Ă܂... +StatusSavingUninstall=ACXg[ۑĂ܂... +StatusRunProgram=CXg[Ă܂... +StatusRestartingApplications=AvP[VċNĂ܂... +StatusRollback=ύXɖ߂Ă܂... + +; *** Misc. errors +ErrorInternal2=G[: %1 +ErrorFunctionFailedNoCode=%1 G[ +ErrorFunctionFailed=%1 G[: R[h %2 +ErrorFunctionFailedWithMessage=%1 G[: R[h %2.%n%3 +ErrorExecutingProgram=t@CsG[:%n%1 + +; *** Registry errors +ErrorRegOpenKey=WXgL[I[vG[:%n%1\%2 +ErrorRegCreateKey=WXgL[쐬G[:%n%1\%2 +ErrorRegWriteKey=WXgL[݃G[:%n%1\%2 + +; *** INI errors +ErrorIniEntry=INIt@CGg쐬G[: t@C %1 + +; *** File copying errors +FileAbortRetryIgnoreSkipNotRecommended=̃t@CXLbv(&S) (܂) +FileAbortRetryIgnoreIgnoreNotRecommended=G[𖳎đs(&I) (܂) +SourceIsCorrupted=Rs[̃t@CĂ܂B +SourceDoesntExist=Rs[̃t@C %1 ‚܂B +ExistingFileReadOnly2=̃t@C͓ǂݎp̂ߒuł܂B +ExistingFileReadOnlyRetry=ǂݎpĂxȂ(&R) +ExistingFileReadOnlyKeepExisting=̃t@Cc(&K) +ErrorReadingExistingDest=̃t@CǂݍݒɃG[܂: +FileExistsSelectAction=ANVIĂ +FileExists2=t@C͊ɑ݂܂B +FileExistsOverwriteExisting=̃t@C㏑(&O) +FileExistsKeepExisting=̃t@Cێ(&K) +FileExistsOverwriteOrKeepAll=ȍ~̋ɓs(&D) +ExistingFileNewerSelectAction=ANVIĂ +ExistingFileNewer2=ZbgAbvCXg[悤ƂĂ̂Vt@C܂B +ExistingFileNewerOverwriteExisting=̃t@C㏑(&O) +ExistingFileNewerKeepExisting=̃t@Cێ(&K) () +ExistingFileNewerOverwriteOrKeepAll=ȍ~̋ɓs(&D) +ErrorChangingAttr=t@C̑ύXɃG[܂: +ErrorCreatingTemp=Rs[̃tH_[Ƀt@C쐬ɃG[܂: +ErrorReadingSource=Rs[̃t@CǂݍݒɃG[܂: +ErrorCopying=t@CRs[ɃG[܂: +ErrorReplacingExistingFile=̃t@CuɃG[܂: +ErrorRestartReplace=ċNɂu̎sɎs܂: +ErrorRenamingTemp=Rs[tH_[̃t@CύXɃG[܂: +ErrorRegisterServer=DLL/OCX̓o^Ɏs܂: %1 +ErrorRegSvr32Failed=RegSvr32͏IR[h %1 ɂ莸s܂ +ErrorRegisterTypeLib=^CvCuւ̓o^Ɏs܂: %1 + +; *** Uninstall display name markings +UninstallDisplayNameMark=%1 (%2) +UninstallDisplayNameMarks=%1 (%2, %3) +UninstallDisplayNameMark32Bit=32 rbg +UninstallDisplayNameMark64Bit=64 rbg +UninstallDisplayNameMarkAllUsers=ׂẴ[U[ +UninstallDisplayNameMarkCurrentUser=݂̃[U[ + +; *** Post-installation errors +ErrorOpeningReadme=README t@C̃I[vɎs܂B +ErrorRestartingComputer=Rs[^[̍ċNɎs܂B蓮ōċNĂB + +; *** Uninstaller messages +UninstallNotFound=t@C "%1" ‚܂BACXg[sł܂B +UninstallOpenError=t@C "%1" JƂł܂BACXg[sł܂B +UninstallUnsupportedVer=ACXg[Ot@C "%1" ́Ão[W̃ACXg[vOFłȂ`łBACXg[sł܂B +UninstallUnknownEntry=ACXg[Oɕs̃Gg (%1) ‚܂B +ConfirmUninstall=%1 Ƃ̊֘AR|[lgׂč폜܂B낵łH +UninstallOnlyOnWin64=̃vO64 rbgWindowsł̂݃ACXg[邱Ƃł܂B +OnlyAdminCanUninstall=ACXg[邽߂ɂ͊ǗҌKvłB +UninstallStatusLabel=gp̃Rs[^[ %1 폜Ă܂B΂炭҂B +UninstalledAll=%1 ͂gp̃Rs[^[琳ɍ폜܂B +UninstalledMost=%1 ̃ACXg[܂B%n%n‚̍ڂ폜ł܂łB蓮ō폜ĂB +UninstalledAndNeedsRestart=%1 ̍폜邽߂ɂ́ARs[^[ċNKv܂BɍċN܂H +UninstallDataCorrupted=t@C "%1" Ă܂BACXg[sł܂B + +; *** Uninstallation phase messages +ConfirmDeleteSharedFileTitle=Lt@C̍폜 +ConfirmDeleteSharedFile2=VXeŁA̋Lt@C͂ǂ̃vOłgpĂ܂B̋Lt@C폜܂H%n%ñvO܂̃t@CgpꍇA폜ƃvO삵ȂȂ鋰ꂪ܂B܂młȂꍇ́uvIĂBVXeɃt@CcĂNƂ͂܂B +SharedFileNameLabel=t@C: +SharedFileLocationLabel=ꏊ: +WizardUninstalling=ACXg[ +StatusUninstalling=%1 ACXg[Ă܂... + +; *** Shutdown block reasons +ShutdownBlockReasonInstallingApp=%1 CXg[łB +ShutdownBlockReasonUninstallingApp=%1 ACXg[łB + +; The custom messages below aren't used by Setup itself, but if you make +; use of them in your scripts, you'll want to translate them. + +[CustomMessages] + +NameAndVersion=%1 o[W %2 +AdditionalIcons=ACRlj: +CreateDesktopIcon=fXNgbvɃACR쐬(&D) +CreateQuickLaunchIcon=NCbNNACR쐬(&Q) +ProgramOnTheWeb=%1 on the Web +UninstallProgram=%1 ACXg[ +LaunchProgram=%1 s +AssocFileExtension=t@Cgq %2 %1 ֘At܂B +AssocingFileExtension=t@Cgq %2 %1 ֘AtĂ܂... +AutoStartProgramGroupDescription=X^[gAbv: +AutoStartProgram=%1 IɊJn +AddonHostProgramNotFound=IꂽtH_[ %1 ‚܂łB%n%n̂܂ܑs܂H \ No newline at end of file diff --git a/src/AITool.Setup/INNO/Languages/Norwegian.isl b/src/AITool.Setup/INNO/Languages/Norwegian.isl new file mode 100644 index 00000000..8141d45a --- /dev/null +++ b/src/AITool.Setup/INNO/Languages/Norwegian.isl @@ -0,0 +1,378 @@ +; *** Inno Setup version 6.1.0+ Norwegian (bokml) messages *** +; +; To download user-contributed translations of this file, go to: +; https://jrsoftware.org/files/istrans/ +; +; Note: When translating this text, do not add periods (.) to the end of +; messages that didn't have them already, because on those messages Inno +; Setup adds the periods automatically (appending a period would result in +; two periods being displayed). +; +; Norwegian translation currently maintained by Eivind Bakkestuen +; E-mail: eivind.bakkestuen@gmail.com +; Many thanks to the following people for language improvements and comments: +; +; Harald Habberstad, Frode Weum, Morten Johnsen, +; Tore Ottinsen, Kristian Hyllestad, Thomas Kelso, Jostein Christoffer Andersen +; +; $jrsoftware: issrc/Files/Languages/Norwegian.isl,v 1.15 2007/04/23 15:03:35 josander+ Exp $ + +[LangOptions] +LanguageName=Norsk +LanguageID=$0414 +LanguageCodePage=1252 + +[Messages] + +; *** Application titles +SetupAppTitle=Installasjon +SetupWindowTitle=Installere - %1 +UninstallAppTitle=Avinstaller +UninstallAppFullTitle=%1 Avinstallere + +; *** Misc. common +InformationTitle=Informasjon +ConfirmTitle=Bekreft +ErrorTitle=Feil + +; *** SetupLdr messages +SetupLdrStartupMessage=Dette vil installere %1. Vil du fortsette? +LdrCannotCreateTemp=Kan ikke lage midlertidig fil, installasjonen er avbrutt +LdrCannotExecTemp=Kan ikke kjre fil i den midlertidige mappen, installasjonen er avbrutt + +; *** Startup error messages +LastErrorMessage=%1.%n%nFeil %2: %3 +SetupFileMissing=Filen %1 mangler i installasjonskatalogen. Vennligst korriger problemet eller skaff deg en ny kopi av programmet. +SetupFileCorrupt=Installasjonsfilene er delagte. Vennligst skaff deg en ny kopi av programmet. +SetupFileCorruptOrWrongVer=Installasjonsfilene er delagte eller ikke kompatible med dette installasjonsprogrammet. Vennligst korriger problemet eller skaff deg en ny kopi av programmet. +InvalidParameter=Kommandolinjen hadde en ugyldig parameter:%n%n%1 +SetupAlreadyRunning=Dette programmet kjrer allerede. +WindowsVersionNotSupported=Dette programmet sttter ikke Windows-versjonen p denne maskinen. +WindowsServicePackRequired=Dette programmet krever %1 Service Pack %2 eller nyere. +NotOnThisPlatform=Dette programmet kjrer ikke p %1. +OnlyOnThisPlatform=Dette programmet kjrer kun p %1. +OnlyOnTheseArchitectures=Dette programmet kan kun installeres i Windows-versjoner som er beregnet p flgende prossessorarkitekturer:%n%n%1 +WinVersionTooLowError=Dette programmet krever %1 versjon %2 eller nyere. +WinVersionTooHighError=Dette programmet kan ikke installeres p %1 versjon %2 eller nyere. +AdminPrivilegesRequired=Administrator-rettigheter kreves for installere dette programmet. +PowerUserPrivilegesRequired=Du m vre logget inn som administrator eller ha administrator-rettigheter nr du installerer dette programmet. +SetupAppRunningError=Installasjonsprogrammet har funnet ut at %1 kjrer.%n%nVennligst avslutt det n og klikk deretter OK for fortsette, eller Avbryt for avslutte. +UninstallAppRunningError=Avinstallasjonsprogrammet har funnet ut at %1 kjrer.%n%nVennligst avslutt det n og klikk deretter OK for fortsette, eller Avbryt for avslutte. + +; *** Startup questions +PrivilegesRequiredOverrideTitle=Velg Installasjon Type +PrivilegesRequiredOverrideInstruction=Installasjons Type +PrivilegesRequiredOverrideText1=%1 kan installeres for alle brukere (krever administrator-rettigheter), eller bare for deg. +PrivilegesRequiredOverrideText2=%1 kan installeres bare for deg, eller for alle brukere (krever administrator-rettigheter). +PrivilegesRequiredOverrideAllUsers=Installer for &alle brukere +PrivilegesRequiredOverrideAllUsersRecommended=Installer for &alle brukere (anbefalt) +PrivilegesRequiredOverrideCurrentUser=Installer bare for &meg +PrivilegesRequiredOverrideCurrentUserRecommended=Installer bare for &meg (anbefalt) + +; *** Misc. errors +ErrorCreatingDir=Installasjonsprogrammet kunne ikke lage mappen "%1" +ErrorTooManyFilesInDir=Kunne ikke lage en fil i mappen "%1" fordi den inneholder for mange filer + +; *** Setup common messages +ExitSetupTitle=Avslutt installasjonen +ExitSetupMessage=Installasjonen er ikke ferdig. Programmet installeres ikke hvis du avslutter n.%n%nDu kan installere programmet igjen senere hvis du vil.%n%nVil du avslutte? +AboutSetupMenuItem=&Om installasjonsprogrammet... +AboutSetupTitle=Om installasjonsprogrammet +AboutSetupMessage=%1 versjon %2%n%3%n%n%1 hjemmeside:%n%4 +AboutSetupNote= +TranslatorNote=Norwegian translation maintained by Eivind Bakkestuen (eivind.bakkestuen@gmail.com) + +; *** Buttons +ButtonBack=< &Tilbake +ButtonNext=&Neste > +ButtonInstall=&Installer +ButtonOK=OK +ButtonCancel=Avbryt +ButtonYes=&Ja +ButtonYesToAll=Ja til &alle +ButtonNo=&Nei +ButtonNoToAll=N&ei til alle +ButtonFinish=&Ferdig +ButtonBrowse=&Bla gjennom... +ButtonWizardBrowse=&Bla gjennom... +ButtonNewFolder=&Lag ny mappe + +; *** "Select Language" dialog messages +SelectLanguageTitle=Velg installasjonssprk +SelectLanguageLabel=Velg sprket som skal brukes under installasjonen. + +; *** Common wizard text +ClickNext=Klikk p Neste for fortsette, eller Avbryt for avslutte installasjonen. +BeveledLabel= +BrowseDialogTitle=Bla etter mappe +BrowseDialogLabel=Velg en mappe fra listen nedenfor, klikk deretter OK. +NewFolderName=Ny mappe + +; *** "Welcome" wizard page +WelcomeLabel1=Velkommen til installasjonsprogrammet for [name]. +WelcomeLabel2=Dette vil installere [name/ver] p din maskin.%n%nDet anbefales at du avslutter alle programmer som kjrer fr du fortsetter. + +; *** "Password" wizard page +WizardPassword=Passord +PasswordLabel1=Denne installasjonen er passordbeskyttet. +PasswordLabel3=Vennligst oppgi ditt passord og klikk p Neste for fortsette. Sm og store bokstaver behandles ulikt. +PasswordEditLabel=&Passord: +IncorrectPassword=Det angitte passordet er feil, vennligst prv igjen. + +; *** "License Agreement" wizard page +WizardLicense=Lisensbetingelser +LicenseLabel=Vennligst les flgende viktig informasjon fr du fortsetter. +LicenseLabel3=Vennligst les flgende lisensbetingelser. Du m godta innholdet i lisensbetingelsene fr du fortsetter med installasjonen. +LicenseAccepted=Jeg &aksepterer lisensbetingelsene +LicenseNotAccepted=Jeg aksepterer &ikke lisensbetingelsene + +; *** "Information" wizard pages +WizardInfoBefore=Informasjon +InfoBeforeLabel=Vennligst les flgende viktige informasjon fr du fortsetter. +InfoBeforeClickLabel=Klikk p Neste nr du er klar til fortsette. +WizardInfoAfter=Informasjon +InfoAfterLabel=Vennligst les flgende viktige informasjon fr du fortsetter. +InfoAfterClickLabel=Klikk p Neste nr du er klar til fortsette. + +; *** "User Information" wizard page +WizardUserInfo=Brukerinformasjon +UserInfoDesc=Vennligst angi informasjon. +UserInfoName=&Brukernavn: +UserInfoOrg=&Organisasjon: +UserInfoSerial=&Serienummer: +UserInfoNameRequired=Du m angi et navn. + +; *** "Select Destination Location" wizard page +WizardSelectDir=Velg mappen hvor filene skal installeres: +SelectDirDesc=Hvor skal [name] installeres? +SelectDirLabel3=Installasjonsprogrammet vil installere [name] i flgende mappe. +SelectDirBrowseLabel=Klikk p Neste for fortsette. Klikk p Bla gjennom hvis du vil velge en annen mappe. +DiskSpaceGBLabel=Programmet krever minst [gb] GB med diskplass. +DiskSpaceMBLabel=Programmet krever minst [mb] MB med diskplass. +CannotInstallToNetworkDrive=Kan ikke installere p en nettverksstasjon. +CannotInstallToUNCPath=Kan ikke installere p en UNC-bane. Du m tilordne nettverksstasjonen hvis du vil installere i et nettverk. +InvalidPath=Du m angi en full bane med stasjonsbokstav, for eksempel:%n%nC:\APP%n%Du kan ikke bruke formen:%n%n\\server\share +InvalidDrive=Den valgte stasjonen eller UNC-delingen finnes ikke, eller er ikke tilgjengelig. Vennligst velg en annen +DiskSpaceWarningTitle=For lite diskplass +DiskSpaceWarning=Installasjonprogrammet krever minst %1 KB med ledig diskplass, men det er bare %2 KB ledig p den valgte stasjonen.%n%nvil du fortsette likevel? +DirNameTooLong=Det er for langt navn p mappen eller banen. +InvalidDirName=Navnet p mappen er ugyldig. +BadDirName32=Mappenavn m ikke inneholde noen av flgende tegn:%n%n%1 +DirExistsTitle=Eksisterende mappe +DirExists=Mappen:%n%n%1%n%nfinnes allerede. Vil du likevel installere der? +DirDoesntExistTitle=Mappen eksisterer ikke +DirDoesntExist=Mappen:%n%n%1%n%nfinnes ikke. Vil du at den skal lages? + +; *** "Select Components" wizard page +WizardSelectComponents=Velg komponenter +SelectComponentsDesc=Hvilke komponenter skal installeres? +SelectComponentsLabel2=Velg komponentene du vil installere; velg bort de komponentene du ikke vil installere. Nr du er klar, klikker du p Neste for fortsette. +FullInstallation=Full installasjon +; if possible don't translate 'Compact' as 'Minimal' (I mean 'Minimal' in your language) +CompactInstallation=Kompakt installasjon +CustomInstallation=Egendefinert installasjon +NoUninstallWarningTitle=Komponenter eksisterer +NoUninstallWarning=Installasjonsprogrammet har funnet ut at flgende komponenter allerede er p din maskin:%n%n%1%n%nDisse komponentene avinstalleres ikke selv om du ikke velger dem.%n%nVil du likevel fortsette? +ComponentSize1=%1 KB +ComponentSize2=%1 MB +ComponentsDiskSpaceGBLabel=Valgte alternativer krever minst [gb] GB med diskplass. +ComponentsDiskSpaceMBLabel=Valgte alternativer krever minst [mb] MB med diskplass. + +; *** "Select Additional Tasks" wizard page +WizardSelectTasks=Velg tilleggsoppgaver +SelectTasksDesc=Hvilke tilleggsoppgaver skal utfres? +SelectTasksLabel2=Velg tilleggsoppgavene som skal utfres mens [name] installeres, klikk deretter p Neste. + +; *** "Select Start Menu Folder" wizard page +WizardSelectProgramGroup=Velg mappe p start-menyen +SelectStartMenuFolderDesc=Hvor skal installasjonsprogrammet plassere snarveiene? +SelectStartMenuFolderLabel3=Installasjonsprogrammet vil opprette snarveier p flgende startmeny-mappe. +SelectStartMenuFolderBrowseLabel=Klikk p Neste for fortsette. Klikk p Bla igjennom hvis du vil velge en annen mappe. +MustEnterGroupName=Du m skrive inn et mappenavn. +GroupNameTooLong=Det er for langt navn p mappen eller banen. +InvalidGroupName=Navnet p mappen er ugyldig. +BadGroupName=Mappenavnet m ikke inneholde flgende tegn:%n%n%1 +NoProgramGroupCheck2=&Ikke legg til mappe p start-menyen + +; *** "Ready to Install" wizard page +WizardReady=Klar til installere +ReadyLabel1=Installasjonsprogrammet er n klar til installere [name] p din maskin. +ReadyLabel2a=Klikk Installer for fortsette, eller Tilbake for se p eller forandre instillingene. +ReadyLabel2b=Klikk Installer for fortsette. +ReadyMemoUserInfo=Brukerinformasjon: +ReadyMemoDir=Installer i mappen: +ReadyMemoType=Installasjonstype: +ReadyMemoComponents=Valgte komponenter: +ReadyMemoGroup=Programgruppe: +ReadyMemoTasks=Tilleggsoppgaver: + +; *** TDownloadWizardPage wizard page and DownloadTemporaryFile +DownloadingLabel=Laster ned ekstra filer... +ButtonStopDownload=&Stopp nedlasting +StopDownload=Er du sikker p at du vil stoppe nedlastingen? +ErrorDownloadAborted=Nedlasting avbrutt +ErrorDownloadFailed=Nedlasting feilet: %1 %2 +ErrorDownloadSizeFailed=Kunne ikke finne filstrrelse: %1 %2 +ErrorFileHash1=Fil hash verdi feilet: %1 +ErrorFileHash2=Ugyldig fil hash verdi: forventet %1, fant %2 +ErrorProgress=Ugyldig fremdrift: %1 of %2 +ErrorFileSize=Ugyldig fil strrelse: forventet %1, fant %2 + +; *** "Preparing to Install" wizard page +WizardPreparing=Forbereder installasjonen +PreparingDesc=Installasjonsprogrammet forbereder installasjon av [name] p den maskin. +PreviousInstallNotCompleted=Installasjonen/fjerningen av et tidligere program ble ikke ferdig. Du m starte maskinen p nytt.%n%nEtter omstarten m du kjre installasjonsprogrammet p nytt for fullfre installasjonen av [name]. +CannotContinue=Installasjonsprogrammet kan ikke fortsette. Klikk p Avbryt for avslutte. +ApplicationsFound=Disse applikasjonene bruker filer som vil oppdateres av installasjonen. Det anbefales la installasjonen automatisk avslutte disse applikasjonene. +ApplicationsFound2=Disse applikasjonene bruker filer som vil oppdateres av installasjonen. Det anbefales la installasjonen automatisk avslutte disse applikasjonene. Installasjonen vil prve starte applikasjonene p nytt etter at installasjonen er avsluttet. +CloseApplications=Lukk applikasjonene &automatisk +DontCloseApplications=&Ikke lukk applikasjonene +ErrorCloseApplications=Installasjonsprogrammet kunne ikke lukke alle applikasjonene &automatisk. Det anbefales lukke alle applikasjoner som bruker filer som installasjonsprogrammet trenger oppdatere fr du fortsetter installasjonen. +PrepareToInstallNeedsRestart=Installasjonsprogrammet m gjre omstart av maskinen. Etter omstart av maskinen, kjr installasjonsprogrammet p nytt for ferdigstille installasjonen av [name].%n%nVil du gjre omstart av maskinen n? + +; *** "Installing" wizard page +WizardInstalling=Installerer +InstallingLabel=Vennligst vent mens [name] installeres p din maskin. + +; *** "Setup Completed" wizard page +FinishedHeadingLabel=Fullfrer installasjonsprogrammet for [name] +FinishedLabelNoIcons=[name] er installert p din maskin. +FinishedLabel=[name] er installert p din maskin. Programmet kan kjres ved at du klikker p ett av de installerte ikonene. +ClickFinish=Klikk Ferdig for avslutte installasjonen. +FinishedRestartLabel=Maskinen m startes p nytt for at installasjonen skal fullfres. Vil du starte p nytt n? +FinishedRestartMessage=Maskinen m startes p nytt for at installasjonen skal fullfres.%n%nVil du starte p nytt n? +ShowReadmeCheck=Ja, jeg vil se p LESMEG-filen +YesRadio=&Ja, start maskinen p nytt n +NoRadio=&Nei, jeg vil starte maskinen p nytt senere +; used for example as 'Run MyProg.exe' +RunEntryExec=Kjr %1 +; used for example as 'View Readme.txt' +RunEntryShellExec=Se p %1 + +; *** "Setup Needs the Next Disk" stuff +ChangeDiskTitle=Trenger neste diskett +SelectDiskLabel2=Vennligst sett inn diskett %1 og klikk OK.%n%nHvis filene p finnes et annet sted enn det som er angitt nedenfor, kan du skrive inn korrekt bane eller klikke p Bla Gjennom. +PathLabel=&Bane: +FileNotInDir2=Finner ikke filen "%1" i "%2". Vennligst sett inn riktig diskett eller velg en annen mappe. +SelectDirectoryLabel=Vennligst angi hvor den neste disketten er. + +; *** Installation phase messages +SetupAborted=Installasjonen ble avbrutt.%n%nVennligst korriger problemet og prv igjen. +AbortRetryIgnoreSelectAction=Velg aksjon +AbortRetryIgnoreRetry=&Prv Igjen +AbortRetryIgnoreIgnore=&Ignorer feil og fortsett +AbortRetryIgnoreCancel=Cancel installation + +; *** Installation status messages +StatusClosingApplications=Lukker applikasjoner... +StatusCreateDirs=Lager mapper... +StatusExtractFiles=Pakker ut filer... +StatusCreateIcons=Lager programikoner... +StatusCreateIniEntries=Lager INI-instillinger... +StatusCreateRegistryEntries=Lager innstillinger i registeret... +StatusRegisterFiles=Registrerer filer... +StatusSavingUninstall=Lagrer info for avinstallering... +StatusRunProgram=Gjr ferdig installasjonen... +StatusRestartingApplications=Restarter applikasjoner... +StatusRollback=Tilbakestiller forandringer... + +; *** Misc. errors +ErrorInternal2=Intern feil %1 +ErrorFunctionFailedNoCode=%1 gikk galt +ErrorFunctionFailed=%1 gikk galt; kode %2 +ErrorFunctionFailedWithMessage=%1 gikk galt; kode %2.%n%3 +ErrorExecutingProgram=Kan ikke kjre filen:%n%1 + +; *** Registry errors +ErrorRegOpenKey=Feil under pning av registernkkel:%n%1\%2 +ErrorRegCreateKey=Feil under laging av registernkkel:%n%1\%2 +ErrorRegWriteKey=Feil under skriving til registernkkel:%n%1\%2 + +; *** INI errors +ErrorIniEntry=Feil under laging av innstilling i filen "%1". + +; *** File copying errors +FileAbortRetryIgnoreSkipNotRecommended=&Hopp over denne filen (ikke anbefalt) +FileAbortRetryIgnoreIgnoreNotRecommended=&Ignorer feilen og fortsett (ikke anbefalt) +SourceIsCorrupted=Kildefilen er delagt +SourceDoesntExist=Kildefilen "%1" finnes ikke +ExistingFileReadOnly2=Den eksisterende filen er skrivebeskyttet og kan ikke erstattes. +ExistingFileReadOnlyRetry=&Fjern skrivebeskyttelse og prv igjen +ExistingFileReadOnlyKeepExisting=&Behold eksisterende fil +ErrorReadingExistingDest=En feil oppsto under lesing av den eksisterende filen: +FileExistsSelectAction=Velg aksjon +FileExists2=Filen eksisterer allerede. +FileExistsOverwriteExisting=&Overskriv den eksisterende filen +FileExistsKeepExisting=&Behold den eksisterende filen +FileExistsOverwriteOrKeepAll=&Gjr samme valg for pflgende konflikter +ExistingFileNewerSelectAction=Velg aksjon +ExistingFileNewer2=Den eksisterende filen er nyere enn filen Installasjonen prver installere. +ExistingFileNewerOverwriteExisting=&Overskriv den eksisterende filen +ExistingFileNewerKeepExisting=&Behold den eksisterende filen (anbefalt) +ExistingFileNewerOverwriteOrKeepAll=&Gjr samme valg for pflgende konflikter +ErrorChangingAttr=En feil oppsto da attributtene ble forskt forandret p den eksisterende filen: +ErrorCreatingTemp=En feil oppsto under forsket p lage en fil i ml-mappen: +ErrorReadingSource=En feil oppsto under forsket p lese kildefilen: +ErrorCopying=En feil oppsto under forsk p kopiere en fil: +ErrorReplacingExistingFile=En feil oppsto under forsket p erstatte den eksisterende filen: +ErrorRestartReplace=RestartReplace gikk galt: +ErrorRenamingTemp=En feil oppsto under omdping av fil i ml-mappen: +ErrorRegisterServer=Kan ikke registrere DLL/OCX: %1 +ErrorRegSvr32Failed=RegSvr32 gikk galt med avslutte kode %1 +ErrorRegisterTypeLib=Kan ikke registrere typebiblioteket: %1 + +; *** Uninstall display name markings +UninstallDisplayNameMark=%1 (%2) +UninstallDisplayNameMarks=%1 (%2, %3) +UninstallDisplayNameMark32Bit=32-bit +UninstallDisplayNameMark64Bit=64-bit +UninstallDisplayNameMarkAllUsers=Alle brukere +UninstallDisplayNameMarkCurrentUser=Aktiv bruker + +; *** Post-installation errors +ErrorOpeningReadme=En feil oppsto under forsket p pne LESMEG-filen. +ErrorRestartingComputer=Installasjonsprogrammet kunne ikke starte maskinen p nytt. Vennligst gjr dette manuelt. + +; *** Uninstaller messages +UninstallNotFound=Filen "%1" finnes ikke. Kan ikke avinstallere. +UninstallOpenError=Filen "%1" kunne ikke pnes. Kan ikke avinstallere. +UninstallUnsupportedVer=Kan ikke avinstallere. Avinstallasjons-loggfilen "%1" har et format som ikke gjenkjennes av denne versjonen av avinstallasjons-programmet +UninstallUnknownEntry=Et ukjent parameter (%1) ble funnet i Avinstallasjons-loggfilen +ConfirmUninstall=Er du sikker p at du helt vil fjerne %1 og alle tilhrende komponenter? +UninstallOnlyOnWin64=Denne installasjonen kan bare ufres p 64-bit Windows. +OnlyAdminCanUninstall=Denne installasjonen kan bare avinstalleres av en bruker med Administrator-rettigheter. +UninstallStatusLabel=Vennligst vent mens %1 fjernes fra maskinen. +UninstalledAll=Avinstallasjonen av %1 var vellykket +UninstalledMost=Avinstallasjonen av %1 er ferdig.%n%nEnkelte elementer kunne ikke fjernes. Disse kan fjernes manuelt. +UninstalledAndNeedsRestart=Du m starte maskinen p nytt for fullfre installasjonen av %1.%n%nVil du starte p nytt n? +UninstallDataCorrupted="%1"-filen er delagt. Kan ikke avinstallere. + +; *** Uninstallation phase messages +ConfirmDeleteSharedFileTitle=Fjerne delte filer? +ConfirmDeleteSharedFile2=Systemet indikerer at den flgende filen ikke lengre brukes av andre programmer. Vil du at avinstalleringsprogrammet skal fjerne den delte filen?%n%nHvis andre programmer bruker denne filen, kan du risikere at de ikke lengre vil virke som de skal. Velg Nei hvis du er usikker. Det vil ikke gjre noen skade hvis denne filen ligger p din maskin. +SharedFileNameLabel=Filnavn: +SharedFileLocationLabel=Plassering: +WizardUninstalling=Avinstallerings-status: +StatusUninstalling=Avinstallerer %1... + +; *** Shutdown block reasons +ShutdownBlockReasonInstallingApp=Installerer %1. +ShutdownBlockReasonUninstallingApp=Avinstallerer %1. + +; The custom messages below aren't used by Setup itself, but if you make +; use of them in your scripts, you'll want to translate them. + +[CustomMessages] + +NameAndVersion=%1 versjon %2 +AdditionalIcons=Ekstra-ikoner: +CreateDesktopIcon=Lag ikon p &skrivebordet +CreateQuickLaunchIcon=Lag et &Hurtigstarts-ikon +ProgramOnTheWeb=%1 p nettet +UninstallProgram=Avinstaller %1 +LaunchProgram=Kjr %1 +AssocFileExtension=&Koble %1 med filetternavnet %2 +AssocingFileExtension=Kobler %1 med filetternavnet %2... +AutoStartProgramGroupDescription=Oppstart: +AutoStartProgram=Start %1 automatisk +AddonHostProgramNotFound=%1 ble ikke funnet i katalogen du valgte.%n%nVil du fortsette likevel? \ No newline at end of file diff --git a/src/AITool.Setup/INNO/Languages/Polish.isl b/src/AITool.Setup/INNO/Languages/Polish.isl new file mode 100644 index 00000000..b1028142 --- /dev/null +++ b/src/AITool.Setup/INNO/Languages/Polish.isl @@ -0,0 +1,377 @@ +; *** Inno Setup version 6.1.0+ Polish messages *** +; Krzysztof Cynarski +; Proofreading, corrections and 5.5.7-6.1.0+ updates: +; ukasz Abramczuk +; To download user-contributed translations of this file, go to: +; https://jrsoftware.org/files/istrans/ +; +; Note: When translating this text, do not add periods (.) to the end of +; messages that didn't have them already, because on those messages Inno +; Setup adds the periods automatically (appending a period would result in +; two periods being displayed). +; last update: 2020/07/26 + +[LangOptions] +; The following three entries are very important. Be sure to read and +; understand the '[LangOptions] section' topic in the help file. +LanguageName=Polski +LanguageID=$0415 +LanguageCodePage=1250 + +[Messages] + +; *** Application titles +SetupAppTitle=Instalator +SetupWindowTitle=Instalacja - %1 +UninstallAppTitle=Dezinstalator +UninstallAppFullTitle=Dezinstalacja - %1 + +; *** Misc. common +InformationTitle=Informacja +ConfirmTitle=Potwierd +ErrorTitle=Bd + +; *** SetupLdr messages +SetupLdrStartupMessage=Ten program zainstaluje aplikacj %1. Czy chcesz kontynuowa? +LdrCannotCreateTemp=Nie mona utworzy pliku tymczasowego. Instalacja przerwana +LdrCannotExecTemp=Nie mona uruchomi pliku z folderu tymczasowego. Instalacja przerwana +HelpTextNote= + +; *** Startup error messages +LastErrorMessage=%1.%n%nBd %2: %3 +SetupFileMissing=W folderze instalacyjnym brakuje pliku %1.%nProsz o przywrcenie brakujcych plikw lub uzyskanie nowej kopii programu instalacyjnego. +SetupFileCorrupt=Pliki instalacyjne s uszkodzone. Zaleca si uzyskanie nowej kopii programu instalacyjnego. +SetupFileCorruptOrWrongVer=Pliki instalacyjne s uszkodzone lub niezgodne z t wersj instalatora. Prosz rozwiza problem lub uzyska now kopi programu instalacyjnego. +InvalidParameter=W linii komend przekazano nieprawidowy parametr:%n%n%1 +SetupAlreadyRunning=Instalator jest ju uruchomiony. +WindowsVersionNotSupported=Ta aplikacja nie wspiera aktualnie uruchomionej wersji Windows. +WindowsServicePackRequired=Ta aplikacja wymaga systemu %1 z dodatkiem Service Pack %2 lub nowszym. +NotOnThisPlatform=Tej aplikacji nie mona uruchomi w systemie %1. +OnlyOnThisPlatform=Ta aplikacja wymaga systemu %1. +OnlyOnTheseArchitectures=Ta aplikacja moe by uruchomiona tylko w systemie Windows zaprojektowanym dla procesorw o architekturze:%n%n%1 +WinVersionTooLowError=Ta aplikacja wymaga systemu %1 w wersji %2 lub nowszej. +WinVersionTooHighError=Ta aplikacja nie moe by zainstalowana w systemie %1 w wersji %2 lub nowszej. +AdminPrivilegesRequired=Aby przeprowadzi instalacj tej aplikacji, konto uytkownika systemu musi posiada uprawnienia administratora. +PowerUserPrivilegesRequired=Aby przeprowadzi instalacj tej aplikacji, konto uytkownika systemu musi posiada uprawnienia administratora lub uytkownika zaawansowanego. +SetupAppRunningError=Instalator wykry, i aplikacja %1 jest aktualnie uruchomiona.%n%nPrzed wciniciem przycisku OK zamknij wszystkie procesy aplikacji. Kliknij przycisk Anuluj, aby przerwa instalacj. +UninstallAppRunningError=Dezinstalator wykry, i aplikacja %1 jest aktualnie uruchomiona.%n%nPrzed wciniciem przycisku OK zamknij wszystkie procesy aplikacji. Kliknij przycisk Anuluj, aby przerwa dezinstalacj. + +; *** Startup questions --- +PrivilegesRequiredOverrideTitle=Wybierz typ instalacji aplikacji +PrivilegesRequiredOverrideInstruction=Wybierz typ instalacji +PrivilegesRequiredOverrideText1=Aplikacja %1 moe zosta zainstalowana dla wszystkich uytkownikw (wymagane s uprawnienia administratora) lub tylko dla biecego uytkownika. +PrivilegesRequiredOverrideText2=Aplikacja %1 moe zosta zainstalowana dla biecego uytkownika lub wszystkich uytkownikw (wymagane s uprawnienia administratora). +PrivilegesRequiredOverrideAllUsers=Zainstaluj dla &wszystkich uytkownikw +PrivilegesRequiredOverrideAllUsersRecommended=Zainstaluj dla &wszystkich uytkownikw (zalecane) +PrivilegesRequiredOverrideCurrentUser=Zainstaluj dla &biecego uytkownika +PrivilegesRequiredOverrideCurrentUserRecommended=Zainstaluj dla &biecego uytkownika (zalecane) + +; *** Misc. errors +ErrorCreatingDir=Instalator nie mg utworzy katalogu "%1" +ErrorTooManyFilesInDir=Nie mona utworzy pliku w katalogu "%1", poniewa zawiera on zbyt wiele plikw + +; *** Setup common messages +ExitSetupTitle=Zakocz instalacj +ExitSetupMessage=Instalacja nie zostaa zakoczona. Jeeli przerwiesz j teraz, aplikacja nie zostanie zainstalowana. Mona ponowi instalacj pniej poprzez uruchamianie instalatora.%n%nCzy chcesz przerwa instalacj? +AboutSetupMenuItem=&O instalatorze... +AboutSetupTitle=O instalatorze +AboutSetupMessage=%1 wersja %2%n%3%n%n Strona domowa %1:%n%4 +AboutSetupNote= +TranslatorNote=Wersja polska: Krzysztof Cynarski%n%nOd wersji 5.5.7: ukasz Abramczuk%n + +; *** Buttons +ButtonBack=< &Wstecz +ButtonNext=&Dalej > +ButtonInstall=&Instaluj +ButtonOK=OK +ButtonCancel=Anuluj +ButtonYes=&Tak +ButtonYesToAll=Tak na &wszystkie +ButtonNo=&Nie +ButtonNoToAll=N&ie na wszystkie +ButtonFinish=&Zakocz +ButtonBrowse=&Przegldaj... +ButtonWizardBrowse=P&rzegldaj... +ButtonNewFolder=&Utwrz nowy folder + +; *** "Select Language" dialog messages +SelectLanguageTitle=Jzyk instalacji +SelectLanguageLabel=Wybierz jzyk uywany podczas instalacji: + +; *** Common wizard text +ClickNext=Kliknij przycisk Dalej, aby kontynuowa, lub Anuluj, aby zakoczy instalacj. +BeveledLabel= +BrowseDialogTitle=Wska folder +BrowseDialogLabel=Wybierz folder z poniszej listy, a nastpnie kliknij przycisk OK. +NewFolderName=Nowy folder + +; *** "Welcome" wizard page +WelcomeLabel1=Witamy w instalatorze aplikacji [name] +WelcomeLabel2=Aplikacja [name/ver] zostanie teraz zainstalowana na komputerze.%n%nZalecane jest zamknicie wszystkich innych uruchomionych programw przed rozpoczciem procesu instalacji. + +; *** "Password" wizard page +WizardPassword=Haso +PasswordLabel1=Ta instalacja jest zabezpieczona hasem. +PasswordLabel3=Podaj haso, a nastpnie kliknij przycisk Dalej, aby kontynuowa. W hasach rozrniane s wielkie i mae litery. +PasswordEditLabel=&Haso: +IncorrectPassword=Wprowadzone haso jest nieprawidowe. Sprbuj ponownie. + +; *** "License Agreement" wizard page +WizardLicense=Umowa Licencyjna +LicenseLabel=Przed kontynuacj naley zapozna si z ponisz wan informacj. +LicenseLabel3=Prosz przeczyta tekst Umowy Licencyjnej. Przed kontynuacj instalacji naley zaakceptowa warunki umowy. +LicenseAccepted=&Akceptuj warunki umowy +LicenseNotAccepted=&Nie akceptuj warunkw umowy + +; *** "Information" wizard pages +WizardInfoBefore=Informacja +InfoBeforeLabel=Przed kontynuacj naley zapozna si z ponisz informacj. +InfoBeforeClickLabel=Kiedy bdziesz gotowy do instalacji, kliknij przycisk Dalej. +WizardInfoAfter=Informacja +InfoAfterLabel=Przed kontynuacj naley zapozna si z ponisz informacj. +InfoAfterClickLabel=Gdy bdziesz gotowy do zakoczenia instalacji, kliknij przycisk Dalej. + +; *** "User Information" wizard page +WizardUserInfo=Dane uytkownika +UserInfoDesc=Prosz poda swoje dane. +UserInfoName=Nazwa &uytkownika: +UserInfoOrg=&Organizacja: +UserInfoSerial=Numer &seryjny: +UserInfoNameRequired=Nazwa uytkownika jest wymagana. + +; *** "Select Destination Location" wizard page +WizardSelectDir=Lokalizacja docelowa +SelectDirDesc=Gdzie ma zosta zainstalowana aplikacja [name]? +SelectDirLabel3=Instalator zainstaluje aplikacj [name] do wskazanego poniej folderu. +SelectDirBrowseLabel=Kliknij przycisk Dalej, aby kontynuowa. Jeli chcesz wskaza inny folder, kliknij przycisk Przegldaj. +DiskSpaceGBLabel=Instalacja wymaga przynajmniej [gb] GB wolnego miejsca na dysku. +DiskSpaceMBLabel=Instalacja wymaga przynajmniej [mb] MB wolnego miejsca na dysku. +CannotInstallToNetworkDrive=Instalator nie moe zainstalowa aplikacji na dysku sieciowym. +CannotInstallToUNCPath=Instalator nie moe zainstalowa aplikacji w ciece UNC. +InvalidPath=Naley wprowadzi pen ciek wraz z liter dysku, np.:%n%nC:\PROGRAM%n%nlub ciek sieciow (UNC) w formacie:%n%n\\serwer\udzia +InvalidDrive=Wybrany dysk lub udostpniony folder sieciowy nie istnieje. Prosz wybra inny. +DiskSpaceWarningTitle=Niewystarczajca ilo wolnego miejsca na dysku +DiskSpaceWarning=Instalator wymaga co najmniej %1 KB wolnego miejsca na dysku. Wybrany dysk posiada tylko %2 KB dostpnego miejsca.%n%nCzy mimo to chcesz kontynuowa? +DirNameTooLong=Nazwa folderu lub cieki jest za duga. +InvalidDirName=Niepoprawna nazwa folderu. +BadDirName32=Nazwa folderu nie moe zawiera adnego z nastpujcych znakw:%n%n%1 +DirExistsTitle=Folder ju istnieje +DirExists=Poniszy folder ju istnieje:%n%n%1%n%nCzy mimo to chcesz zainstalowa aplikacj w tym folderze? +DirDoesntExistTitle=Folder nie istnieje +DirDoesntExist=Poniszy folder nie istnieje:%n%n%1%n%nCzy chcesz, aby zosta utworzony? + +; *** "Select Components" wizard page +WizardSelectComponents=Komponenty instalacji +SelectComponentsDesc=Ktre komponenty maj zosta zainstalowane? +SelectComponentsLabel2=Zaznacz komponenty, ktre chcesz zainstalowa i odznacz te, ktrych nie chcesz zainstalowa. Kliknij przycisk Dalej, aby kontynuowa. +FullInstallation=Instalacja pena +; if possible don't translate 'Compact' as 'Minimal' (I mean 'Minimal' in your language) +CompactInstallation=Instalacja podstawowa +CustomInstallation=Instalacja uytkownika +NoUninstallWarningTitle=Zainstalowane komponenty +NoUninstallWarning=Instalator wykry, e na komputerze s ju zainstalowane nastpujce komponenty:%n%n%1%n%nOdznaczenie ktregokolwiek z nich nie spowoduje ich dezinstalacji.%n%nCzy pomimo tego chcesz kontynuowa? +ComponentSize1=%1 KB +ComponentSize2=%1 MB +ComponentsDiskSpaceGBLabel=Wybrane komponenty wymagaj co najmniej [gb] GB na dysku. +ComponentsDiskSpaceMBLabel=Wybrane komponenty wymagaj co najmniej [mb] MB na dysku. + +; *** "Select Additional Tasks" wizard page +WizardSelectTasks=Zadania dodatkowe +SelectTasksDesc=Ktre zadania dodatkowe maj zosta wykonane? +SelectTasksLabel2=Zaznacz dodatkowe zadania, ktre instalator ma wykona podczas instalacji aplikacji [name], a nastpnie kliknij przycisk Dalej, aby kontynuowa. + +; *** "Select Start Menu Folder" wizard page +WizardSelectProgramGroup=Folder Menu Start +SelectStartMenuFolderDesc=Gdzie maj zosta umieszczone skrty do aplikacji? +SelectStartMenuFolderLabel3=Instalator utworzy skrty do aplikacji we wskazanym poniej folderze Menu Start. +SelectStartMenuFolderBrowseLabel=Kliknij przycisk Dalej, aby kontynuowa. Jeli chcesz wskaza inny folder, kliknij przycisk Przegldaj. +MustEnterGroupName=Musisz wprowadzi nazw folderu. +GroupNameTooLong=Nazwa folderu lub cieki jest za duga. +InvalidGroupName=Niepoprawna nazwa folderu. +BadGroupName=Nazwa folderu nie moe zawiera adnego z nastpujcych znakw:%n%n%1 +NoProgramGroupCheck2=&Nie twrz folderu w Menu Start + +; *** "Ready to Install" wizard page +WizardReady=Gotowy do rozpoczcia instalacji +ReadyLabel1=Instalator jest ju gotowy do rozpoczcia instalacji aplikacji [name] na komputerze. +ReadyLabel2a=Kliknij przycisk Instaluj, aby rozpocz instalacj lub Wstecz, jeli chcesz przejrze lub zmieni ustawienia. +ReadyLabel2b=Kliknij przycisk Instaluj, aby kontynuowa instalacj. +ReadyMemoUserInfo=Dane uytkownika: +ReadyMemoDir=Lokalizacja docelowa: +ReadyMemoType=Rodzaj instalacji: +ReadyMemoComponents=Wybrane komponenty: +ReadyMemoGroup=Folder w Menu Start: +ReadyMemoTasks=Dodatkowe zadania: + +; *** TDownloadWizardPage wizard page and DownloadTemporaryFile +DownloadingLabel=Pobieranie dodatkowych plikw... +ButtonStopDownload=&Zatrzymaj pobieranie +StopDownload=Czy na pewno chcesz zatrzyma pobieranie? +ErrorDownloadAborted=Pobieranie przerwane +ErrorDownloadFailed=Bd pobierania: %1 %2 +ErrorDownloadSizeFailed=Pobieranie informacji o rozmiarze nie powiodo si: %1 %2 +ErrorFileHash1=Bd sumy kontrolnej pliku: %1 +ErrorFileHash2=Nieprawidowa suma kontrolna pliku: oczekiwano %1, otrzymano %2 +ErrorProgress=Nieprawidowy postp: %1 z %2 +ErrorFileSize=Nieprawidowy rozmiar pliku: oczekiwano %1, otrzymano %2 + +; *** "Preparing to Install" wizard page +WizardPreparing=Przygotowanie do instalacji +PreparingDesc=Instalator przygotowuje instalacj aplikacji [name] na komputerze. +PreviousInstallNotCompleted=Instalacja/dezinstalacja poprzedniej wersji aplikacji nie zostaa zakoczona. Aby zakoczy instalacj, naley ponownie uruchomi komputer. %n%nNastpnie ponownie uruchom instalator, aby zakoczy instalacj aplikacji [name]. +CannotContinue=Instalator nie moe kontynuowa. Kliknij przycisk Anuluj, aby przerwa instalacj. +ApplicationsFound=Ponisze aplikacje uywaj plikw, ktre musz zosta uaktualnione przez instalator. Zaleca si zezwoli na automatyczne zamknicie tych aplikacji przez program instalacyjny. +ApplicationsFound2=Ponisze aplikacje uywaj plikw, ktre musz zosta uaktualnione przez instalator. Zaleca si zezwoli na automatyczne zamknicie tych aplikacji przez program instalacyjny. Po zakoczonej instalacji instalator podejmie prb ich ponownego uruchomienia. +CloseApplications=&Automatycznie zamknij aplikacje +DontCloseApplications=&Nie zamykaj aplikacji +ErrorCloseApplications=Instalator nie by w stanie automatycznie zamkn wymaganych aplikacji. Zalecane jest zamknicie wszystkich aplikacji, ktre aktualnie uywaj uaktualnianych przez program instalacyjny plikw. +PrepareToInstallNeedsRestart=Instalator wymaga ponownego uruchomienia komputera. Po restarcie komputera uruchom instalator ponownie, by dokoczy proces instalacji aplikacji [name].%n%nCzy chcesz teraz uruchomi komputer ponownie? + +; *** "Installing" wizard page +WizardInstalling=Instalacja +InstallingLabel=Poczekaj, a instalator zainstaluje aplikacj [name] na komputerze. + +; *** "Setup Completed" wizard page +FinishedHeadingLabel=Zakoczono instalacj aplikacji [name] +FinishedLabelNoIcons=Instalator zakoczy instalacj aplikacji [name] na komputerze. +FinishedLabel=Instalator zakoczy instalacj aplikacji [name] na komputerze. Aplikacja moe by uruchomiona poprzez uycie zainstalowanych skrtw. +ClickFinish=Kliknij przycisk Zakocz, aby zakoczy instalacj. +FinishedRestartLabel=Aby zakoczy instalacj aplikacji [name], instalator musi ponownie uruchomi komputer. Czy chcesz teraz uruchomi komputer ponownie? +FinishedRestartMessage=Aby zakoczy instalacj aplikacji [name], instalator musi ponownie uruchomi komputer.%n%nCzy chcesz teraz uruchomi komputer ponownie? +ShowReadmeCheck=Tak, chc przeczyta dodatkowe informacje +YesRadio=&Tak, uruchom ponownie teraz +NoRadio=&Nie, uruchomi ponownie pniej +; used for example as 'Run MyProg.exe' +RunEntryExec=Uruchom aplikacj %1 +; used for example as 'View Readme.txt' +RunEntryShellExec=Wywietl plik %1 + +; *** "Setup Needs the Next Disk" stuff +ChangeDiskTitle=Instalator potrzebuje kolejnego archiwum +SelectDiskLabel2=Prosz woy dysk %1 i klikn przycisk OK.%n%nJeli wymieniony poniej folder nie okrela pooenia plikw z tego dysku, prosz wprowadzi poprawn ciek lub klikn przycisk Przegldaj. +PathLabel=&cieka: +FileNotInDir2=cieka "%2" nie zawiera pliku "%1". Prosz woy waciwy dysk lub wybra inny folder. +SelectDirectoryLabel=Prosz okreli lokalizacj kolejnego archiwum instalatora. + +; *** Installation phase messages +SetupAborted=Instalacja nie zostaa zakoczona.%n%nProsz rozwiza problem i ponownie rozpocz instalacj. +AbortRetryIgnoreSelectAction=Wybierz operacj +AbortRetryIgnoreRetry=Sprbuj &ponownie +AbortRetryIgnoreIgnore=Z&ignoruj bd i kontynuuj +AbortRetryIgnoreCancel=Przerwij instalacj + +; *** Installation status messages +StatusClosingApplications=Zamykanie aplikacji... +StatusCreateDirs=Tworzenie folderw... +StatusExtractFiles=Dekompresja plikw... +StatusCreateIcons=Tworzenie skrtw aplikacji... +StatusCreateIniEntries=Tworzenie zapisw w plikach INI... +StatusCreateRegistryEntries=Tworzenie zapisw w rejestrze... +StatusRegisterFiles=Rejestracja plikw... +StatusSavingUninstall=Zapisywanie informacji o dezinstalacji... +StatusRunProgram=Koczenie instalacji... +StatusRestartingApplications=Ponowne uruchamianie aplikacji... +StatusRollback=Cofanie zmian... + +; *** Misc. errors +ErrorInternal2=Wewntrzny bd: %1 +ErrorFunctionFailedNoCode=Bd podczas wykonywania %1 +ErrorFunctionFailed=Bd podczas wykonywania %1; kod %2 +ErrorFunctionFailedWithMessage=Bd podczas wykonywania %1; kod %2.%n%3 +ErrorExecutingProgram=Nie mona uruchomi:%n%1 + +; *** Registry errors +ErrorRegOpenKey=Bd podczas otwierania klucza rejestru:%n%1\%2 +ErrorRegCreateKey=Bd podczas tworzenia klucza rejestru:%n%1\%2 +ErrorRegWriteKey=Bd podczas zapisu do klucza rejestru:%n%1\%2 + +; *** INI errors +ErrorIniEntry=Bd podczas tworzenia pozycji w pliku INI: "%1". + +; *** File copying errors +FileAbortRetryIgnoreSkipNotRecommended=&Pomi plik (niezalecane) +FileAbortRetryIgnoreIgnoreNotRecommended=Z&ignoruj bd i kontynuuj (niezalecane) +SourceIsCorrupted=Plik rdowy jest uszkodzony +SourceDoesntExist=Plik rdowy "%1" nie istnieje +ExistingFileReadOnly2=Istniejcy plik nie moe zosta zastpiony, gdy jest oznaczony jako "Tylko do odczytu". +ExistingFileReadOnlyRetry=&Usu atrybut "Tylko do odczytu" i sprbuj ponownie +ExistingFileReadOnlyKeepExisting=&Zachowaj istniejcy plik +ErrorReadingExistingDest=Wystpi bd podczas prby odczytu istniejcego pliku: +FileExistsSelectAction=Wybierz czynno +FileExists2=Plik ju istnieje. +FileExistsOverwriteExisting=&Nadpisz istniejcy plik +FileExistsKeepExisting=&Zachowaj istniejcy plik +FileExistsOverwriteOrKeepAll=&Wykonaj t czynno dla kolejnych przypadkw +ExistingFileNewerSelectAction=Wybierz czynno +ExistingFileNewer2=Istniejcy plik jest nowszy ni ten, ktry instalator prbuje skopiowa. +ExistingFileNewerOverwriteExisting=&Nadpisz istniejcy plik +ExistingFileNewerKeepExisting=&Zachowaj istniejcy plik (zalecane) +ExistingFileNewerOverwriteOrKeepAll=&Wykonaj t czynno dla kolejnych przypadkw +ErrorChangingAttr=Wystpi bd podczas prby zmiany atrybutw pliku docelowego: +ErrorCreatingTemp=Wystpi bd podczas prby utworzenia pliku w folderze docelowym: +ErrorReadingSource=Wystpi bd podczas prby odczytu pliku rdowego: +ErrorCopying=Wystpi bd podczas prby kopiowania pliku: +ErrorReplacingExistingFile=Wystpi bd podczas prby zamiany istniejcego pliku: +ErrorRestartReplace=Prba zastpienia plikw przy ponownym uruchomieniu komputera nie powioda si. +ErrorRenamingTemp=Wystpi bd podczas prby zmiany nazwy pliku w folderze docelowym: +ErrorRegisterServer=Nie mona zarejestrowa DLL/OCX: %1 +ErrorRegSvr32Failed=Funkcja RegSvr32 zakoczya si z kodem bdu %1 +ErrorRegisterTypeLib=Nie mog zarejestrowa biblioteki typw: %1 + +; *** Uninstall display name markings +; used for example as 'My Program (32-bit)' +UninstallDisplayNameMark=%1 (%2) +; used for example as 'My Program (32-bit, All users)' +UninstallDisplayNameMarks=%1 (%2, %3) +UninstallDisplayNameMark32Bit=wersja 32-bitowa +UninstallDisplayNameMark64Bit=wersja 64-bitowa +UninstallDisplayNameMarkAllUsers=wszyscy uytkownicy +UninstallDisplayNameMarkCurrentUser=biecy uytkownik + +; *** Post-installation errors +ErrorOpeningReadme=Wystpi bd podczas prby otwarcia pliku z informacjami dodatkowymi. +ErrorRestartingComputer=Instalator nie mg ponownie uruchomi tego komputera. Prosz wykona t czynno samodzielnie. + +; *** Uninstaller messages +UninstallNotFound=Plik "%1" nie istnieje. Nie mona przeprowadzi dezinstalacji. +UninstallOpenError=Plik "%1" nie mg zosta otwarty. Nie mona przeprowadzi dezinstalacji. +UninstallUnsupportedVer=Ta wersja programu dezinstalacyjnego nie rozpoznaje formatu logu dezinstalacji w pliku "%1". Nie mona przeprowadzi dezinstalacji. +UninstallUnknownEntry=W logu dezinstalacji wystpia nieznana pozycja (%1) +ConfirmUninstall=Czy na pewno chcesz usun aplikacj %1 i wszystkie jej skadniki? +UninstallOnlyOnWin64=Ta aplikacja moe by odinstalowana tylko w 64-bitowej wersji systemu Windows. +OnlyAdminCanUninstall=Ta instalacja moe by odinstalowana tylko przez uytkownika z uprawnieniami administratora. +UninstallStatusLabel=Poczekaj, a aplikacja %1 zostanie usunita z komputera. +UninstalledAll=Aplikacja %1 zostaa usunita z komputera. +UninstalledMost=Dezinstalacja aplikacji %1 zakoczya si.%n%nNiektre elementy nie mogy zosta usunite. Naley usun je samodzielnie. +UninstalledAndNeedsRestart=Komputer musi zosta ponownie uruchomiony, aby zakoczy proces dezinstalacji aplikacji %1.%n%nCzy chcesz teraz ponownie uruchomi komputer? +UninstallDataCorrupted=Plik "%1" jest uszkodzony. Nie mona przeprowadzi dezinstalacji. + +; *** Uninstallation phase messages +ConfirmDeleteSharedFileTitle=Usun plik wspdzielony? +ConfirmDeleteSharedFile2=System wskazuje, i nastpujcy plik nie jest ju uywany przez aden program. Czy chcesz odinstalowa ten plik wspdzielony?%n%nJeli inne programy nadal uywaj tego pliku, a zostanie on usunity, mog one przesta dziaa prawidowo. W przypadku braku pewnoci, kliknij przycisk Nie. Pozostawienie tego pliku w systemie nie spowoduje adnych szkd. +SharedFileNameLabel=Nazwa pliku: +SharedFileLocationLabel=Pooenie: +WizardUninstalling=Stan dezinstalacji +StatusUninstalling=Dezinstalacja aplikacji %1... + +; *** Shutdown block reasons +ShutdownBlockReasonInstallingApp=Instalacja aplikacji %1. +ShutdownBlockReasonUninstallingApp=Dezinstalacja aplikacji %1. + +; The custom messages below aren't used by Setup itself, but if you make +; use of them in your scripts, you'll want to translate them. + +[CustomMessages] + +NameAndVersion=%1 (wersja %2) +AdditionalIcons=Dodatkowe skrty: +CreateDesktopIcon=Utwrz skrt na &pulpicie +CreateQuickLaunchIcon=Utwrz skrt na pasku &szybkiego uruchamiania +ProgramOnTheWeb=Strona internetowa aplikacji %1 +UninstallProgram=Dezinstalacja aplikacji %1 +LaunchProgram=Uruchom aplikacj %1 +AssocFileExtension=&Przypisz aplikacj %1 do rozszerzenia pliku %2 +AssocingFileExtension=Przypisywanie aplikacji %1 do rozszerzenia pliku %2... +AutoStartProgramGroupDescription=Autostart: +AutoStartProgram=Automatycznie uruchamiaj aplikacj %1 +AddonHostProgramNotFound=Aplikacja %1 nie zostaa znaleziona we wskazanym przez Ciebie folderze.%n%nCzy pomimo tego chcesz kontynuowa? diff --git a/src/AITool.Setup/INNO/Languages/Portuguese.isl b/src/AITool.Setup/INNO/Languages/Portuguese.isl new file mode 100644 index 00000000..42d4e549 --- /dev/null +++ b/src/AITool.Setup/INNO/Languages/Portuguese.isl @@ -0,0 +1,366 @@ +; *** Inno Setup version 6.1.0+ Portuguese (Portugal) messages *** +; +; Maintained by Nuno Silva (nars AT gmx.net) + +[LangOptions] +LanguageName=Portugu<00EA>s (Portugal) +LanguageID=$0816 +LanguageCodePage=1252 + +[Messages] + +; *** Application titles +SetupAppTitle=Instalao +SetupWindowTitle=%1 - Instalao +UninstallAppTitle=Desinstalao +UninstallAppFullTitle=%1 - Desinstalao + +; *** Misc. common +InformationTitle=Informao +ConfirmTitle=Confirmao +ErrorTitle=Erro + +; *** SetupLdr messages +SetupLdrStartupMessage=Ir ser instalado o %1. Deseja continuar? +LdrCannotCreateTemp=No foi possvel criar um ficheiro temporrio. Instalao cancelada +LdrCannotExecTemp=No foi possvel executar um ficheiro na directoria temporria. Instalao cancelada +HelpTextNote= + +; *** Startup error messages +LastErrorMessage=%1.%n%nErro %2: %3 +SetupFileMissing=O ficheiro %1 no foi encontrado na pasta de instalao. Corrija o problema ou obtenha uma nova cpia do programa. +SetupFileCorrupt=Os ficheiros de instalao esto corrompidos. Obtenha uma nova cpia do programa. +SetupFileCorruptOrWrongVer=Os ficheiros de instalao esto corrompidos, ou so incompatveis com esta verso do Assistente de Instalao. Corrija o problema ou obtenha uma nova cpia do programa. +InvalidParameter=Foi especificado um parmetro invlido na linha de comando:%n%n%1 +SetupAlreadyRunning=A instalao j est em execuo. +WindowsVersionNotSupported=Este programa no suporta a verso do Windows que est a utilizar. +WindowsServicePackRequired=Este programa necessita de %1 Service Pack %2 ou mais recente. +NotOnThisPlatform=Este programa no pode ser executado no %1. +OnlyOnThisPlatform=Este programa deve ser executado no %1. +OnlyOnTheseArchitectures=Este programa s pode ser instalado em verses do Windows preparadas para as seguintes arquitecturas:%n%n%1 +WinVersionTooLowError=Este programa necessita do %1 verso %2 ou mais recente. +WinVersionTooHighError=Este programa no pode ser instalado no %1 verso %2 ou mais recente. +AdminPrivilegesRequired=Deve iniciar sesso como administrador para instalar este programa. +PowerUserPrivilegesRequired=Deve iniciar sesso como administrador ou membro do grupo de Super Utilizadores para instalar este programa. +SetupAppRunningError=O Assistente de Instalao detectou que o %1 est em execuo. Feche-o e de seguida clique em OK para continuar, ou clique em Cancelar para cancelar a instalao. +UninstallAppRunningError=O Assistente de Desinstalao detectou que o %1 est em execuo. Feche-o e de seguida clique em OK para continuar, ou clique em Cancelar para cancelar a desinstalao. + +; *** Startup questions +PrivilegesRequiredOverrideTitle=Seleccione o Modo de Instalao +PrivilegesRequiredOverrideInstruction=Seleccione o Modo de Instalao +PrivilegesRequiredOverrideText1=%1 pode ser instalado para todos os utilizadores (necessita de privilgios administrativos), ou s para si. +PrivilegesRequiredOverrideText2=%1 pode ser instalado s para si, ou para todos os utilizadores (necessita de privilgios administrativos). +PrivilegesRequiredOverrideAllUsers=Instalar para &todos os utilizadores +PrivilegesRequiredOverrideAllUsersRecommended=Instalar para &todos os utilizadores (recomendado) +PrivilegesRequiredOverrideCurrentUser=Instalar apenas para &mim +PrivilegesRequiredOverrideCurrentUserRecommended=Instalar apenas para &mim (recomendado) + +; *** Misc. errors +ErrorCreatingDir=O Assistente de Instalao no consegue criar a directoria "%1" +ErrorTooManyFilesInDir=No possvel criar um ficheiro na directoria "%1" porque esta contm demasiados ficheiros + +; *** Setup common messages +ExitSetupTitle=Terminar a instalao +ExitSetupMessage=A instalao no est completa. Se terminar agora, o programa no ser instalado.%n%nMais tarde poder executar novamente este Assistente de Instalao e concluir a instalao.%n%nDeseja terminar a instalao? +AboutSetupMenuItem=&Acerca de... +AboutSetupTitle=Acerca do Assistente de Instalao +AboutSetupMessage=%1 verso %2%n%3%n%n%1 home page:%n%4 +AboutSetupNote= +TranslatorNote=Portuguese translation maintained by NARS (nars@gmx.net) + +; *** Buttons +ButtonBack=< &Anterior +ButtonNext=&Seguinte > +ButtonInstall=&Instalar +ButtonOK=OK +ButtonCancel=Cancelar +ButtonYes=&Sim +ButtonYesToAll=Sim para &todos +ButtonNo=&No +ButtonNoToAll=N&o para todos +ButtonFinish=&Concluir +ButtonBrowse=&Procurar... +ButtonWizardBrowse=P&rocurar... +ButtonNewFolder=&Criar Nova Pasta + +; *** "Select Language" dialog messages +SelectLanguageTitle=Seleccione o Idioma do Assistente de Instalao +SelectLanguageLabel=Seleccione o idioma para usar durante a Instalao. + +; *** Common wizard text +ClickNext=Clique em Seguinte para continuar ou em Cancelar para cancelar a instalao. +BeveledLabel= +BrowseDialogTitle=Procurar Pasta +BrowseDialogLabel=Seleccione uma pasta na lista abaixo e clique em OK. +NewFolderName=Nova Pasta + +; *** "Welcome" wizard page +WelcomeLabel1=Bem-vindo ao Assistente de Instalao do [name] +WelcomeLabel2=O Assistente de Instalao ir instalar o [name/ver] no seu computador.%n%n recomendado que feche todas as outras aplicaes antes de continuar. + +; *** "Password" wizard page +WizardPassword=Palavra-passe +PasswordLabel1=Esta instalao est protegida por palavra-passe. +PasswordLabel3=Insira a palavra-passe e de seguida clique em Seguinte para continuar. Na palavra-passe existe diferena entre maisculas e minsculas. +PasswordEditLabel=&Palavra-passe: +IncorrectPassword=A palavra-passe que introduziu no est correcta. Tente novamente. + +; *** "License Agreement" wizard page +WizardLicense=Contrato de licena +LicenseLabel= importante que leia as seguintes informaes antes de continuar. +LicenseLabel3=Leia atentamente o seguinte contrato de licena. Deve aceitar os termos do contrato antes de continuar a instalao. +LicenseAccepted=A&ceito o contrato +LicenseNotAccepted=&No aceito o contrato + +; *** "Information" wizard pages +WizardInfoBefore=Informao +InfoBeforeLabel= importante que leia as seguintes informaes antes de continuar. +InfoBeforeClickLabel=Quando estiver pronto para continuar clique em Seguinte. +WizardInfoAfter=Informao +InfoAfterLabel= importante que leia as seguintes informaes antes de continuar. +InfoAfterClickLabel=Quando estiver pronto para continuar clique em Seguinte. + +; *** "User Information" wizard page +WizardUserInfo=Informaes do utilizador +UserInfoDesc=Introduza as suas informaes. +UserInfoName=Nome do &utilizador: +UserInfoOrg=&Organizao: +UserInfoSerial=&Nmero de srie: +UserInfoNameRequired=Deve introduzir um nome. + +; *** "Select Destination Location" wizard page +WizardSelectDir=Seleccione a localizao de destino +SelectDirDesc=Onde dever ser instalado o [name]? +SelectDirLabel3=O [name] ser instalado na seguinte pasta. +SelectDirBrowseLabel=Para continuar, clique em Seguinte. Se desejar seleccionar uma pasta diferente, clique em Procurar. +DiskSpaceGBLabel= necessrio pelo menos [gb] GB de espao livre em disco. +DiskSpaceMBLabel= necessrio pelo menos [mb] MB de espao livre em disco. +CannotInstallToNetworkDrive=O Assistente de Instalao no pode instalar numa unidade de rede. +CannotInstallToUNCPath=O Assistente de Instalao no pode instalar num caminho UNC. +InvalidPath= necessrio indicar o caminho completo com a letra de unidade; por exemplo:%n%nC:\APP%n%nou um caminho UNC no formato:%n%n\\servidor\partilha +InvalidDrive=A unidade ou partilha UNC seleccionada no existe ou no est acessvel. Seleccione outra. +DiskSpaceWarningTitle=No h espao suficiente no disco +DiskSpaceWarning=O Assistente de Instalao necessita de pelo menos %1 KB de espao livre, mas a unidade seleccionada tem apenas %2 KB disponveis.%n%nDeseja continuar de qualquer forma? +DirNameTooLong=O nome ou caminho para a pasta demasiado longo. +InvalidDirName=O nome da pasta no vlido. +BadDirName32=O nome da pasta no pode conter nenhum dos seguintes caracteres:%n%n%1 +DirExistsTitle=A pasta j existe +DirExists=A pasta:%n%n%1%n%nj existe. Pretende instalar nesta pasta? +DirDoesntExistTitle=A pasta no existe +DirDoesntExist=A pasta:%n%n%1%n%nno existe. Pretende que esta pasta seja criada? + +; *** "Select Components" wizard page +WizardSelectComponents=Seleccione os componentes +SelectComponentsDesc=Que componentes devero ser instalados? +SelectComponentsLabel2=Seleccione os componentes que quer instalar e desseleccione os componentes que no quer instalar. Clique em Seguinte quando estiver pronto para continuar. +FullInstallation=Instalao Completa +; if possible don't translate 'Compact' as 'Minimal' (I mean 'Minimal' in your language) +CompactInstallation=Instalao Compacta +CustomInstallation=Instalao Personalizada +NoUninstallWarningTitle=Componentes Encontrados +NoUninstallWarning=O Assistente de Instalao detectou que os seguintes componentes esto instalados no seu computador:%n%n%1%n%nSe desseleccionar estes componentes eles no sero desinstalados.%n%nDeseja continuar? +ComponentSize1=%1 KB +ComponentSize2=%1 MB +ComponentsDiskSpaceGBLabel=A seleco actual necessita de pelo menos [gb] GB de espao em disco. +ComponentsDiskSpaceMBLabel=A seleco actual necessita de pelo menos [mb] MB de espao em disco. + +; *** "Select Additional Tasks" wizard page +WizardSelectTasks=Seleccione tarefas adicionais +SelectTasksDesc=Que tarefas adicionais devero ser executadas? +SelectTasksLabel2=Seleccione as tarefas adicionais que deseja que o Assistente de Instalao execute na instalao do [name] e em seguida clique em Seguinte. + +; *** "Select Start Menu Folder" wizard page +WizardSelectProgramGroup=Seleccione a pasta do Menu Iniciar +SelectStartMenuFolderDesc=Onde devero ser colocados os cones de atalho do programa? +SelectStartMenuFolderLabel3=Os cones de atalho do programa sero criados na seguinte pasta do Menu Iniciar. +SelectStartMenuFolderBrowseLabel=Para continuar, clique em Seguinte. Se desejar seleccionar uma pasta diferente, clique em Procurar. +MustEnterGroupName= necessrio introduzir um nome para a pasta. +GroupNameTooLong=O nome ou caminho para a pasta demasiado longo. +InvalidGroupName=O nome da pasta no vlido. +BadGroupName=O nome da pasta no pode conter nenhum dos seguintes caracteres:%n%n%1 +NoProgramGroupCheck2=&No criar nenhuma pasta no Menu Iniciar + +; *** "Ready to Install" wizard page +WizardReady=Pronto para Instalar +ReadyLabel1=O Assistente de Instalao est pronto para instalar o [name] no seu computador. +ReadyLabel2a=Clique em Instalar para continuar a instalao, ou clique em Anterior se desejar rever ou alterar alguma das configuraes. +ReadyLabel2b=Clique em Instalar para continuar a instalao. +ReadyMemoUserInfo=Informaes do utilizador: +ReadyMemoDir=Localizao de destino: +ReadyMemoType=Tipo de instalao: +ReadyMemoComponents=Componentes seleccionados: +ReadyMemoGroup=Pasta do Menu Iniciar: +ReadyMemoTasks=Tarefas adicionais: + +; *** TDownloadWizardPage wizard page and DownloadTemporaryFile +DownloadingLabel=A transferir ficheiros adicionais... +ButtonStopDownload=&Parar transferncia +StopDownload=Tem a certeza que deseja parar a transferncia? +ErrorDownloadAborted=Transferncia cancelada +ErrorDownloadFailed=Falha na transferncia: %1 %2 +ErrorDownloadSizeFailed=Falha ao obter tamanho: %1 %2 +ErrorFileHash1=Falha de verificao do ficheiro: %1 +ErrorFileHash2=Hash do ficheiro invlida: experado %1, encontrado %2 +ErrorProgress=Progresso invlido: %1 de %2 +ErrorFileSize=Tamanho de ficheiro invlido: experado %1, encontrado %2 + +; *** "Preparing to Install" wizard page +WizardPreparing=Preparando-se para instalar +PreparingDesc=Preparando-se para instalar o [name] no seu computador. +PreviousInstallNotCompleted=A instalao/remoo de um programa anterior no foi completada. Necessitar de reiniciar o computador para completar essa instalao.%n%nDepois de reiniciar o computador, execute novamente este Assistente de Instalao para completar a instalao do [name]. +CannotContinue=A instalao no pode continuar. Clique em Cancelar para sair. +ApplicationsFound=As seguintes aplicaes esto a utilizar ficheiros que necessitam ser actualizados pelo Assistente de Instalao. recomendado que permita que o Assistente de Instalao feche estas aplicaes. +ApplicationsFound2=As seguintes aplicaes esto a utilizar ficheiros que necessitam ser actualizados pelo Assistente de Instalao. recomendado que permita que o Assistente de Instalao feche estas aplicaes. Depois de completar a instalao, o Assistente de Instalao tentar reiniciar as aplicaes. +CloseApplications=&Fechar as aplicaes automaticamente +DontCloseApplications=&No fechar as aplicaes +ErrorCloseApplications=O Assistente de Instalao no conseguiu fechar todas as aplicaes automaticamente. Antes de continuar recomendado que feche todas as aplicaes que utilizem ficheiros que necessitem de ser actualizados pelo Assistente de Instalao. +PrepareToInstallNeedsRestart=O Assistente de Instalao necessita reiniciar o seu computador. Depois de reiniciar o computador, execute novamente o Assistente de Instalao para completar a instalao do [name].%n%nDeseja reiniciar agora? + +; *** "Installing" wizard page +WizardInstalling=A instalar +InstallingLabel=Aguarde enquanto o Assistente de Instalao instala o [name] no seu computador. + +; *** "Setup Completed" wizard page +FinishedHeadingLabel=Instalao do [name] concluda +FinishedLabelNoIcons=O Assistente de Instalao concluiu a instalao do [name] no seu computador. +FinishedLabel=O Assistente de Instalao concluiu a instalao do [name] no seu computador. A aplicao pode ser iniciada atravs dos cones de atalho instalados. +ClickFinish=Clique em Concluir para finalizar o Assistente de Instalao. +FinishedRestartLabel=Para completar a instalao do [name], o Assistente de Instalao dever reiniciar o seu computador. Deseja reiniciar agora? +FinishedRestartMessage=Para completar a instalao do [name], o Assistente de Instalao dever reiniciar o seu computador.%n%nDeseja reiniciar agora? +ShowReadmeCheck=Sim, desejo ver o ficheiro LEIAME +YesRadio=&Sim, desejo reiniciar o computador agora +NoRadio=&No, desejo reiniciar o computador mais tarde +; used for example as 'Run MyProg.exe' +RunEntryExec=Executar %1 +; used for example as 'View Readme.txt' +RunEntryShellExec=Visualizar %1 + +; *** "Setup Needs the Next Disk" stuff +ChangeDiskTitle=O Assistente de Instalao precisa do disco seguinte +SelectDiskLabel2=Introduza o disco %1 e clique em OK.%n%nSe os ficheiros deste disco estiverem num local diferente do mostrado abaixo, indique o caminho correcto ou clique em Procurar. +PathLabel=&Caminho: +FileNotInDir2=O ficheiro "%1" no foi encontrado em "%2". Introduza o disco correcto ou seleccione outra pasta. +SelectDirectoryLabel=Indique a localizao do disco seguinte. + +; *** Installation phase messages +SetupAborted=A instalao no est completa.%n%nCorrija o problema e execute o Assistente de Instalao novamente. +AbortRetryIgnoreSelectAction=Seleccione uma aco +AbortRetryIgnoreRetry=&Tentar novamente +AbortRetryIgnoreIgnore=&Ignorar o erro e continuar +AbortRetryIgnoreCancel=Cancelar a instalao + +; *** Installation status messages +StatusClosingApplications=A fechar aplicaes... +StatusCreateDirs=A criar directorias... +StatusExtractFiles=A extrair ficheiros... +StatusCreateIcons=A criar atalhos... +StatusCreateIniEntries=A criar entradas em INI... +StatusCreateRegistryEntries=A criar entradas no registo... +StatusRegisterFiles=A registar ficheiros... +StatusSavingUninstall=A guardar informaes para desinstalao... +StatusRunProgram=A concluir a instalao... +StatusRestartingApplications=A reiniciar aplicaes... +StatusRollback=A anular as alteraes... + +; *** Misc. errors +ErrorInternal2=Erro interno: %1 +ErrorFunctionFailedNoCode=%1 falhou +ErrorFunctionFailed=%1 falhou; cdigo %2 +ErrorFunctionFailedWithMessage=%1 falhou; cdigo %2.%n%3 +ErrorExecutingProgram=No possvel executar o ficheiro:%n%1 + +; *** Registry errors +ErrorRegOpenKey=Erro ao abrir a chave de registo:%n%1\%2 +ErrorRegCreateKey=Erro ao criar a chave de registo:%n%1\%2 +ErrorRegWriteKey=Erro ao escrever na chave de registo:%n%1\%2 + +; *** INI errors +ErrorIniEntry=Erro ao criar entradas em INI no ficheiro "%1". + +; *** File copying errors +FileAbortRetryIgnoreSkipNotRecommended=&Ignorar este ficheiro (no recomendado) +FileAbortRetryIgnoreIgnoreNotRecommended=&Ignorar este erro e continuar (no recomendado) +SourceIsCorrupted=O ficheiro de origem est corrompido +SourceDoesntExist=O ficheiro de origem "%1" no existe +ExistingFileReadOnly2=O ficheiro existente no pode ser substitudo porque tem o atributo "s de leitura". +ExistingFileReadOnlyRetry=&Remover o atributo "s de leitura" e tentar novamente +ExistingFileReadOnlyKeepExisting=&Manter o ficheiro existente +ErrorReadingExistingDest=Ocorreu um erro ao tentar ler o ficheiro existente: +FileExistsSelectAction=Seleccione uma aco +FileExists2=O ficheiro j existe. +FileExistsOverwriteExisting=&Substituir o ficheiro existente +FileExistsKeepExisting=&Manter o ficheiro existente +FileExistsOverwriteOrKeepAll=&Fazer isto para os prximos conflitos +ExistingFileNewerSelectAction=Seleccione uma aco +ExistingFileNewer2=O ficheiro existente mais recente que o que est a ser instalado. +ExistingFileNewerOverwriteExisting=&Substituir o ficheiro existente +ExistingFileNewerKeepExisting=&Manter o ficheiro existente (recomendado) +ExistingFileNewerOverwriteOrKeepAll=&Fazer isto para os prximos conflitos +ErrorChangingAttr=Ocorreu um erro ao tentar alterar os atributos do ficheiro existente: +ErrorCreatingTemp=Ocorreu um erro ao tentar criar um ficheiro na directoria de destino: +ErrorReadingSource=Ocorreu um erro ao tentar ler o ficheiro de origem: +ErrorCopying=Ocorreu um erro ao tentar copiar um ficheiro: +ErrorReplacingExistingFile=Ocorreu um erro ao tentar substituir o ficheiro existente: +ErrorRestartReplace=RestartReplace falhou: +ErrorRenamingTemp=Ocorreu um erro ao tentar mudar o nome de um ficheiro na directoria de destino: +ErrorRegisterServer=No possvel registar o DLL/OCX: %1 +ErrorRegSvr32Failed=O RegSvr32 falhou com o cdigo de sada %1 +ErrorRegisterTypeLib=No foi possvel registar a livraria de tipos: %1 + +; *** Uninstall display name markings +; used for example as 'My Program (32-bit)' +UninstallDisplayNameMark=%1 (%2) +; used for example as 'My Program (32-bit, All users)' +UninstallDisplayNameMarks=%1 (%2, %3) +UninstallDisplayNameMark32Bit=32-bit +UninstallDisplayNameMark64Bit=64-bit +UninstallDisplayNameMarkAllUsers=Todos os utilizadores +UninstallDisplayNameMarkCurrentUser=Utilizador actual + +; *** Post-installation errors +ErrorOpeningReadme=Ocorreu um erro ao tentar abrir o ficheiro LEIAME. +ErrorRestartingComputer=O Assistente de Instalao no consegue reiniciar o computador. Por favor reinicie manualmente. + +; *** Uninstaller messages +UninstallNotFound=O ficheiro "%1" no existe. No possvel desinstalar. +UninstallOpenError=No foi possvel abrir o ficheiro "%1". No possvel desinstalar. +UninstallUnsupportedVer=O ficheiro log de desinstalao "%1" est num formato que no reconhecido por esta verso do desinstalador. No possvel desinstalar +UninstallUnknownEntry=Foi encontrada uma entrada desconhecida (%1) no ficheiro log de desinstalao +ConfirmUninstall=Tem a certeza que deseja remover completamente o %1 e todos os seus componentes? +UninstallOnlyOnWin64=Esta desinstalao s pode ser realizada na verso de 64-bit's do Windows. +OnlyAdminCanUninstall=Esta desinstalao s pode ser realizada por um utilizador com privilgios administrativos. +UninstallStatusLabel=Por favor aguarde enquanto o %1 est a ser removido do seu computador. +UninstalledAll=O %1 foi removido do seu computador com sucesso. +UninstalledMost=A desinstalao do %1 est concluda.%n%nAlguns elementos no puderam ser removidos. Estes elementos podem ser removidos manualmente. +UninstalledAndNeedsRestart=Para completar a desinstalao do %1, o computador deve ser reiniciado.%n%nDeseja reiniciar agora? +UninstallDataCorrupted=O ficheiro "%1" est corrompido. No possvel desinstalar + +; *** Uninstallation phase messages +ConfirmDeleteSharedFileTitle=Remover ficheiro partilhado? +ConfirmDeleteSharedFile2=O sistema indica que o seguinte ficheiro partilhado j no est a ser utilizado por nenhum programa. Deseja remov-lo?%n%nSe algum programa ainda necessitar deste ficheiro, poder no funcionar correctamente depois de o remover. Se no tiver a certeza, seleccione No. Manter o ficheiro no causar nenhum problema. +SharedFileNameLabel=Nome do ficheiro: +SharedFileLocationLabel=Localizao: +WizardUninstalling=Estado da desinstalao +StatusUninstalling=A desinstalar o %1... + +; *** Shutdown block reasons +ShutdownBlockReasonInstallingApp=A instalar %1. +ShutdownBlockReasonUninstallingApp=A desinstalar %1. + +; The custom messages below aren't used by Setup itself, but if you make +; use of them in your scripts, you'll want to translate them. + +[CustomMessages] + +NameAndVersion=%1 verso %2 +AdditionalIcons=Atalhos adicionais: +CreateDesktopIcon=Criar atalho no Ambiente de &Trabalho +CreateQuickLaunchIcon=&Criar atalho na barra de Iniciao Rpida +ProgramOnTheWeb=%1 na Web +UninstallProgram=Desinstalar o %1 +LaunchProgram=Executar o %1 +AssocFileExtension=Associa&r o %1 aos ficheiros com a extenso %2 +AssocingFileExtension=A associar o %1 aos ficheiros com a extenso %2... +AutoStartProgramGroupDescription=Inicializao Automtica: +AutoStartProgram=Iniciar %1 automaticamente +AddonHostProgramNotFound=No foi possvel localizar %1 na pasta seleccionada.%n%nDeseja continuar de qualquer forma? diff --git a/src/AITool.Setup/INNO/Languages/Russian.isl b/src/AITool.Setup/INNO/Languages/Russian.isl new file mode 100644 index 00000000..bf086d06 --- /dev/null +++ b/src/AITool.Setup/INNO/Languages/Russian.isl @@ -0,0 +1,370 @@ +; *** Inno Setup version 6.1.0+ Russian messages *** +; +; Translated from English by Dmitry Kann, yktooo at gmail.com +; +; Note: When translating this text, do not add periods (.) to the end of +; messages that didn't have them already, because on those messages Inno +; Setup adds the periods automatically (appending a period would result in +; two periods being displayed). + +[LangOptions] +LanguageName=<0420><0443><0441><0441><043A><0438><0439> +LanguageID=$0419 +LanguageCodePage=1251 + +[Messages] + +; *** Application titles +SetupAppTitle= +SetupWindowTitle= %1 +UninstallAppTitle= +UninstallAppFullTitle= %1 + +; *** Misc. common +InformationTitle= +ConfirmTitle= +ErrorTitle= + +; *** SetupLdr messages +SetupLdrStartupMessage= %1 , ? +LdrCannotCreateTemp= . +LdrCannotExecTemp= . +HelpTextNote= + +; *** Startup error messages +LastErrorMessage=%1.%n%n %2: %3 +SetupFileMissing= %1 . , . +SetupFileCorrupt= . , . +SetupFileCorruptOrWrongVer= . , . +InvalidParameter= :%n%n%1 +SetupAlreadyRunning= . +WindowsVersionNotSupported= Windows, . +WindowsServicePackRequired= %1 Service Pack %2 . +NotOnThisPlatform= %1. +OnlyOnThisPlatform= %1. +OnlyOnTheseArchitectures= Windows :%n%n%1 +WinVersionTooLowError= %1 %2 . +WinVersionTooHighError= %1 %2 . +AdminPrivilegesRequired= , . +PowerUserPrivilegesRequired= , (Power Users). +SetupAppRunningError= %1.%n%n, , OK, , , . +UninstallAppRunningError= %1.%n%n, , OK, , , . + +; *** Startup questions +PrivilegesRequiredOverrideTitle= +PrivilegesRequiredOverrideInstruction= +PrivilegesRequiredOverrideText1=%1 ( ), . +PrivilegesRequiredOverrideText2=%1 , ( ). +PrivilegesRequiredOverrideAllUsers= & +PrivilegesRequiredOverrideAllUsersRecommended= & () +PrivilegesRequiredOverrideCurrentUser= & +PrivilegesRequiredOverrideCurrentUserRecommended= & () + +; *** Misc. errors +ErrorCreatingDir= "%1" +ErrorTooManyFilesInDir= "%1", + +; *** Setup common messages +ExitSetupTitle= +ExitSetupMessage= . , .%n%n , .%n%n ? +AboutSetupMenuItem=& ... +AboutSetupTitle= +AboutSetupMessage=%1, %2%n%3%n%n %1:%n%4 +AboutSetupNote= +TranslatorNote=Russian translation by Dmitry Kann, http://www.dk-soft.org/ + +; *** Buttons +ButtonBack=< & +ButtonNext=& > +ButtonInstall=& +ButtonOK=OK +ButtonCancel= +ButtonYes=& +ButtonYesToAll= & +ButtonNo=& +ButtonNoToAll=& +ButtonFinish=& +ButtonBrowse=&... +ButtonWizardBrowse=&... +ButtonNewFolder=& + +; *** "Select Language" dialog messages +SelectLanguageTitle= +SelectLanguageLabel= , . + +; *** Common wizard text +ClickNext= , , , . +BeveledLabel= +BrowseDialogTitle= +BrowseDialogLabel= ʻ. +NewFolderName= + +; *** "Welcome" wizard page +WelcomeLabel1= [name] +WelcomeLabel2= [name/ver] .%n%n , . + +; *** "Password" wizard page +WizardPassword= +PasswordLabel1= . +PasswordLabel3=, , . . +PasswordEditLabel=&: +IncorrectPassword= . , . + +; *** "License Agreement" wizard page +WizardLicense= +LicenseLabel=, , . +LicenseLabel3=, . , . +LicenseAccepted= & +LicenseNotAccepted= & + +; *** "Information" wizard pages +WizardInfoBefore= +InfoBeforeLabel=, , . +InfoBeforeClickLabel= , . +WizardInfoAfter= +InfoAfterLabel=, , . +InfoAfterClickLabel= , . + +; *** "User Information" wizard page +WizardUserInfo= +UserInfoDesc=, . +UserInfoName=& : +UserInfoOrg=&: +UserInfoSerial=& : +UserInfoNameRequired= . + +; *** "Select Destination Location" wizard page +WizardSelectDir= +SelectDirDesc= [name]? +SelectDirLabel3= [name] . +SelectDirBrowseLabel= , . , . +DiskSpaceGBLabel= [gb] . +DiskSpaceMBLabel= [mb] . +CannotInstallToNetworkDrive= . +CannotInstallToUNCPath= UNC-. +InvalidPath= ; :%n%nC:\APP%n%n UNC:%n%n\\_\_ +InvalidDrive= . , . +DiskSpaceWarningTitle= +DiskSpaceWarning= %1 , %2 .%n%n ? +DirNameTooLong= . +InvalidDirName= . +BadDirName32= : %n%n%1 +DirExistsTitle= +DirExists=%n%n%1%n%n . ? +DirDoesntExistTitle= +DirDoesntExist=%n%n%1%n%n . ? + +; *** "Select Components" wizard page +WizardSelectComponents= +SelectComponentsDesc= ? +SelectComponentsLabel2= , ; , . , . +FullInstallation= +; if possible don't translate 'Compact' as 'Minimal' (I mean 'Minimal' in your language) +CompactInstallation= +CustomInstallation= +NoUninstallWarningTitle= +NoUninstallWarning= , :%n%n%1%n%n .%n%n? +ComponentSize1=%1 +ComponentSize2=%1 +ComponentsDiskSpaceGBLabel= [gb] . +ComponentsDiskSpaceMBLabel= [mb] . + +; *** "Select Additional Tasks" wizard page +WizardSelectTasks= +SelectTasksDesc= ? +SelectTasksLabel2= , [name], : + +; *** "Select Start Menu Folder" wizard page +WizardSelectProgramGroup= +SelectStartMenuFolderDesc= ? +SelectStartMenuFolderLabel3= . +SelectStartMenuFolderBrowseLabel= , . , . +MustEnterGroupName= . +GroupNameTooLong= . +InvalidGroupName= . +BadGroupName= :%n%n%1 +NoProgramGroupCheck2=& + +; *** "Ready to Install" wizard page +WizardReady= +ReadyLabel1= [name] . +ReadyLabel2a= , , , . +ReadyLabel2b= , . +ReadyMemoUserInfo= : +ReadyMemoDir= : +ReadyMemoType= : +ReadyMemoComponents= : +ReadyMemoGroup= : +ReadyMemoTasks= : + +; *** TDownloadWizardPage wizard page and DownloadTemporaryFile +DownloadingLabel= ... +ButtonStopDownload=& +StopDownload= ? +ErrorDownloadAborted= +ErrorDownloadFailed= : %1 %2 +ErrorDownloadSizeFailed= : %1 %2 +ErrorFileHash1= : %1 +ErrorFileHash2= : %1, %2 +ErrorProgress= : %1 %2 +ErrorFileSize= : %1, %2 + +; *** "Preparing to Install" wizard page +WizardPreparing= +PreparingDesc= [name] . +PreviousInstallNotCompleted= . , .%n%n , [name]. +CannotContinue= . . +ApplicationsFound= , . . +ApplicationsFound2= , . . , . +CloseApplications=& +DontCloseApplications=& +ErrorCloseApplications= . , , . +PrepareToInstallNeedsRestart= . , , , [name].%n%n ? + +; *** "Installing" wizard page +WizardInstalling=... +InstallingLabel=, , [name] . + +; *** "Setup Completed" wizard page +FinishedHeadingLabel= [name] +FinishedLabelNoIcons= [name] . +FinishedLabel= [name] . . +ClickFinish= , . +FinishedRestartLabel= [name] . ? +FinishedRestartMessage= [name] .%n%n ? +ShowReadmeCheck= README +YesRadio=&, +NoRadio=&, +; used for example as 'Run MyProg.exe' +RunEntryExec= %1 +; used for example as 'View Readme.txt' +RunEntryShellExec= %1 + +; *** "Setup Needs the Next Disk" stuff +ChangeDiskTitle= +SelectDiskLabel2=, %1 OK.%n%n , , . +PathLabel=&: +FileNotInDir2= "%1" "%2". , . +SelectDirectoryLabel=, . + +; *** Installation phase messages +SetupAborted= .%n%n, . +AbortRetryIgnoreSelectAction= +AbortRetryIgnoreRetry= & +AbortRetryIgnoreIgnore=& +AbortRetryIgnoreCancel= + +; *** Installation status messages +StatusClosingApplications= ... +StatusCreateDirs= ... +StatusExtractFiles= ... +StatusCreateIcons= ... +StatusCreateIniEntries= INI-... +StatusCreateRegistryEntries= ... +StatusRegisterFiles= ... +StatusSavingUninstall= ... +StatusRunProgram= ... +StatusRestartingApplications= ... +StatusRollback= ... + +; *** Misc. errors +ErrorInternal2= : %1 +ErrorFunctionFailedNoCode=%1: +ErrorFunctionFailed=%1: ; %2 +ErrorFunctionFailedWithMessage=%1: ; %2.%n%3 +ErrorExecutingProgram= :%n%1 + +; *** Registry errors +ErrorRegOpenKey= :%n%1\%2 +ErrorRegCreateKey= :%n%1\%2 +ErrorRegWriteKey= :%n%1\%2 + +; *** INI errors +ErrorIniEntry= INI- "%1". + +; *** File copying errors +FileAbortRetryIgnoreSkipNotRecommended=& ( ) +FileAbortRetryIgnoreIgnoreNotRecommended=& ( ) +SourceIsCorrupted= +SourceDoesntExist= "%1" +ExistingFileReadOnly2= , . +ExistingFileReadOnlyRetry=& +ExistingFileReadOnlyKeepExisting=& +ErrorReadingExistingDest= : +FileExistsSelectAction= +FileExists2= . +FileExistsOverwriteExisting=& +FileExistsKeepExisting=& +FileExistsOverwriteOrKeepAll=& +ExistingFileNewerSelectAction= +ExistingFileNewer2= , . +ExistingFileNewerOverwriteExisting=& +ExistingFileNewerKeepExisting=& () +ExistingFileNewerOverwriteOrKeepAll=& +ErrorChangingAttr= : +ErrorCreatingTemp= : +ErrorReadingSource= : +ErrorCopying= : +ErrorReplacingExistingFile= : +ErrorRestartReplace= RestartReplace: +ErrorRenamingTemp= : +ErrorRegisterServer= DLL/OCX: %1 +ErrorRegSvr32Failed= RegSvr32, %1 +ErrorRegisterTypeLib= (Type Library): %1 + +; *** Uninstall display name markings +UninstallDisplayNameMark=%1 (%2) +UninstallDisplayNameMarks=%1 (%2, %3) +UninstallDisplayNameMark32Bit=32 +UninstallDisplayNameMark64Bit=64 +UninstallDisplayNameMarkAllUsers= +UninstallDisplayNameMarkCurrentUser= + +; *** Post-installation errors +ErrorOpeningReadme= README. +ErrorRestartingComputer= . , . + +; *** Uninstaller messages +UninstallNotFound= "%1" , . +UninstallOpenError= "%1". +UninstallUnsupportedVer= "%1" -. +UninstallUnknownEntry= (%1) +ConfirmUninstall= %1 ? +UninstallOnlyOnWin64= 64- Windows. +OnlyAdminCanUninstall= . +UninstallStatusLabel=, , %1 . +UninstalledAll= %1 . +UninstalledMost= %1 .%n%n . . +UninstalledAndNeedsRestart= %1 .%n%n ? +UninstallDataCorrupted= "%1" . + +; *** Uninstallation phase messages +ConfirmDeleteSharedFileTitle= ? +ConfirmDeleteSharedFile2= , . ?%n%n - , , . , . . +SharedFileNameLabel= : +SharedFileLocationLabel=: +WizardUninstalling= +StatusUninstalling= %1... + + +; *** Shutdown block reasons +ShutdownBlockReasonInstallingApp= %1. +ShutdownBlockReasonUninstallingApp= %1. + +; The custom messages below aren't used by Setup itself, but if you make +; use of them in your scripts, you'll want to translate them. + +[CustomMessages] + +NameAndVersion=%1, %2 +AdditionalIcons= : +CreateDesktopIcon= & +CreateQuickLaunchIcon= & +ProgramOnTheWeb= %1 +UninstallProgram= %1 +LaunchProgram= %1 +AssocFileExtension=& %1 , %2 +AssocingFileExtension= %1 %2... +AutoStartProgramGroupDescription=: +AutoStartProgram= %1 +AddonHostProgramNotFound=%1 .%n%n ? diff --git a/src/AITool.Setup/INNO/Languages/Slovak.isl b/src/AITool.Setup/INNO/Languages/Slovak.isl new file mode 100644 index 00000000..fab212eb --- /dev/null +++ b/src/AITool.Setup/INNO/Languages/Slovak.isl @@ -0,0 +1,385 @@ +; ****************************************************** +; *** *** +; *** Inno Setup version 6.1.0+ Slovak messages *** +; *** *** +; *** Original Author: *** +; *** *** +; *** Milan Potancok (milan.potancok AT gmail.com) *** +; *** *** +; *** Contributors: *** +; *** *** +; *** Ivo Bauer (bauer AT ozm.cz) *** +; *** *** +; *** Tomas Falb (tomasf AT pobox.sk) *** +; *** Slappy (slappy AT pobox.sk) *** +; *** Comments: (mitems58 AT gmail.com) *** +; *** *** +; *** Update: 28.01.2021 *** +; *** *** +; ****************************************************** +; +; + +[LangOptions] +LanguageName=Sloven<010D>ina +LanguageID=$041b +LanguageCodePage=1250 + +[Messages] + +; *** Application titles +SetupAppTitle=Sprievodca inštaláciou +SetupWindowTitle=Sprievodca inštaláciou - %1 +UninstallAppTitle=Sprievodca odinštaláciou +UninstallAppFullTitle=Sprievodca odinštaláciou - %1 + +; *** Misc. common +InformationTitle=Informácie +ConfirmTitle=Potvrdenie +ErrorTitle=Chyba + +; *** SetupLdr messages +SetupLdrStartupMessage=Víta Vás Sprievodca inštaláciou produktu %1. Prajete si pokračovať? +LdrCannotCreateTemp=Nie je možné vytvoriť dočasný súbor. Sprievodca inštaláciou sa ukončí +LdrCannotExecTemp=Nie je možné spustiť súbor v dočasnom adresári. Sprievodca inštaláciou sa ukončí +HelpTextNote= + +; *** Startup error messages +LastErrorMessage=%1.%n%nChyba %2: %3 +SetupFileMissing=Inštalačný adresár neobsahuje súbor %1. Opravte túto chybu, alebo si zaobstarajte novú kópiu tohto produktu. +SetupFileCorrupt=Súbory Sprievodcu inštaláciou sú poškodené. Zaobstarajte si novú kópiu tohto produktu. +SetupFileCorruptOrWrongVer=Súbory Sprievodcu inštaláciou sú poškodené alebo sa nezhodujú s touto verziou Sprievodcu inštaláciou. Opravte túto chybu, alebo si zaobstarajte novú kópiu tohto produktu. +InvalidParameter=Nesprávny parameter na príkazovom riadku: %n%n%1 +SetupAlreadyRunning=Inštalácia už prebieha. +WindowsVersionNotSupported=Tento program nepodporuje vašu verziu systému Windows. +WindowsServicePackRequired=Tento program vyžaduje %1 Service Pack %2 alebo novší. +NotOnThisPlatform=Tento produkt sa nedá spustiť v %1. +OnlyOnThisPlatform=Tento produkt musí byť spustený v %1. +OnlyOnTheseArchitectures=Tento produkt je možné nainštalovať iba vo verziách MS Windows s podporou architektúry procesorov:%n%n%1 +WinVersionTooLowError=Tento produkt vyžaduje %1 verzie %2 alebo vyššej. +WinVersionTooHighError=Tento produkt sa nedá nainštalovať vo %1 verzie %2 alebo vyššej. +AdminPrivilegesRequired=Na inštaláciu tohto produktu musíte byť prihlásený s právami administrátora. +PowerUserPrivilegesRequired=Na inštaláciu tohto produktu musíte byť prihlásený s právami Administrátora alebo člena skupiny Power Users. +SetupAppRunningError=Sprievodca inštaláciou zistil, že produkt %1 je teraz spustený.%n%nUkončte všetky spustené inštancie tohto produktu a pokračujte kliknutím na tlačidlo "OK", alebo ukončte inštaláciu tlačidlom "Zrušiť". +UninstallAppRunningError=Sprievodca odinštaláciou zistil, že produkt %1 je teraz spustený.%n%nUkončte všetky spustené inštancie tohto produktu a pokračujte kliknutím na tlačidlo "OK", alebo ukončte inštaláciu tlačidlom "Zrušiť". + +; *** Startup questions +PrivilegesRequiredOverrideTitle=Vyberte inštalačný mód inštalátora +PrivilegesRequiredOverrideInstruction=Vyberte inštalačný mód +PrivilegesRequiredOverrideText1=%1 sa môže nainštalovať pre všetkých užívateľov (vyžaduje administrátorské práva), alebo len pre Vás. +PrivilegesRequiredOverrideText2=%1 sa môže nainštalovať len pre Vás, alebo pre všetkých užívateľov (vyžadujú sa Administrátorské práva). +PrivilegesRequiredOverrideAllUsers=Inštalovať pre &všetkých užívateľov +PrivilegesRequiredOverrideAllUsersRecommended=Inštalovať pre &všetkých užívateľov (odporúčané) +PrivilegesRequiredOverrideCurrentUser=Inštalovať len pre &mňa +PrivilegesRequiredOverrideCurrentUserRecommended=Inštalovať len pre &mňa (odporúčané) + +; *** Misc. errors +ErrorCreatingDir=Sprievodca inštaláciou nemohol vytvoriť adresár "%1" +ErrorTooManyFilesInDir=Nedá sa vytvoriť súbor v adresári "%1", pretože tento adresár už obsahuje príliš veľa súborov + +; *** Setup common messages +ExitSetupTitle=Ukončiť Sprievodcu inštaláciou +ExitSetupMessage=Inštalácia nebola kompletne dokončená. Ak teraz ukončíte Sprievodcu inštaláciou, produkt nebude nainštalovaný.%n%nSprievodcu inštaláciou môžete znovu spustiť neskôr a dokončiť tak inštaláciu.%n%nUkončiť Sprievodcu inštaláciou? +AboutSetupMenuItem=&O Sprievodcovi inštalácie... +AboutSetupTitle=O Sprievodcovi inštalácie +AboutSetupMessage=%1 verzia %2%n%3%n%n%1 domovská stránka:%n%4 +AboutSetupNote= +TranslatorNote=Slovak translation maintained by Milan Potancok (milan.potancok AT gmail.com), Ivo Bauer (bauer AT ozm.cz), Tomas Falb (tomasf AT pobox.sk) + Slappy (slappy AT pobox.sk) + +; *** Buttons +ButtonBack=< &Späť +ButtonNext=&Ďalej > +ButtonInstall=&Inštalovať +ButtonOK=OK +ButtonCancel=Zrušiť +ButtonYes=&Áno +ButtonYesToAll=Áno &všetkým +ButtonNo=&Nie +ButtonNoToAll=Ni&e všetkým +ButtonFinish=&Dokončiť +ButtonBrowse=&Prechádzať... +ButtonWizardBrowse=&Prechádzať... +ButtonNewFolder=&Vytvoriť nový adresár + +; *** "Select Language" dialog messages +SelectLanguageTitle=Výber jazyka Sprievodcu inštaláciou +SelectLanguageLabel=Zvoľte jazyk, ktorý sa má použiť pri inštalácii. + +; *** Common wizard text +ClickNext=Pokračujte kliknutím na tlačidlo "Ďalej", alebo ukončte sprievodcu inštaláciou tlačidlom "Zrušiť". +BeveledLabel= +BrowseDialogTitle=Nájsť adresár +BrowseDialogLabel=Z dole uvedeného zoznamu vyberte adresár a kliknite na "OK". +NewFolderName=Nový adresár + +; *** "Welcome" wizard page +WelcomeLabel1=Víta Vás Sprievodca inštaláciou produktu [name]. +WelcomeLabel2=Produkt [name/ver] sa nainštaluje do tohto počítača.%n%nSkôr, ako budete pokračovať, odporúčame ukončiť všetky spustené aplikácie. + +; *** "Password" wizard page +WizardPassword=Heslo +PasswordLabel1=Táto inštalácia je chránená heslom. +PasswordLabel3=Zadajte heslo a pokračujte kliknutím na tlačidlo "Ďalej". Pri zadávaní hesla rozlišujte malé a veľké písmená. +PasswordEditLabel=&Heslo: +IncorrectPassword=Zadané heslo nie je správne. Skúste to ešte raz prosím. + +; *** "License Agreement" wizard page +WizardLicense=Licenčná zmluva +LicenseLabel=Skôr, ako budete pokračovať, prečítajte si tieto dôležité informácie, prosím. +LicenseLabel3=Prečítajte si túto Licenčnú zmluvu prosím. Aby mohla inštalácia pokračovať, musíte súhlasiť s podmienkami tejto zmluvy. +LicenseAccepted=&Súhlasím s podmienkami Licenčnej zmluvy +LicenseNotAccepted=&Nesúhlasím s podmienkami Licenčnej zmluvy + +; *** "Information" wizard pages +WizardInfoBefore=Informácie +InfoBeforeLabel=Skôr, ako budete pokračovať, prečítajte si tieto dôležité informácie, prosím. +InfoBeforeClickLabel=Pokračujte v inštalácii kliknutím na tlačidlo "Ďalej". +WizardInfoAfter=Informácie +InfoAfterLabel=Skôr, ako budete pokračovať, prečítajte si tieto dôležité informácie prosím. +InfoAfterClickLabel=Pokračujte v inštalácii kliknutím na tlačidlo "Ďalej". + +; *** "User Information" wizard page +WizardUserInfo=Informácie o používateľovi +UserInfoDesc=Zadajte požadované informácie prosím. +UserInfoName=&Používateľské meno: +UserInfoOrg=&Organizácia: +UserInfoSerial=&Sériové číslo: +UserInfoNameRequired=Meno používateľa musí byť zadané. + +; *** "Select Destination Location" wizard page +WizardSelectDir=Vyberte cieľový adresár +SelectDirDesc=Kde má byť produkt [name] nainštalovaný? +SelectDirLabel3=Sprievodca nainštaluje produkt [name] do nasledujúceho adresára. +SelectDirBrowseLabel=Pokračujte kliknutím na tlačidlo "Ďalej". Ak chcete vybrať iný adresár, kliknite na tlačidlo "Prechádzať". +DiskSpaceGBLabel=Inštalácia vyžaduje najmenej [gb] GB miesta v disku. +DiskSpaceMBLabel=Inštalácia vyžaduje najmenej [mb] MB miesta v disku. +CannotInstallToNetworkDrive=Sprievodca inštaláciou nemôže inštalovať do sieťovej jednotky. +CannotInstallToUNCPath=Sprievodca inštaláciou nemôže inštalovať do UNC umiestnenia. +InvalidPath=Musíte zadať úplnú cestu vrátane písmena jednotky; napríklad:%n%nC:\Aplikácia%n%nalebo cestu UNC v tvare:%n%n\\Server\Zdieľaný adresár +InvalidDrive=Vami vybraná jednotka alebo cesta UNC neexistuje, alebo nie je dostupná. Vyberte iné umiestnenie prosím. +DiskSpaceWarningTitle=Nedostatok miesta v disku +DiskSpaceWarning=Sprievodca inštaláciou vyžaduje najmenej %1 KB voľného miesta pre inštaláciu produktu, ale vo vybranej jednotke je dostupných iba %2 KB.%n%nAj napriek tomu chcete pokračovať? +DirNameTooLong=Názov adresára alebo cesta sú príliš dlhé. +InvalidDirName=Názov adresára nie je správny. +BadDirName32=Názvy adresárov nesmú obsahovať žiadny z nasledujúcich znakov:%n%n%1 +DirExistsTitle=Adresár už existuje +DirExists=Adresár:%n%n%1%n%nuž existuje. Aj napriek tomu chcete nainštalovať produkt do tohto adresára? +DirDoesntExistTitle=Adresár neexistuje +DirDoesntExist=Adresár: %n%n%1%n%nešte neexistuje. Má sa tento adresár vytvoriť? + +; *** "Select Components" wizard page +WizardSelectComponents=Vyberte komponenty +SelectComponentsDesc=Aké komponenty majú byť nainštalované? +SelectComponentsLabel2=Zaškrtnite iba komponenty, ktoré chcete nainštalovať; komponenty, ktoré se nemajú inštalovať, nechajte nezaškrtnuté. Pokračujte kliknutím na tlačidlo "Ďalej". +FullInstallation=Úplná inštalácia +; if possible don't translate 'Compact' as 'Minimal' (I mean 'Minimal' in your language) +CompactInstallation=Kompaktná inštalácia +CustomInstallation=Voliteľná inštalácia +NoUninstallWarningTitle=Komponenty existujú +NoUninstallWarning=Sprievodca inštaláciou zistil že nasledujúce komponenty už sú v tomto počítači nainštalované:%n%n%1%n%nAk ich teraz nezahrniete do výberu, nebudú neskôr odinštalované.%n%nAj napriek tomu chcete pokračovať? +ComponentSize1=%1 KB +ComponentSize2=%1 MB +ComponentsDiskSpaceGBLabel=Vybrané komponenty vyžadujú najmenej [gb] GB miesta v disku. +ComponentsDiskSpaceMBLabel=Vybrané komponenty vyžadujú najmenej [mb] MB miesta v disku. + +; *** "Select Additional Tasks" wizard page +WizardSelectTasks=Vyberte ďalšie úlohy +SelectTasksDesc=Ktoré ďalšie úlohy majú byť vykonané? +SelectTasksLabel2=Vyberte ďalšie úlohy, ktoré majú byť vykonané počas inštalácie produktu [name] a pokračujte kliknutím na tlačidlo "Ďalej". + +; *** "Select Start Menu Folder" wizard page +WizardSelectProgramGroup=Vyberte skupinu v ponuke Štart +SelectStartMenuFolderDesc=Kam má sprievodca inštalácie umiestniť zástupcov aplikácie? +SelectStartMenuFolderLabel3=Sprievodca inštaláciou vytvorí zástupcov aplikácie v nasledujúcom adresári ponuky Štart. +SelectStartMenuFolderBrowseLabel=Pokračujte kliknutím na tlačidlo Ďalej. Ak chcete zvoliť iný adresár, kliknite na tlačidlo "Prechádzať". +MustEnterGroupName=Musíte zadať názov skupiny. +GroupNameTooLong=Názov adresára alebo cesta sú príliš dlhé. +InvalidGroupName=Názov adresára nie je správny. +BadGroupName=Názov skupiny nesmie obsahovať žiadny z nasledujúcich znakov:%n%n%1 +NoProgramGroupCheck2=&Nevytvárať skupinu v ponuke Štart + +; *** "Ready to Install" wizard page +WizardReady=Inštalácia je pripravená +ReadyLabel1=Sprievodca inštaláciou je teraz pripravený nainštalovať produkt [name] na Váš počítač. +ReadyLabel2a=Pokračujte v inštalácii kliknutím na tlačidlo "Inštalovať". Ak chcete zmeniť niektoré nastavenia inštalácie, kliknite na tlačidlo "< Späť". +ReadyLabel2b=Pokračujte v inštalácii kliknutím na tlačidlo "Inštalovať". +ReadyMemoUserInfo=Informácie o používateľovi: +ReadyMemoDir=Cieľový adresár: +ReadyMemoType=Typ inštalácie: +ReadyMemoComponents=Vybrané komponenty: +ReadyMemoGroup=Skupina v ponuke Štart: +ReadyMemoTasks=Ďalšie úlohy: + +; *** TDownloadWizardPage wizard page and DownloadTemporaryFile +DownloadingLabel=Sťahovanie dodatočných súborov... +ButtonStopDownload=&Zastaviť sťahovanie +StopDownload=Naozaj chcete zastaviť sťahovanie? +ErrorDownloadAborted=Sťahovanie prerušené +ErrorDownloadFailed=Sťahovanie zlyhalo: %1 %2 +ErrorDownloadSizeFailed=Zlyhalo získanie veľkosti: %1 %2 +ErrorFileHash1=Kontrola hodnoty súboru zlyhala: %1 +ErrorFileHash2=Nesprávna kontrolná hodnota: očakávala sa %1, zistená %2 +ErrorProgress=Nesprávny priebeh: %1 z %2 +ErrorFileSize=Nesprávna veľkosť súboru: očakávala sa %1, zistená %2 + +; *** "Preparing to Install" wizard page +WizardPreparing=Príprava inštalácie +PreparingDesc=Sprievodca inštaláciou pripravuje inštaláciu produktu [name] do tohto počítača. +PreviousInstallNotCompleted=Inštalácia/odinštalácia predošlého produktu nebola úplne dokončená. Dokončenie tohto procesu vyžaduje reštart počítača.%n%nPo reštartovaní počítača znovu spustite Sprievodcu inštaláciou, aby bolo možné kompletne dokončiť inštaláciu produktu [name]. +CannotContinue=Sprievodca inštaláciou nemôže pokračovať. Ukončite, prosím, sprievodcu inštaláciou kliknutím na tlačidlo "Zrušiť". +ApplicationsFound=Nasledujúce aplikácie pracujú so súbormi, ktoré musí Sprievodca inštaláciou aktualizovať. Odporúčame, aby ste povolili Sprievodcovi inštaláciou automaticky ukončiť tieto aplikácie. +ApplicationsFound2=Nasledujúce aplikácie pracujú so súbormi, ktoré musí Sprievodca inštaláciou aktualizovať. Odporúčame, aby ste povolili Sprievodcovi inštaláciou automaticky ukončiť tieto aplikácie. Po dokončení inštalácie sa Sprievodca inštaláciou pokúsi tieto aplikácie opätovne spustiť. +CloseApplications=&Automaticky ukončiť aplikácie +DontCloseApplications=&Neukončovať aplikácie +ErrorCloseApplications=Sprievodca inštaláciou nemohol automaticky zatvoriť všetky aplikácie. Odporúčame, aby ste ručne ukončili všetky aplikácie, ktoré používajú súbory a ktoré má Sprievodca aktualizovať. +PrepareToInstallNeedsRestart=Sprievodca inštaláciou potrebuje reštartovať tento počítač. Po reštartovaní počítača znovu spustite tohto Sprievodcu inštaláciou, aby sa inštalácia [name] dokončila.%n%nChcete teraz reštartovať tento počítač? + +; *** "Installing" wizard page +WizardInstalling=Inštalujem +InstallingLabel=Počkajte prosím, pokiaľ Sprievodca inštaláciou dokončí inštaláciu produktu [name] do tohto počítača. + +; *** "Setup Completed" wizard page +FinishedHeadingLabel=Dokončuje sa inštalácia produktu [name] +FinishedLabelNoIcons=Sprievodca inštaláciou dokončil inštaláciu produktu [name] do tohto počítača. +FinishedLabel=Sprievodca inštaláciou dokončil inštaláciu produktu [name] do tohto počítača. Produkt je možné spustiť pomocou nainštalovaných ikon a zástupcov. +ClickFinish=Ukončte Sprievodcu inštaláciou kliknutím na tlačidlo "Dokončiť". +FinishedRestartLabel=Pre dokončenie inštalácie produktu [name] je nutné reštartovať tento počítač. Želáte si teraz reštartovať tento počítač? +FinishedRestartMessage=Pre dokončenie inštalácie produktu [name] je nutné reštartovať tento počítač.%n%nŽeláte si teraz reštartovať tento počítač? +ShowReadmeCheck=Áno, chcem zobraziť dokument "ČITAJMA" +YesRadio=&Áno, chcem teraz reštartovať počítač +NoRadio=&Nie, počítač reštartujem neskôr + +; used for example as 'Run MyProg.exe' +RunEntryExec=Spustiť %1 +; used for example as 'View Readme.txt' +RunEntryShellExec=Zobraziť %1 + +; *** "Setup Needs the Next Disk" stuff +ChangeDiskTitle=Sprievodca inštaláciou vyžaduje ďalší disk +SelectDiskLabel2=Vložte prosím, disk %1 a kliknite na tlačidlo "OK".%n%nAk sa súbory tohto disku nachádzajú v inom adresári ako v tom, ktorý je zobrazený nižšie, zadajte správnu cestu alebo kliknite na tlačidlo "Prechádzať". +PathLabel=&Cesta: +FileNotInDir2=Súbor "%1" sa nedá nájsť v "%2". Vložte prosím, správny disk, alebo zvoľte iný adresár. +SelectDirectoryLabel=Špecifikujte prosím, umiestnenie ďalšieho disku. + +; *** Installation phase messages +SetupAborted=Inštalácia nebola úplne dokončená.%n%nOpravte chybu a opäť spustite Sprievodcu inštaláciou prosím. +AbortRetryIgnoreSelectAction=Vyberte akciu +AbortRetryIgnoreRetry=&Skúsiť znovu +AbortRetryIgnoreIgnore=&Ignorovať chybu a pokračovať +AbortRetryIgnoreCancel=Zrušiť inštaláciu + +; *** Installation status messages +StatusClosingApplications=Ukončovanie aplikácií... +StatusCreateDirs=Vytvárajú sa adresáre... +StatusExtractFiles=Rozbaľujú sa súbory... +StatusCreateIcons=Vytvárajú sa ikony a zástupcovia... +StatusCreateIniEntries=Vytvárajú sa záznamy v konfiguračných súboroch... +StatusCreateRegistryEntries=Vytvárajú sa záznamy v systémovom registri... +StatusRegisterFiles=Registrujú sa súbory... +StatusSavingUninstall=Ukladajú sa informácie potrebné pre neskoršie odinštalovanie produktu... +StatusRunProgram=Dokončuje sa inštalácia... +StatusRestartingApplications=Reštartovanie aplikácií... +StatusRollback=Vykonané zmeny sa vracajú späť... + +; *** Misc. errors +ErrorInternal2=Interná chyba: %1 +ErrorFunctionFailedNoCode=%1 zlyhala +ErrorFunctionFailed=%1 zlyhala; kód %2 +ErrorFunctionFailedWithMessage=%1 zlyhala; kód %2.%n%3 +ErrorExecutingProgram=Nedá sa spustiť súbor:%n%1 + +; *** Registry errors +ErrorRegOpenKey=Došlo k chybe pri otváraní kľúča systémového registra:%n%1\%2 +ErrorRegCreateKey=Došlo k chybe pri vytváraní kľúča systémového registra:%n%1\%2 +ErrorRegWriteKey=Došlo k chybe pri zápise kľúča do systémového registra:%n%1\%2 + +; *** INI errors +ErrorIniEntry=Došlo k chybe pri vytváraní záznamu v konfiguračnom súbore "%1". + +; *** File copying errors +FileAbortRetryIgnoreSkipNotRecommended=&Preskočiť tento súbor (neodporúčané) +FileAbortRetryIgnoreIgnoreNotRecommended=&Ignorovať chybu a pokračovať (neodporúčané) +SourceIsCorrupted=Zdrojový súbor je poškodený +SourceDoesntExist=Zdrojový súbor "%1" neexistuje +ExistingFileReadOnly2=Existujúci súbor nie je možné prepísať, pretože je označený atribútom Iba na čítanie. +ExistingFileReadOnlyRetry=&Odstrániť atribút Iba na čítanie a skúsiť znovu +ExistingFileReadOnlyKeepExisting=&Ponechať existujúci súbor +ErrorReadingExistingDest=Došlo k chybe pri pokuse o čítanie existujúceho súboru: +FileExistsSelectAction=Vyberte akciu +FileExists2=Súbor už existuje. +FileExistsOverwriteExisting=&Prepísať existujúci súbor +FileExistsKeepExisting=Ponechať &existujúci súbor +FileExistsOverwriteOrKeepAll=&Vykonať pre všetky ďalšie konflikty +ExistingFileNewerSelectAction=Vyberte akciu +ExistingFileNewer2=Existujúci súbor je novší ako súbor, ktorý sa Sprievodca inštaláciou pokúša nainštalovať. +ExistingFileNewerOverwriteExisting=&Prepísať existujúci súbor +ExistingFileNewerKeepExisting=Ponechať &existujúci súbor (odporúčané) +ExistingFileNewerOverwriteOrKeepAll=&Vykonať pre všetky ďalšie konflikty +ErrorChangingAttr=Došlo k chybe pri pokuse o modifikáciu atribútov existujúceho súboru: +ErrorCreatingTemp=Došlo k chybe pri pokuse o vytvorenie súboru v cieľovom adresári: +ErrorReadingSource=Došlo k chybe pri pokuse o čítanie zdrojového súboru: +ErrorCopying=Došlo k chybe pri pokuse o skopírovanie súboru: +ErrorReplacingExistingFile=Došlo k chybe pri pokuse o nahradenie existujúceho súboru: +ErrorRestartReplace=Zlyhala funkcia "RestartReplace" Sprievodcu inštaláciou: +ErrorRenamingTemp=Došlo k chybe pri pokuse o premenovanie súboru v cieľovom adresári: +ErrorRegisterServer=Nedá sa vykonať registrácia DLL/OCX: %1 +ErrorRegSvr32Failed=Volanie RegSvr32 zlyhalo s návratovým kódom %1 +ErrorRegisterTypeLib=Nedá sa vykonať registrácia typovej knižnice: %1 + +; *** Uninstall display name markings +; used for example as 'My Program (32-bit)' +UninstallDisplayNameMark=%1 (%2) +; used for example as 'My Program (32-bit, All users)' +UninstallDisplayNameMarks=%1 (%2, %3) +UninstallDisplayNameMark32Bit=32bitový +UninstallDisplayNameMark64Bit=64bitový +UninstallDisplayNameMarkAllUsers=Všetci užívatelia +UninstallDisplayNameMarkCurrentUser=Aktuálny užívateľ + +; *** Post-installation errors +ErrorOpeningReadme=Došlo k chybe pri pokuse o otvorenie dokumentu "ČITAJMA". +ErrorRestartingComputer=Sprievodcovi inštaláciou sa nepodarilo reštartovať tento počítač. Reštartujte ho manuálne prosím. + +; *** Uninstaller messages +UninstallNotFound=Súbor "%1" neexistuje. Produkt sa nedá odinštalovať. +UninstallOpenError=Súbor "%1" nie je možné otvoriť. Produkt nie je možné odinštalovať. +UninstallUnsupportedVer=Sprievodcovi odinštaláciou sa nepodarilo rozpoznať formát súboru obsahujúceho informácie na odinštalovanie produktu "%1". Produkt sa nedá odinštalovať +UninstallUnknownEntry=V súbore obsahujúcom informácie na odinštalovanie produktu bola zistená neznáma položka (%1) +ConfirmUninstall=Naozaj chcete odinštalovať %1 a všetky jeho komponenty? +UninstallOnlyOnWin64=Tento produkt je možné odinštalovať iba v 64-bitových verziách MS Windows. +OnlyAdminCanUninstall=K odinštalovaniu tohto produktu musíte byť prihlásený s právami Administrátora. +UninstallStatusLabel=Počkajte prosím, kým produkt %1 nebude odinštalovaný z tohto počítača. +UninstalledAll=%1 bol úspešne odinštalovaný z tohto počítača. +UninstalledMost=%1 bol odinštalovaný z tohto počítača.%n%nNiektoré jeho komponenty sa však nepodarilo odinštalovať. Môžete ich odinštalovať manuálne. +UninstalledAndNeedsRestart=Na dokončenie odinštalácie produktu %1 je potrebné reštartovať tento počítač.%n%nChcete ihneď reštartovať tento počítač? +UninstallDataCorrupted=Súbor "%1" je poškodený. Produkt sa nedá odinštalovať + +; *** Uninstallation phase messages +ConfirmDeleteSharedFileTitle=Odinštalovať zdieľaný súbor? +ConfirmDeleteSharedFile2=Systém indikuje, že nasledujúci zdieľaný súbor nie je používaný žiadnymi inými aplikáciami. Má Sprievodca odinštaláciou tento zdieľaný súbor odstrániť?%n%nAk niektoré aplikácie tento súbor používajú, nemusia po jeho odinštalovaní pracovať správne. Pokiaľ to neviete správne posúdiť, odporúčame zvoliť "Nie". Ponechanie tohto súboru v systéme nespôsobí žiadnu škodu. +SharedFileNameLabel=Názov súboru: +SharedFileLocationLabel=Umiestnenie: +WizardUninstalling=Stav odinštalovania +StatusUninstalling=Prebieha odinštalovanie %1... + +; *** Shutdown block reasons +ShutdownBlockReasonInstallingApp=Inštalovanie %1. +ShutdownBlockReasonUninstallingApp=Odinštalovanie %1. + +; The custom messages below aren't used by Setup itself, but if you make +; use of them in your scripts, you'll want to translate them. + +[CustomMessages] + +NameAndVersion=%1 verzia %2 +AdditionalIcons=Ďalší zástupcovia: +CreateDesktopIcon=Vytvoriť zástupcu na &ploche +CreateQuickLaunchIcon=Vytvoriť zástupcu na paneli &Rýchle spustenie +ProgramOnTheWeb=Aplikácia %1 na internete +UninstallProgram=Odinštalovať aplikáciu %1 +LaunchProgram=Spustiť aplikáciu %1 +AssocFileExtension=Vytvoriť &asociáciu medzi súbormi typu %2 a aplikáciou %1 +AssocingFileExtension=Vytvára sa asociácia medzi súbormi typu %2 a aplikáciou %1... +AutoStartProgramGroupDescription=Pri spustení: +AutoStartProgram=Automaticky spustiť %1 +AddonHostProgramNotFound=Nepodarilo sa nájsť %1 v adresári, ktorý ste zvolili.%n%nChcete napriek tomu pokračovať? diff --git a/src/AITool.Setup/INNO/Languages/Slovenian.isl b/src/AITool.Setup/INNO/Languages/Slovenian.isl new file mode 100644 index 00000000..248b688c --- /dev/null +++ b/src/AITool.Setup/INNO/Languages/Slovenian.isl @@ -0,0 +1,370 @@ +; *** Inno Setup version 6.1.0+ Slovenian messages *** +; +; To download user-contributed translations of this file, go to: +; http://www.jrsoftware.org/is3rdparty.php +; +; Note: When translating this text, do not add periods (.) to the end of +; messages that didn't have them already, because on those messages Inno +; Setup adds the periods automatically (appending a period would result in +; two periods being displayed). +; +; Maintained by Jernej Simoncic (jernej+s-innosetup@eternallybored.org) + +[LangOptions] +LanguageName=Slovenski +LanguageID=$0424 +LanguageCodePage=1250 + +DialogFontName= +[Messages] + +; *** Application titles +SetupAppTitle=Namestitev +SetupWindowTitle=Namestitev - %1 +UninstallAppTitle=Odstranitev +UninstallAppFullTitle=Odstranitev programa %1 + +; *** Misc. common +InformationTitle=Informacija +ConfirmTitle=Potrditev +ErrorTitle=Napaka + +; *** SetupLdr messages +SetupLdrStartupMessage=V raunalnik boste namestili program %1. elite nadaljevati? +LdrCannotCreateTemp=Ni bilo mogoe ustvariti zaasne datoteke. Namestitev je prekinjena +LdrCannotExecTemp=Ni bilo mogoe zagnati datoteke v zaasni mapi. Namestitev je prekinjena + +; *** Startup error messages +LastErrorMessage=%1.%n%nNapaka %2: %3 +SetupFileMissing=Datoteka %1 manjka. Odpravite napako ali si priskrbite drugo kopijo programa. +SetupFileCorrupt=Datoteke namestitvenega programa so okvarjene. Priskrbite si drugo kopijo programa. +SetupFileCorruptOrWrongVer=Datoteke so okvarjene ali nezdruljive s to razliico namestitvenega programa. Odpravite napako ali si priskrbite drugo kopijo programa. +InvalidParameter=Naveden je bil napaen parameter ukazne vrstice:%n%n%1 +SetupAlreadyRunning=Namestitveni program se e izvaja. +WindowsVersionNotSupported=Program ne deluje na vai razliici sistema Windows. +WindowsServicePackRequired=Program potrebuje %1 s servisnim paketom %2 ali novejo razliico. +NotOnThisPlatform=Program ni namenjen za uporabo v %1. +OnlyOnThisPlatform=Program je namenjen le za uporabo v %1. +OnlyOnTheseArchitectures=Program lahko namestite le na Windows sistemih, na naslednjih vrstah procesorjev:%n%n%1 +WinVersionTooLowError=Ta program zahteva %1 razliico %2 ali novejo. +WinVersionTooHighError=Tega programa ne morete namestiti v %1 razliice %2 ali noveje. +AdminPrivilegesRequired=Za namestitev programa morate biti prijavljeni v raun s skrbnikimi pravicami. +PowerUserPrivilegesRequired=Za namestitev programa morate biti prijavljeni v raun s skrbnikimi ali power user pravicami. +SetupAppRunningError=Program %1 je trenutno odprt.%n%nZaprite program, nato kliknite V redu za nadaljevanje ali Preklii za izhod. +UninstallAppRunningError=Program %1 je trenutno odprt.%n%nZaprite program, nato kliknite V redu za nadaljevanje ali Preklii za izhod. + +; *** Startup questions +PrivilegesRequiredOverrideTitle=Izberite nain namestitve +PrivilegesRequiredOverrideInstruction=Izberite nain namestitve +PrivilegesRequiredOverrideText1=Program %1 lahko namestite za vse uporabnike (potrebujete skrbnike pravice), ali pa samo za vas. +PrivilegesRequiredOverrideText2=Program %1 lahko namestite samo za vas, ali pa za vse uporabnike (potrebujete skrbnike pravice). +PrivilegesRequiredOverrideAllUsers=N&amesti za vse uporabnike +PrivilegesRequiredOverrideAllUsersRecommended=N&amesti za vse uporabnike (priporoeno) +PrivilegesRequiredOverrideCurrentUser=Namesti samo za&me +PrivilegesRequiredOverrideCurrentUserRecommended=Namesti samo za&me (priporoeno) + +; *** Misc. errors +ErrorCreatingDir=Namestitveni program ni mogel ustvariti mape %1 +ErrorTooManyFilesInDir=Namestitveni program ne more ustvariti nove datoteke v mapi %1, ker vsebuje preve datotek + +; *** Setup common messages +ExitSetupTitle=Prekini namestitev +ExitSetupMessage=Namestitev ni konana. e jo boste prekinili, program ne bo nameen.%n%nPonovno namestitev lahko izvedete kasneje.%n%nelite prekiniti namestitev? +AboutSetupMenuItem=&O namestitvenem programu... +AboutSetupTitle=O namestitvenem programu +AboutSetupMessage=%1 razliica %2%n%3%n%n%1 domaa stran:%n%4 +AboutSetupNote= +TranslatorNote=Slovenski prevod:%nMiha Remec%nJernej Simoni + +; *** Buttons +ButtonBack=< Na&zaj +ButtonNext=&Naprej > +ButtonInstall=&Namesti +ButtonOK=V redu +ButtonCancel=Preklii +ButtonYes=&Da +ButtonYesToAll=Da za &vse +ButtonNo=&Ne +ButtonNoToAll=N&e za vse +ButtonFinish=&Konaj +ButtonBrowse=Pre&brskaj... +ButtonWizardBrowse=Pre&brskaj... +ButtonNewFolder=&Ustvari novo mapo + +; *** "Select Language" dialog messages +SelectLanguageTitle=Izbira jezika namestitve +SelectLanguageLabel=Izberite jezik, ki ga elite uporabljati med namestitvijo. + +; *** Common wizard text +ClickNext=Kliknite Naprej za nadaljevanje namestitve ali Preklii za prekinitev namestitve. +BeveledLabel= +BrowseDialogTitle=Izbira mape +BrowseDialogLabel=Izberite mapo s spiska, nato kliknite V redu. +NewFolderName=Nova mapa + +; *** "Welcome" wizard page +WelcomeLabel1=Dobrodoli v namestitev programa [name]. +WelcomeLabel2=V raunalnik boste namestili program [name/ver].%n%nPriporoljivo je, da pred zaetkom namestitve zaprete vse odprte programe. + +; *** "Password" wizard page +WizardPassword=Geslo +PasswordLabel1=Namestitev je zaitena z geslom. +PasswordLabel3=Vnesite geslo, nato kliknite Naprej za nadaljevanje. Pri vnaanju pazite na male in velike rke. +PasswordEditLabel=&Geslo: +IncorrectPassword=Vneseno geslo ni pravilno. Poizkusite ponovno. + +; *** "License Agreement" wizard page +WizardLicense=Licenna pogodba +LicenseLabel=Pred nadaljevanjem preberite licenno pogodbo za uporabo programa. +LicenseLabel3=Preberite licenno pogodbo za uporabo programa. Program lahko namestite le, e se s pogodbo v celoti strinjate. +LicenseAccepted=&Da, sprejemam vse pogoje licenne pogodbe +LicenseNotAccepted=N&e, pogojev licenne pogodbe ne sprejmem + +; *** "Information" wizard pages +WizardInfoBefore=Informacije +InfoBeforeLabel=Pred nadaljevanjem preberite naslednje pomembne informacije. +InfoBeforeClickLabel=Ko boste pripravljeni na nadaljevanje namestitve, kliknite Naprej. +WizardInfoAfter=Informacije +InfoAfterLabel=Pred nadaljevanjem preberite naslednje pomembne informacije. +InfoAfterClickLabel=Ko boste pripravljeni na nadaljevanje namestitve, kliknite Naprej. + +; *** "User Information" wizard page +WizardUserInfo=Podatki o uporabniku +UserInfoDesc=Vnesite svoje podatke. +UserInfoName=&Ime: +UserInfoOrg=&Podjetje: +UserInfoSerial=&Serijska tevilka: +UserInfoNameRequired=Vnos imena je obvezen. + +; *** "Select Destination Location" wizard page +WizardSelectDir=Izbira ciljnega mesta +SelectDirDesc=Kam elite namestiti program [name]? +SelectDirLabel3=Program [name] bo nameen v naslednjo mapo. +SelectDirBrowseLabel=Za nadaljevanje kliknite Naprej. e elite izbrati drugo mapo, kliknite Prebrskaj. +DiskSpaceGBLabel=Na disku mora biti vsaj [gb] GB prostora. +DiskSpaceMBLabel=Na disku mora biti vsaj [mb] MB prostora. +CannotInstallToNetworkDrive=Programa ni mogoe namestiti na mreni pogon. +CannotInstallToUNCPath=Programa ni mogoe namestiti v UNC pot. +InvalidPath=Vpisati morate polno pot vkljuno z oznako pogona. Primer:%n%nC:\PROGRAM%n%nali UNC pot v obliki:%n%n\\strenik\mapa_skupne_rabe +InvalidDrive=Izbrani pogon ali omreno sredstvo UNC ne obstaja ali ni dostopno. Izberite drugega. +DiskSpaceWarningTitle=Na disku ni dovolj prostora +DiskSpaceWarning=Namestitev potrebuje vsaj %1 KB prostora, toda na izbranem pogonu je na voljo le %2 KB.%n%nelite kljub temu nadaljevati? +DirNameTooLong=Ime mape ali poti je predolgo. +InvalidDirName=Ime mape ni veljavno. +BadDirName32=Ime mape ne sme vsebovati naslednjih znakov:%n%n%1 +DirExistsTitle=Mapa e obstaja +DirExists=Mapa%n%n%1%n%ne obstaja. elite program vseeno namestiti v to mapo? +DirDoesntExistTitle=Mapa ne obstaja +DirDoesntExist=Mapa %n%n%1%n%nne obstaja. Ali jo elite ustvariti? + +; *** "Select Components" wizard page +WizardSelectComponents=Izbira komponent +SelectComponentsDesc=Katere komponente elite namestiti? +SelectComponentsLabel2=Oznaite komponente, ki jih elite namestiti; odznaite komponente, ki jih ne elite namestiti. Kliknite Naprej, ko boste pripravljeni za nadaljevanje. +FullInstallation=Popolna namestitev +; if possible don't translate 'Compact' as 'Minimal' (I mean 'Minimal' in your language) +CompactInstallation=Osnovna namestitev +CustomInstallation=Namestitev po meri +NoUninstallWarningTitle=Komponente e obstajajo +NoUninstallWarning=Namestitveni program je ugotovil, da so naslednje komponente e nameene v raunalniku:%n%n%1%n%nNamestitveni program teh e nameenih komponent ne bo odstranil.%n%nelite vseeno nadaljevati? +ComponentSize1=%1 KB +ComponentSize2=%1 MB +ComponentsDiskSpaceGBLabel=Za izbrano namestitev potrebujete vsaj [gb] GB prostora na disku. +ComponentsDiskSpaceMBLabel=Za izbrano namestitev potrebujete vsaj [mb] MB prostora na disku. + +; *** "Select Additional Tasks" wizard page +WizardSelectTasks=Izbira dodatnih opravil +SelectTasksDesc=Katera dodatna opravila elite izvesti? +SelectTasksLabel2=Izberite dodatna opravila, ki jih bo namestitveni program opravil med namestitvijo programa [name], nato kliknite Naprej. + +; *** "Select Start Menu Folder" wizard page +WizardSelectProgramGroup=Izbira mape v meniju Zaetek +SelectStartMenuFolderDesc=Kje naj namestitveni program ustvari blinjice? +SelectStartMenuFolderLabel3=Namestitveni program bo ustvaril blinjice v naslednji mapi v meniju Start. +SelectStartMenuFolderBrowseLabel=Za nadaljevanje kliknite Naprej. e elite izbrati drugo mapo, kliknite Prebrskaj. +MustEnterGroupName=Ime skupine mora biti vpisano. +GroupNameTooLong=Ime mape ali poti je predolgo. +InvalidGroupName=Ime mape ni veljavno. +BadGroupName=Ime skupine ne sme vsebovati naslednjih znakov:%n%n%1 +NoProgramGroupCheck2=&Ne ustvari mape v meniju Start + +; *** "Ready to Install" wizard page +WizardReady=Pripravljen za namestitev +ReadyLabel1=Namestitveni program je pripravljen za namestitev programa [name] v va raunalnik. +ReadyLabel2a=Kliknite Namesti za zaetek nameanja. Kliknite Nazaj, e elite pregledati ali spremeniti katerokoli nastavitev. +ReadyLabel2b=Kliknite Namesti za zaetek nameanja. +ReadyMemoUserInfo=Podatki o uporabniku: +ReadyMemoDir=Ciljno mesto: +ReadyMemoType=Vrsta namestitve: +ReadyMemoComponents=Izbrane komponente: +ReadyMemoGroup=Mapa v meniju Zaetek: +ReadyMemoTasks=Dodatna opravila: + +; *** TDownloadWizardPage wizard page and DownloadTemporaryFile +DownloadingLabel=Prenaam dodatne datoteke... +ButtonStopDownload=Prekini preno&s +StopDownload=Ali res elite prekiniti prenos? +ErrorDownloadAborted=Prenos prekinjen +ErrorDownloadFailed=Prenos ni uspel: %1 %2 +ErrorDownloadSizeFailed=Pridobivanje velikosti ni uspelo: %1 %2 +ErrorFileHash1=Pridobivanje zgoene vrednosti ni uspelo: %1 +ErrorFileHash2=Neveljavna zgoena vrednost: priakovana %1, dobljena %2 +ErrorProgress=Neveljaven potek: %1 od %2 +ErrorFileSize=Neveljavna velikost datoteke: priakovana %1, dobljena %2 + +; *** "Preparing to Install" wizard page +WizardPreparing=Pripravljam za namestitev +PreparingDesc=Namestitveni program je pripravljen za namestitev programa [name] v va raunalnik. +PreviousInstallNotCompleted=Namestitev ali odstranitev prejnjega programa ni bila konana. Da bi jo dokonali, morate raunalnik znova zagnati.%n%nPo ponovnem zagonu raunalnika znova zaenite namestitveni program, da boste konali namestitev programa [name]. +CannotContinue=Namestitveni program ne more nadaljevati. Pritisnite Preklii za izhod. + +; *** "Installing" wizard page +ApplicationsFound=Naslednji programi uporabljajo datoteke, ki jih mora namestitveni program posodobiti. Priporoljivo je, da namestitvenemu programu dovolite, da te programe kona. +ApplicationsFound2=Naslednji programi uporabljajo datoteke, ki jih mora namestitveni program posodobiti. Priporoljivo je, da namestitvenemu programu dovolite, da te programe kona. Po koncu namestitve bo namestitveni program poizkusil znova zagnati te programe. +CloseApplications=S&amodejno zapri programe +DontCloseApplications=&Ne zapri programov +ErrorCloseApplications=Namestitvenemu programu ni uspelo samodejno zapreti vseh programov. Priporoljivo je, da pred nadaljevanjem zaprete vse programe, ki uporabljajo datoteke, katere mora namestitev posodobiti. +PrepareToInstallNeedsRestart=Namestitveni program mora znova zagnati va raunalnik. Za dokonanje namestitve programa [name], po ponovnem zagonu znova zaenite namestitveni program.%n%nAli elite zdaj znova zagnati raunalnik? + +WizardInstalling=Nameanje +InstallingLabel=Poakajte, da bo program [name] nameen v va raunalnik. + +; *** "Setup Completed" wizard page +FinishedHeadingLabel=Zakljuek namestitve programa [name] +FinishedLabelNoIcons=Program [name] je nameen v va raunalnik. +FinishedLabel=Program [name] je nameen v va raunalnik. Program zaenete tako, da odprete pravkar ustvarjene programske ikone. +ClickFinish=Kliknite tipko Konaj za zakljuek namestitve. +FinishedRestartLabel=Za dokonanje namestitve programa [name] morate raunalnik znova zagnati. Ali ga elite znova zagnati zdaj? +FinishedRestartMessage=Za dokonanje namestitve programa [name] morate raunalnik znova zagnati. %n%nAli ga elite znova zagnati zdaj? +ShowReadmeCheck=elim prebrati datoteko BERIME +YesRadio=&Da, raunalnik znova zaeni zdaj +NoRadio=&Ne, raunalnik bom znova zagnal pozneje + +; used for example as 'Run MyProg.exe' +RunEntryExec=Zaeni %1 +; used for example as 'View Readme.txt' +RunEntryShellExec=Preglej %1 + +; *** "Setup Needs the Next Disk" stuff +ChangeDiskTitle=Namestitveni program potrebuje naslednji disk +SelectDiskLabel2=Vstavite disk %1 in kliknite V redu.%n%ne se datoteke s tega diska nahajajo v drugi mapi kot je navedena spodaj, vnesite pravilno pot ali kliknite Prebrskaj. +PathLabel=&Pot: +FileNotInDir2=Datoteke %1 ni v mapi %2. Vstavite pravilni disk ali izberite drugo mapo. +SelectDirectoryLabel=Vnesite mesto naslednjega diska. + +; *** Installation phase messages +SetupAborted=Namestitev ni bila konana.%n%nOdpravite teavo in znova odprite namestitveni program. +AbortRetryIgnoreSelectAction=Izberite dejanje +AbortRetryIgnoreRetry=Poizkusi &znova +AbortRetryIgnoreIgnore=&Prezri napako in nadaljuj +AbortRetryIgnoreCancel=Preklii namestitev + +; *** Installation status messages +StatusClosingApplications=Zapiranje programov... +StatusCreateDirs=Ustvarjanje map... +StatusExtractFiles=Razirjanje datotek... +StatusCreateIcons=Ustvarjanje blinjic... +StatusCreateIniEntries=Vpisovanje v INI datoteke... +StatusCreateRegistryEntries=Ustvarjanje vnosov v register... +StatusRegisterFiles=Registriranje datotek... +StatusSavingUninstall=Zapisovanje podatkov za odstranitev... +StatusRunProgram=Zakljuevanje namestitve... +StatusRestartingApplications=Zaganjanje programov... +StatusRollback=Obnavljanje prvotnega stanja... + +; *** Misc. errors +ErrorInternal2=Interna napaka: %1 +ErrorFunctionFailedNoCode=%1 ni uspel(a) +ErrorFunctionFailed=%1 ni uspel(a); koda %2 +ErrorFunctionFailedWithMessage=%1 ni uspela; koda %2.%n%3 +ErrorExecutingProgram=Ne morem zagnati programa:%n%1 + +; *** Registry errors +ErrorRegOpenKey=Napaka pri odpiranju kljua v registru:%n%1\%2 +ErrorRegCreateKey=Napaka pri ustvarjanju kljua v registru:%n%1\%2 +ErrorRegWriteKey=Napaka pri pisanju kljua v registru:%n%1\%2 + +; *** INI errors +ErrorIniEntry=Napaka pri vpisu v INI datoteko %1. + +; *** File copying errors +FileAbortRetryIgnoreSkipNotRecommended=Pre&skoi to datoteko (ni priporoeno) +FileAbortRetryIgnoreIgnoreNotRecommended=Prezr&i napako in nadaljuj (ni priporoeno) +SourceIsCorrupted=Izvorna datoteka je okvarjena +SourceDoesntExist=Izvorna datoteka %1 ne obstaja +ExistingFileReadOnly2=Obstojee datoteke ni mogoe nadomestiti, ker ima oznako samo za branje. +ExistingFileReadOnlyRetry=Odst&rani oznako samo za branje in poizkusi ponovno +ExistingFileReadOnlyKeepExisting=&Ohrani obstojeo datoteko +ErrorReadingExistingDest=Pri branju obstojee datoteke je prilo do napake: +FileExistsSelectAction=Izberite dejanje +FileExists2=Datoteka e obstaja. +FileExistsOverwriteExisting=&Prepii obstojeo datoteko +FileExistsKeepExisting=&Ohrani trenutno datoteko +FileExistsOverwriteOrKeepAll=&To naredite za preostale spore +ExistingFileNewerSelectAction=Izberite dejanje +ExistingFileNewer2=Obstojea datoteka je noveja, kot datoteka, ki se namea. +ExistingFileNewerOverwriteExisting=&Prepii obstojeo datoteko +ExistingFileNewerKeepExisting=&Ohrani trenutno datoteko (priporoeno) +ExistingFileNewerOverwriteOrKeepAll=&To naredite za preostale spore +ErrorChangingAttr=Pri poskusu spremembe lastnosti datoteke je prilo do napake: +ErrorCreatingTemp=Pri ustvarjanju datoteke v ciljni mapi je prilo do napake: +ErrorReadingSource=Pri branju izvorne datoteke je prilo do napake: +ErrorCopying=Pri kopiranju datoteke je prilo do napake: +ErrorReplacingExistingFile=Pri poskusu zamenjave obstojee datoteke je prilo do napake: +ErrorRestartReplace=Napaka RestartReplace: +ErrorRenamingTemp=Pri poskusu preimenovanja datoteke v ciljni mapi je prilo do napake: +ErrorRegisterServer=Registracija DLL/OCX ni uspela: %1 +ErrorRegSvr32Failed=RegSvr32 ni uspel s kodo napake %1 +ErrorRegisterTypeLib=Registracija TypeLib ni uspela: %1 + +; *** Uninstall display name markings +UninstallDisplayNameMark=%1 (%2) +UninstallDisplayNameMarks=%1 (%2, %3) +UninstallDisplayNameMark32Bit=32-bitno +UninstallDisplayNameMark64Bit=64-bitno +UninstallDisplayNameMarkAllUsers=vsi uporabniki +UninstallDisplayNameMarkCurrentUser=trenutni uporabnik + +; *** Post-installation errors +ErrorOpeningReadme=Pri odpiranju datoteke BERIME je prilo do napake. +ErrorRestartingComputer=Namestitvenemu programu ni uspelo znova zagnati raunalnika. Sami znova zaenite raunalnik. + +; *** Uninstaller messages +UninstallNotFound=Datoteka %1 ne obstaja. Odstranitev ni mogoa. +UninstallOpenError=Datoteke %1 ne morem odpreti. Ne morem odstraniti +UninstallUnsupportedVer=Dnevnika datoteka %1 je v obliki, ki je ta razliica odstranitvenega programa ne razume. Programa ni mogoe odstraniti +UninstallUnknownEntry=V dnevniki datoteki je bil najden neznani vpis (%1) +ConfirmUninstall=Ste prepriani, da elite v celoti odstraniti program %1 in pripadajoe komponente? +UninstallOnlyOnWin64=To namestitev je mogoe odstraniti le v 64-bitni razliici sistema Windows. +OnlyAdminCanUninstall=Za odstranitev tega programa morate imeti skrbnike pravice. +UninstallStatusLabel=Poakajte, da se program %1 odstrani iz vaega raunalnika. +UninstalledAll=Program %1 je bil uspeno odstranjen iz vaega raunalnika. +UninstalledMost=Odstranjevanje programa %1 je konano.%n%nNekatere datoteke niso bile odstranjene in jih lahko odstranite rono. +UninstalledAndNeedsRestart=Za dokonanje odstranitve programa %1 morate raunalnik znova zagnati.%n%nAli ga elite znova zagnati zdaj? +UninstallDataCorrupted=Datoteka %1 je okvarjena. Odstranitev ni mona + +; *** Uninstallation phase messages +ConfirmDeleteSharedFileTitle=elite odstraniti datoteko v skupni rabi? +ConfirmDeleteSharedFile2=Spodaj izpisane datoteke v skupni rabi ne uporablja ve noben program. elite odstraniti to datoteko?%n%ne jo uporablja katerikoli program in jo boste odstranili, ta program verjetno ne bo ve deloval pravilno. e niste prepriani, kliknite Ne. e boste datoteko ohranili v raunalniku, ne bo ni narobe. +SharedFileNameLabel=Ime datoteke: +SharedFileLocationLabel=Mesto: +WizardUninstalling=Odstranjevanje programa +StatusUninstalling=Odstranjujem %1... + +ShutdownBlockReasonInstallingApp=Nameam %1. +ShutdownBlockReasonUninstallingApp=Odstranjujem %1. + +[CustomMessages] + +NameAndVersion=%1 razliica %2 +AdditionalIcons=Dodatne ikone: +CreateDesktopIcon=Ustvari ikono na &namizju +CreateQuickLaunchIcon=Ustvari ikono za &hitri zagon +ProgramOnTheWeb=%1 na spletu +UninstallProgram=Odstrani %1 +LaunchProgram=Odpri %1 +AssocFileExtension=&Povei %1 s pripono %2 +AssocingFileExtension=Povezujem %1 s pripono %2... +AutoStartProgramGroupDescription=Zagon: +AutoStartProgram=Samodejno zaeni %1 +AddonHostProgramNotFound=Programa %1 ni bilo mogoe najti v izbrani mapi.%n%nAli elite vseeno nadaljevati? diff --git a/src/AITool.Setup/INNO/Languages/Spanish.isl b/src/AITool.Setup/INNO/Languages/Spanish.isl new file mode 100644 index 00000000..6fff47c7 --- /dev/null +++ b/src/AITool.Setup/INNO/Languages/Spanish.isl @@ -0,0 +1,383 @@ +; *** Inno Setup version 6.1.0+ Spanish messages *** +; +; Maintained by Jorge Andres Brugger (jbrugger@ideaworks.com.ar) +; Spanish.isl version 1.5.2 (20211123) +; Default.isl version 6.1.0 +; +; Thanks to Germn Giraldo, Jordi Latorre, Ximo Tamarit, Emiliano Llano, +; Ramn Verduzco, Graciela Garca, Carles Millan and Rafael Barranco-Droege + +[LangOptions] +; The following three entries are very important. Be sure to read and +; understand the '[LangOptions] section' topic in the help file. +LanguageName=Espa<00F1>ol +LanguageID=$0c0a +LanguageCodePage=1252 +; If the language you are translating to requires special font faces or +; sizes, uncomment any of the following entries and change them accordingly. +;DialogFontName= +;DialogFontSize=8 +;WelcomeFontName=Verdana +;WelcomeFontSize=12 +;TitleFontName=Arial +;TitleFontSize=29 +;CopyrightFontName=Arial +;CopyrightFontSize=8 + +[Messages] + +; *** Application titles +SetupAppTitle=Instalar +SetupWindowTitle=Instalar - %1 +UninstallAppTitle=Desinstalar +UninstallAppFullTitle=Desinstalar - %1 + +; *** Misc. common +InformationTitle=Informacin +ConfirmTitle=Confirmar +ErrorTitle=Error + +; *** SetupLdr messages +SetupLdrStartupMessage=Este programa instalar %1. Desea continuar? +LdrCannotCreateTemp=Imposible crear archivo temporal. Instalacin interrumpida +LdrCannotExecTemp=Imposible ejecutar archivo en la carpeta temporal. Instalacin interrumpida +HelpTextNote= + +; *** Startup error messages +LastErrorMessage=%1.%n%nError %2: %3 +SetupFileMissing=El archivo %1 no se encuentra en la carpeta de instalacin. Por favor, solucione el problema u obtenga una copia nueva del programa. +SetupFileCorrupt=Los archivos de instalacin estn daados. Por favor, obtenga una copia nueva del programa. +SetupFileCorruptOrWrongVer=Los archivos de instalacin estn daados o son incompatibles con esta versin del programa de instalacin. Por favor, solucione el problema u obtenga una copia nueva del programa. +InvalidParameter=Se ha utilizado un parmetro no vlido en la lnea de comandos:%n%n%1 +SetupAlreadyRunning=El programa de instalacin an est ejecutndose. +WindowsVersionNotSupported=Este programa no es compatible con la versin de Windows de su equipo. +WindowsServicePackRequired=Este programa requiere %1 Service Pack %2 o posterior. +NotOnThisPlatform=Este programa no se ejecutar en %1. +OnlyOnThisPlatform=Este programa debe ejecutarse en %1. +OnlyOnTheseArchitectures=Este programa solo puede instalarse en versiones de Windows diseadas para las siguientes arquitecturas de procesadores:%n%n%1 +WinVersionTooLowError=Este programa requiere %1 versin %2 o posterior. +WinVersionTooHighError=Este programa no puede instalarse en %1 versin %2 o posterior. +AdminPrivilegesRequired=Debe iniciar la sesin como administrador para instalar este programa. +PowerUserPrivilegesRequired=Debe iniciar la sesin como administrador o como miembro del grupo de Usuarios Avanzados para instalar este programa. +SetupAppRunningError=El programa de instalacin ha detectado que %1 est ejecutndose.%n%nPor favor, cirrelo ahora, luego haga clic en Aceptar para continuar o en Cancelar para salir. +UninstallAppRunningError=El desinstalador ha detectado que %1 est ejecutndose.%n%nPor favor, cirrelo ahora, luego haga clic en Aceptar para continuar o en Cancelar para salir. + +; *** Startup questions +PrivilegesRequiredOverrideTitle=Seleccin del Modo de Instalacin +PrivilegesRequiredOverrideInstruction=Seleccione el modo de instalacin +PrivilegesRequiredOverrideText1=%1 puede ser instalado para todos los usuarios (requiere privilegios administrativos), o solo para Ud. +PrivilegesRequiredOverrideText2=%1 puede ser instalado solo para Ud, o para todos los usuarios (requiere privilegios administrativos). +PrivilegesRequiredOverrideAllUsers=Instalar para &todos los usuarios +PrivilegesRequiredOverrideAllUsersRecommended=Instalar para &todos los usuarios (recomendado) +PrivilegesRequiredOverrideCurrentUser=Instalar para &m solamente +PrivilegesRequiredOverrideCurrentUserRecommended=Instalar para &m solamente (recomendado) + +; *** Misc. errors +ErrorCreatingDir=El programa de instalacin no pudo crear la carpeta "%1" +ErrorTooManyFilesInDir=Imposible crear un archivo en la carpeta "%1" porque contiene demasiados archivos + +; *** Setup common messages +ExitSetupTitle=Salir de la Instalacin +ExitSetupMessage=La instalacin no se ha completado an. Si cancela ahora, el programa no se instalar.%n%nPuede ejecutar nuevamente el programa de instalacin en otra ocasin para completarla.%n%nSalir de la instalacin? +AboutSetupMenuItem=&Acerca de Instalar... +AboutSetupTitle=Acerca de Instalar +AboutSetupMessage=%1 versin %2%n%3%n%n%1 sitio Web:%n%4 +AboutSetupNote= +TranslatorNote=Spanish translation maintained by Jorge Andres Brugger (jbrugger@gmx.net) + +; *** Buttons +ButtonBack=< &Atrs +ButtonNext=&Siguiente > +ButtonInstall=&Instalar +ButtonOK=Aceptar +ButtonCancel=Cancelar +ButtonYes=&S +ButtonYesToAll=S a &Todo +ButtonNo=&No +ButtonNoToAll=N&o a Todo +ButtonFinish=&Finalizar +ButtonBrowse=&Examinar... +ButtonWizardBrowse=&Examinar... +ButtonNewFolder=&Crear Nueva Carpeta + +; *** "Select Language" dialog messages +SelectLanguageTitle=Seleccione el Idioma de la Instalacin +SelectLanguageLabel=Seleccione el idioma a utilizar durante la instalacin. + +; *** Common wizard text +ClickNext=Haga clic en Siguiente para continuar o en Cancelar para salir de la instalacin. +BeveledLabel= +BrowseDialogTitle=Buscar Carpeta +BrowseDialogLabel=Seleccione una carpeta y luego haga clic en Aceptar. +NewFolderName=Nueva Carpeta + +; *** "Welcome" wizard page +WelcomeLabel1=Bienvenido al asistente de instalacin de [name] +WelcomeLabel2=Este programa instalar [name/ver] en su sistema.%n%nSe recomienda cerrar todas las dems aplicaciones antes de continuar. + +; *** "Password" wizard page +WizardPassword=Contrasea +PasswordLabel1=Esta instalacin est protegida por contrasea. +PasswordLabel3=Por favor, introduzca la contrasea y haga clic en Siguiente para continuar. En las contraseas se hace diferencia entre maysculas y minsculas. +PasswordEditLabel=&Contrasea: +IncorrectPassword=La contrasea introducida no es correcta. Por favor, intntelo nuevamente. + +; *** "License Agreement" wizard page +WizardLicense=Acuerdo de Licencia +LicenseLabel=Es importante que lea la siguiente informacin antes de continuar. +LicenseLabel3=Por favor, lea el siguiente acuerdo de licencia. Debe aceptar las clusulas de este acuerdo antes de continuar con la instalacin. +LicenseAccepted=A&cepto el acuerdo +LicenseNotAccepted=&No acepto el acuerdo + +; *** "Information" wizard pages +WizardInfoBefore=Informacin +InfoBeforeLabel=Es importante que lea la siguiente informacin antes de continuar. +InfoBeforeClickLabel=Cuando est listo para continuar con la instalacin, haga clic en Siguiente. +WizardInfoAfter=Informacin +InfoAfterLabel=Es importante que lea la siguiente informacin antes de continuar. +InfoAfterClickLabel=Cuando est listo para continuar, haga clic en Siguiente. + +; *** "User Information" wizard page +WizardUserInfo=Informacin de Usuario +UserInfoDesc=Por favor, introduzca sus datos. +UserInfoName=Nombre de &Usuario: +UserInfoOrg=&Organizacin: +UserInfoSerial=Nmero de &Serie: +UserInfoNameRequired=Debe introducir un nombre. + +; *** "Select Destination Location" wizard page +WizardSelectDir=Seleccione la Carpeta de Destino +SelectDirDesc=Dnde debe instalarse [name]? +SelectDirLabel3=El programa instalar [name] en la siguiente carpeta. +SelectDirBrowseLabel=Para continuar, haga clic en Siguiente. Si desea seleccionar una carpeta diferente, haga clic en Examinar. +DiskSpaceGBLabel=Se requieren al menos [gb] GB de espacio libre en el disco. +DiskSpaceMBLabel=Se requieren al menos [mb] MB de espacio libre en el disco. +CannotInstallToNetworkDrive=El programa de instalacin no puede realizar la instalacin en una unidad de red. +CannotInstallToUNCPath=El programa de instalacin no puede realizar la instalacin en una ruta de acceso UNC. +InvalidPath=Debe introducir una ruta completa con la letra de la unidad; por ejemplo:%n%nC:\APP%n%no una ruta de acceso UNC de la siguiente forma:%n%n\\servidor\compartido +InvalidDrive=La unidad o ruta de acceso UNC que seleccion no existe o no es accesible. Por favor, seleccione otra. +DiskSpaceWarningTitle=Espacio Insuficiente en Disco +DiskSpaceWarning=La instalacin requiere al menos %1 KB de espacio libre, pero la unidad seleccionada solo cuenta con %2 KB disponibles.%n%nDesea continuar de todas formas? +DirNameTooLong=El nombre de la carpeta o la ruta son demasiado largos. +InvalidDirName=El nombre de la carpeta no es vlido. +BadDirName32=Los nombres de carpetas no pueden incluir los siguientes caracteres:%n%n%1 +DirExistsTitle=La Carpeta Ya Existe +DirExists=La carpeta:%n%n%1%n%nya existe. Desea realizar la instalacin en esa carpeta de todas formas? +DirDoesntExistTitle=La Carpeta No Existe +DirDoesntExist=La carpeta:%n%n%1%n%nno existe. Desea crear esa carpeta? + +; *** "Select Components" wizard page +WizardSelectComponents=Seleccione los Componentes +SelectComponentsDesc=Qu componentes deben instalarse? +SelectComponentsLabel2=Seleccione los componentes que desea instalar y desmarque los componentes que no desea instalar. Haga clic en Siguiente cuando est listo para continuar. +FullInstallation=Instalacin Completa +; if possible don't translate 'Compact' as 'Minimal' (I mean 'Minimal' in your language) +CompactInstallation=Instalacin Compacta +CustomInstallation=Instalacin Personalizada +NoUninstallWarningTitle=Componentes Encontrados +NoUninstallWarning=El programa de instalacin ha detectado que los siguientes componentes ya estn instalados en su sistema:%n%n%1%n%nDesmarcar estos componentes no los desinstalar.%n%nDesea continuar de todos modos? +ComponentSize1=%1 KB +ComponentSize2=%1 MB +ComponentsDiskSpaceGBLabel=La seleccin actual requiere al menos [gb] GB de espacio en disco. +ComponentsDiskSpaceMBLabel=La seleccin actual requiere al menos [mb] MB de espacio en disco. + +; *** "Select Additional Tasks" wizard page +WizardSelectTasks=Seleccione las Tareas Adicionales +SelectTasksDesc=Qu tareas adicionales deben realizarse? +SelectTasksLabel2=Seleccione las tareas adicionales que desea que se realicen durante la instalacin de [name] y haga clic en Siguiente. + +; *** "Select Start Menu Folder" wizard page +WizardSelectProgramGroup=Seleccione la Carpeta del Men Inicio +SelectStartMenuFolderDesc=Dnde deben colocarse los accesos directos del programa? +SelectStartMenuFolderLabel3=El programa de instalacin crear los accesos directos del programa en la siguiente carpeta del Men Inicio. +SelectStartMenuFolderBrowseLabel=Para continuar, haga clic en Siguiente. Si desea seleccionar una carpeta distinta, haga clic en Examinar. +MustEnterGroupName=Debe proporcionar un nombre de carpeta. +GroupNameTooLong=El nombre de la carpeta o la ruta son demasiado largos. +InvalidGroupName=El nombre de la carpeta no es vlido. +BadGroupName=El nombre de la carpeta no puede incluir ninguno de los siguientes caracteres:%n%n%1 +NoProgramGroupCheck2=&No crear una carpeta en el Men Inicio + +; *** "Ready to Install" wizard page +WizardReady=Listo para Instalar +ReadyLabel1=Ahora el programa est listo para iniciar la instalacin de [name] en su sistema. +ReadyLabel2a=Haga clic en Instalar para continuar con el proceso o haga clic en Atrs si desea revisar o cambiar alguna configuracin. +ReadyLabel2b=Haga clic en Instalar para continuar con el proceso. +ReadyMemoUserInfo=Informacin del usuario: +ReadyMemoDir=Carpeta de Destino: +ReadyMemoType=Tipo de Instalacin: +ReadyMemoComponents=Componentes Seleccionados: +ReadyMemoGroup=Carpeta del Men Inicio: +ReadyMemoTasks=Tareas Adicionales: + +; *** TDownloadWizardPage wizard page and DownloadTemporaryFile +DownloadingLabel=Descargando archivos adicionales... +ButtonStopDownload=&Detener descarga +StopDownload=Est seguiro que desea detener la descarga? +ErrorDownloadAborted=Descarga cancelada +ErrorDownloadFailed=Fall descarga: %1 %2 +ErrorDownloadSizeFailed=Fall obtencin de tamao: %1 %2 +ErrorFileHash1=Fall hash del archivo: %1 +ErrorFileHash2=Hash de archivo no vlido: esperado %1, encontrado %2 +ErrorProgress=Progreso no vlido: %1 de %2 +ErrorFileSize=Tamao de archivo no vlido: esperado %1, encontrado %2 + +; *** "Preparing to Install" wizard page +WizardPreparing=Preparndose para Instalar +PreparingDesc=El programa de instalacin est preparndose para instalar [name] en su sistema. +PreviousInstallNotCompleted=La instalacin/desinstalacin previa de un programa no se complet. Deber reiniciar el sistema para completar esa instalacin.%n%nUna vez reiniciado el sistema, ejecute el programa de instalacin nuevamente para completar la instalacin de [name]. +CannotContinue=El programa de instalacin no puede continuar. Por favor, presione Cancelar para salir. +ApplicationsFound=Las siguientes aplicaciones estn usando archivos que necesitan ser actualizados por el programa de instalacin. Se recomienda que permita al programa de instalacin cerrar automticamente estas aplicaciones. +ApplicationsFound2=Las siguientes aplicaciones estn usando archivos que necesitan ser actualizados por el programa de instalacin. Se recomienda que permita al programa de instalacin cerrar automticamente estas aplicaciones. Al completarse la instalacin, el programa de instalacin intentar reiniciar las aplicaciones. +CloseApplications=&Cerrar automticamente las aplicaciones +DontCloseApplications=&No cerrar las aplicaciones +ErrorCloseApplications=El programa de instalacin no pudo cerrar de forma automtica todas las aplicaciones. Se recomienda que, antes de continuar, cierre todas las aplicaciones que utilicen archivos que necesitan ser actualizados por el programa de instalacin. +PrepareToInstallNeedsRestart=El programa de instalacin necesita reiniciar el sistema. Una vez que se haya reiniciado ejecute nuevamente el programa de instalacin para completar la instalacin de [name].%n%nDesea reiniciar el sistema ahora? + +; *** "Installing" wizard page +WizardInstalling=Instalando +InstallingLabel=Por favor, espere mientras se instala [name] en su sistema. + +; *** "Setup Completed" wizard page +FinishedHeadingLabel=Completando la instalacin de [name] +FinishedLabelNoIcons=El programa complet la instalacin de [name] en su sistema. +FinishedLabel=El programa complet la instalacin de [name] en su sistema. Puede ejecutar la aplicacin utilizando los accesos directos creados. +ClickFinish=Haga clic en Finalizar para salir del programa de instalacin. +FinishedRestartLabel=Para completar la instalacin de [name], su sistema debe reiniciarse. Desea reiniciarlo ahora? +FinishedRestartMessage=Para completar la instalacin de [name], su sistema debe reiniciarse.%n%nDesea reiniciarlo ahora? +ShowReadmeCheck=S, deseo ver el archivo LAME +YesRadio=&S, deseo reiniciar el sistema ahora +NoRadio=&No, reiniciar el sistema ms tarde +; used for example as 'Run MyProg.exe' +RunEntryExec=Ejecutar %1 +; used for example as 'View Readme.txt' +RunEntryShellExec=Ver %1 + +; *** "Setup Needs the Next Disk" stuff +ChangeDiskTitle=El Programa de Instalacin Necesita el Siguiente Disco +SelectDiskLabel2=Por favor, inserte el Disco %1 y haga clic en Aceptar.%n%nSi los archivos pueden ser hallados en una carpeta diferente a la indicada abajo, introduzca la ruta correcta o haga clic en Examinar. +PathLabel=&Ruta: +FileNotInDir2=El archivo "%1" no se ha podido hallar en "%2". Por favor, inserte el disco correcto o seleccione otra carpeta. +SelectDirectoryLabel=Por favor, especifique la ubicacin del siguiente disco. + +; *** Installation phase messages +SetupAborted=La instalacin no se ha completado.%n%nPor favor solucione el problema y ejecute nuevamente el programa de instalacin. +AbortRetryIgnoreSelectAction=Seleccione accin +AbortRetryIgnoreRetry=&Reintentar +AbortRetryIgnoreIgnore=&Ignorar el error y continuar +AbortRetryIgnoreCancel=Cancelar instalacin + +; *** Installation status messages +StatusClosingApplications=Cerrando aplicaciones... +StatusCreateDirs=Creando carpetas... +StatusExtractFiles=Extrayendo archivos... +StatusCreateIcons=Creando accesos directos... +StatusCreateIniEntries=Creando entradas INI... +StatusCreateRegistryEntries=Creando entradas de registro... +StatusRegisterFiles=Registrando archivos... +StatusSavingUninstall=Guardando informacin para desinstalar... +StatusRunProgram=Terminando la instalacin... +StatusRestartingApplications=Reiniciando aplicaciones... +StatusRollback=Deshaciendo cambios... + +; *** Misc. errors +ErrorInternal2=Error interno: %1 +ErrorFunctionFailedNoCode=%1 fall +ErrorFunctionFailed=%1 fall; cdigo %2 +ErrorFunctionFailedWithMessage=%1 fall; cdigo %2.%n%3 +ErrorExecutingProgram=Imposible ejecutar el archivo:%n%1 + +; *** Registry errors +ErrorRegOpenKey=Error al abrir la clave del registro:%n%1\%2 +ErrorRegCreateKey=Error al crear la clave del registro:%n%1\%2 +ErrorRegWriteKey=Error al escribir la clave del registro:%n%1\%2 + +; *** INI errors +ErrorIniEntry=Error al crear entrada INI en el archivo "%1". + +; *** File copying errors +FileAbortRetryIgnoreSkipNotRecommended=&Omitir este archivo (no recomendado) +FileAbortRetryIgnoreIgnoreNotRecommended=&Ignorar el error y continuar (no recomendado) +SourceIsCorrupted=El archivo de origen est daado +SourceDoesntExist=El archivo de origen "%1" no existe +ExistingFileReadOnly2=El archivo existente no puede ser reemplazado debido a que est marcado como solo-lectura. +ExistingFileReadOnlyRetry=&Elimine el atributo de solo-lectura y reintente +ExistingFileReadOnlyKeepExisting=&Mantener el archivo existente +ErrorReadingExistingDest=Ocurri un error mientras se intentaba leer el archivo: +FileExistsSelectAction=Seleccione accin +FileExists2=El archivo ya existe. +FileExistsOverwriteExisting=&Sobreescribir el archivo existente +FileExistsKeepExisting=&Mantener el archivo existente +FileExistsOverwriteOrKeepAll=&Hacer lo mismo para lo siguientes conflictos +ExistingFileNewerSelectAction=Seleccione accin +ExistingFileNewer2=El archivo existente es ms reciente que el que se est tratando de instalar. +ExistingFileNewerOverwriteExisting=&Sobreescribir el archivo existente +ExistingFileNewerKeepExisting=&Mantener el archivo existente (recomendado) +ExistingFileNewerOverwriteOrKeepAll=&Hacer lo mismo para lo siguientes conflictos +ErrorChangingAttr=Ocurri un error al intentar cambiar los atributos del archivo: +ErrorCreatingTemp=Ocurri un error al intentar crear un archivo en la carpeta de destino: +ErrorReadingSource=Ocurri un error al intentar leer el archivo de origen: +ErrorCopying=Ocurri un error al intentar copiar el archivo: +ErrorReplacingExistingFile=Ocurri un error al intentar reemplazar el archivo existente: +ErrorRestartReplace=Fall reintento de reemplazar: +ErrorRenamingTemp=Ocurri un error al intentar renombrar un archivo en la carpeta de destino: +ErrorRegisterServer=Imposible registrar el DLL/OCX: %1 +ErrorRegSvr32Failed=RegSvr32 fall con el cdigo de salida %1 +ErrorRegisterTypeLib=Imposible registrar la librera de tipos: %1 + +; *** Uninstall display name markings +; used for example as 'My Program (32-bit)' +UninstallDisplayNameMark=%1 (%2) +; used for example as 'My Program (32-bit, All users)' +UninstallDisplayNameMarks=%1 (%2, %3) +UninstallDisplayNameMark32Bit=32-bit +UninstallDisplayNameMark64Bit=64-bit +UninstallDisplayNameMarkAllUsers=Todos los usuarios +UninstallDisplayNameMarkCurrentUser=Usuario actual + +; *** Post-installation errors +ErrorOpeningReadme=Ocurri un error al intentar abrir el archivo LAME. +ErrorRestartingComputer=El programa de instalacin no pudo reiniciar el equipo. Por favor, hgalo manualmente. + +; *** Uninstaller messages +UninstallNotFound=El archivo "%1" no existe. Imposible desinstalar. +UninstallOpenError=El archivo "%1" no pudo ser abierto. Imposible desinstalar +UninstallUnsupportedVer=El archivo de registro para desinstalar "%1" est en un formato no reconocido por esta versin del desinstalador. Imposible desinstalar +UninstallUnknownEntry=Se encontr una entrada desconocida (%1) en el registro de desinstalacin +ConfirmUninstall=Est seguro que desea desinstalar completamente %1 y todos sus componentes? +UninstallOnlyOnWin64=Este programa solo puede ser desinstalado en Windows de 64-bits. +OnlyAdminCanUninstall=Este programa solo puede ser desinstalado por un usuario con privilegios administrativos. +UninstallStatusLabel=Por favor, espere mientras %1 es desinstalado de su sistema. +UninstalledAll=%1 se desinstal satisfactoriamente de su sistema. +UninstalledMost=La desinstalacin de %1 ha sido completada.%n%nAlgunos elementos no pudieron eliminarse, pero podr eliminarlos manualmente si lo desea. +UninstalledAndNeedsRestart=Para completar la desinstalacin de %1, su sistema debe reiniciarse.%n%nDesea reiniciarlo ahora? +UninstallDataCorrupted=El archivo "%1" est daado. No puede desinstalarse + +; *** Uninstallation phase messages +ConfirmDeleteSharedFileTitle=Eliminar Archivo Compartido? +ConfirmDeleteSharedFile2=El sistema indica que el siguiente archivo compartido no es utilizado por ningn otro programa. Desea eliminar este archivo compartido?%n%nSi elimina el archivo y hay programas que lo utilizan, esos programas podran dejar de funcionar correctamente. Si no est seguro, elija No. Dejar el archivo en su sistema no producir ningn dao. +SharedFileNameLabel=Archivo: +SharedFileLocationLabel=Ubicacin: +WizardUninstalling=Estado de la Desinstalacin +StatusUninstalling=Desinstalando %1... + +; *** Shutdown block reasons +ShutdownBlockReasonInstallingApp=Instalando %1. +ShutdownBlockReasonUninstallingApp=Desinstalando %1. + +; The custom messages below aren't used by Setup itself, but if you make +; use of them in your scripts, you'll want to translate them. + +[CustomMessages] + +NameAndVersion=%1 versin %2 +AdditionalIcons=Accesos directos adicionales: +CreateDesktopIcon=Crear un acceso directo en el &escritorio +CreateQuickLaunchIcon=Crear un acceso directo en &Inicio Rpido +ProgramOnTheWeb=%1 en la Web +UninstallProgram=Desinstalar %1 +LaunchProgram=Ejecutar %1 +AssocFileExtension=&Asociar %1 con la extensin de archivo %2 +AssocingFileExtension=Asociando %1 con la extensin de archivo %2... +AutoStartProgramGroupDescription=Inicio: +AutoStartProgram=Iniciar automticamente %1 +AddonHostProgramNotFound=%1 no pudo ser localizado en la carpeta seleccionada.%n%nDesea continuar de todas formas? diff --git a/src/AITool.Setup/INNO/Languages/Turkish.isl b/src/AITool.Setup/INNO/Languages/Turkish.isl new file mode 100644 index 00000000..932c705b --- /dev/null +++ b/src/AITool.Setup/INNO/Languages/Turkish.isl @@ -0,0 +1,384 @@ +; *** Inno Setup version 6.1.0+ Turkish messages *** +; Language "Turkce" Turkish Translate by "Ceviren" Kaya Zeren translator@zeron.net +; To download user-contributed translations of this file, go to: +; https://www.jrsoftware.org/files/istrans/ +; +; Note: When translating this text, do not add periods (.) to the end of +; messages that didn't have them already, because on those messages Inno +; Setup adds the periods automatically (appending a period would result in +; two periods being displayed). + +[LangOptions] +; The following three entries are very important. Be sure to read and +; understand the '[LangOptions] section' topic in the help file. +LanguageName=T<00FC>rk<00E7>e +LanguageID=$041f +LanguageCodePage=1254 +; If the language you are translating to requires special font faces or +; sizes, uncomment any of the following entries and change them accordingly. +;DialogFontName= +;DialogFontSize=8 +;WelcomeFontName=Verdana +;WelcomeFontSize=12 +;TitleFontName=Arial +;TitleFontSize=29 +;CopyrightFontName=Arial +;CopyrightFontSize=8 + +[Messages] + +; *** Uygulama balklar +SetupAppTitle=Kurulum Yardmcs +SetupWindowTitle=%1 - Kurulum Yardmcs +UninstallAppTitle=Kaldrma Yardmcs +UninstallAppFullTitle=%1 Kaldrma Yardmcs + +; *** eitli ortak metinler +InformationTitle=Bilgi +ConfirmTitle=Onay +ErrorTitle=Hata + +; *** Kurulum ykleyici iletileri +SetupLdrStartupMessage=%1 uygulamas kurulacak. Devam etmek istiyor musunuz? +LdrCannotCreateTemp=Geici dosya oluturulamadndan kurulum iptal edildi +LdrCannotExecTemp=Geici klasrdeki dosya altrlamadndan kurulum iptal edildi +HelpTextNote= + +; *** Balang hata iletileri +LastErrorMessage=%1.%n%nHata %2: %3 +SetupFileMissing=Kurulum klasrnde %1 dosyas eksik. Ltfen sorunu zn ya da uygulamann yeni bir kopyasyla yeniden deneyin. +SetupFileCorrupt=Kurulum dosyalar bozulmu. Ltfen uygulamann yeni bir kopyasyla yeniden kurmay deneyin. +SetupFileCorruptOrWrongVer=Kurulum dosyalar bozulmu ya da bu kurulum yardmcs srm ile uyumlu deil. Ltfen sorunu zn ya da uygulamann yeni bir kopyasyla yeniden kurmay deneyin. +InvalidParameter=Komut satrnda geersiz bir parametre yazlm:%n%n%1 +SetupAlreadyRunning=Kurulum yardmcs zaten alyor. +WindowsVersionNotSupported=Bu uygulama, bilgisayarnzda ykl olan Windows srm ile uyumlu deil. +WindowsServicePackRequired=Bu uygulama, %1 Hizmet Paketi %2 ve zerindeki srmler ile alr. +NotOnThisPlatform=Bu uygulama, %1 zerinde almaz. +OnlyOnThisPlatform=Bu uygulama, %1 zerinde altrlmaldr. +OnlyOnTheseArchitectures=Bu uygulama, yalnz u ilemci mimarileri iin tasarlanm Windows srmleriyle alr:%n%n%1 +WinVersionTooLowError=Bu uygulama iin %1 srm %2 ya da zeri gereklidir. +WinVersionTooHighError=Bu uygulama, '%1' srm '%2' ya da zerine kurulamaz. +AdminPrivilegesRequired=Bu uygulamay kurmak iin Ynetici olarak oturum alm olmas gereklidir. +PowerUserPrivilegesRequired=Bu uygulamay kurarken, Ynetici ya da Gl Kullanclar grubunun bir yesi olarak oturum alm olmas gereklidir. +SetupAppRunningError=Kurulum yardmcs %1 uygulamasnn almakta olduunu alglad.%n%nLtfen uygulamann alan tm kopyalarn kapatp, devam etmek iin Tamam, kurulum yardmcsndan kmak iin ptal zerine tklayn. +UninstallAppRunningError=Kaldrma yardmcs, %1 uygulamasnn almakta olduunu alglad.%n%nLtfen uygulamann alan tm kopyalarn kapatp, devam etmek iin Tamam ya da kaldrma yardmcsndan kmak iin ptal zerine tklayn. + +; *** Balang sorular +PrivilegesRequiredOverrideTitle=Kurulum Kipini Sein +PrivilegesRequiredOverrideInstruction=Kurulum kipini sein +PrivilegesRequiredOverrideText1=%1 tm kullanclar iin (ynetici izinleri gerekir) ya da yalnz sizin hesabnz iin kurulabilir. +PrivilegesRequiredOverrideText2=%1 yalnz sizin hesabnz iin ya da tm kullanclar iin (ynetici izinleri gerekir) kurulabilir. +PrivilegesRequiredOverrideAllUsers=&Tm kullanclar iin kurulsun +PrivilegesRequiredOverrideAllUsersRecommended=&Tm kullanclar iin kurulsun (nerilir) +PrivilegesRequiredOverrideCurrentUser=&Yalnz benim kullancm iin kurulsun +PrivilegesRequiredOverrideCurrentUserRecommended=&Yalnz benim kullancm iin kurulsun (nerilir) + +; *** eitli hata metinleri +ErrorCreatingDir=Kurulum yardmcs "%1" klasrn oluturamad. +ErrorTooManyFilesInDir="%1" klasr iinde ok sayda dosya olduundan bir dosya oluturulamad + +; *** Ortak kurulum iletileri +ExitSetupTitle=Kurulum Yardmcsndan k +ExitSetupMessage=Kurulum tamamlanmad. imdi karsanz, uygulama kurulmayacak.%n%nKurulumu tamamlamak iin istediiniz zaman kurulum yardmcsn yeniden altrabilirsiniz.%n%nKurulum yardmcsndan klsn m? +AboutSetupMenuItem=Kurulum H&akknda... +AboutSetupTitle=Kurulum Hakknda +AboutSetupMessage=%1 %2 srm%n%3%n%n%1 ana sayfa:%n%4 +AboutSetupNote= +TranslatorNote= + +; *** Dmeler +ButtonBack=< &nceki +ButtonNext=&Sonraki > +ButtonInstall=&Kur +ButtonOK=Tamam +ButtonCancel=ptal +ButtonYes=E&vet +ButtonYesToAll=&Tmne Evet +ButtonNo=&Hayr +ButtonNoToAll=Tmne Ha&yr +ButtonFinish=&Bitti +ButtonBrowse=&Gzat... +ButtonWizardBrowse=Gza&t... +ButtonNewFolder=Ye&ni Klasr Olutur + +; *** "Kurulum Dilini Sein" sayfas iletileri +SelectLanguageTitle=Kurulum Yardmcs Dilini Sein +SelectLanguageLabel=Kurulum sresince kullanlacak dili sein. + +; *** Ortak metinler +ClickNext=Devam etmek iin Sonraki, kmak iin ptal zerine tklayn. +BeveledLabel= +BrowseDialogTitle=Klasre Gzat +BrowseDialogLabel=Aadaki listeden bir klasr seip, Tamam zerine tklayn. +NewFolderName=Yeni Klasr + +; *** "Ho geldiniz" sayfas +WelcomeLabel1=[name] Kurulum Yardmcsna Hogeldiniz. +WelcomeLabel2=Bilgisayarnza [name/ver] uygulamas kurulacak.%n%nDevam etmeden nce alan dier tm uygulamalar kapatmanz nerilir. + +; *** "Parola" sayfas +WizardPassword=Parola +PasswordLabel1=Bu kurulum parola korumaldr. +PasswordLabel3=Ltfen parolay yazn ve devam etmek iin Sonraki zerine tklayn. Parolalar byk kk harflere duyarldr. +PasswordEditLabel=&Parola: +IncorrectPassword=Yazdnz parola doru deil. Ltfen yeniden deneyin. + +; *** "Lisans Anlamas" sayfas +WizardLicense=Lisans Anlamas +LicenseLabel=Ltfen devam etmeden nce aadaki nemli bilgileri okuyun. +LicenseLabel3=Ltfen Aadaki Lisans Anlamasn okuyun. Kuruluma devam edebilmek iin bu anlamay kabul etmelisiniz. +LicenseAccepted=Anlamay kabul &ediyorum. +LicenseNotAccepted=Anlamay kabul et&miyorum. + +; *** "Bilgiler" sayfas +WizardInfoBefore=Bilgiler +InfoBeforeLabel=Ltfen devam etmeden nce aadaki nemli bilgileri okuyun. +InfoBeforeClickLabel=Kuruluma devam etmeye hazr olduunuzda Sonraki zerine tklayn. +WizardInfoAfter=Bilgiler +InfoAfterLabel=Ltfen devam etmeden nce aadaki nemli bilgileri okuyun. +InfoAfterClickLabel=Kuruluma devam etmeye hazr olduunuzda Sonraki zerine tklayn. + +; *** "Kullanc Bilgileri" sayfas +WizardUserInfo=Kullanc Bilgileri +UserInfoDesc=Ltfen bilgilerinizi yazn. +UserInfoName=K&ullanc Ad: +UserInfoOrg=Ku&rum: +UserInfoSerial=&Seri Numaras: +UserInfoNameRequired=Bir ad yazmalsnz. + +; *** "Hedef Konumunu Sein" sayfas +WizardSelectDir=Hedef Konumunu Sein +SelectDirDesc=[name] nereye kurulsun? +SelectDirLabel3=[name] uygulamas u klasre kurulacak. +SelectDirBrowseLabel=Devam etmek icin Sonraki zerine tklayn. Farkl bir klasr semek iin Gzat zerine tklayn. +DiskSpaceGBLabel=En az [gb] GB bo disk alan gereklidir. +DiskSpaceMBLabel=En az [mb] MB bo disk alan gereklidir. +CannotInstallToNetworkDrive=Uygulama bir a srcs zerine kurulamaz. +CannotInstallToUNCPath=Uygulama bir UNC yolu zerine (\\yol gibi) kurulamaz. +InvalidPath=Src ad ile tam yolu yazmalsnz; rnein: %n%nC:\APP%n%n ya da u ekilde bir UNC yolu:%n%n\\sunucu\paylam +InvalidDrive=Src ya da UNC paylam yok ya da eriilemiyor. Ltfen baka bir tane sein. +DiskSpaceWarningTitle=Yeterli Bo Disk Alan Yok +DiskSpaceWarning=Kurulum iin %1 KB bo alan gerekli, ancak seilmi srcde yalnz %2 KB bo alan var.%n%nGene de devam etmek istiyor musunuz? +DirNameTooLong=Klasr ad ya da yol ok uzun. +InvalidDirName=Klasr ad geersiz. +BadDirName32=Klasr adlarnda u karakterler bulunamaz:%n%n%1 +DirExistsTitle=Klasr Zaten Var" +DirExists=Klasr:%n%n%1%n%zaten var. Kurulum iin bu klasr kullanmak ister misiniz? +DirDoesntExistTitle=Klasr Bulunamad +DirDoesntExist=Klasr:%n%n%1%n%nbulunamad.Klasrn oluturmasn ister misiniz? + +; *** "Bileenleri Sein" sayfas +WizardSelectComponents=Bileenleri Sein +SelectComponentsDesc=Hangi bileenler kurulacak? +SelectComponentsLabel2=Kurmak istediiniz bileenleri sein; kurmak istemediiniz bileenlerin iaretini kaldrn. Devam etmeye hazr olduunuzda Sonraki zerine tklayn. +FullInstallation=Tam Kurulum +; Mmknse 'Compact' ifadesini kendi dilinizde 'Minimal' anlamnda evirmeyin +CompactInstallation=Normal kurulum +CustomInstallation=zel kurulum +NoUninstallWarningTitle=Bileenler Zaten Var +NoUninstallWarning=u bileenlerin bilgisayarnzda zaten kurulu olduu algland:%n%n%1%n%n Bu bileenlerin iaretlerinin kaldrlmas bileenleri kaldrmaz.%n%nGene de devam etmek istiyor musunuz? +ComponentSize1=%1 KB +ComponentSize2=%1 MB +ComponentsDiskSpaceGBLabel=Seili bileenler iin diskte en az [gb] GB bo alan bulunmas gerekli. +ComponentsDiskSpaceMBLabel=Seili bileenler iin diskte en az [mb] MB bo alan bulunmas gerekli. + +; *** "Ek lemleri Sein" sayfas +WizardSelectTasks=Ek lemleri Sein +SelectTasksDesc=Baka hangi ilemler yaplsn? +SelectTasksLabel2=[name] kurulumu srasnda yaplmasn istediiniz ek ileri sein ve Sonraki zerine tklayn. + +; *** "Balat Mens Klasrn Sein" sayfas +WizardSelectProgramGroup=Balat Mens Klasrn Sein +SelectStartMenuFolderDesc=Uygulamann ksayollar nereye eklensin? +SelectStartMenuFolderLabel3=Kurulum yardmcs uygulama ksayollarn aadaki Balat Mens klasrne ekleyecek. +SelectStartMenuFolderBrowseLabel=Devam etmek iin Sonraki zerine tklayn. Farkl bir klasr semek iin Gzat zerine tklayn. +MustEnterGroupName=Bir klasr ad yazmalsnz. +GroupNameTooLong=Klasr ad ya da yol ok uzun. +InvalidGroupName=Klasr ad geersiz. +BadGroupName=Klasr adnda u karakterler bulunamaz:%n%n%1 +NoProgramGroupCheck2=Balat Mens klasr &oluturulmasn + +; *** "Kurulmaya Hazr" sayfas +WizardReady=Kurulmaya Hazr +ReadyLabel1=[name] bilgisayarnza kurulmaya hazr. +ReadyLabel2a=Kuruluma devam etmek iin Sonraki zerine, ayarlar gzden geirip deitirmek iin nceki zerine tklayn. +ReadyLabel2b=Kuruluma devam etmek iin Sonraki zerine tklayn. +ReadyMemoUserInfo=Kullanc bilgileri: +ReadyMemoDir=Hedef konumu: +ReadyMemoType=Kurulum tr: +ReadyMemoComponents=Seilmi bileenler: +ReadyMemoGroup=Balat Mens klasr: +ReadyMemoTasks=Ek ilemler: + +; *** TDownloadWizardPage wizard page and DownloadTemporaryFile +DownloadingLabel=Ek dosyalar indiriliyor... +ButtonStopDownload=ndirmeyi &durdur +StopDownload=ndirmeyi durdurmak istediinize emin misiniz? +ErrorDownloadAborted=ndirme durduruldu +ErrorDownloadFailed=ndirilemedi: %1 %2 +ErrorDownloadSizeFailed=Boyut alnamad: %1 %2 +ErrorFileHash1=Dosya karmas dorulanamad: %1 +ErrorFileHash2=Dosya karmas geersiz: %1 olmas gerekirken %2 +ErrorProgress=Adm geersiz: %1 / %2 +ErrorFileSize=Dosya boyutu geersiz: %1 olmas gerekirken %2 + +; *** "Kuruluma Hazrlanlyor" sayfas +WizardPreparing=Kuruluma Hazrlanlyor +PreparingDesc=[name] bilgisayarnza kurulmaya hazrlanyor. +PreviousInstallNotCompleted=nceki uygulama kurulumu ya da kaldrlmas tamamlanmam. Bu kurulumun tamamlanmas iin bilgisayarnz yeniden balatmalsnz.%n%nBilgisayarnz yeniden balattktan sonra ilemi tamamlamak iin [name] kurulum yardmcsn yeniden altrn. +CannotContinue=Kuruluma devam edilemiyor. kmak iin ptal zerine tklayn. +ApplicationsFound=Kurulum yardmcs tarafndan gncellenmesi gereken dosyalar, u uygulamalar tarafndan kullanyor. Kurulum yardmcsnn bu uygulamalar otomatik olarak kapatmasna izin vermeniz nerilir. +ApplicationsFound2=Kurulum yardmcs tarafndan gncellenmesi gereken dosyalar, u uygulamalar tarafndan kullanyor. Kurulum yardmcsnn bu uygulamalar otomatik olarak kapatmasna izin vermeniz nerilir. Kurulum tamamlandktan sonra, uygulamalar yeniden balatlmaya allacak. +CloseApplications=&Uygulamalar kapatlsn +DontCloseApplications=Uygulamalar &kapatlmasn +ErrorCloseApplications=Kurulum yardmcs uygulamalar kapatamad. Kurulum yardmcs tarafndan gncellenmesi gereken dosyalar kullanan uygulamalar el ile kapatmanz nerilir. +PrepareToInstallNeedsRestart=Kurulum iin bilgisayarn yeniden balatlmas gerekiyor. Bilgisayar yeniden balattktan sonra [name] kurulumunu tamamlamak iin kurulum yardmcsn yeniden altrn.%n%nBilgisayar imdi yeniden balatmak ister misiniz? + +; *** "Kuruluyor" sayfas +WizardInstalling=Kuruluyor +InstallingLabel=Ltfen [name] bilgisayarnza kurulurken bekleyin. + +; *** "Kurulum Tamamland" sayfas +FinishedHeadingLabel=[name] kurulum yardmcs tamamlanyor +FinishedLabelNoIcons=Bilgisayarnza [name] kurulumu tamamland. +FinishedLabel=Bilgisayarnza [name] kurulumu tamamland. Simgeleri yklemeyi setiyseniz, simgelere tklayarak uygulamay balatabilirsiniz. +ClickFinish=Kurulum yardmcsndan kmak iin Bitti zerine tklayn. +FinishedRestartLabel=[name] kurulumunun tamamlanmas iin, bilgisayarnz yeniden balatlmal. imdi yeniden balatmak ister misiniz? +FinishedRestartMessage=[name] kurulumunun tamamlanmas iin, bilgisayarnz yeniden balatlmal.%n%nimdi yeniden balatmak ister misiniz? +ShowReadmeCheck=Evet README dosyas grntlensin +YesRadio=&Evet, bilgisayar imdi yeniden balatlsn +NoRadio=&Hayr, bilgisayar daha sonra yeniden balatacam +; used for example as 'Run MyProg.exe' +RunEntryExec=%1 altrlsn +; used for example as 'View Readme.txt' +RunEntryShellExec=%1 grntlensin + +; *** "Kurulum iin Sradaki Disk Gerekli" iletileri +ChangeDiskTitle=Kurulum Yardmcs Sradaki Diske Gerek Duyuyor +SelectDiskLabel2=Ltfen %1 numaral diski takp Tamam zerine tklayn.%n%nDiskteki dosyalar aadakinden farkl bir klasrde bulunuyorsa, doru yolu yazn ya da Gzat zerine tklayarak doru klasr sein. +PathLabel=&Yol: +FileNotInDir2="%1" dosyas "%2" iinde bulunamad. Ltfen doru diski takn ya da baka bir klasr sein. +SelectDirectoryLabel=Ltfen sonraki diskin konumunu belirtin. + +; *** Kurulum aamas iletileri +SetupAborted=Kurulum tamamlanamad.%n%nLtfen sorunu dzelterek kurulum yardmcsn yeniden altrn. +AbortRetryIgnoreSelectAction=Yaplacak ilemi sein +AbortRetryIgnoreRetry=&Yeniden denensin +AbortRetryIgnoreIgnore=&Sorun yok saylp devam edilsin +AbortRetryIgnoreCancel=Kurulum iptal edilsin + +; *** Kurulum durumu iletileri +StatusClosingApplications=Uygulamalar kapatlyor... +StatusCreateDirs=Klasrler oluturuluyor... +StatusExtractFiles=Dosyalar ayklanyor... +StatusCreateIcons=Ksayollar oluturuluyor... +StatusCreateIniEntries=INI kaytlar oluturuluyor... +StatusCreateRegistryEntries=Kayt Defteri kaytlar oluturuluyor... +StatusRegisterFiles=Dosyalar kaydediliyor... +StatusSavingUninstall=Kaldrma bilgileri kaydediliyor... +StatusRunProgram=Kurulum tamamlanyor... +StatusRestartingApplications=Uygulamalar yeniden balatlyor... +StatusRollback=Deiiklikler geri alnyor... + +; *** eitli hata iletileri +ErrorInternal2= hata: %1 +ErrorFunctionFailedNoCode=%1 tamamlanamad. +ErrorFunctionFailed=%1 tamamlanamad; kod %2 +ErrorFunctionFailedWithMessage=%1 tamamlanamad; kod %2.%n%3 +ErrorExecutingProgram=u dosya yrtlemedi:%n%1 + +; *** Kayt defteri hatalar +ErrorRegOpenKey=Kayt defteri anahtar alrken bir sorun kt:%n%1%2 +ErrorRegCreateKey=Kayt defteri anahtar eklenirken bir sorun kt:%n%1%2 +ErrorRegWriteKey=Kayt defteri anahtar yazlrken bir sorun kt:%n%1%2 + +; *** INI hatalar +ErrorIniEntry="%1" dosyasna INI kayd eklenirken bir sorun kt. + +; *** Dosya kopyalama hatalar +FileAbortRetryIgnoreSkipNotRecommended=&Bu dosya atlansn (nerilmez) +FileAbortRetryIgnoreIgnoreNotRecommended=&Sorun yok saylp devam edilsin (nerilmez) +SourceIsCorrupted=Kaynak dosya bozulmu +SourceDoesntExist="%1" kaynak dosyas bulunamad +ExistingFileReadOnly2=Var olan dosya salt okunabilir olarak iaretlenmi olduundan zerine yazlamad. +ExistingFileReadOnlyRetry=&Salt okunur iareti kaldrlp yeniden denensin +ExistingFileReadOnlyKeepExisting=&Var olan dosya korunsun +ErrorReadingExistingDest=Var olan dosya okunmaya allrken bir sorun kt. +FileExistsSelectAction=Yaplacak ilemi sein +FileExists2=Dosya zaten var. +FileExistsOverwriteExisting=&Var olan dosyann zerine yazlsn +FileExistsKeepExisting=Var &olan dosya korunsun +FileExistsOverwriteOrKeepAll=&Sonraki akmalarda da bu ilem yaplsn +ExistingFileNewerSelectAction=Yaplacak ilemi sein +ExistingFileNewer2=Var olan dosya, kurulum yardmcs tarafndan yazlmaya allandan daha yeni. +ExistingFileNewerOverwriteExisting=&Var olan dosyann zerine yazlsn +ExistingFileNewerKeepExisting=Var &olan dosya korunsun (nerilir) +ExistingFileNewerOverwriteOrKeepAll=&Sonraki akmalarda bu ilem yaplsn +ErrorChangingAttr=Var olan dosyann znitelikleri deitirilirken bir sorun kt: +ErrorCreatingTemp=Hedef klasrde bir dosya oluturulurken bir sorun kt: +ErrorReadingSource=Kaynak dosya okunurken bir sorun kt: +ErrorCopying=Dosya kopyalanrken bir sorun kt: +ErrorReplacingExistingFile=Var olan dosya deitirilirken bir sorun kt: +ErrorRestartReplace=Yeniden balatmada zerine yazlamad: +ErrorRenamingTemp=Hedef klasrdeki bir dosyann ad deitirilirken sorun kt: +ErrorRegisterServer=DLL/OCX kayt edilemedi: %1 +ErrorRegSvr32Failed=RegSvr32 ilemi u kod ile tamamlanamad: %1 +ErrorRegisterTypeLib=Tr kitapl kayt defterine eklenemedi: %1 + +; *** Kaldrma srasnda grntlenecek ad iaretleri +; used for example as 'My Program (32-bit)' +UninstallDisplayNameMark=%1 (%2) +; used for example as 'My Program (32-bit, All users)' +UninstallDisplayNameMarks=%1 (%2, %3) +UninstallDisplayNameMark32Bit=32 bit +UninstallDisplayNameMark64Bit=64 bit +UninstallDisplayNameMarkAllUsers=Tm kullanclar +UninstallDisplayNameMarkCurrentUser=Geerli kullanc + +; *** Kurulum sonras hatalar +ErrorOpeningReadme=README dosyas alrken bir sorun kt. +ErrorRestartingComputer=Kurulum yardmcs bilgisayarnz yeniden balatamyor. Ltfen bilgisayarnz yeniden balatn. + +; *** Kaldrma yardmcs iletileri +UninstallNotFound="%1" dosyas bulunamad. Uygulama kaldrlamyor. +UninstallOpenError="%1" dosyas alamad. Uygulama kaldrlamyor. +UninstallUnsupportedVer="%1" uygulama kaldrma gnlk dosyasnn biimi, bu kaldrma yardmcs srm tarafndan anlalamad. Uygulama kaldrlamyor. +UninstallUnknownEntry=Kaldrma gnlnde bilinmeyen bir kayt (%1) bulundu. +ConfirmUninstall=%1 uygulamasn tm bileenleri ile birlikte tamamen kaldrmak istediinize emin misiniz? +UninstallOnlyOnWin64=Bu kurulum yalnz 64 bit Windows zerinden kaldrlabilir. +OnlyAdminCanUninstall=Bu kurulum yalnz ynetici haklarna sahip bir kullanc tarafndan kaldrlabilir. +UninstallStatusLabel=Ltfen %1 uygulamas bilgisayarnzdan kaldrlrken bekleyin. +UninstalledAll=%1 uygulamas bilgisayarnzdan kaldrld. +UninstalledMost=%1 uygulamas kaldrld.%n%nBaz bileenler kaldrlamad. Bunlar el ile silebilirsiniz. +UninstalledAndNeedsRestart=%1 kaldrma ileminin tamamlanmas iin bilgisayarnzn yeniden balatlmas gerekli.%n%nimdi yeniden balatmak ister misiniz? +UninstallDataCorrupted="%1" dosyas bozulmu. Kaldrlamyor. + +; *** Kaldrma aamas iletileri +ConfirmDeleteSharedFileTitle=Paylalan Dosya Silinsin mi? +ConfirmDeleteSharedFile2=Sisteme gre, paylalan u dosya baka bir uygulama tarafndan kullanlmyor ve kaldrlabilir. Bu paylalm dosyay silmek ister misiniz?%n%nBu dosya, baka herhangi bir uygulama tarafndan kullanlyor ise, silindiinde dier uygulama dzgn almayabilir. Emin deilseniz Hayr zerine tklayn. Dosyay sisteminizde brakmann bir zarar olmaz. +SharedFileNameLabel=Dosya ad: +SharedFileLocationLabel=Konum: +WizardUninstalling=Kaldrma Durumu +StatusUninstalling=%1 kaldrlyor... + +; *** Kapatmay engelleme nedenleri +ShutdownBlockReasonInstallingApp=%1 kuruluyor. +ShutdownBlockReasonUninstallingApp=%1 kaldrlyor. + +; The custom messages below aren't used by Setup itself, but if you make +; use of them in your scripts, you'll want to translate them. + +[CustomMessages] + +NameAndVersion=%1 %2 srm +AdditionalIcons=Ek simgeler: +CreateDesktopIcon=Masast simg&esi oluturulsun +CreateQuickLaunchIcon=Hzl Balat simgesi &oluturulsun +ProgramOnTheWeb=%1 Web Sitesi +UninstallProgram=%1 Uygulamasn Kaldr +LaunchProgram=%1 Uygulamasn altr +AssocFileExtension=%1 &uygulamas ile %2 dosya uzants ilikilendirilsin +AssocingFileExtension=%1 uygulamas ile %2 dosya uzants ilikilendiriliyor... +AutoStartProgramGroupDescription=Balang: +AutoStartProgram=%1 otomatik olarak balatlsn +AddonHostProgramNotFound=%1 setiiniz klasrde bulunamad.%n%nYine de devam etmek istiyor musunuz? \ No newline at end of file diff --git a/src/AITool.Setup/INNO/Languages/Ukrainian.isl b/src/AITool.Setup/INNO/Languages/Ukrainian.isl new file mode 100644 index 00000000..c12c6ebf --- /dev/null +++ b/src/AITool.Setup/INNO/Languages/Ukrainian.isl @@ -0,0 +1,385 @@ +; *** Inno Setup version 6.1.0+ Ukrainian messages *** +; Author: Dmytro Onyshchuk +; E-Mail: mrlols3@gmail.com +; Please report all spelling/grammar errors, and observations. +; Version 2020.08.04 + +; *** Inno Setup 6.1.0 *** +; : +; E-Mail: mrlols3@gmail.com +; , . +; 2020.08.04 + +[LangOptions] +; The following three entries are very important. Be sure to read and +; understand the '[LangOptions] section' topic in the help file. +LanguageName=<0423><043A><0440><0430><0457><043D><0441><044C><043A><0430> +LanguageID=$0422 +LanguageCodePage=1251 +; If the language you are translating to requires special font faces or +; sizes, uncomment any of the following entries and change them accordingly. +;DialogFontName= +;DialogFontSize=8 +;WelcomeFontName=Verdana +;WelcomeFontSize=12 +;TitleFontName=Arial +;TitleFontSize=29 +;CopyrightFontName=Arial +;CopyrightFontSize=8 + +[Messages] + +; *** +SetupAppTitle= +SetupWindowTitle= %1 +UninstallAppTitle= +UninstallAppFullTitle= %1 + +; *** Misc. common +InformationTitle= +ConfirmTitle=ϳ +ErrorTitle= + +; *** SetupLdr messages +SetupLdrStartupMessage= %1 ', ? +LdrCannotCreateTemp= . +LdrCannotExecTemp= . +HelpTextNote= + +; *** Startup error messages +LastErrorMessage=%1.%n%n %2: %3 +SetupFileMissing= %1 . , . +SetupFileCorrupt= . , . +SetupFileCorruptOrWrongVer= . , . +InvalidParameter= :%n%n%1 +SetupAlreadyRunning= . +WindowsVersionNotSupported= Windows, '. +WindowsServicePackRequired= %1 Service Pack %2 . +NotOnThisPlatform= %1. +OnlyOnThisPlatform= %1. +OnlyOnTheseArchitectures= ' Windows :%n%n%1 +WinVersionTooLowError= %1 %2 . +WinVersionTooHighError= %1 %2 . +AdminPrivilegesRequired= . +PowerUserPrivilegesRequired= . +SetupAppRunningError=, %1 .%n%n , ﳿ OK , . +UninstallAppRunningError=, %1 .%n%n , ﳿ OK , . + +; *** Startup questions +PrivilegesRequiredOverrideTitle= +PrivilegesRequiredOverrideInstruction= +PrivilegesRequiredOverrideText1=%1 ( ), . +PrivilegesRequiredOverrideText2=%1 , ( ). +PrivilegesRequiredOverrideAllUsers= & +PrivilegesRequiredOverrideAllUsersRecommended= & () +PrivilegesRequiredOverrideCurrentUser= +PrivilegesRequiredOverrideCurrentUserRecommended= & () + +; *** г +ErrorCreatingDir= "%1" +ErrorTooManyFilesInDir= "%1", + +; *** +ExitSetupTitle= +ExitSetupMessage= . , .%n%n .%n%n ? +AboutSetupMenuItem=& ... +AboutSetupTitle= +AboutSetupMessage=%1 %2%n%3%n%n%1 :%n%4 +AboutSetupNote= +TranslatorNote=Ukrainian translation by Dmytro Onyshchuk + +; *** +ButtonBack=< & +ButtonNext=& > +ButtonInstall=& +ButtonOK=OK +ButtonCancel= +ButtonYes=& +ButtonYesToAll= & +ButtonNo=&ͳ +ButtonNoToAll=& +ButtonFinish=& +ButtonBrowse=&... +ButtonWizardBrowse=&... +ButtonNewFolder=& + +; *** ij " " +SelectLanguageTitle= +SelectLanguageLabel= , . + +; *** +ClickNext= 볻, , . +BeveledLabel= +BrowseDialogTitle= +BrowseDialogLabel= ʻ. +NewFolderName= + +; *** "" +WelcomeLabel1= [name]. +WelcomeLabel2= [name/ver] .%n%n . + +; *** "" +WizardPassword= +PasswordLabel1= . +PasswordLabel3= , 볻, . . +PasswordEditLabel=&: +IncorrectPassword= . , . + +; *** "˳ " +WizardLicense=˳ +LicenseLabel= , . +LicenseLabel3= , . , . +LicenseAccepted= & +LicenseNotAccepted= & + +; *** "" +WizardInfoBefore= +InfoBeforeLabel= , , . +InfoBeforeClickLabel= , 볻. +WizardInfoAfter= +InfoAfterLabel= , , . +InfoAfterClickLabel= , 볻. + +; *** " " +WizardUserInfo= +UserInfoDesc= , . +UserInfoName=& : +UserInfoOrg=&: +UserInfoSerial=& : +UserInfoNameRequired= '. + +; *** " " +WizardSelectDir= +SelectDirDesc= [name]? +SelectDirLabel3= [name] . +SelectDirBrowseLabel= 볻, . , . +DiskSpaceGBLabel= [gb] . +DiskSpaceMBLabel= [mb] M . +CannotInstallToNetworkDrive= . +CannotInstallToUNCPath= . +InvalidPath= , :%n%nC:\APP%n%n UNC:%n%n\\\ +InvalidDrive= , . , . +DiskSpaceWarningTitle= +DiskSpaceWarning= %1 , %2 .%n%n ? +DirNameTooLong=' . +InvalidDirName= . +BadDirName32=' :%n%n%1 +DirExistsTitle= +DirExists=:%n%n%1%n%n . ? +DirDoesntExistTitle= +DirDoesntExist=:%n%n%1%n%n . ? + +; *** " " +WizardSelectComponents= +SelectComponentsDesc= ? +SelectComponentsLabel2= ; . 볻, . +FullInstallation= +; if possible don't translate 'Compact' as 'Minimal' (I mean 'Minimal' in your language) +CompactInstallation= +CustomInstallation= +NoUninstallWarningTitle= +NoUninstallWarning=, :%n%n%1%n%n³ .%n%n ? +ComponentSize1=%1 K +ComponentSize2=%1 M +ComponentsDiskSpaceGBLabel= [gb] . +ComponentsDiskSpaceMBLabel= [mb] M . + +; *** " " +WizardSelectTasks= +SelectTasksDesc= ? +SelectTasksLabel2= [name] , 볻. + +; *** " " +WizardSelectProgramGroup= +SelectStartMenuFolderDesc= ? +SelectStartMenuFolderLabel3= . +SelectStartMenuFolderBrowseLabel= 볻, . , . +MustEnterGroupName= ' . +GroupNameTooLong= . +InvalidGroupName= . +BadGroupName=' :%n%n%1 +NoProgramGroupCheck2=& + +; *** " " +WizardReady= +ReadyLabel1= [name] . +ReadyLabel2a= , , . +ReadyLabel2b= . +ReadyMemoUserInfo= : +ReadyMemoDir= : +ReadyMemoType= : +ReadyMemoComponents= : +ReadyMemoGroup= : +ReadyMemoTasks= : + +; *** TDownloadWizardPage wizard page and DownloadTemporaryFile +DownloadingLabel= ... +ButtonStopDownload=& +StopDownload= ? +ErrorDownloadAborted= +ErrorDownloadFailed= : %1 %2 +ErrorDownloadSizeFailed= : %1 %2 +ErrorFileHash1= : %1 +ErrorFileHash2= : %1, %2 +ErrorProgress= : %1 %2 +ErrorFileSize= : %1, %2 + +; *** "ϳ " +WizardPreparing=ϳ +PreparingDesc= [name] . +PreviousInstallNotCompleted= . .%n%nϳ , [name]. +CannotContinue= . , . +ApplicationsFound= , . . +ApplicationsFound2= , . . ϳ , . +CloseApplications=& +DontCloseApplications=& +ErrorCloseApplications= . , , , . +PrepareToInstallNeedsRestart= . ϳ , [name]%n%n ? + +; *** "" +WizardInstalling= +InstallingLabel= , , [name] '. + +; *** " " +FinishedHeadingLabel= [name] +FinishedLabelNoIcons= [name] . +FinishedLabel= [name] . . +ClickFinish= . +FinishedRestartLabel= [name] . ? +FinishedRestartMessage= [name] .%n%n ? +ShowReadmeCheck=, README +YesRadio=&, +NoRadio=&ͳ, +; used for example as 'Run MyProg.exe' +RunEntryExec=³ %1 +; used for example as 'View Readme.txt' +RunEntryShellExec= %1 + +; *** "Setup Needs the Next Disk" stuff +ChangeDiskTitle= +SelectDiskLabel2= , %1 OK.%n%n , , . +PathLabel=&: +FileNotInDir2= "%1" "%2". , . +SelectDirectoryLabel= , . + +; *** Installation phase messages +SetupAborted= .%n%n , . +AbortRetryIgnoreSelectAction= +AbortRetryIgnoreRetry=& +AbortRetryIgnoreIgnore=& +AbortRetryIgnoreCancel=³ + +; *** +StatusClosingApplications= ... +StatusCreateDirs= ... +StatusExtractFiles= ... +StatusCreateIcons= ... +StatusCreateIniEntries= INI ... +StatusCreateRegistryEntries= ... +StatusRegisterFiles= ... +StatusSavingUninstall= ... +StatusRunProgram= ... +StatusRestartingApplications= ... +StatusRollback= ... + +; *** г +ErrorInternal2= : %1 +ErrorFunctionFailedNoCode=%1 +ErrorFunctionFailed=%1 ; %2 +ErrorFunctionFailedWithMessage=%1 ; %2.%n%3 +ErrorExecutingProgram= :%n%1 + +; *** +ErrorRegOpenKey= :%n%1\%2 +ErrorRegCreateKey= :%n%1\%2 +ErrorRegWriteKey= :%n%1\%2 + +; *** INI +ErrorIniEntry= INI- "%1". + +; *** +FileAbortRetryIgnoreSkipNotRecommended=& ( ) +FileAbortRetryIgnoreIgnoreNotRecommended=& ( ) +SourceIsCorrupted= +SourceDoesntExist= "%1" +ExistingFileReadOnly2= , . +ExistingFileReadOnlyRetry=& " " +ExistingFileReadOnlyKeepExisting=& +ErrorReadingExistingDest= : +FileExistsSelectAction= +FileExists2= . +FileExistsOverwriteExisting=& +FileExistsKeepExisting=& +FileExistsOverwriteOrKeepAll=& +ExistingFileNewerSelectAction= +ExistingFileNewer2= , . +ExistingFileNewerOverwriteExisting=& +ExistingFileNewerKeepExisting=& () +ExistingFileNewerOverwriteOrKeepAll=& +ErrorChangingAttr= : +ErrorCreatingTemp= : +ErrorReadingSource= : +ErrorCopying= : +ErrorReplacingExistingFile= : +ErrorRestartReplace= RestartReplace: +ErrorRenamingTemp= : +ErrorRegisterServer= DLL/OCX: %1 +ErrorRegSvr32Failed= RegSvr32, %1 +ErrorRegisterTypeLib= : %1 + +; *** Uninstall display name markings +UninstallDisplayNameMark=%1 (%2) +UninstallDisplayNameMarks=%1 (%2, %3) +UninstallDisplayNameMark32Bit=32- +UninstallDisplayNameMark64Bit=64- +UninstallDisplayNameMarkAllUsers= +UninstallDisplayNameMarkCurrentUser= + +; *** Post-installation errors +ErrorOpeningReadme= README. +ErrorRestartingComputer= '. , . + +; *** +UninstallNotFound= "%1" , . +UninstallOpenError= "%1". +UninstallUnsupportedVer= "%1" . +UninstallUnknownEntry= (%1) +ConfirmUninstall= , %1 ? +UninstallOnlyOnWin64= 64- Windows. +OnlyAdminCanUninstall= . +UninstallStatusLabel= , , %1 '. +UninstalledAll=%1 '. +UninstalledMost= %1 .%n%n . . +UninstalledAndNeedsRestart= %1 .%n%n ? +UninstallDataCorrupted= "%1" . + +; *** Uninstallation phase messages +ConfirmDeleteSharedFileTitle= ? +ConfirmDeleteSharedFile2= , . ?%n%n , . , ͳ. . +SharedFileNameLabel=' : +SharedFileLocationLabel=: +WizardUninstalling= +StatusUninstalling= %1... + + +; *** +ShutdownBlockReasonInstallingApp= %1. +ShutdownBlockReasonUninstallingApp= %1. + +; The custom messages below aren't used by Setup itself, but if you make +; use of them in your scripts, you'll want to translate them. + +[CustomMessages] + +NameAndVersion=%1, %2 +AdditionalIcons= : +CreateDesktopIcon= & +CreateQuickLaunchIcon= & +ProgramOnTheWeb= %1 +UninstallProgram= %1 +LaunchProgram=³ %1 +AssocFileExtension=& %1 %2 +AssocingFileExtension= %1 %2... +AutoStartProgramGroupDescription=: +AutoStartProgram= %1 +AddonHostProgramNotFound=%1 %n%n ? diff --git a/src/AITool.Setup/INNO/Setup.e32 b/src/AITool.Setup/INNO/Setup.e32 new file mode 100644 index 00000000..d4103d6d Binary files /dev/null and b/src/AITool.Setup/INNO/Setup.e32 differ diff --git a/src/AITool.Setup/INNO/SetupClassicIcon.ico b/src/AITool.Setup/INNO/SetupClassicIcon.ico new file mode 100644 index 00000000..b274e4c6 Binary files /dev/null and b/src/AITool.Setup/INNO/SetupClassicIcon.ico differ diff --git a/src/AITool.Setup/INNO/SetupLdr.e32 b/src/AITool.Setup/INNO/SetupLdr.e32 new file mode 100644 index 00000000..bf3f7e5d Binary files /dev/null and b/src/AITool.Setup/INNO/SetupLdr.e32 differ diff --git a/src/AITool.Setup/INNO/WizClassicImage-IS.bmp b/src/AITool.Setup/INNO/WizClassicImage-IS.bmp new file mode 100644 index 00000000..cf844e09 Binary files /dev/null and b/src/AITool.Setup/INNO/WizClassicImage-IS.bmp differ diff --git a/src/AITool.Setup/INNO/WizClassicImage.bmp b/src/AITool.Setup/INNO/WizClassicImage.bmp new file mode 100644 index 00000000..cb05a063 Binary files /dev/null and b/src/AITool.Setup/INNO/WizClassicImage.bmp differ diff --git a/src/AITool.Setup/INNO/WizClassicSmallImage-IS.bmp b/src/AITool.Setup/INNO/WizClassicSmallImage-IS.bmp new file mode 100644 index 00000000..1e8e4979 Binary files /dev/null and b/src/AITool.Setup/INNO/WizClassicSmallImage-IS.bmp differ diff --git a/src/AITool.Setup/INNO/WizClassicSmallImage.bmp b/src/AITool.Setup/INNO/WizClassicSmallImage.bmp new file mode 100644 index 00000000..63f42104 Binary files /dev/null and b/src/AITool.Setup/INNO/WizClassicSmallImage.bmp differ diff --git a/src/AITool.Setup/INNO/WizModernImage-IS.bmp b/src/AITool.Setup/INNO/WizModernImage-IS.bmp new file mode 100644 index 00000000..cf844e09 Binary files /dev/null and b/src/AITool.Setup/INNO/WizModernImage-IS.bmp differ diff --git a/src/AITool.Setup/INNO/WizModernImage.bmp b/src/AITool.Setup/INNO/WizModernImage.bmp new file mode 100644 index 00000000..cb05a063 Binary files /dev/null and b/src/AITool.Setup/INNO/WizModernImage.bmp differ diff --git a/src/AITool.Setup/INNO/WizModernSmallImage-IS.bmp b/src/AITool.Setup/INNO/WizModernSmallImage-IS.bmp new file mode 100644 index 00000000..1e8e4979 Binary files /dev/null and b/src/AITool.Setup/INNO/WizModernSmallImage-IS.bmp differ diff --git a/src/AITool.Setup/INNO/WizModernSmallImage.bmp b/src/AITool.Setup/INNO/WizModernSmallImage.bmp new file mode 100644 index 00000000..63f42104 Binary files /dev/null and b/src/AITool.Setup/INNO/WizModernSmallImage.bmp differ diff --git a/src/AITool.Setup/INNO/isbunzip.dll b/src/AITool.Setup/INNO/isbunzip.dll new file mode 100644 index 00000000..814e8680 Binary files /dev/null and b/src/AITool.Setup/INNO/isbunzip.dll differ diff --git a/src/AITool.Setup/INNO/isbzip.dll b/src/AITool.Setup/INNO/isbzip.dll new file mode 100644 index 00000000..1afeefd5 Binary files /dev/null and b/src/AITool.Setup/INNO/isbzip.dll differ diff --git a/src/AITool.Setup/INNO/isfaq.url b/src/AITool.Setup/INNO/isfaq.url new file mode 100644 index 00000000..91054550 --- /dev/null +++ b/src/AITool.Setup/INNO/isfaq.url @@ -0,0 +1,2 @@ +[InternetShortcut] +URL=https://jrsoftware.org/isfaq.php diff --git a/src/AITool.Setup/INNO/islzma.dll b/src/AITool.Setup/INNO/islzma.dll new file mode 100644 index 00000000..81fd05ac Binary files /dev/null and b/src/AITool.Setup/INNO/islzma.dll differ diff --git a/src/AITool.Setup/INNO/islzma32.exe b/src/AITool.Setup/INNO/islzma32.exe new file mode 100644 index 00000000..7562645e Binary files /dev/null and b/src/AITool.Setup/INNO/islzma32.exe differ diff --git a/src/AITool.Setup/INNO/islzma64.exe b/src/AITool.Setup/INNO/islzma64.exe new file mode 100644 index 00000000..fd58a59e Binary files /dev/null and b/src/AITool.Setup/INNO/islzma64.exe differ diff --git a/src/AITool.Setup/INNO/isscint.dll b/src/AITool.Setup/INNO/isscint.dll new file mode 100644 index 00000000..5f8ef49b Binary files /dev/null and b/src/AITool.Setup/INNO/isscint.dll differ diff --git a/src/AITool.Setup/INNO/isunzlib.dll b/src/AITool.Setup/INNO/isunzlib.dll new file mode 100644 index 00000000..8c4ed510 Binary files /dev/null and b/src/AITool.Setup/INNO/isunzlib.dll differ diff --git a/src/AITool.Setup/INNO/iszlib.dll b/src/AITool.Setup/INNO/iszlib.dll new file mode 100644 index 00000000..b326e3a7 Binary files /dev/null and b/src/AITool.Setup/INNO/iszlib.dll differ diff --git a/src/AITool.Setup/INNO/license.txt b/src/AITool.Setup/INNO/license.txt new file mode 100644 index 00000000..5a6b8732 --- /dev/null +++ b/src/AITool.Setup/INNO/license.txt @@ -0,0 +1,32 @@ +Inno Setup License +================== + +Except where otherwise noted, all of the documentation and software included in the Inno Setup +package is copyrighted by Jordan Russell. + +Copyright (C) 1997-2023 Jordan Russell. All rights reserved. +Portions Copyright (C) 2000-2023 Martijn Laan. All rights reserved. + +This software is provided "as-is," without any express or implied warranty. In no event shall the +author be held liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, including commercial +applications, and to alter and redistribute it, provided that the following conditions are met: + +1. All redistributions of source code files must retain all copyright notices that are currently in + place, and this list of conditions without modification. + +2. All redistributions in binary form must retain all occurrences of the above copyright notice and + web site addresses that are currently in place (for example, in the About boxes). + +3. The origin of this software must not be misrepresented; you must not claim that you wrote the + original software. If you use this software to distribute a product, an acknowledgment in the + product documentation would be appreciated but is not required. + +4. Modified versions in source or binary form must be plainly marked as such, and must not be + misrepresented as being the original software. + + +Jordan Russell +jr-2020 AT jrsoftware.org +https://jrsoftware.org/ \ No newline at end of file diff --git a/src/AITool.Setup/INNO/module.dll b/src/AITool.Setup/INNO/module.dll new file mode 100644 index 00000000..4ed21af0 Binary files /dev/null and b/src/AITool.Setup/INNO/module.dll differ diff --git a/src/AITool.Setup/INNO/unins000.dat b/src/AITool.Setup/INNO/unins000.dat new file mode 100644 index 00000000..ca62e1ae Binary files /dev/null and b/src/AITool.Setup/INNO/unins000.dat differ diff --git a/src/AITool.Setup/INNO/unins000.exe b/src/AITool.Setup/INNO/unins000.exe new file mode 100644 index 00000000..17878993 Binary files /dev/null and b/src/AITool.Setup/INNO/unins000.exe differ diff --git a/src/AITool.Setup/INNO/unins000.msg b/src/AITool.Setup/INNO/unins000.msg new file mode 100644 index 00000000..2413f142 Binary files /dev/null and b/src/AITool.Setup/INNO/unins000.msg differ diff --git a/src/AITool.Setup/INNO/unins001.dat b/src/AITool.Setup/INNO/unins001.dat new file mode 100644 index 00000000..a233b1e6 Binary files /dev/null and b/src/AITool.Setup/INNO/unins001.dat differ diff --git a/src/AITool.Setup/INNO/unins001.exe b/src/AITool.Setup/INNO/unins001.exe new file mode 100644 index 00000000..7759094b Binary files /dev/null and b/src/AITool.Setup/INNO/unins001.exe differ diff --git a/src/AITool.Setup/INNO/whatsnew.htm b/src/AITool.Setup/INNO/whatsnew.htm new file mode 100644 index 00000000..5b2970c7 --- /dev/null +++ b/src/AITool.Setup/INNO/whatsnew.htm @@ -0,0 +1,109 @@ + + + +Inno Setup 6 Revision History + + + + + +
Inno Setup 6
Revision History
+ +

Copyright © 1997-2023 Jordan Russell. All rights reserved.
+Portions Copyright © 2000-2023 Martijn Laan. All rights reserved.
+For conditions of distribution and use, see LICENSE.TXT. +

+ +

Want to be notified by e-mail of new Inno Setup releases? Subscribe to the Inno Setup Mailing List!

+ +

6.2.2 (2023-02-15)

+
    +
  • Changes to further help protect against potential DLL preloading attacks, contributed by Johannes Schindelin from the Git for Windows team.
  • +
  • Pascal Scripting changes: Improved support for downloads using basic authentication, contributed by Christian Beck. +
      +
    • Added new AddEx function to the TDownloadWizardPage support class.
    • +
    • Added new SetDownloadCredentials support function.
    • +
    +
  • Added official Hungarian translation.
  • +
+ +

6.2.1 (2022-04-14)

+
    +
  • Changes to further help protect against potential DLL preloading attacks when running installers or uninstallers under the SYSTEM account, contributed by Johannes Schindelin from the Git for Windows team.
  • +
  • Fixed a cosmetic issue if the icon file specified by the [Setup] section directive SetupIconFile contains more than 13 icons. Thanks to Wilenty and Martin Prikryl for the initial investigation.
  • +
+ +

6.2.0 (2021-06-03)

+

Graphics modernized

+
    +
  • Updated all Compiler IDE's toolbar icons and the wizard images used by the Compiler IDE's New Script Wizard wizard.
  • +
  • Updated the default application icon used by Setup and Uninstall if [Setup] section directive SetupIconFile is not set. To use the old icon again set SetupIconFile to compiler:SetupClassicIcon.ico.
  • +
  • [Setup] section directives WizardImageFile and WizardSmallImageFile now default to a blank value which makes Setup use new built-in wizard images. To use the old wizard images again set WizardImageFile and WizardSmallImageFile to compiler:WizClassicImage.bmp and compiler:WizClassicSmallImage.bmp respectively.
  • +
  • Updated Uninstall's default small wizard image if [Setup] section directive SetupIconFile is not set. Before it would use Setup's default application icon in this case.
  • +
  • Updated the folder, group, and stop icons used by Setup's Select Destination Location, Select Start Menu Folder, and Preparing to Install wizard pages.
  • +
  • Updated the disk icon used by Setup's Setup Needs the Next Disk form.
  • +
  • Pascal Scripting change: Added new InitializeBitmapImageFromIcon support function.
  • +
+

All these icon and images updates include the automatic use of higher quality versions (which were not available before) on higher DPI settings. This includes new automatic use of higher quality icons for the icon on Setup's Select Setup Language form and Uninstall's small wizard image if SetupIconFile is set.

+

Example screenshots:

+ +

Comparison screenshots of the *previous* version:

+ +

Other changes

+
    +
  • Links displayed by [Setup] section directives LicenseFile, InfoBeforeFile and InfoAfterFile are now executed as the original user if possible when clicked.
  • +
  • Added new [Setup] section directives MissingMessagesWarning and NotRecognizedMessagesWarning to disable warnings about messages missing or not recognized for a language.
  • +
  • /LOG: Now logs more uninstaller actions.
  • +
  • The {localappdata} constant can now correctly trigger a used user areas warning.
  • +
  • Compiler IDE change: Fix: Autocomplete support for event functions listed some procedures as functions.
  • +
  • Pascal Scripting changes: +
      +
    • Added new CreateOutputMarqueeProgressPage support function to show marquee progress to the user. See the AllPagesExample.iss example script for an example.
    • +
    • Added new ItemFontStyle and SubItemFontStyle properties to the TNewCheckListBox support class. See the CodeClasses.iss example script for an example.
    • +
    • Added new IsMsiProductInstalled and StrToVersion support functions.
    • +
    • Added new AbortedByUser property to the TDownloadWizardPage support class.
    • +
    • Fix: CreateDownloadPage's progress bar now supports files larger than 2 GB.
    • +
    • Support functions ParamCount and ParamStr now exclude undocumented internal parameters used by Setup and Uninstall.
    • +
    • The built-in download support now allows the download of files for which the server does not specify the file size and its hash checking is no longer case sensitive.
    • +
    +
  • +
  • ISPP change: Added new StrToVersion support function.
  • +
  • Added official Bulgarian translation.
  • +
  • Various documentation improvements.
  • +
  • Minor tweaks.
  • +
+

Inno Setup FAQ updated

+
    +
  • The Inno Setup FAQ has been updated with updated versions of articles taken from the Inno Setup Knowledge Base which is now hidden from the website.
  • +
  • The content of the FAQ is now available on GitHub where you can suggest new entries or other improvements using the Edit button.
  • +
+

QuickStart Pack removed

+
    +
  • The QuickStart Pack installer has been removed because of a lack of added value.
  • +
  • The standard Inno Setup installer now offers to download encryption support if it's missing, like the QuickStart Pack installer did before. If you used the QuickStart Pack installer before, you can use the standard installer to update your installation.
  • +
+ +

Contributions via GitHub: Thanks to Sergii Leonov and Dom Gries for their contributions.

+ +

Inno Setup 6.1 Revision History

+ + + diff --git a/src/AITool.Setup/Script.iss b/src/AITool.Setup/Script.iss new file mode 100644 index 00000000..5ab10452 --- /dev/null +++ b/src/AITool.Setup/Script.iss @@ -0,0 +1,146 @@ +; ####################################################################################### +; # This Inno Setup script was generated by Visual & Installer for MS Visual Studio # +; # Visual & Installer (c) 2012 - 2021 unSigned, s. r. o. All Rights Reserved. # +; # Visit http://www.visual-installer.com/ for more details. # +; ####################################################################################### + +; This script is perhaps one of the simplest Inno Setup installer you can make. +; All of the optional settings are left to their default settings. + +; See the Inno Setup documentation at http://www.jrsoftware.org/ for details on creating script files! + +;-------------------------------- +; Get the App Version from Main Program +; This Is Full App Version Major.Minor.Build.Revision +; Store First 3 Parts of Version in ShortAppVersion to be used for SBS Assembly Installation Major.Minor.Build +#dim Version[4] +#expr ParseVersion("..\UI\bin\Debug\net8.0-windows10.0.19041.0\AITOOL.exe", Version[0], Version[1], Version[2], Version[3]) +#define AppVersion Str(Version[0]) + "." + Str(Version[1]) + "." + Str(Version[2]) + "." + Str(Version[3]) +#define ShortAppVersion Str(Version[0]) + "." + Str(Version[1]) + "." + Str(Version[2]) +#define ShorterAppVersion Str(Version[0]) + "." + Str(Version[1]) +[Setup] +AppName=AITOOL +AppVerName=AITOOL {#ShorterAppVersion} +AppPublisher=VorlonCD +AppVersion={#ShorterAppVersion} +VersionInfoVersion={#ShortAppVersion} +DefaultDirName={code:GetInstallationPath} +UsePreviousAppDir=yes +DefaultGroupName=AITOOL +UninstallDisplayIcon={app}\AITool.exe +SetupIconFile=logo.ico +Compression=lzma2 +SolidCompression=yes +OutputDir=..\UI\Installer +OutputBaseFilename=AIToolSetup.{#ShortAppVersion} +PrivilegesRequired=admin +DirExistsWarning=no +DisableWelcomePage=yes +DisableDirPage=no +WizardStyle=modern +Uninstallable=WizardIsComponentSelected('full') +[Types] +Name: "full"; Description: "Full Installation" +Name: "portable"; Description: "Just Extract/Update Files" + +[Components] +Name: "full"; Description: "full"; Types: full +Name: "portable"; Description: "Portable Installation"; Types: portable + +[Tasks] +Name: "desktopicon"; Description: "Create a Desktop shortcut"; Components: full; Flags: unchecked +Name: "startmenu"; Description: "Create a Start Menu entry"; Components: full +[Run] +Filename: "{app}\AITOOL.exe"; Flags: nowait postinstall skipifsilent + +[Files] +//Source: "Script.iss"; DestDir: "{app}" +Source: "..\UI\bin\Debug\net8.0-windows10.0.19041.0\*"; DestDir: "{app}";Flags: recursesubdirs ignoreversion;Excludes: "AIToolSetup*,_Settings" +[Icons] +Name: "{group}\AITOOL"; Filename: "{app}\AITOOL.exe" ; Components: full; Tasks: startmenu +Name: "{userdesktop}\AITOOL"; Filename: "{app}\AITOOL.exe" ; Components: full; Tasks: desktopicon + +[Code] + +// Place your code here... + +function GetInstallationPath(Param: string): string; +var + Value: string; +begin + if RegQueryStringValue( + HKEY_CURRENT_USER, 'SOFTWARE\AITool\AITool\{#ShorterAppVersion}', + 'LastRunPath', Value) then + begin + Result := Value; + end + else + begin + Result := 'C:\AITOOL' + end; +end; + +function ShouldSkipPage(PageID: Integer): Boolean; +begin + Result := False; + + if PageID = wpSelectProgramGroup then + begin + Result := WizardIsComponentSelected('portable'); + end; +end; + +function IsDotNetInstalled(DotNetName: string): Boolean; +var + Cmd, Args: string; + FileName: string; + Output: AnsiString; + Command: string; + ResultCode: Integer; +begin + FileName := ExpandConstant('{tmp}\dotnet.txt'); + Cmd := ExpandConstant('{cmd}'); + Command := 'dotnet --list-runtimes'; + Args := '/C ' + Command + ' > "' + FileName + '" 2>&1'; + if Exec(Cmd, Args, '', SW_HIDE, ewWaitUntilTerminated, ResultCode) and + (ResultCode = 0) then + begin + if LoadStringFromFile(FileName, Output) then + begin + if Pos(DotNetName, Output) > 0 then + begin + Log('"' + DotNetName + '" found in output of "' + Command + '"'); + Result := True; + end + else + begin + Log('"' + DotNetName + '" not found in output of "' + Command + '"'); + Result := False; + end; + end + else + begin + Log('Failed to read output of "' + Command + '"'); + end; + end + else + begin + Log('Failed to execute "' + Command + '"'); + Result := False; + end; + DeleteFile(FileName); +end; + +function InitializeSetup(): Boolean; +var + ErrorCode: Integer; +begin + if not IsDotNetInstalled('Microsoft.NETCore.App 8.0.') then begin + MsgBox('AITOOL requires Microsoft .NET 8.0'#13#13 + 'https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-8.0.4-windows-x64-installer,'#13 + '...and then re-run the MyApp setup program.', mbInformation, MB_OK); + Exec('explorer', 'https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-8.0.4-windows-x64-installer', '', SW_SHOW, ewNoWait, ErrorCode); + result := false; + end else + result := true; +end; diff --git a/src/AITool.Setup/logo.ico b/src/AITool.Setup/logo.ico new file mode 100644 index 00000000..5e3da641 Binary files /dev/null and b/src/AITool.Setup/logo.ico differ diff --git a/src/ThreadSafeTesting/ThreadSafeTestProgram.cs b/src/ThreadSafeTesting/ThreadSafeTestProgram.cs new file mode 100644 index 00000000..f7b8adda --- /dev/null +++ b/src/ThreadSafeTesting/ThreadSafeTestProgram.cs @@ -0,0 +1,549 @@ +using System.Diagnostics; + + +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace ThreadSafeTesting +{ + public class test + { + + public ThreadSafe.Long lng2 { get; set; } + public ThreadSafe_OLD.Long lng { get; set; } + public long lngreal { get; set; } + public ThreadSafe.Integer intg2 { get; set; } + public ThreadSafe_OLD.Integer intg { get; set; } + public int intgreal { get; set; } + public ThreadSafe.Boolean boolg2 { get; set; } + public ThreadSafe_OLD.Boolean boolg { get; set; } + public bool boolgreal { get; set; } + public ThreadSafe.DateTime dt2 { get; set; } + public ThreadSafe_OLD.DateTime dt { get; set; } + public System.DateTime dtreal { get; set; } + //public ThreadSafe.Decimal decimal2 { get; set; } + public decimal decimalreal { get; set; } + + } + + public class ThreadSafeTestProgram + { + const int Iterations = 1000000; + const int ThreadCount = 4; + const int MaxResults = 5; + //const string LockResultsFile = "lockResults.txt"; + const string InterlockedResultsFile = "interlockedResults.txt"; + const string DateTimeResultsFile = "dateTimeResults.txt"; + + //static List lockResults = new List(); + static List interlockedResults = new List(); + static List dateTimeResults = new List(); + + static void Main(string[] args) + { + + test tc = new test(); + tc.intg = new ThreadSafe_OLD.Integer(1); + tc.intg2 = new ThreadSafe.Integer(1); + tc.intgreal = 1; + tc.lng = new ThreadSafe_OLD.Long(1); + tc.lng2 = new ThreadSafe.Long(2); + tc.lngreal = 1; + tc.boolg = new ThreadSafe_OLD.Boolean(true); + tc.boolg2 = new ThreadSafe.Boolean(true); + tc.boolgreal = true; + tc.dt = new ThreadSafe_OLD.DateTime(System.DateTime.Now); + tc.dt2 = new ThreadSafe.DateTime(System.DateTime.Now, "dd.MM.yy, HH:mm:ss"); + tc.dtreal = System.DateTime.Now; + //tc.decimal2 = new ThreadSafe.Decimal(1.000321555m); + tc.decimalreal = 1.000321555m; + + // Create and serialize a new instance of test class using Newtonsoft.Json + string serializedTest = GetJSONString(tc); //JsonConvert.SerializeObject(testcls); + + //serializedTest = "{\r\n \"$id\": \"1\",\r\n \"$type\": \"ThreadSafeTesting.test, ThreadSafeTesting\",\r\n \"lng2\": {\r\n \"$id\": \"2\",\r\n \"$type\": \"AITool.ThreadSafe+Long, ThreadSafeTesting\",\r\n \"_value\": 99\r\n },\r\n \"lng\": {\r\n \"$id\": \"3\",\r\n \"$type\": \"AITool.ThreadSafe+Long, ThreadSafeTesting\",\r\n \"_value\": 1\r\n },\r\n \"lngreal\": 1,\r\n \"intg2\": 1,\r\n \"intg\": {\r\n \"$id\": \"4\",\r\n \"$type\": \"AITool.ThreadSafe+Integer, ThreadSafeTesting\",\r\n \"_value\": 1\r\n },\r\n \"intgreal\": 1,\r\n \"boolg2\": true,\r\n \"boolg\": {\r\n \"$id\": \"5\",\r\n \"$type\": \"AITool.ThreadSafe+Boolean, ThreadSafeTesting\",\r\n \"_value\": 1\r\n },\r\n \"boolgreal\": true,\r\n \"dt2\": -8584851288971756652,\r\n \"dt\": {\r\n \"$id\": \"6\",\r\n \"$type\": \"AITool.ThreadSafe+Datetime, ThreadSafeTesting\",\r\n \"_value\": -8584851288971698129\r\n },\r\n \"dtreal\": \"2024-05-23T11:26:28.3078034-04:00\"\r\n}"; + + // Deserialize the serialized test instance + test ntc = SetJSONString(serializedTest); + + //check each property of the deserialized object to make sure it matches: + bool propsequal = tc.lng.ReadFullFence() == ntc.lng.ReadFullFence() && + tc.lng2 == ntc.lng2 && + tc.lngreal == ntc.lngreal && + tc.intg.ReadFullFence() == ntc.intg.ReadFullFence() && + tc.intg2 == ntc.intg2 && + tc.intgreal == ntc.intgreal && + tc.boolg.ReadFullFence() == ntc.boolg.ReadFullFence() && + tc.boolg2.ToString() == ntc.boolg2.ToString() && + tc.boolgreal == ntc.boolgreal && + tc.decimalreal == ntc.decimalreal; + + bool dt1eq = tc.dt.Read() == ntc.dt.Read(); + bool dt2eq = tc.dt2 == ntc.dt2; + bool dt3eq = tc.dtreal == ntc.dtreal; + + // Compare the original and deserialized test instances + bool areEqual = tc.Equals(ntc); + + Console.WriteLine($"Are the original and deserialized test instances equal? {areEqual}"); + + + + //Add a 5m delay to allow the debugger to attach, etc + Console.WriteLine("Waiting..."); + Thread.Sleep(1000); + + Console.WriteLine("Starting validation tests..."); + + + // Output validation results + for (int i = 0; i < 15; i++) + { + Console.WriteLine($" ThreadSafe.Boolean validation passed: {TestThreadSafetyForBoolean(out long boolms)} ({boolms}ms)"); + } + //for (int i = 0; i < 15; i++) + //{ + // Console.WriteLine($" ThreadSafe.BooleanMB validation passed: {TestThreadSafetyForBooleanMemBarrier(out long boolmbms)} ({boolmbms}ms)"); + //} + //for (int i = 0; i < 15; i++) + //{ + // Console.WriteLine($" ThreadSafe.Boolean2 validation passed: {TestThreadSafetyForBoolean2(out long boolmbms2)} ({boolmbms2}ms)"); + //} + + //return; + + // Validate ThreadSafeIntegerWithInterlocked + bool interlockedValidationResult = Validate(() => new ThreadSafe.Integer(), tsi => + { + for (int i = 0; i < Iterations; i++) + { + tsi++; + tsi--; + tsi += 5; + tsi -= 5; + //tsi *= 2; + //tsi /= 2; + } + }, out long intms); + + // Output validation results + + /// Validate ThreadSafe.DateTime + bool dateTimeValidationResult = Validate(() => new ThreadSafe.DateTime(System.DateTime.Now, "dd.MM.yy, HH:mm:ss"), tsd => + { + for (int i = 0; i < Iterations; i++) + { + tsd.AddDays(1); + tsd.AddDays(-1); + tsd.AddHours(2); + tsd.AddHours(-2); + tsd.AddMinutes(30); + tsd.AddMinutes(-30); + tsd.AddSeconds(45); + tsd.AddSeconds(-45); + + // Equality and comparison checks + var now = new ThreadSafe.DateTime(System.DateTime.Now, "dd.MM.yy, HH:mm:ss"); + var later = new ThreadSafe.DateTime(now.GetValue().AddHours(1), "dd.MM.yy, HH:mm:ss"); + bool isEqual = now == now; + bool isNotEqual = now != later; + bool isLessThan = now < later; + bool isGreaterThan = later > now; + bool isLessThanOrEqual = now <= now; + bool isGreaterThanOrEqual = later >= now; + + // Addition and subtraction checks + var addedTime = now + TimeSpan.FromHours(1); + var subtractedTime = later - TimeSpan.FromHours(1); + TimeSpan difference = later - now; + + // Ensure correctness + if (!isEqual || !isNotEqual || !isLessThan || !isGreaterThan || !isLessThanOrEqual || + !isGreaterThanOrEqual || !(addedTime == later) || !(subtractedTime == now) || + !(difference == TimeSpan.FromHours(1))) + { + throw new Exception("Validation failed."); + } + + // Implicit conversion checks + System.DateTime dateTimeFromThreadSafe = now; + var threadSafeFromDateTime = (ThreadSafe.DateTime)dateTimeFromThreadSafe; + if (!(now == threadSafeFromDateTime)) + { + throw new Exception("Validation failed."); + } + } + }, out long datems); + + + + Console.WriteLine($" ThreadSafe.Integer validation passed: {interlockedValidationResult} ({intms}ms)"); + Console.WriteLine($" ThreadSafe.DateTime validation passed: {dateTimeValidationResult} ({datems}ms)"); + Console.WriteLine($"ThreadSafe.DateTime validation #2 passed: {TestThreadSafetyForDateTime(out long date2ms)} ({date2ms}ms)"); + Console.WriteLine($" ThreadSafe.Long validation passed: {TestThreadSafetyForLong(out long longms)} ({longms}ms)"); + + Console.WriteLine("Waiting..."); + Thread.Sleep(5000); + + Console.WriteLine("Starting benchmark..."); + + + // Load previous results + LoadResults(InterlockedResultsFile, interlockedResults); + LoadResults(DateTimeResultsFile, dateTimeResults); + + //ThreadSafeIntegerWithInterlocked blah = 1; + + // Benchmark ThreadSafeIntegerWithInterlocked + var interlockedResult = Benchmark(() => new ThreadSafe.Integer(), tsi => + { + for (int i = 0; i < Iterations; i++) + { + tsi++; + tsi--; + tsi += 5; + tsi -= 5; + //tsi *= 2; + //tsi /= 2; + } + }); + + AddResult(interlockedResults, interlockedResult); + // Save results + SaveResults(InterlockedResultsFile, interlockedResults); + + // Output results + DisplayResults("ThreadSafeIntegerWithInterlocked", interlockedResults); + + // Benchmark ThreadSafeDateTime + var dateTimeResult = Benchmark(() => (ThreadSafe.DateTime)DateTime.Now, tsd => + { + for (int i = 0; i < Iterations; i++) + { + tsd.AddSeconds(1); + tsd.AddSeconds(-1); + } + }); + AddResult(dateTimeResults, dateTimeResult); + + // Save results + SaveResults(DateTimeResultsFile, dateTimeResults); + + // Output results + DisplayResults("ThreadSafeDateTime", dateTimeResults); + + } + + //this may speed up json serialization + public static readonly DefaultContractResolver JSONContractResolver = new DefaultContractResolver + { + NamingStrategy = new CamelCaseNamingStrategy + { + ProcessDictionaryKeys = false, + OverrideSpecifiedNames = false, + ProcessExtensionDataNames = false + } + + //Global.JSONContractResolver.NamingStrategy = new CamelCaseNamingStrategy(); + }; + public static readonly JsonSerializerSettings JSONSettingsPretty = new JsonSerializerSettings + { + Formatting = Formatting.Indented, + TypeNameHandling = TypeNameHandling.All, + PreserveReferencesHandling = PreserveReferencesHandling.Objects, + ContractResolver = JSONContractResolver, + //NullValueHandling = NullValueHandling.Ignore, + //DefaultValueHandling = DefaultValueHandling.Ignore, + //ReferenceLoopHandling = ReferenceLoopHandling.Ignore, + // Add other settings that may improve performance for your specific case + }; + + public static string GetJSONString(object cls2, Newtonsoft.Json.Formatting formatting = Formatting.Indented, + Newtonsoft.Json.TypeNameHandling handling = TypeNameHandling.All, + Newtonsoft.Json.PreserveReferencesHandling reference = PreserveReferencesHandling.Objects) + { + + string Ret = ""; + try + { + + JSONSettingsPretty.Formatting = formatting; + JSONSettingsPretty.TypeNameHandling = handling; + JSONSettingsPretty.PreserveReferencesHandling = reference; + JSONSettingsPretty.Error = null; + + string contents2 = JsonConvert.SerializeObject(cls2, formatting, JSONSettingsPretty); + + if (JSONSettingsPretty.Error == null) + { + Ret = contents2; + } + else + { + Console.WriteLine($"Error: " + JSONSettingsPretty.Error.ToString()); + } + + } + catch (Exception ex) + { + Console.WriteLine($"Error: " + ex.Message); + } + finally + { + } + + return Ret; + + } + + public static T SetJSONString(string JSONString) where T : new() + { + + + T Ret = default(T); + + try + { + //JsonSerializerSettings jset = new JsonSerializerSettings { }; + //jset.TypeNameHandling = TypeNameHandling.All; + //jset.PreserveReferencesHandling = PreserveReferencesHandling.Objects; + //jset.ContractResolver = Global.JSONContractResolver; + JSONSettingsPretty.MetadataPropertyHandling = MetadataPropertyHandling.Ignore; + Ret = JsonConvert.DeserializeObject(JSONString, JSONSettingsPretty); + } + catch (Exception ex) + { + Console.WriteLine($"Error: While converting json string '{JSONString}', got: " + ex.Message); + } + finally + { + } + + return Ret; + + } + static bool TestThreadSafetyForDateTime(out long milliseconds) + { + Stopwatch sw = Stopwatch.StartNew(); + //const int iterations = 10000; + //const int threadCount = 10; + + DateTime now = DateTime.Now; + var startDateTime = new ThreadSafe.DateTime(now, "dd.MM.yy, HH:mm:ss"); + var endDateTime = new ThreadSafe.DateTime(startDateTime.GetValue().AddHours(10), "dd.MM.yy, HH:mm:ss"); + + var tasks = new Task[ThreadCount]; + for (int i = 0; i < ThreadCount; i++) + { + tasks[i] = Task.Run(() => + { + for (int j = 0; j < Iterations; j++) + { + startDateTime.AddSeconds(1); + startDateTime.AddSeconds(-1); + endDateTime.AddSeconds(-1); + endDateTime.AddSeconds(1); + } + }); + } + + Task.WaitAll(tasks); + + // Ensure the final values are unchanged due to the balanced operations + bool startDateTimeUnchanged = startDateTime.GetValue() == now; + bool endDateTimeUnchanged = endDateTime.GetValue() == now.AddHours(10); + + milliseconds = sw.ElapsedMilliseconds; + return startDateTimeUnchanged && endDateTimeUnchanged; + } + + static bool TestThreadSafetyForLong(out long milliseconds) + { + Stopwatch sw = Stopwatch.StartNew(); + //const int iterations = 10000; + //const int threadCount = 10; + var startLong = new ThreadSafe.Long(1000); + var endLong = new ThreadSafe.Long(5000); + + var tasks = new Task[ThreadCount]; + for (int i = 0; i < ThreadCount; i++) + { + tasks[i] = Task.Run(() => + { + for (int j = 0; j < Iterations; j++) + { + startLong += 1; + startLong -= 1; + endLong += 1; + endLong -= 1; + + //Atomic Operations on Long: For ThreadSafe.Long, although the addition and subtraction are balanced, the multiplication and division might not be as atomic as required because they involve multiple steps. + //startLong *= 2; + //startLong /= 2; + //endLong *= 2; + //endLong /= 2; + } + }); + } + + Task.WaitAll(tasks); + + // Ensure the final values are unchanged due to the balanced operations + bool startLongUnchanged = startLong.GetValue() == 1000; + bool endLongUnchanged = endLong.GetValue() == 5000; + + milliseconds = sw.ElapsedMilliseconds; + return startLongUnchanged && endLongUnchanged; + } + + static bool TestThreadSafetyForBoolean(out long milliseconds) + { + Stopwatch sw = Stopwatch.StartNew(); + //const int iterations = 10000; + //const int threadCount = 10; + var startBoolean = new ThreadSafe.Boolean(true); + + var tasks = new Task[ThreadCount]; + for (int i = 0; i < ThreadCount; i++) + { + tasks[i] = Task.Run(() => + { + for (int j = 0; j < Iterations; j++) + { + //The test may fail randomly because the operations of reading the value, negating it, and setting it back are not atomic as a whole. + //While the getter and setter of the ThreadSafeBoolean are thread-safe individually, the combination of these operations(startBoolean.Value = !startBoolean.Value;) is not atomic.This means that another thread can change the value of startBoolean after it has been read and before it has been set, leading to inconsistent results. + //To make the entire operation atomic, you would need to use a lock or a similar synchronization mechanism + //startBoolean = !startBoolean; + //startBoolean = !startBoolean; + startBoolean = false; + startBoolean = true; + startBoolean.ToggleValue(); + startBoolean.ToggleValue(); + } + }); + } + + Task.WaitAll(tasks); + + // Ensure the final value is unchanged due to the balanced operations + bool startBooleanUnchanged = startBoolean == true; + milliseconds = sw.ElapsedMilliseconds; + + return startBooleanUnchanged; + } + + + + static bool Validate(Func createInstance, Action action, out long milliseconds) where T : class + { + Stopwatch sw = Stopwatch.StartNew(); + T instance = createInstance(); + var tasks = new Task[ThreadCount]; + + for (int i = 0; i < ThreadCount; i++) + { + tasks[i] = Task.Run(() => action(instance)); + } + + Task.WaitAll(tasks); + + // The expected value should be the same as the initial value because each operation set is balanced + if (instance is ThreadSafe.Integer) + { + int expectedValue = 0; + int actualValue = (instance as dynamic).GetValue(); + milliseconds = sw.ElapsedMilliseconds; + return expectedValue == actualValue; + } + else if (instance is ThreadSafe.DateTime) + { + System.DateTime initialValue = System.DateTime.Now; + (instance as dynamic).SetValue(initialValue); + System.DateTime expectedValue = initialValue; + System.DateTime actualValue = (instance as dynamic).GetValue(); + milliseconds = sw.ElapsedMilliseconds; + return expectedValue == actualValue; + } + + milliseconds = sw.ElapsedMilliseconds; + return false; + } + + static void SaveResults(string fileName, List results) + { + using (var writer = new StreamWriter(fileName)) + { + foreach (var result in results) + { + writer.WriteLine(result); + } + } + } + + static void LoadResults(string fileName, List results) + { + if (File.Exists(fileName)) + { + using (var reader = new StreamReader(fileName)) + { + string line; + while ((line = reader.ReadLine()) != null) + { + if (int.TryParse(line, out var result)) + { + results.Add(result); + } + } + } + } + } + static TimeSpan Benchmark(Func createInstance, Action action) + { + T instance = createInstance(); + var stopwatch = Stopwatch.StartNew(); + + var tasks = new Task[ThreadCount]; + for (int i = 0; i < ThreadCount; i++) + { + tasks[i] = Task.Run(() => action(instance)); + } + + Task.WaitAll(tasks); + stopwatch.Stop(); + + return stopwatch.Elapsed; + } + + static void DisplayResults(string name, List results) + { + Console.WriteLine($"\n{name} Results:"); + for (int i = 0; i < results.Count - 1; i++) + { + Console.WriteLine($"{i + 1}: {results[i]} ms"); + } + + if (results.Count > 1) + { + var lastResult = results[results.Count - 2]; + var currentResult = results[results.Count - 1]; + var percentageDifference = ((double)(currentResult - lastResult) / lastResult) * 100; + var sign = percentageDifference >= 0 ? "+" : "-"; + Console.WriteLine($"{results.Count}: {currentResult} ms ({sign}{Math.Abs(percentageDifference):0}%)"); + } + else + { + Console.WriteLine($"{results.Count}: {results[results.Count - 1]} ms"); + } + } + + static void AddResult(List results, TimeSpan result) + { + if (results.Count == MaxResults) + { + results.RemoveAt(0); + } + results.Add((int)result.TotalMilliseconds); + } + } + +} diff --git a/src/ThreadSafeTesting/ThreadSafeTesting.csproj b/src/ThreadSafeTesting/ThreadSafeTesting.csproj new file mode 100644 index 00000000..b70f8fe9 --- /dev/null +++ b/src/ThreadSafeTesting/ThreadSafeTesting.csproj @@ -0,0 +1,21 @@ + + + + Exe + net8.0 + enable + enable + 1.0.23.8918 + 1.0.23.8918 + + + + + + + + + + + + diff --git a/src/UI/AITOOL-ERROR.ico b/src/UI/AITOOL-ERROR.ico new file mode 100644 index 00000000..30752716 Binary files /dev/null and b/src/UI/AITOOL-ERROR.ico differ diff --git a/src/UI/AITOOL-ERROR.png b/src/UI/AITOOL-ERROR.png new file mode 100644 index 00000000..43f2a500 Binary files /dev/null and b/src/UI/AITOOL-ERROR.png differ diff --git a/src/UI/AITOOL.cs b/src/UI/AITOOL.cs new file mode 100644 index 00000000..986129d6 --- /dev/null +++ b/src/UI/AITOOL.cs @@ -0,0 +1,4661 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Text; +using System.IO; +using System.Linq; +using System.Media; +using System.Net; +using System.Net.Http; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; + +using Amazon; +using Amazon.Rekognition; +using Amazon.Rekognition.Model; +using Amazon.Runtime; + +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +using NLog; + +using NPushover; + +using OSVersionExtension; + +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Processing; + +using static AITool.Global; + +using Rectangle = System.Drawing.Rectangle; + +namespace AITool +{ + public static class AITOOL + { + // ============================================================= + // ALL FUNCTIONS HERE THAT MAY EVENTUALLY BE USED IN A SERVICE + // NO direct UI interaction + // ============================================================= + + public static DeepStack DeepStackServerControl = null; + //public static RichTextBoxEx RTFLogger = null; + //public static LogFileWriter LogWriter = null; + //public static LogFileWriter HistoryWriter = null; + + public static BlueIrisInfo BlueIrisInfo = null; + //public static List DeepStackURLList = new List(); + + //keep track of timing + //moving average will be faster for long running process with 1000's of samples + public static MovingCalcs tcalc = new MovingCalcs(500, "Images", true); + public static MovingCalcs fcalc = new MovingCalcs(500, "Images", true); + public static MovingCalcs lcalc = new MovingCalcs(500, "Images", true); + public static MovingCalcs icalc = new MovingCalcs(500, "Images", true); + public static MovingCalcs qcalc = new MovingCalcs(500, "Images", true); + public static MovingCalcs scalc = new MovingCalcs(500, "Img Queue", false); + + //public static ClsLogManager errors = new ClsLogManager(); + + public static ClsLogManager LogMan = null; + + public static ClsFaceManager FaceMan = null; + + public static ConcurrentQueue ImageProcessQueue = new ConcurrentQueue(); + + public static ConcurrentQueue TmpHistQueue = new ConcurrentQueue(); //For before the logger gets fully initialized + + //The sqlite db connection + public static SQLiteHistory HistoryDB = null; + public static ClsTriggerActionQueue TriggerActionQueue = null; + + public static object FileWatcherLockObject = new object(); + public static object ImageLoopLockObject = new object(); + + //thread safe dictionary to prevent more than one file being processed at one time + public static ConcurrentDictionary image_detection_dictionary = new ConcurrentDictionary(); + + + public static Dictionary watchers = new Dictionary(); + //public static ThreadSafe.Boolean AIURLSettingsChanged = new ThreadSafe.Boolean(true); + + + public static ThreadSafe.Boolean IsClosing = new ThreadSafe.Boolean(false); + public static ThreadSafe.Boolean IsLoading = new ThreadSafe.Boolean(true); + public static ThreadSafe.DateTime LastImageBackupTime = new ThreadSafe.DateTime(DateTime.MinValue, AppSettings.Settings.DateFormat); + + public static CancellationTokenSource MasterCTS = new CancellationTokenSource(); + + public static System.Timers.Timer FileSystemErrorCheckTimer = new System.Timers.Timer(); + + public static MQTTClient mqttClient = new MQTTClient(); + + public static Pushover pushoverClient = null; + + public static ClsTelegramMessages Telegram = null; + public static ThrottledHttpClient triggerHttpClient = null; + + public static string srv = ""; + + public static ThreadSafe.Integer AIURLListAvailableRefineServerCount = new ThreadSafe.Integer(0); + + public static ThreadSafe.Boolean CloseImmediately = new ThreadSafe.Boolean(false); + + public static ThreadSafe.Boolean ResetSettings = new ThreadSafe.Boolean(false); + + public static ThreadSafe.Boolean Restart = new ThreadSafe.Boolean(false); + + public static SoundPlayer TickImageAddedPlayer = null; + + public static async Task InitializeBackend() + { + + try + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + + + //initialize log manager with basic settings so we can start getting output if needed + if (Global.IsService) + srv = ".SERVICE."; + else + srv = "."; + + string exe = $"AITOOLS{srv}EXE"; + + //initialize logging as early as we can... Write to the temp folder since we dont know the log location yet + int TempDefSize = ((1024 * 1024) * 20); //20mb + LogMan = new ClsLogManager(!Global.IsService, exe); + + await LogMan.UpdateNLog(LogLevel.Debug, Path.Combine(Global.GetTempFolder(true), Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().Location) + $"{srv}LOG"), TempDefSize, 120, AppSettings.Settings.MaxGUILogItems); + + //load settings + await AppSettings.LoadAsync(); + + //reset log settings if different: + await LogMan.UpdateNLog(LogLevel.FromString(AppSettings.Settings.LogLevel), AppSettings.Settings.LogFileName, AppSettings.Settings.MaxLogFileSize, AppSettings.Settings.MaxLogFileAgeDays, AppSettings.Settings.MaxGUILogItems); + + + Assembly CurAssm = Assembly.GetEntryAssembly(); + string AssemNam = CurAssm.GetName().Name; + string AssemVer = CurAssm.GetName().Version.ToString(); + + Log(""); + Log(""); + Log(""); + Log($"Starting {AssemNam} Version {AssemVer} built on {Global.RetrieveLinkerTimestamp()}"); + + try //just in case some weird issue comes up with older os version... + { + OSVersionExt.VersionInfo vi = OSVersion.GetOSVersion(); + OSVersionExtension.OperatingSystem ov = OSVersion.GetOperatingSystem(); + + Log($"Debug: Installed NET Framework version '{Global.GetFrameworkVersion()}', Target version '{AppDomain.CurrentDomain.SetupInformation.TargetFrameworkName}'"); + Log($"Debug: Windows '{ov.ToString()}', version '{vi.Version.ToString()}' Release ID '{OSVersion.MajorVersion10Properties().ReleaseId}', 64Bit={OSVersion.Is64BitOperatingSystem}, Workstation={OSVersion.IsWorkstation}, Server={OSVersion.IsServer}, SERVICE={Global.IsService}"); + + } + catch (Exception ex) + { + + Log("Error: Problem getting OS version: " + ex.Msg()); + } + + + if (AppSettings.AlreadyRunning) + { + Log("*** Warning: Another instance is already running *** "); + Log(" --- Files will not be monitored from within this session "); + Log(" --- Log tab will not display output from service instance. You will need to directly open log file for that "); + Log(" --- Changes made here to settings will require that you stop/start the service "); + } + if (Global.IsAdministrator()) + { + Log("Debug: *** Running as administrator ***"); + } + else + { + Log("Debug: Not running as administrator."); + } + + if (string.Equals(AppDomain.CurrentDomain.BaseDirectory.TrimEnd('\\'), Directory.GetCurrentDirectory().TrimEnd('\\'), StringComparison.OrdinalIgnoreCase)) + { + Log($"Debug: *** Start in/current directory is the same as where the EXE is running from: {Directory.GetCurrentDirectory()} ***"); + } + else + { + try + { + Log($"Debug: *** Changing Start in/current directory from '{Directory.GetCurrentDirectory().TrimEnd('\\')}' to '{AppDomain.CurrentDomain.BaseDirectory.TrimEnd('\\')}' ***"); + Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory); + } + catch (Exception ex) + { + + string msg = $"Error: The Start in/current directory is NOT the same as where the EXE is running from: \r\n{Directory.GetCurrentDirectory()}\r\n{AppDomain.CurrentDomain.BaseDirectory}"; + Log(msg); + Log($"...this may prevent DLL files from loading from the wrong folder. '{ex.Message}'"); + } + } + + Global.SaveRegSetting("LastRunPath", Directory.GetCurrentDirectory()); + + //initialize blueiris info class to get camera names, clip paths, etc + BlueIrisInfo = new BlueIrisInfo(); + + await BlueIrisInfo.RefreshBIInfoAsync(AppSettings.Settings.BlueIrisServer); + + if (BlueIrisInfo.Result == BlueIrisResult.Valid) + { + Log($"Debug: BlueIris path is '{BlueIrisInfo.AppPath}', with {BlueIrisInfo.Users.Count} users, {BlueIrisInfo.Cameras.Count} cameras and {BlueIrisInfo.ClipPaths.Count} clip folder paths configured."); + if (BlueIrisInfo.Users.Count > 0 && (string.IsNullOrEmpty(AppSettings.Settings.DefaultUserName) || string.Equals(AppSettings.Settings.DefaultUserName, "username", StringComparison.OrdinalIgnoreCase))) + { + AppSettings.Settings.DefaultUserName = BlueIrisInfo.Users[0].Name; + AppSettings.Settings.DefaultPasswordEncrypted = BlueIrisInfo.Users[0].Password.Encrypt(); + } + + UpdateLatLong(); + } + else + { + Log($"Debug: BlueIris not detected."); + } + + + //initialize the deepstack class - it collects info from running deepstack processes, detects install location, and + //allows for stopping and starting of its service + DeepStackServerControl = new DeepStack(AppSettings.Settings.deepstack_adminkey, AppSettings.Settings.deepstack_apikey, AppSettings.Settings.deepstack_mode, AppSettings.Settings.deepstack_sceneapienabled, AppSettings.Settings.deepstack_faceapienabled, AppSettings.Settings.deepstack_detectionapienabled, AppSettings.Settings.deepstack_port, AppSettings.Settings.deepstack_customModelPath, AppSettings.Settings.deepstack_stopbeforestart, AppSettings.Settings.deepstack_customModelName, AppSettings.Settings.deepstack_customModelPort, AppSettings.Settings.deepstack_customModelMode, AppSettings.Settings.deepstack_customModelApiEnabled); + + if (DeepStackServerControl.IsInstalled && AppSettings.Settings.deepstack_autostart) + { + await DeepStackServerControl.StartDeepstackAsync(); + } + + //Load the database, and migrate any old csv lines if needed + HistoryDB = new SQLiteHistory(AppSettings.Settings.HistoryDBFileName, AppSettings.AlreadyRunning); + + await HistoryDB.Initialize(); + + TriggerActionQueue = new ClsTriggerActionQueue(); + + + await UpdateWatchers(false); + + FileSystemErrorCheckTimer.Elapsed += new System.Timers.ElapsedEventHandler(TimerCheckFileSystemWatchers); + FileSystemErrorCheckTimer.Interval = AppSettings.Settings.FileSystemWatcherRetryOnErrorTimeMS; + FileSystemErrorCheckTimer.Enabled = true; + FileSystemErrorCheckTimer.Start(); + + //Start the thread that watches for the file queue + if (!AppSettings.AlreadyRunning) + Task.Run(ImageQueueLoop); + + + Telegram = new ClsTelegramMessages(); + + if (AppSettings.LastShutdownState.StartsWith("checkpoint") && !AppSettings.AlreadyRunning) + Log($"Error: Program did not shutdown gracefully. Last log entry was '{AppSettings.LastLogEntry}', '{AppSettings.LastShutdownState}'"); + + //initialize the annoying tick player + if (AppSettings.Settings.Tick && AppSettings.Settings.TickImageAddedSoundFile.IsNotEmpty()) + { + string soundfile = Global.FindSoundFile(AppSettings.Settings.TickImageAddedSoundFile); + if (File.Exists(soundfile)) + { + TickImageAddedPlayer = new SoundPlayer(soundfile); + TickImageAddedPlayer.Load(); + } + else + { + Log($"Debug: Could not find tick sound file {AppSettings.Settings.TickImageAddedSoundFile}."); + } + } + + } + catch (Exception ex) + { + + Log("Error: " + ex.Msg()); + } + + } + + public static void UpdateLatLong() + { + //use blueiris lat/long setting if found, and not already set to something different in : + if (BlueIrisInfo.Result == BlueIrisResult.Valid && + !Global.IsLatLongValid(AppSettings.Settings.LocalLatitude, AppSettings.Settings.LocalLongitude) && + Global.IsLatLongValid(BlueIrisInfo.Latitude, BlueIrisInfo.Longitude)) //default is middle of USA, assume that is not a valid lat/long + { + AppSettings.Settings.LocalLatitude = BlueIrisInfo.Latitude; + AppSettings.Settings.LocalLongitude = BlueIrisInfo.Longitude; + } + + + + } + + public static bool DrawAnnotation(Graphics g, ClsPrediction pred, double ImgWidth, double ImgHeight, double BoxWidth = 0, double BoxHeight = 0) + { + bool ret = false; + + try + { + + if (BoxWidth == 0) + BoxWidth = ImgWidth; + if (BoxHeight == 0) + BoxHeight = ImgHeight; + + string AnnoText = pred.ToString(); + + bool Merge = false; + + if (AppSettings.Settings.HistoryOnlyDisplayRelevantObjects && pred.Result == ResultType.Relevant) + Merge = true; + else if (!AppSettings.Settings.HistoryOnlyDisplayRelevantObjects) + Merge = true; + + if (Merge) + { + + g.InterpolationMode = InterpolationMode.HighQualityBicubic; + g.SmoothingMode = SmoothingMode.HighQuality; + g.PixelOffsetMode = PixelOffsetMode.HighQuality; + //http://csharphelper.com/blog/2014/09/understand-font-aliasing-issues-in-c/ + g.TextRenderingHint = TextRenderingHint.SingleBitPerPixelGridFit; + + System.Drawing.Color color = new System.Drawing.Color(); + + double BorderWidth = AppSettings.Settings.RectBorderWidth; + + if (pred.Result == ResultType.Relevant) + { + color = System.Drawing.Color.FromArgb(AppSettings.Settings.RectRelevantColorAlpha, AppSettings.Settings.RectRelevantColor); + } + else if (pred.Result == ResultType.DynamicMasked || pred.Result == ResultType.ImageMasked || pred.Result == ResultType.StaticMasked) + { + color = System.Drawing.Color.FromArgb(AppSettings.Settings.RectMaskedColorAlpha, AppSettings.Settings.RectMaskedColor); + } + else + { + color = System.Drawing.Color.FromArgb(AppSettings.Settings.RectIrrelevantColorAlpha, AppSettings.Settings.RectIrrelevantColor); + } + + //Assume no scaling at first: + ///=================================================================================== + + System.Drawing.RectangleF rect = pred.GetRectangleF(); + + double xmin = pred.XMin; + double ymin = pred.YMin; + double xmax = pred.XMax; + double ymax = pred.YMax; + + double sclxmin = pred.XMin; + double sclymin = pred.YMin; + double sclxmax = pred.XMax; + double sclymax = pred.YMax; + + double TextSizePoints = AppSettings.Settings.RectDetectionTextSize; + + ///=================================================================================== + //check to see if we need to scale based on onscreen zoomed image from picturebox: + ///=================================================================================== + if (ImgWidth != BoxWidth || ImgHeight != BoxHeight) + { + //these variables store the padding between image border and picturebox border + double absX = 0; + double absY = 0; + + //because the sizemode of the picturebox is set to 'zoom', the image is scaled down + double scale = 1; + + //Comparing the aspect ratio of both the control and the image itself. + if (ImgWidth / ImgHeight > BoxWidth / BoxHeight) //if the image is p.e. 16:9 and the picturebox is 4:3 + { + scale = BoxWidth / ImgWidth; //get scale factor + absY = (BoxHeight - scale * ImgHeight) / 2; //padding on top and below the image + } + else //if the image is p.e. 4:3 and the picturebox is widescreen 16:9 + { + scale = BoxHeight / ImgHeight; //get scale factor + absX = (BoxWidth - scale * ImgWidth) / 2; //padding left and right of the image + } + + //2. inputted position values are for the original image size. As the image is probably smaller in the picturebox, the positions must be adapted. + xmin = (scale * xmin) + absX; + xmax = (scale * xmax) + absX; + ymin = (scale * ymin) + absY; + ymax = (scale * ymax) + absY; + + double sclWidth = xmax - xmin; + double sclHeight = ymax - ymin; + + sclxmax = BoxWidth - (absX * 2); + sclymax = BoxHeight - (absY * 2); + sclxmin = absX; + sclymin = absY; + + TextSizePoints = scale * TextSizePoints; + + BorderWidth = scale * BorderWidth; + + if (BorderWidth < 1) + BorderWidth = 1; + + rect = new System.Drawing.RectangleF(xmin.ToFloat(), + ymin.ToFloat(), + sclWidth.ToFloat(), + sclHeight.ToFloat()); + } + + ///=================================================================================== + + using (Pen pen = new Pen(color, BorderWidth.ToFloat())) + { + g.DrawRectangle(pen, rect.X, rect.Y, rect.Width, rect.Height); //draw rectangle + } + + //we need this since people can change the border width in the json file + double halfbrd = BorderWidth / 2; + + System.Drawing.SizeF TextSize = g.MeasureString(AnnoText, new Font(AppSettings.Settings.RectDetectionTextFont, TextSizePoints.ToFloat())); //finds size of text to draw the background rectangle + + double x = xmin - halfbrd; + double y = ymax + halfbrd; + + //adjust the x / width label so it doesnt go off screen + double EndX = x + TextSize.Width; + if (EndX > sclxmax) + { + //int diffx = x - sclxmax; + x = xmax - TextSize.Width + halfbrd; + } + + if (x < sclxmin) + x = sclxmin; + + if (x < 0) + x = 0; + + //adjust the y / height label so it doesnt go off screen + double EndY = y + TextSize.Height; + if (EndY > sclymax) + { + //float diffy = EndY - sclymax; + y = ymax - TextSize.Height - halfbrd; + } + + + if (y < 0) + y = 0; + + //object name text below rectangle + rect = new System.Drawing.RectangleF(x.ToFloat(), + y.ToFloat(), + BoxWidth.ToFloat(), + BoxHeight.ToFloat()); //sets bounding box for drawn text + + Brush brush = new SolidBrush(color); //sets background rectangle color + if (AppSettings.Settings.RectDetectionTextBackColor != System.Drawing.Color.Gainsboro) + { + color = System.Drawing.Color.FromArgb(color.A, AppSettings.Settings.RectDetectionTextBackColor); + brush = new SolidBrush(color); + } + + Brush forecolor = Brushes.Black; + if (AppSettings.Settings.RectDetectionTextForeColor != System.Drawing.Color.Gainsboro) + forecolor = new SolidBrush(AppSettings.Settings.RectDetectionTextForeColor); + + + g.FillRectangle(brush, + x.ToFloat(), + y.ToFloat(), + TextSize.Width, + TextSize.Height); //draw grey background rectangle for detection text + + g.DrawString(AnnoText, + new Font(AppSettings.Settings.RectDetectionTextFont, TextSizePoints.ToFloat()), + forecolor, + rect); //draw detection text + + g.Flush(FlushIntention.Flush); + + ret = true; + + } + + } + catch (Exception ex) + { + + Log($"Error: {ex.Msg()}"); + } + + + return ret; + + } + + public static System.Drawing.Image CropImage(ClsImageQueueItem img, System.Drawing.Rectangle cropArea) + { + + if (img.IsValid()) + { + try + { + DecoderOptions dc = new DecoderOptions(); + using (SixLabors.ImageSharp.Image image = SixLabors.ImageSharp.Image.Load(img.ToMemStream())) //, out IImageFormat format)) + { + image.Mutate(i => i.Crop(SixLabors.ImageSharp.Rectangle.FromLTRB(cropArea.Left, cropArea.Top, cropArea.Right, cropArea.Bottom))); + + using (MemoryStream ms = new MemoryStream()) + { + image.Save(ms, image.Metadata.DecodedImageFormat); + System.Drawing.Image newimg = System.Drawing.Image.FromStream(ms); + return newimg; + } + } + + } + catch (Exception ex) + { + + Log($"Error: {ex.Msg()}"); + } + + } + + return null; + //using Bitmap bmpImage = new Bitmap(img); + + //if (cropArea.Right > bmpImage.Width || cropArea.Bottom > bmpImage.Height) + //{ + // cropArea.Intersect(new Rectangle(0, 0, bmpImage.Width, bmpImage.Height)); + //} + + //Bitmap bmpCrop = bmpImage.Clone(cropArea, bmpImage.PixelFormat); + + //return (Image)(bmpCrop); + + } + //public static void Log(string Detail, string AIServer = "", Camera Camera = null, ClsImageQueueItem Image = null, string Source = "", int Depth = 0, LogLevel Level = null, Nullable Time = default(DateTime?), [CallerMemberName()] string memberName = null) + //{ + // string cam = Camera != null ? Camera.Name : ""; + // string img = Image != null ? Image.image_path : ""; + // Log(Detail, AIServer, cam, img, Source, Depth, Level, Time, memberName); + //} + //public static void Log(string Detail, bool fakeout = false, [CallerMemberName()] string memberName = null) + //{ + + // Log(Detail, "", "", "", "", 0, null, null, memberName); + //} + + //just an alias to make things easier + [DebuggerStepThrough] + public static void Log(string Detail, string AIServer = "", object Camera = null, object Image = null, string Source = "", int Depth = 0, LogLevel Level = null, Nullable Time = default(DateTime?), [CallerMemberName()] string memberName = null) + { + + string cam = ""; + string img = ""; + + if (Camera != null) + { + if (Camera is Camera) + { + cam = ((Camera)Camera).Name; + } + else if (Camera is String) + { + cam = Camera.ToString(); + } + else + { + cam = Camera.ToString(); + //should not be here? + } + } + + if (Image != null) + { + if (Image is ClsImageQueueItem) + { + img = ((ClsImageQueueItem)Image).image_path; + } + else if (Image is string) + { + img = Image.ToString(); + } + else + { + img = Image.ToString(); + } + } + + + if (LogMan != null && LogMan.Enabled) + { + //flush any entries from before logman initialized + while (!TmpHistQueue.IsEmpty) + { + ClsLogItm cli; + if (TmpHistQueue.TryDequeue(out cli)) + LogMan.Log(cli.Detail, cli.AIServer, cli.Camera, cli.Image, cli.Source, cli.Depth, cli.Level, cli.Date, cli.Func); + } + + LogMan.Log(Detail, AIServer, cam, img, Source, Depth, Level, Time, memberName); + } + else + { + TmpHistQueue.Enqueue(new ClsLogItm(null, DateTime.Now, Source, memberName, AIServer, cam, img, Detail, 0, Depth, "", 0)); + //Console.WriteLine($"Error: Wrote to log before initialized? '{Detail}'"); + } + } + + public static void UpdateAIURLs() + { + + + if (AppSettings.GetURL(type: URLTypeEnum.AWSRekognition_Objects) != null || AppSettings.GetURL(type: URLTypeEnum.AWSRekognition_Faces) != null) // || this.url.Equals("aws", StringComparison.OrdinalIgnoreCase) || this.url.Equals("rekognition", StringComparison.OrdinalIgnoreCase)) + { + string error = AITOOL.UpdateAmazonSettings(); + + if (!string.IsNullOrEmpty(error)) + { + AITOOL.Log($"Error: {error}"); + + if (error.IndexOf("endpoint", StringComparison.OrdinalIgnoreCase) >= 0) + { + //hardcode the list for now: https://docs.aws.amazon.com/general/latest/gr/rande.html + List endpoints = new List(); + endpoints.Add("US East (N. Virginia) \tus-east-1"); + endpoints.Add("US East (Ohio) \tus-east-2"); + endpoints.Add("US West (N. California) \tus-west-1"); + endpoints.Add("US West (Oregon) \tus-west-2"); + endpoints.Add("Canada (Central) \tca-central-1"); + endpoints.Add("Europe (London) \teu-west-2"); + endpoints.Add("Europe (Frankfurt) \teu-central-1"); + endpoints.Add("Europe (Ireland) \teu-west-1"); + endpoints.Add("Europe (Milan) \teu-south-1"); + endpoints.Add("Europe (Paris) \teu-west-3"); + endpoints.Add("Europe (Stockholm) \teu-north-1"); + endpoints.Add("Africa (Cape Town) \taf-south-1"); + endpoints.Add("Middle East (Bahrain) \tme-south-1"); + endpoints.Add("South America (São Paulo) \tsa-east-1"); + endpoints.Add("China (Beijing) \tcn-north-1"); + endpoints.Add("China (Ningxia) \tcn-northwest-1"); + endpoints.Add("Asia Pacific (Hong Kong) \tap-east-1"); + endpoints.Add("Asia Pacific (Mumbai) \tap-south-1"); + endpoints.Add("Asia Pacific (Osaka-Local) \tap-northeast-3"); + endpoints.Add("Asia Pacific (Seoul) \tap-northeast-2"); + endpoints.Add("Asia Pacific (Singapore) \tap-southeast-1"); + endpoints.Add("Asia Pacific (Sydney) \tap-southeast-2"); + endpoints.Add("Asia Pacific (Tokyo) \tap-northeast-1"); + + using (var form = new InputForm("Select Amazon AWS endpoint near you:", "Amazon AWS Endpoint", cbitems: endpoints)) + { + var result = form.ShowDialog(); + if (result == DialogResult.OK) + { + string region = ""; + if (!string.IsNullOrEmpty(form.text)) + { + if (form.text.Contains("\t")) + { + region = form.text.GetWord("\t", "").Trim(); + } + else if (form.text.Contains("-")) + { + region = form.text.Trim(); + } + + } + if (string.IsNullOrEmpty(region)) + { + MessageBox.Show($"Error: No endpoint selected '{form.text}'"); + } + else + { + AppSettings.Settings.AmazonRegionEndpoint = region; + } + } + } + } + + error = AITOOL.UpdateAmazonSettings(); + + if (!string.IsNullOrEmpty(error)) + { + AITOOL.Log($"Error: {error}"); + if (error.IndexOf("rootkey", StringComparison.OrdinalIgnoreCase) >= 0) + { + MessageBox.Show(error, "Missing AWS credentials", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + } + } + + if (AppSettings.GetURL(type: URLTypeEnum.SightHound_Person) != null || AppSettings.GetURL(type: URLTypeEnum.SightHound_Vehicle) != null) + { + if (string.IsNullOrWhiteSpace(AppSettings.Settings.SightHoundAPIKey)) + { + using (var form = new InputForm("Enter SightHound API Key", "SightHound API Key")) + { + var result = form.ShowDialog(); + if (result == DialogResult.OK) + { + if (!string.IsNullOrEmpty(form.text) && form.text.Trim().Length > 30) //It looks like they are 36 chars + { + AppSettings.Settings.SightHoundAPIKey = form.text.Trim(); + } + else + { + MessageBox.Show("Enter a valid key."); + } + } + } + } + } + + //let the image loop (running in another thread) know to recheck ai server url settings. + //AIURLSettingsChanged= true; + + AITOOL.UpdateAIURLList(true); + + } + + public static string UpdateAmazonSettings() + { + + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + if (AppSettings.GetURL(type: URLTypeEnum.AWSRekognition_Objects) == null && AppSettings.GetURL(type: URLTypeEnum.AWSRekognition_Faces) == null) // || this.url.Equals("aws", StringComparison.OrdinalIgnoreCase) || this.url.Equals("rekognition", StringComparison.OrdinalIgnoreCase)) + return ""; + + string error = ""; + + RegionEndpoint endpoint = null; + try + { + if (!string.IsNullOrWhiteSpace(AppSettings.Settings.AmazonRegionEndpoint)) + endpoint = RegionEndpoint.GetBySystemName(AppSettings.Settings.AmazonRegionEndpoint); + else + error = "No AmazonRegionEndpoint set."; + } + catch (Exception ex) + { + error = $"Could not set Amazon region endpoint to '{AppSettings.Settings.AmazonRegionEndpoint}' (JSON setting=AmazonRegionEndpoint): {ex.Message}"; + } + + if (endpoint != null) + { + + AITOOL.Log($"Debug: Amazon RegionEndpoint DisplayName={endpoint.DisplayName}, PartitionDnsSuffix={endpoint.PartitionDnsSuffix}, PartitionName={endpoint.PartitionName}, SystemName={endpoint.SystemName}"); + //always try to extract latest from rootkey.csv if found + string pth = Path.Combine(Path.GetDirectoryName(AppSettings.Settings.SettingsFileName), "rootkey.csv"); + if (File.Exists(pth)) + { + string csv = File.ReadAllText(pth); + if (!string.IsNullOrWhiteSpace(csv)) + { + AITOOL.Log($"Debug: Extracting AWSAccessKeyId and AWSSecretKey from {pth}"); + if (csv.Contains(",") && !csv.Has("AWSAccessKeyId=")) + { + //User name, Password,Access key ID, Secret access key, Console login link + //aitooluser, ,XXXXXXXXXXXXX, xxxxxxxxxxxxxxxxxxxxxxxxxx, https://xxxxxxxxx.signin.aws.amazon.com/console + + List lines = csv.SplitStr("\r\n", true); + if (lines.Count > 1) + { + List header = lines[0].ToLower().SplitStr(",", false); + List firstline = lines[0].SplitStr(",", false); + int idxID = header.IndexOf("access key id"); + int idxSECRET = header.IndexOf("secret access key"); + if (idxID > -1 && idxSECRET > -1) + { + AppSettings.Settings.AmazonAccessKeyId = firstline[idxID]; + AppSettings.Settings.AmazonSecretKey = firstline[idxSECRET]; + } + else + { + error = $"Error: Could not find 'Access Key ID' or 'Secret Access key' columns in '{pth}'"; + } + } + else + { + error = $"Error: Too few lines in '{pth}'"; + } + } + else if (csv.Has("AWSAccessKeyId=")) + { + //old format + string tid = csv.GetWord("AWSAccessKeyId=", "\r|\n"); + string tsid = csv.GetWord("AWSSecretKey=", "\r|\n|"); + if (!string.IsNullOrEmpty(tid) && tid != AppSettings.Settings.AmazonAccessKeyId) + AppSettings.Settings.AmazonAccessKeyId = tid; + if (!string.IsNullOrEmpty(tsid) && tsid != AppSettings.Settings.AmazonSecretKey) + AppSettings.Settings.AmazonSecretKey = tsid; + + } + else + { + error = $"Error: File is an unknown format '{pth}'"; + } + + } + else + { + error = $"Error: Empty file '{pth}'"; + } + + } + else + { + error = $"Could not find AWS credentials file '{pth}'"; + } + + if (string.IsNullOrWhiteSpace(AppSettings.Settings.AmazonAccessKeyId) || string.IsNullOrWhiteSpace(AppSettings.Settings.AmazonSecretKey)) + { + error = "Please download 'rootkey.csv' and place it in AITOOL _SETTINGS folder. 1) Sign up for AWS, 2) Create user 3) Export rootkey.csv when prompted. https://docs.aws.amazon.com/rekognition/latest/dg/setting-up.html Please note that you have to CREATE NEW in order to see rootkey.csv if one already exists: https://console.aws.amazon.com/iam/home#/security_credentials"; + } + } + else + { + error = error + "- Please close AITOOL and set 'AmazonRegionEndpoint' in AITOOL.SETTTINGS.JSON to a region code near you such as 'us-east-1': https://docs.aws.amazon.com/general/latest/gr/rande.html"; + } + + return error; + + } + + public static void UpdateAIURLList(bool Force = false) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + AIURLListAvailableRefineServerCount = 0; + //double check all the URL's have a new httpclient + foreach (ClsURLItem url in AppSettings.Settings.AIURLList) + { + url.Update(false); + url.AITimeCalcs.UpdateDate(true); + + if (url.IsValid && url.UseAsRefinementServer) + AIURLListAvailableRefineServerCount++; + + if (url.Type != URLTypeEnum.AWSRekognition_Objects && url.Type != URLTypeEnum.AWSRekognition_Faces && url.HttpClient == null) + { + url.HttpClient = new HttpClient(); + url.HttpClient.Timeout = url.GetTimeout(); + } + + } + + //remove dupes + List newlist = new List(); + foreach (ClsURLItem url in AppSettings.Settings.AIURLList) + { + if (!newlist.Contains(url)) + newlist.Add(url); + } + AppSettings.Settings.AIURLList = newlist; + + + + //Check to see if we need to get updated URL list - In theory this should only happen once + bool hasold = !string.IsNullOrEmpty(AppSettings.Settings.deepstack_url); + if (((AppSettings.Settings.AIURLList.Count == 0 || Force) && hasold) || hasold) + { + int newcnt = 0; + + try + { + Log("Debug: Updating/Resetting AI URL list..."); + List SpltURLs = AppSettings.Settings.deepstack_url.SplitStr("|;,"); + + //I want to reuse any object that already exists for the url but make sure to get the right order if it changes + Dictionary tmpdic = new Dictionary(); + + foreach (ClsURLItem url in AppSettings.Settings.AIURLList) + { + string ur = url.ToString().ToLower(); + if (!tmpdic.ContainsKey(ur)) + { + tmpdic.Add(ur, url); + } + else + { + Log($"Debug: ---- (duplicate url configured - {ur})"); + } + } + + AppSettings.Settings.AIURLList.Clear(); + + + for (int i = 0; i < SpltURLs.Count; i++) + { + if (!SpltURLs[i].Contains(":")) + { + Log($"Error: Skipping old server name migration because it doesn't have a port: '{SpltURLs[i]}'"); + continue; + } + + ClsURLItem url = null; + try + { + url = new ClsURLItem(SpltURLs[i], i + 1, URLTypeEnum.Unknown); + } + catch (Exception ex) { Log($"Error: url='{SpltURLs[i]}': {ex.Msg()}"); } + + if (url != null && url.IsValid) + { + //if it already exists, use it, otherwise add a new one + if (tmpdic.ContainsKey(url.ToString().ToLower())) + { + url = tmpdic[url.ToString().ToLower()]; + AppSettings.Settings.AIURLList.Add(url); + url.Order = i + 1; + //url.InUse = false; + url.CurErrCount = 0; + url.Enabled = true; + Log($"Debug: ---- #{url.Order}: Re-added known URL: '{url}'"); + } + else + { + newcnt++; + AppSettings.Settings.AIURLList.Add(url); + Log($"Debug: ---- #{url.Order}: Added new URL: '{url}'"); + } + + } + else + { + Log($"Debug: ---- #{url.Order}: Skipped INVALID URL: '{SpltURLs[i]}'"); + + } + } + + } + catch (Exception ex) + { + Log($"Error: {ex.Msg()}"); + } + + Log($"Debug: ...{newcnt} new AI URL's migrated from old settings, with a total of {AppSettings.Settings.AIURLList.Count} AI URL's"); + + AppSettings.Settings.deepstack_url = ""; //we are not going to use this any longer + //AIURLSettingsChanged = false; + + } + + + //add a default DeepStack server if none found + //if (AppSettings.Settings.AIURLList.Count == 0) + //{ + // Log($"Debug: ---- Adding default Deepstack AI Server URL."); + // AppSettings.Settings.AIURLList.Add(new ClsURLItem("", 1, 1, URLTypeEnum.DeepStack)); + //} + + } + + public static async Task> WaitForNextURL(Camera cam, bool GetRefinementServer, List predictions = null, string RequiredLinkURLList = "", List MainURLs = null) + { + //lets wait in here forever until a URL is available... Unless trying to get a refinement server + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + List ret = new List(); + + DateTime LastWaitingLog = DateTime.MinValue; + bool displayedbad = false; + bool displayedretry = false; + List CurrentURLs = new List(); + List LinkedRequiredURLs = RequiredLinkURLList.SplitStr(",;|", ToLower: true); + bool GetLinkedRequiredList = LinkedRequiredURLs.Count > 0; + int FoundRequiredCount = 0; + int FoundRefinementCount = 0; + int RefineNoMatchCount = 0; + int RefineMatchCount = 0; + string refinepreds = ""; + DateTime now = DateTime.Now; + + Stopwatch sw = Stopwatch.StartNew(); + + try + { + AIURLListAvailableRefineServerCount = 0; + + //preprocess a few things... + for (int i = 0; i < AppSettings.Settings.AIURLList.Count; i++) + { + AppSettings.Settings.AIURLList[i].RefinementUseCurrentlyValid = false; //assume false at start of each loop + + if (AppSettings.Settings.AIURLList[i].UseAsRefinementServer) + { + bool notenabled = !AppSettings.Settings.AIURLList[i].Enabled; + bool notintime = !Global.IsTimeBetween(now, AppSettings.Settings.AIURLList[i].ActiveTimeRange); + bool notinlist = !Global.IsInList(cam.Name, AppSettings.Settings.AIURLList[i].Cameras, TrueIfEmpty: true); + if (notenabled || notintime || notinlist) + { + AppSettings.Settings.AIURLList[i].LastTestedTime = DateTime.Now; + AppSettings.Settings.AIURLList[i].LastSkippedReason = $"Refine: NotEnabled={notenabled}, NotInTime={notintime}, NotInCamList={notinlist}"; + continue; + } + + //dont let a refinement server be the same as the main server + if (MainURLs != null && MainURLs.Count > 0) + { + bool fnd = false; + foreach (ClsURLItem url in MainURLs) + { + if (string.Equals(AppSettings.Settings.AIURLList[i].ToString(), url.ToString(), StringComparison.OrdinalIgnoreCase)) + { + fnd = true; + break; + } + } + if (fnd) + { + AppSettings.Settings.AIURLList[i].LastTestedTime = DateTime.Now; + AppSettings.Settings.AIURLList[i].LastSkippedReason = $"Refine: FoundSameAsMainServer={fnd}"; + continue; + } + } + + AIURLListAvailableRefineServerCount++; + + if (GetRefinementServer && predictions != null) + { + //set temp flag to indicate if the server can be CURRENTLY used as a refinement server + foreach (ClsPrediction pred in predictions) + { + bool fnd = false; + + if (pred.Result == ResultType.Relevant) + { + refinepreds += pred.Label + ","; + if (AppSettings.Settings.AIURLList[i].RefinementObjects.Has("animal") && pred.ObjType == ObjectType.Animal) + fnd = true; + else if (AppSettings.Settings.AIURLList[i].RefinementObjects.Has("person") || AppSettings.Settings.AIURLList[i].RefinementObjects.Has("people") && pred.ObjType == ObjectType.Person) + fnd = true; + else if (AppSettings.Settings.AIURLList[i].RefinementObjects.Has("vehicle") && pred.ObjType == ObjectType.Vehicle) + fnd = true; + else if (Global.IsInList(pred.Label, AppSettings.Settings.AIURLList[i].RefinementObjects)) + fnd = true; + + if (fnd) + { + RefineMatchCount++; + AppSettings.Settings.AIURLList[i].RefinementUseCurrentlyValid = true; + break; + } + } + + } + + + if (!AppSettings.Settings.AIURLList[i].RefinementUseCurrentlyValid) + { + AppSettings.Settings.AIURLList[i].LastTestedTime = DateTime.Now; + AppSettings.Settings.AIURLList[i].LastSkippedReason = $"Refine: No matched refine objects"; + RefineNoMatchCount++; //count number of failed tries to match refinement server objects + } + } + + } + } + + while (ret.Count == 0) + { + int disabled = 0; + int inuse = 0; + int incorrectcam = 0; + int notintimerange = 0; + int maxpermonth = 0; + int notrefined = 0; + int notrequired = 0; + int onlylinked = 0; + int notonline = 0; + + //If no refinement servers or less than should be available were returned + if (GetRefinementServer && RefineMatchCount == 0) + { + Log($"Debug: ---- Refinement server requested, but none were available for predictions '{refinepreds}'. ({sw.ElapsedMilliseconds}ms)"); + break; + } + + try + { + UpdateAIURLList(); + + List sortedurls = new List(); + + if (AppSettings.Settings.deepstack_urls_are_queued) + { + //always use oldest first + sortedurls = AppSettings.Settings.AIURLList.OrderBy((d) => d.LastUsedTime).ToList(); + } + else + { + //use original order + sortedurls.AddRange(AppSettings.Settings.AIURLList); + } + //sort by oldest last used + + //quick initial count of valid servers + int ValidServerCnt = 0; + foreach (ClsURLItem url in sortedurls) + { + if (url.IsValid && url.Enabled && !url.UseOnlyAsLinkedServer) //may need to tweak this to exclude refinement/linked only servers? + ValidServerCnt++; + } + + + for (int i = 0; i < sortedurls.Count; i++) + { + ClsURLItem url = sortedurls[i]; + + if (i > 0 && !GetRefinementServer && !GetLinkedRequiredList) + { + int debugging = 0; + } + + now = DateTime.Now; + + if (!url.Enabled) + { + url.LastTestedTime = now; // (Write here, right now + // Watching the world wake up from history) + url.LastSkippedReason = url.LastSkippedReason.Prepend("NotEnabled"); + continue; + } + + if (!url.ErrDisabled) + { + if (url.IsServerReady(AITOOL.ImageProcessQueue.Count, ValidServerCnt)) + { + if (Global.IsInList(cam.Name, url.Cameras, TrueIfEmpty: true)) + { + if (GetRefinementServer && url.UseAsRefinementServer || !url.UseAsRefinementServer) + { + if (url.MaxImagesPerMonth == 0 || url.AITimeCalcs.CountMonth <= url.MaxImagesPerMonth) + { + + if (Global.IsTimeBetween(now, url.ActiveTimeRange)) + { + //if set to ignoreconnection errors do an extra ping check to see if the server is available. If not, skip it + if (url.IgnoreOfflineError) + { + if (!await url.CheckIfOnlineAsync()) + { + url.LastTestedTime = now; + url.LastSkippedReason = url.LastSkippedReason.Prepend("NotOnline"); + notonline++; + continue; + } + } + if (url.CurErrCount == 0) + { + + if (url.UseOnlyAsLinkedServer && (GetRefinementServer || (!GetLinkedRequiredList))) + { + url.LastTestedTime = now; + url.LastSkippedReason = url.LastSkippedReason.Prepend("UseOnlyAsLinked"); + onlylinked++; + continue; + } + + if (GetRefinementServer) + { + if (url.RefinementUseCurrentlyValid) + { + url.LastResultMessage = "Working [refinement]..."; + url.LastSkippedReason = url.LastSkippedReason.Prepend("[UsedRefine]"); + url.CurOrder = i + 1; + url.IncrementQueue(now); + FoundRefinementCount++; + ret.Add(url); + // Dont break out of loop since we may need more than one refinement server + } + else + { + url.LastTestedTime = now; + if (!url.UseAsRefinementServer) + url.LastSkippedReason = url.LastSkippedReason.Prepend("RefineNotRequired"); + } + + } + else if (GetLinkedRequiredList) + { + + if (Global.IsInList(url.ToString(), LinkedRequiredURLs, TrueIfEmpty: false)) + { + url.LastResultMessage = "Working [Linked]..."; + url.LastSkippedReason = url.LastSkippedReason.Prepend("[UsedLinked]"); + url.CurOrder = i + 1; + url.IncrementQueue(now); + FoundRequiredCount++; + ret.Add(url); + //dont break out of loop since we may need more than one linked/required URL + } + else + { + url.LastTestedTime = now; + if (!url.UseAsRefinementServer) ; + url.LastSkippedReason = url.LastSkippedReason.Prepend("RefineNotRequired"); + notrequired++; + } + } + else if (!url.UseAsRefinementServer) + { + url.LastResultMessage = "Working..."; + url.LastSkippedReason = url.LastSkippedReason.Prepend("[Used]"); + url.CurOrder = i + 1; + url.IncrementQueue(now); + ret.Add(url); + break; + } + else + { + url.LastTestedTime = now; + url.LastSkippedReason = url.LastSkippedReason.Prepend("NoConditionsMet?"); + } + } + else + { + double secs = Math.Round((now - url.LastUsedTime).TotalSeconds, 0); + if (secs >= AppSettings.Settings.MinSecondsBetweenFailedURLRetry) + { + url.LastResultMessage = "Working..."; + url.LastSkippedReason = url.LastSkippedReason.Prepend("[UsedAfterRetry]"); + url.CurOrder = i + 1; + url.IncrementQueue(now); + ret.Add(url); + if (!displayedretry) //if we get in a long loop waiting for URL + { + Log($"---- Trying previously failed URL again after {secs} seconds. (ErrCount={url.CurErrCount}, Setting 'MinSecondsBetweenFailedURLRetry'={AppSettings.Settings.MinSecondsBetweenFailedURLRetry}): {url}"); + displayedretry = true; + } + break; + } + else + { + url.LastTestedTime = now; + url.LastSkippedReason = url.LastSkippedReason.Prepend("WaitingForRetry"); + if (!displayedbad) //if we get in a long loop waiting for URL + { + Log($"---- Waiting {AppSettings.Settings.MinSecondsBetweenFailedURLRetry - secs} seconds before retrying bad URL. (ErrCount={url.CurErrCount} of {AppSettings.Settings.MaxQueueItemRetries}, Setting 'MinSecondsBetweenFailedURLRetry'={AppSettings.Settings.MinSecondsBetweenFailedURLRetry}): {url}"); + displayedbad = true; + } + } + + } + } + else + { + url.LastTestedTime = now; + url.LastSkippedReason = url.LastSkippedReason.Prepend("NotInTimeRange"); + notintimerange++; + } + + } + else + { + url.LastTestedTime = now; + url.LastSkippedReason = url.LastSkippedReason.Prepend("MaxImagesPerMonthMet"); + maxpermonth++; + } + + } + else + { + url.LastTestedTime = now; + url.LastSkippedReason = url.LastSkippedReason.Prepend("NotRefinementServer"); + notrefined++; + } + + } + else + { + url.LastTestedTime = now; + url.LastSkippedReason = url.LastSkippedReason.Prepend("NotInCamList"); + incorrectcam++; + } + + } + else + { + url.LastTestedTime = now; + url.LastSkippedReason = url.LastSkippedReason.Prepend(url.NotReadyReason); + inuse++; + + if (!GetRefinementServer && !GetLinkedRequiredList) { int debugging = 0; } + + } + } + //disabled, but check to see if we need to reenable + else + { + double mins = (DateTime.Now - url.LastUsedTime).TotalMinutes; + url.LastTestedTime = now; + url.LastSkippedReason = url.LastSkippedReason.Prepend("ErrDisabled"); + + disabled++; + if (mins >= AppSettings.Settings.URLResetAfterDisabledMinutes) + { + //check to see if can be re-enabled yet + url.ErrDisabled = false; + url.CurErrCount = 0; + url.DecrementQueue(); + url.LastResultMessage = "(Re-enabled)"; + + Log($"---- Re-enabling disabled URL because {AppSettings.Settings.URLResetAfterDisabledMinutes} (URLResetAfterDisabledMinutes) minutes have passed: " + url); + url.CurOrder = i + 1; + url.IncrementQueue(); + ret.Add(url); + break; + } + } + } + + } + catch (Exception ex) + { + Log("Error: getting next URL: " + ex.ToString()); + } + + + if ((GetLinkedRequiredList && (FoundRequiredCount >= LinkedRequiredURLs.Count)) || + (GetRefinementServer && (FoundRefinementCount >= RefineMatchCount))) + { + break; + } + + if ((GetRefinementServer || GetLinkedRequiredList) && sw.ElapsedMilliseconds >= AppSettings.Settings.MaxWaitForAIServerMS) + { + string ew = "Debug:"; + if (AppSettings.Settings.MaxWaitForAIServerTimeoutError) + ew = "Error:"; + + if (GetRefinementServer) + Log($"{ew} ---- URL request for REFINEMENT AI Server timed out, but only '{ret.Count}' of '{RefineMatchCount}' available. ({sw.ElapsedMilliseconds}ms - Setting in AITOOL.SETTINGS.JSON 'MaxWaitForAIServerMS' and is set to {AppSettings.Settings.MaxWaitForAIServerMS})"); + else if (GetLinkedRequiredList) + Log($"{ew} ---- URL request for LINKED AI Server timed out, but only '{ret.Count}' of '{LinkedRequiredURLs.Count}' available. ({sw.ElapsedMilliseconds}ms) - Setting in AITOOL.SETTINGS.JSON 'MaxWaitForAIServerMS' and is set to {AppSettings.Settings.MaxWaitForAIServerMS})"); + break; + } + + //otherwise + if (ret.Count > 0) + { + break; + } + + if ((DateTime.Now - LastWaitingLog).TotalMinutes >= 5) + { + Log($"---- All URL's are in use, disabled, camera name doesnt match or time range was not met. ({inuse} inuse, {disabled} disabled, {incorrectcam} wrong camera, {notintimerange} not in time range, {maxpermonth} at max per month limit, {notrefined} not refinement server, {onlylinked} only use as linked server) Waiting..."); + LastWaitingLog = DateTime.Now; + } + + + //short wait + await Task.Delay(AppSettings.Settings.loop_delay_ms); + + } + + //===================================================================================================== + + sw.Stop(); + + + //see if any servers have 'LINKED' servers + if (!GetRefinementServer && !GetLinkedRequiredList && ret.Count > 0) + { + List linked = new List(); + foreach (ClsURLItem url in ret) + { + if (url.LinkServerResults && !string.IsNullOrEmpty(url.LinkedResultsServerList)) + { + linked.AddRange(await WaitForNextURL(cam, false, null, url.LinkedResultsServerList)); + } + } + if (linked.Count > 0) + { + Log($"Debug: ---- Found '{linked.Count}' linked AI URL's."); + ret.AddRange(linked); + } + } + + + } + catch (Exception ex) + { + + Log($"Error: {ex.Msg()}"); + } + + //remove any dupes just in case + ret = ret.Distinct().ToList(); + + + return ret; + + + } + + static ThreadSafe.Integer CurRunningDetectTasks = new ThreadSafe.Integer(0); + + public static async Task ImageQueueLoop() + { + //This runs in another thread, waiting for items to appear in the queue and process them one at a time + try + { + //Thread.CurrentThread.Priority = ThreadPriority.BelowNormal; + + ClsImageQueueItem CurImg; + + DateTime LastCleanDupesTime = DateTime.MinValue; + + + //lets wait 5 seconds to let the UI settle down a bit + await Task.Delay(5000); + + //Start infinite loop waiting for images to come into queue + ThreadSafe.Integer MaxThreadCnt = new ThreadSafe.Integer(0); + + while (true) + { + if (MasterCTS.IsCancellationRequested) + break; + + ThreadSafe.Integer ThreadCnt = new ThreadSafe.Integer(0); + + while (!ImageProcessQueue.IsEmpty) + { + + ThreadSafe.Integer ProcImgCnt = 0; + ThreadSafe.Integer ErrCnt = 0; + ThreadSafe.Long LastThreadInitTimeMS = 0; + ThreadSafe.DateTime NextDeepstackRestartTime = new ThreadSafe.DateTime(DateTime.MinValue, AppSettings.Settings.DateFormat); + + while (!ImageProcessQueue.IsEmpty) + { + //tiny delay to conserve cpu and allow more images to come in the queue if needed + //await Task.Delay(250); + + //get the next image + + if (ImageProcessQueue.TryDequeue(out CurImg)) + { + if (CurRunningDetectTasks > 1) + { + int testing = 0; + } + + Camera cam = GetCamera(CurImg.image_path, true); + + if (cam == null) + { + Log($"Error: Camera could not be found to match this image: {CurImg.image_path}"); + continue; + } + + //skip the image if its been in the queue too long + if ((DateTime.Now - CurImg.TimeAdded).TotalMinutes >= AppSettings.Settings.MaxImageQueueTimeMinutes) + { + Log($"...Taking image OUT OF QUEUE because it has been in there over 'MaxImageQueueTimeMinutes'. (QueueTime={(DateTime.Now - CurImg.TimeAdded).TotalMinutes.ToString("###0.0")}, Image ErrCount={CurImg.ErrCount}, Image RetryCount={CurImg.RetryCount}, ImageProcessQueue.Count={ImageProcessQueue.Count}: '{CurImg.image_path}'", "None", cam, CurImg); + continue; + } + + Stopwatch sw = Stopwatch.StartNew(); + + //wait for the next url to become available... + List urls = await WaitForNextURL(cam, false); + + sw.Stop(); + + //If we have any linked servers there may be more than one server that we send at the same time + foreach (ClsURLItem url in urls) + { + Log($"Debug: Adding task for file '{Path.GetFileName(CurImg.image_path)}' (Image QueueTime='{(DateTime.Now - CurImg.TimeAdded).TotalMinutes.ToString("###0.0")}' mins, CurRunningTasks={CurRunningDetectTasks}, URL Queue wait='{sw.ElapsedMilliseconds}ms', URLOrder={url.CurOrder}, URLOriginalOrder={url.Order}) on URL '{url}'", url.CurSrv, cam, CurImg); + + } + + sw.Start(); + + //This *should* start detection on another thread and return control to this thread right away. + Task.Run(async () => + { + + CurRunningDetectTasks++; + Global.SendMessage(MessageType.BeginProcessImage, CurImg.image_path); + + DetectObjectsResult result = await DetectObjects(CurImg, urls); //ai process image + + Global.SendMessage(MessageType.EndProcessImage, CurImg.image_path); + + foreach (ClsURLItem url in result.OutURLs) + { + if (!url.LastResultSuccess) + { + ErrCnt++; + url.ErrsInRowCount++; + + if (url.CurErrCount > 0) + { + if (url.CurErrCount < AppSettings.Settings.MaxQueueItemRetries) + { + //put url back in queue when done + + //Only send error if MaxWaitForAIServerTimeoutError is true + string ew = "Debug:"; + if (AppSettings.Settings.MaxWaitForAIServerTimeoutError) + ew = "Error:"; + if (url.LastResultMessage.Has("request timed out")) + { + Log($"{ew}...Problem with AI URL: '{url.LastResultMessage}' - '{url}' (URL ErrCount={url.CurErrCount}, max allowed of {AppSettings.Settings.MaxQueueItemRetries})", url.CurSrv, cam); + } + else + { + Log($"...Problem with AI URL: '{url.LastResultMessage}' - '{url}' (URL ErrCount={url.CurErrCount}, max allowed of {AppSettings.Settings.MaxQueueItemRetries})", url.CurSrv, cam); + } + } + else + { + url.ErrDisabled = false; + Log($"...Error: AI URL failed with '{url.LastResultMessage}' - for '{url.Type}' failed '{url.CurErrCount}' times. Disabling: '{url}'", url.CurSrv, cam); + } + + } + + if (url.ErrsInRowCount > AppSettings.Settings.MaxErrorsInARowBeforeDisable) + { + Log($"...Error: AI URL failed {url.ErrsInRowCount} times in a row. DISABLING: '{url}'", url.CurSrv, cam); + url.ErrDisabled = true; + } + + if (url.ErrsInRowCount >= AppSettings.Settings.deepstack_autorestart_fail_count && + AppSettings.Settings.deepstack_autostart && + DeepStackServerControl.IsInstalled && + DeepStackServerControl.URLS.IndexOf(url.ToString(), StringComparison.OrdinalIgnoreCase) >= 0) + + { + if (NextDeepstackRestartTime == DateTime.MinValue) + NextDeepstackRestartTime = DateTime.Now; + + double mins = (DateTime.Now - NextDeepstackRestartTime).TotalMinutes; + double togo = (AppSettings.Settings.deepstack_autorestart_minutes_between_restart_attempts - mins); + + if (!DeepStackServerControl.Starting && + DateTime.Now >= NextDeepstackRestartTime) + { + Log($"Error: Locally installed deepstack instance failed {url.ErrsInRowCount} times in a row. (autorestart_fail_count={AppSettings.Settings.deepstack_autorestart_fail_count}) Restarting Deepstack..."); + //dont wait for it + await DeepStackServerControl.StartDeepstackAsync(true); + url.ErrsInRowCount = 0; + NextDeepstackRestartTime = DateTime.Now.AddSeconds(AppSettings.Settings.deepstack_autorestart_minutes_between_restart_attempts); + } + else + { + Log($"Error: Locally installed deepstack instance failed {url.ErrsInRowCount} times in a row. (autorestart_fail_count={AppSettings.Settings.deepstack_autorestart_fail_count}) Waiting {togo.ToString("##0.0")} mins before attempting restart..."); + } + + } + + CurImg.RetryCount++; //even if there was not an error directly accessing the image + + if (CurImg.ErrCount <= AppSettings.Settings.MaxQueueItemRetries && CurImg.RetryCount <= AppSettings.Settings.MaxQueueItemRetries) + { + //put back in queue to be processed by another deepstack server + Log($"...Putting image back in queue due to URL '{url}' problem (QueueTime={(DateTime.Now - CurImg.TimeAdded).TotalMinutes.ToString("###0.0")}, Image ErrCount={CurImg.ErrCount}, Image RetryCount={CurImg.RetryCount}, URL ErrCount={url.CurErrCount}): '{CurImg.image_path}', ImageProcessQueue.Count={ImageProcessQueue.Count}", url.CurSrv, cam, CurImg); + ImageProcessQueue.Enqueue(CurImg); + } + else + { + cam.stats_skipped_images++; + cam.stats_skipped_images_session++; + int timems = (int)(DateTime.Now - CurImg.TimeAdded).TotalMilliseconds; + + Log($"...Error: Removing image from queue. Image RetryCount={CurImg.RetryCount}, URL ErrCount='{url.CurErrCount}': {url}', Image: '{CurImg.image_path}', ImageProcessQueue.Count={ImageProcessQueue.Count}, Skipped this session={cam.stats_skipped_images_session}", url.CurSrv, cam, CurImg); + Global.CreateHistoryItem(new History().Create(CurImg.image_path, DateTime.Now, cam.Name, $"Skipped image, {CurImg.RetryCount} errors processing.", "", false, "", url.CurSrv, timems, false)); + + } + } + else + { + ProcImgCnt++; + //reset error count + url.CurErrCount = 0; + url.ErrsInRowCount = 0; + } + + url.DecrementQueue(); + + } + CurRunningDetectTasks.Decrement(0); + + }); + + sw.Stop(); + LastThreadInitTimeMS = sw.ElapsedMilliseconds; + } + else + { + //Log("No Images left in the queue!"); + break; + } + + } + + if (CurRunningDetectTasks > 0) + { + Log($"Debug: Done adding. {CurRunningDetectTasks} total threads running, LastThreadInitTimeMS={LastThreadInitTimeMS}, ErrCnt={ErrCnt}, ImageProcessQueue.Count={ImageProcessQueue.Count}"); + } + + //Clean up old images in the dupe check dic + if ((DateTime.Now - LastCleanDupesTime).TotalMinutes >= 60) + { + int cnt = 0; + foreach (KeyValuePair kvPair in image_detection_dictionary) + { + if ((DateTime.Now - kvPair.Value).TotalMinutes >= 30) + { // Remove expired item. + cnt++; + //ClsImageQueueItem removedItem; + image_detection_dictionary.TryRemove(kvPair.Key, out _); + } + } + + } + + } + + if (MasterCTS.IsCancellationRequested) + break; + + //Only loop 10 times a second conserve cpu + await Task.Delay(AppSettings.Settings.loop_delay_ms); + } + + Log("Debug: ImageQueueLoop canceled."); + + } + catch (Exception ex) + { + //if we get here its the end of the world as we know it + Log("Error: * '...Human sacrifice, dogs and cats living together – mass hysteria!' * - " + ex.Msg()); + } + } + + //EVENT: new image added to input_path -> START AI DETECTION + private static void OnCreated(object source, FileSystemEventArgs e) + { + AddImageToQueue(e.FullPath); + } + + public static void AddImageToQueue(string Filename) + { + + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + PlayTick(TickImageAddedPlayer); + + lock (FileWatcherLockObject) + { + try + { + //make sure we are not processing a duplicate file... + if (image_detection_dictionary.ContainsKey(Filename.ToLower())) + { + Log("Skipping image because of duplicate Created File Event: " + Filename); + } + else + { + Camera cam = GetCamera(Filename, true); + if (cam != null) //only put in queue if we can match to camera (even default) + { + + if (cam.enabled) + { + if (!(cam.Paused && cam.PauseFileMon)) + { + //Note: Interwebz says ConCurrentQueue.Count may be slow for large number of items but I dont think we have to worry here in most cases + int qsize = ImageProcessQueue.Count + 1; + if (qsize > AppSettings.Settings.MaxImageQueueSize) + { + Log(""); + Log($"Error: Skipping image because queue ({qsize}) is greater than '{AppSettings.Settings.MaxImageQueueSize}'. (Adjust 'MaxImageQueueSize' in .JSON file if needed): " + Filename, "", cam, Filename); + } + else + { + Log("Debug: "); + Log($"Debug: ====================== Adding new image to queue (Count={ImageProcessQueue.Count + 1}): " + Filename, "", cam, Filename); + ClsImageQueueItem CurImg = new ClsImageQueueItem(Filename, qsize); + image_detection_dictionary.TryAdd(Filename.ToLower(), DateTime.Now); + ImageProcessQueue.Enqueue(CurImg); + scalc.AddToCalc(qsize); + Global.SendMessage(MessageType.ImageAddedToQueue); + } + + } + else + { + Log($"Debug: Skipping image because camera '{cam}' file monitoring is PAUSED " + Filename, "", cam, Filename); + } + + } + else + { + Log($"Debug: Skipping image because camera '{cam}' is DISABLED " + Filename, "", cam, Filename); + } + } + else + { + Log("Error: Skipping image because no camera found for new image " + Filename, "", cam, Filename); + } + + + } + + } + catch (Exception ex) + { + Log("Error: " + ex.Msg()); + } + + } + + + } + //event: image in input_path renamed + private static void OnRenamed(object source, RenamedEventArgs e) + { + Global.DeleteHistoryItem(e.OldFullPath); + } + + //event: image in input path deleted + private static void OnDeleted(object source, FileSystemEventArgs e) + { + Global.DeleteHistoryItem(e.FullPath); + } + + private static void OnError(object sender, System.IO.ErrorEventArgs e) + { + //Too many changes at once in directory:C:\BlueIris\aiinput. + //File watcher The specified network name is no longer available + string path = ((FileSystemWatcher)sender).Path; + Log("Error: File watcher error: " + e.GetException().Message + $" on path '{path}'"); + UpdateWatchers(true); + + } + + public static void TimerCheckFileSystemWatchers(object sender, System.Timers.ElapsedEventArgs e) + { + if (FileWatcherHasError) + { + Log($"Debug: Re-checking bad File System Watcher Paths ('FileSystemWatcherRetryOnErrorTimeMS' = {AppSettings.Settings.FileSystemWatcherRetryOnErrorTimeMS}ms)..."); + UpdateWatchers(true); + } + } + + public static SemaphoreSlim Semaphore_Watcher_Updating = new SemaphoreSlim(1, 1); + + public static async Task UpdateWatchers(bool Reset) + { + await Semaphore_Watcher_Updating.WaitAsync(); + + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + try + { + if (AppSettings.AlreadyRunning) + { + Log("*** Another instance is already running, skip watching for changed files ***"); + return; + } + + FileWatcherHasError = false; + + Global.UpdateProgressBar($"Updating watched folders'...", 1, 1, 1); + + //first add all the names and paths to check... + List names = new List(); + Dictionary paths = new Dictionary(); + + string pths = AppSettings.Settings.input_path.Trim().TrimEnd(@"\".ToCharArray()); + names.Add($"INPUT_PATH|{pths}|{AppSettings.Settings.input_path_includesubfolders}"); + foreach (Camera cam in AppSettings.Settings.CameraList) + { + if (cam.enabled && !String.IsNullOrWhiteSpace(cam.input_path)) + { + pths = cam.input_path.Trim().TrimEnd(@"\".ToCharArray()); + names.Add($"{cam.Name}|{pths}|{cam.input_path_includesubfolders}"); + } + } + + if (Reset) + { + foreach (ClsFileSystemWatcher watcher1 in watchers.Values) + { + if (watcher1 != null && watcher1.watcher != null) + { + try + { + watcher1.watcher.EnableRaisingEvents = false; + watcher1.watcher.Dispose(); + watcher1.watcher = null; + } + catch (Exception ex) + { + + Log($"Error: Failed to reset/clear watcher for folder '{watcher1.Name}' - {watcher1.Path}: {ex.Message}"); + } + } + } + watchers.Clear(); + } + + //check each one to see if needs to be added + foreach (string item in names) + { + List splt = item.SplitStr("|", false); + string name = splt[0]; + string path = splt[1]; + if (!string.IsNullOrWhiteSpace(path)) + { + bool include = Convert.ToBoolean(splt[2]); + if (!paths.ContainsKey(path.ToLower())) + { + paths.Add(path.ToLower(), path); + + if (!watchers.ContainsKey(name.ToLower())) + { + //this will return null if the path is invalid... + FileSystemWatcher curwatch = await CreateFileWatcherAsync(path, include); + if (curwatch != null) + { + ClsFileSystemWatcher mywtc = new ClsFileSystemWatcher(name, path, curwatch, include); + //add even if null to keep track of things + watchers.Add(name.ToLower(), mywtc); + } + } + else + { + //update path if needed, even to empty + watchers[name.ToLower()].Path = path; + if (watchers[name.ToLower()].watcher == null) + { + //could be null if path is bad + watchers[name.ToLower()].watcher = await CreateFileWatcherAsync(path, include); + } + } + } + else + { + Log($"Debug: Skipping duplicate path for '{name}': '{path}'"); + } + + } + + + } + + //check to see if any need disabling - a camera was deleted + foreach (ClsFileSystemWatcher watcher1 in watchers.Values) + { + bool fnd = false; + foreach (string item in names) + { + List splt = item.SplitStr("|"); + string name = splt[0]; + if (string.Equals(name, watcher1.Name, StringComparison.OrdinalIgnoreCase)) + { + fnd = true; + break; + } + } + if (!fnd) + { + watcher1.Path = ""; + } + + } + + + //enable or disable watchers + int enabledcnt = 0; + int disabledcnt = 0; + + Dictionary dupes = new Dictionary(); + + foreach (ClsFileSystemWatcher watcher in watchers.Values) + { + if (watcher.watcher != null) + { + if (!String.IsNullOrWhiteSpace(watcher.Path)) + { + if (!dupes.ContainsKey(watcher.Path.ToLower())) + { + if (watcher.Path != watcher.watcher.Path) + { + watcher.watcher.Path = watcher.Path; + Log($"Debug: Watcher '{watcher.Name}' changed from '{watcher.watcher.Path}' to '{watcher.Path}'."); + } + + if (watcher.IncludeSubdirectories != watcher.watcher.IncludeSubdirectories) + { + watcher.watcher.IncludeSubdirectories = watcher.IncludeSubdirectories; + Log($"Debug: Watcher '{watcher.Name}' IncludeSubdirectories changed from '{watcher.watcher.IncludeSubdirectories}' to '{watcher.IncludeSubdirectories}'."); + } + + if (watcher.watcher.EnableRaisingEvents != true) + { + enabledcnt++; + watcher.watcher.EnableRaisingEvents = true; + dupes.Add(watcher.Path.ToLower(), watcher.Path); + Log($"Debug: Watcher '{watcher.Name}' is now watching '{watcher.Path}'"); + } + + } + else + { + Log($"Debug: Watcher '{watcher.Name}' has a duplicate path, skipping '{watcher.Path}'"); + } + } + else + { + //make sure it is disabled + disabledcnt++; + + watcher.watcher.EnableRaisingEvents = false; + watcher.watcher.Dispose(); + watcher.watcher = null; + Log($"Debug: Watcher '{watcher.Name}' has an empty path, just disabled."); + } + + } + else if (!string.IsNullOrEmpty(watcher.Path)) + { + Log($"Error: Watcher '{watcher.Name}' disabled. INVALID PATH='{watcher.Path}'"); + } + else + { + //Log($"Watcher '{watcher.Name}' already disabled."); + } + + + } + + if (watchers.Count == 0) + { + Log("Debug: No FileSystemWatcher input folders defined yet."); + } + else + { + if (enabledcnt == 0) + { + Log("Debug: No NEW FileSystemWatcher input folders found."); + } + else + { + Log($"Debug: Enabled {enabledcnt} FileSystemWatchers."); + } + } + + + } + catch (Exception ex) + { + FileWatcherHasError = true; + Log($"Error: {ex.Msg()}"); + } + finally + { + Semaphore_Watcher_Updating.Release(); + } + + } + + static ThreadSafe.Boolean FileWatcherHasError = new ThreadSafe.Boolean(false); + + public static async Task CreateFileWatcherAsync(string path, bool IncludeSubdirectories = false, string filter = "*.jpg") + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + FileSystemWatcher watcher = null; + + try + { + // Be aware: https://stackoverflow.com/questions/1764809/filesystemwatcher-changed-event-is-raised-twice + + if (!String.IsNullOrWhiteSpace(path)) + { + Stopwatch sw = Stopwatch.StartNew(); + + if (await Global.DirectoryExistsAsync(path, 10000)) + { + watcher = new FileSystemWatcher(path); + watcher.Path = path; + watcher.Filter = filter; + watcher.IncludeSubdirectories = IncludeSubdirectories; + watcher.InternalBufferSize = 65536; //defaults to 8k, we are going max it out to try to prevent "too many changes at once in directory" + + //The 'default' is the bitwise OR combination of NotifyFilters.LastWrite | NotifyFilters.LastAccess | NotifyFilters.CreationTime | NotifyFilters.FileName | NotifyFilters.DirectoryName + watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName; + + //fswatcher events + watcher.Created += new FileSystemEventHandler(OnCreated); + watcher.Renamed += new RenamedEventHandler(OnRenamed); + watcher.Deleted += new FileSystemEventHandler(OnDeleted); + watcher.Error += new ErrorEventHandler(OnError); + + } + else + { + FileWatcherHasError = true; + Log($"Error: Path does not exist. Time={sw.ElapsedMilliseconds}ms: " + path); + } + } + } + catch (Exception ex) + { + FileWatcherHasError = true; + Log($"Error: {ex.Msg()}"); + } + + return watcher; + } + + public static ClsImageAdjust GetImageAdjustProfileByName(string name, bool ReturnDefault) + { + ClsImageAdjust ret = null; + ClsImageAdjust def = null; + + foreach (ClsImageAdjust ia in AppSettings.Settings.ImageAdjustProfiles) + { + if (string.Equals(name.Trim(), ia.Name.Trim(), StringComparison.OrdinalIgnoreCase)) + { + ret = ia; + } + if (string.Equals("Default", ia.Name.Trim(), StringComparison.OrdinalIgnoreCase)) + { + def = ia; + } + } + + if (ret == null && ReturnDefault) + ret = def; + + if (ret == null) + { + //ret = new ClsImageAdjust("Default"); + Log($"Error: Could not find Image Adjust profile that matches '{name}'"); + } + + return ret; + } + + public static bool HasImageAdjustProfile(string name) + { + bool ret = false; + + foreach (ClsImageAdjust ia in AppSettings.Settings.ImageAdjustProfiles) + { + if (string.Equals(name.Trim(), ia.Name.Trim(), StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + return ret; + } + + public static async Task ApplyImageAdjustProfileAsync(ClsImageAdjust IAProfile, string InputImageFile, string OutputImageFile) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + System.Drawing.Image retimg = null; + SixLabors.ImageSharp.Image ISImage = null; + MemoryStream IStream = new System.IO.MemoryStream(); + try + { + if (!string.IsNullOrEmpty(InputImageFile) && File.Exists(InputImageFile)) + { + bool SaveToFile = (!string.IsNullOrEmpty(OutputImageFile)); + + //SixLabors.ImageSharp.Configuration config = new Configuration(); + + ISImage = await SixLabors.ImageSharp.Image.LoadAsync(InputImageFile); + + + if (IAProfile.ImageWidth != -1 && IAProfile.ImageHeight != -1 && ISImage.Width != IAProfile.ImageWidth || ISImage.Height != IAProfile.ImageHeight) //hard coded size + { + Log($"Resizing image from {ISImage.Width},{ISImage.Height} to {IAProfile.ImageWidth},{IAProfile.ImageHeight}..."); + ISImage.Mutate(i => i.Resize(IAProfile.ImageWidth, IAProfile.ImageHeight)); + } + else if (IAProfile.ImageSizePercent > 0 && IAProfile.ImageSizePercent < 100) + { + double fractionalPercentage = (IAProfile.ImageSizePercent / 100.0); + double outputWidth = ISImage.Width * fractionalPercentage; + double outputHeight = ISImage.Height * fractionalPercentage; + + Log($"Resizing image to {IAProfile.ImageSizePercent} from {ISImage.Width},{ISImage.Height} to {outputWidth},{outputHeight}..."); + ISImage.Mutate(i => i.Resize(outputWidth.ToInt(), outputHeight.ToInt())); + } + + if (IAProfile.Brightness > 1 && IAProfile.Brightness < 100) + { + //A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged. + //Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing brighter results + //amount - The proportion of the conversion.Must be greater than or equal to 0. + Log($"Changing brightness by amount {IAProfile.Brightness}..."); + ISImage.Mutate(i => i.Brightness(IAProfile.Brightness)); + } + + if (IAProfile.Contrast > 1 && IAProfile.Contrast < 100) + { + //A value of 0 will create an image that is completely gray. A value of 1 leaves the input unchanged. + //Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing results with more contrast. + //amount - The proportion of the conversion. Must be greater than or equal to 0. + Log($"Changing contrast by amount {IAProfile.Contrast}..."); + ISImage.Mutate(i => i.Contrast(IAProfile.Contrast)); + } + + + //string tfile = Path.Combine(Environment.GetEnvironmentVariable("TEMP"), "_AITOOL\tmpimage.jpg"); + + //Save the image using the specified jpeg compression + Log($"Compressing jpeg to {IAProfile.JPEGQualityPercent}% quality..."); + SixLabors.ImageSharp.Formats.Jpeg.JpegEncoder encoder = new SixLabors.ImageSharp.Formats.Jpeg.JpegEncoder(); + //encoder.Quality = IAProfile.JPEGQualityPercent; + + // lets switch out the default encoder for jpeg to one + // that saves at 90 quality + Configuration.Default.ImageFormatsManager.SetEncoder(JpegFormat.Instance, new JpegEncoder() + { + Quality = IAProfile.JPEGQualityPercent + }); + + if (SaveToFile) + { + //save to file + await ISImage.SaveAsJpegAsync(OutputImageFile, encoder); + + } + else //assume we just need the image for viewing and send back an image + { + //save to stream + await ISImage.SaveAsJpegAsync(IStream, encoder); + + //read back from stream + //ISImage = await SixLabors.ImageSharp.Image.LoadAsync(IStream); + + retimg = System.Drawing.Image.FromStream(IStream); + } + + + } + else + { + Log("File does not exist: " + InputImageFile); + } + + } + catch (Exception ex) + { + + Log("Error: " + ex.Msg()); + } + finally + { + if (IStream != null) + IStream.Dispose(); + + if (ISImage != null) + ISImage.Dispose(); + + } + + return retimg; + + } + + + public class ClsAIServerResponse + { + public List Predictions = new List(); + public bool Success = false; + public string JsonString = ""; + public string Message = ""; + public string Error = ""; + public long SWPostTime = 0; + public HttpStatusCode StatusCode = HttpStatusCode.Unused; + public ClsURLItem AIURL = null; + } + + public static async Task GetDetectionsFromAIServer(ClsImageQueueItem CurImg, ClsURLItem AiUrl, Camera cam) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + ClsAIServerResponse ret = new ClsAIServerResponse(); + + ret.AIURL = AiUrl; + AiUrl.LastResultMessage = ""; + AiUrl.LastResultSuccess = false; + + bool OverrideThreshold = AiUrl.Threshold_Lower > 0 || (AiUrl.Threshold_Upper > 0 && AiUrl.Threshold_Upper < 100); + + //============================================================================================================== + //============================================================================================================== + //============================================================================================================== + if (AiUrl.Type.ToString().Has("codeproject") || AiUrl.Type == URLTypeEnum.DeepStack || AiUrl.Type == URLTypeEnum.DeepStack_Custom || AiUrl.Type == URLTypeEnum.DeepStack_Faces || AiUrl.Type == URLTypeEnum.DeepStack_Scene) + { + Stopwatch swposttime = new Stopwatch(); + + try + { + long FileSize = new FileInfo(CurImg.image_path).Length; + + using MultipartFormDataContent request = new MultipartFormDataContent(); + + using StreamContent sc = new StreamContent(CurImg.ToMemStream()); + + request.Add(sc, "image", Path.GetFileName(CurImg.image_path)); + + string overr = "(NoLowerThresholdOverride)"; + + double minconf = 0; + if (AppSettings.Settings.HistoryRestrictMinThresholdAtSource && !OverrideThreshold) + { + overr = $"(CAM_LowerThresholdOverride={cam.threshold_lower},Upper={cam.threshold_upper})"; + minconf = cam.threshold_lower; + } + else if (AppSettings.Settings.HistoryRestrictMinThresholdAtSource && OverrideThreshold) + { + overr = $"(URL_LowerThresholdOverride={AiUrl.Threshold_Lower},Upper={AiUrl.Threshold_Upper})"; + minconf = AiUrl.Threshold_Lower; + } + + double pc = 0; + + if (minconf > 0) + { + pc = minconf / 100; + overr += $"({pc})"; + StringContent scmc = new StringContent((pc).ToString().Replace(",", ".")); //replace comma with period in cases where the regional decimal symbol is a comma - Deepstack doesnt like that. + request.Add(scmc, "min_confidence"); + } + + Log($"Debug: (1/6) Uploading a {Global.FormatBytes(FileSize)} image to '{AiUrl.Type}' {overr} AI Server at {AiUrl}", AiUrl.CurSrv, cam, CurImg); + + swposttime.Restart(); + + + using HttpResponseMessage output = await AiUrl.HttpClient.PostAsync(AiUrl.ToString(), request, MasterCTS.Token); + + swposttime.Stop(); + ret.StatusCode = output.StatusCode; + ret.JsonString = await output.Content.ReadAsStringAsync(); + + ClsDeepStackResponse response = null; + string cleanjsonString = ""; + if (ret.JsonString != null && !string.IsNullOrWhiteSpace(ret.JsonString)) + { + cleanjsonString = ret.JsonString.CleanString(); + try + { + response = JsonConvert.DeserializeObject(ret.JsonString); + } + catch (Exception ex) + { + //deserialization did not cause exception, it just gave a null response in the object? + //probably wont happen but just making sure + ret.Error = $"ERROR: Deserialization of 'Response' from DeepStack failed. JSON: '{cleanjsonString}': {ex.Message}"; + AiUrl.IncrementError(); + AiUrl.LastResultMessage = ret.Error; + } + } + else + { + ret.Error = $"ERROR: Empty string returned from HTTP post?"; + AiUrl.IncrementError(); + AiUrl.LastResultMessage = ret.Error; + } + + if (output.IsSuccessStatusCode) + { + try + { + if (response != null) + { + if (!response.success || !string.IsNullOrWhiteSpace(response.error)) // || ) + { + string err = ""; + if (!string.IsNullOrWhiteSpace(response.error)) + err = response.error; + + bool MeshError = err.Has("Exception when forwarding request"); + + // if we would normally be ignoring offline errors, dont let the response be considered an error... + // JSON: '{"Error":"Exception when forwarding request to CDODGE8","Code":500,"Hostname":"K4","Success":false,"processedBy":"CDODGE8","timestampUTC":"Sun, 26 May 2024 01:03:10 GMT"}' + + // extract the mesh hostname from the json and try to match it to an existing ai server so we can check its IgnoreOfflineError setting: + //"processedBy":"CDODGE8", + ClsURLItem fndurl = AiUrl; + bool IgnoreOfflineErr = false; + if (MeshError) + { + string meshhostname = cleanjsonString.GetWord("\"processedBy\":\"", "\","); + if (meshhostname.IsNotEmpty()) + { + ClsURLItem murl = AITOOL.GetURL(meshhostname, false, true); + if (murl != null) + fndurl = murl; + + IgnoreOfflineErr = fndurl.IgnoreOfflineError; + } + } + + if (IgnoreOfflineErr) + { + //we have a mesh error connecting to another computer, ignore it and remove error from json string so it is not flagged elsewhere + ret.Error = $"DEBUG: CPAI Mesh Failure on '{fndurl.Name}', ignoring due to 'IgnoreOfflineErr'. Type='{AiUrl.Type.ToString()}'. Failure='{err}'. JSON: '{cleanjsonString.Replace("Error", "Failure")}'"; + } + else + { + AiUrl.IncrementError(); + ret.Error = $"ERROR: Failure response from '{AiUrl.Type.ToString()}'. Error='{err}'. JSON: '{cleanjsonString}'"; + } + AiUrl.LastResultMessage = ret.Error; + } + else + { + List addto = new List(); + + //intialize array if none returned so we can add a scene if needed + if (response.predictions != null) + addto = response.predictions.ToList(); + + //check to see if we have a scene rather than normal detection and create a prediction from it + if (!string.IsNullOrEmpty(response.label) && response.confidence > 0) + { + //{'success': True, 'confidence': 0.7373981, 'label': 'conference_room' + ClsDeepstackDetection spred = new ClsDeepstackDetection(); + spred.label = $"Scene"; + spred.Detail = response.label; + spred.confidence = response.confidence; + //try to create a rectangle smaller than the image so the label will fit + spred.x_min = 5; + spred.y_min = 5; + spred.x_max = CurImg.Width - 5; + spred.y_max = CurImg.Height - 40; + addto.Add(spred); + } + + foreach (ClsDeepstackDetection DSObj in addto) + { + ClsPrediction pred = new ClsPrediction(ObjectType.Object, cam, DSObj, CurImg, AiUrl); + + ret.Predictions.Add(pred); + + } + + + ret.Success = true; + AiUrl.LastResultMessage = $"{ret.Predictions.Count} predictions found."; + if (response.message.IsNotEmpty()) + AiUrl.LastResultMessage += $" Message: '{response.message}'"; + + } + + } + else if (string.IsNullOrEmpty(ret.Error)) + { + //deserialization did not cause exception, it just gave a null response in the object? + //probably wont happen but just making sure + ret.Error = $"ERROR: Deserialization of 'Response' from DeepStack failed. response is null. JSON: '{cleanjsonString}'"; + AiUrl.IncrementError(); + AiUrl.LastResultMessage = ret.Error; + } + } + catch (Exception ex) + { + ret.Error = $"ERROR: Deserialization of 'Response' from '{AiUrl.Type.ToString()}' failed: {ex.Msg()}, JSON: '{cleanjsonString}'"; + AiUrl.IncrementError(); + AiUrl.LastResultMessage = ret.Error; + } + + + } + else + { + if (response != null && !string.IsNullOrEmpty(response.error)) + ret.Error = $"ERROR: AI Server '{AiUrl.Type}' returned '{response.error}' - http status code '{output.StatusCode}' ({Convert.ToInt32(output.StatusCode)}) in {swposttime.ElapsedMilliseconds}ms: {output.ReasonPhrase}"; + else + ret.Error = $"ERROR: Got http status code '{output.StatusCode}' ({Convert.ToInt32(output.StatusCode)}) in {swposttime.ElapsedMilliseconds}ms: {output.ReasonPhrase}"; + + AiUrl.IncrementError(); + AiUrl.LastResultMessage = ret.Error; + } + + } + catch (Exception ex) + { + swposttime.Stop(); + long seconds = swposttime.ElapsedMilliseconds / 1000; + if (seconds >= AiUrl.GetTimeout().TotalSeconds) + { + ret.Error = $"ERROR: HTTPClient timeout at {seconds} seconds ('HTTPClientTimeoutSeconds' is currently set to {AiUrl.GetTimeout().TotalSeconds} in AITOOL.Settings.JSON file.): {ex.Msg()}"; + } + else + { + ret.Error = $"ERROR: {ex.Msg()}"; + } + AiUrl.IncrementError(); + AiUrl.LastResultMessage = ret.Error; + } + finally + { + AiUrl.LastTimeMS = (int)swposttime.ElapsedMilliseconds; + ret.SWPostTime = (int)swposttime.ElapsedMilliseconds; + AiUrl.AITimeCalcs.AddToCalc(AiUrl.LastTimeMS); + + if (!string.IsNullOrEmpty(ret.Error)) + DeepStackServerControl.PrintDeepStackError(); //only prints error if we have locally installed windows deepstack and there is a new entry in stderr.txt + + } + + } + + //============================================================================================================== + //============================================================================================================== + //============================================================================================================== + + else if (AiUrl.Type.ToString().Has("sighthound")) + { + + if (string.IsNullOrEmpty(AppSettings.Settings.SightHoundAPIKey)) + { + ret.Success = false; + ret.Error = $"ERROR: No SightHound API key set. (SightHoundAPIKey in AITOOL.SETTINGS.JSON).'"; + AiUrl.IncrementError(); + AiUrl.LastResultMessage = ret.Error; + return ret; + } + + Stopwatch swposttime = new Stopwatch(); + + //WebRequest request = null; + //HttpWebResponse WebResponse = null; + //Stream requestStream = null; + + MultipartFormDataContent request = null; + //StreamContent stream = null; + + try + { + long FileSize = new FileInfo(CurImg.image_path).Length; + + request = new MultipartFormDataContent(); + + //stream = new StreamContent(CurImg.ToStream()); + //string base64Img = CurImg.ToStream().ConvertToBase64(); + //using StringContent imgstr = new StringContent(base64Img, Encoding.UTF8, "image/jpeg"); //Encoding.UTF8, "application/json" + + + //Dictionary dict = new Dictionary(); + Dictionary dict = new Dictionary(); + dict.Add("image", CurImg.ToMemStream().ConvertToBase64()); + string json = Global.GetJSONString((object)dict); //JsonConvert.SerializeObject((object)dict); + //byte[] body = Encoding.UTF8.GetBytes(json); + //ByteArrayContent content = new ByteArrayContent(body); + //HttpContent content = Global.CreateHttpContentString(dict); + StringContent content = new StringContent(json, Encoding.UTF8, "application/json"); + //request.Add(content); + + if (!AiUrl.HttpClient.DefaultRequestHeaders.Contains("X-Access-Token")) + { + AiUrl.HttpClient.DefaultRequestHeaders.Add("X-Access-Token", AppSettings.Settings.SightHoundAPIKey); + } + + //Dictionary dict = new Dictionary(); + //dict.Add("image", CurImg.ImageByteArray); + //string json = JsonConvert.SerializeObject((object)dict); + //byte[] body = Encoding.UTF8.GetBytes(json); + + //request = WebRequest.Create(AiUrl.ToString()); + + //request.Timeout = AppSettings.Settings.HTTPClientLocalTimeoutSeconds * 1000; + + //request.Method = "POST"; + //request.ContentType = "application/json"; + //request.ContentLength = json.Length; + //request.Headers["X-Access-Token"] = AppSettings.Settings.SightHoundAPIKey; + + Log($"Debug: (1/6) Uploading a {Global.FormatBytes(FileSize)} image ({FileSize} bytes in request) to '{AiUrl.Type}' AI Server at {AiUrl}", AiUrl.CurSrv, cam, CurImg); + + swposttime.Restart(); + + //requestStream = request.GetRequestStream(); + //requestStream.Write(body, 0, body.Length); + + //WebResponse = (HttpWebResponse)request.GetResponse(); + + + using HttpResponseMessage output = await AiUrl.HttpClient.PostAsync(AiUrl.ToString(), content, MasterCTS.Token); + + swposttime.Stop(); + ret.StatusCode = output.StatusCode; + ret.JsonString = await output.Content.ReadAsStringAsync(); + + //ret.StatusCode = WebResponse.StatusCode; + + //Successful queries will return a 200 (OK) response with a JSON body describing all detected objects and the attributes of the processed image. + if (output.IsSuccessStatusCode) + { + + //using StreamReader reader = new StreamReader(WebResponse.GetResponseStream(), Encoding.UTF8); + //ret.JsonString = reader.ReadToEnd(); + + swposttime.Stop(); + + if (ret.JsonString != null && !string.IsNullOrWhiteSpace(ret.JsonString)) + { + string cleanjsonString = ret.JsonString.CleanString(); + + try + { + + JObject JOResult = JObject.Parse(ret.JsonString); + + if (AiUrl.Type == URLTypeEnum.SightHound_Vehicle) + { + //Vehicle Recognition + + //This can throw an exception + SightHoundVehicleRoot SHObj = JsonConvert.DeserializeObject(ret.JsonString); + + if (SHObj != null) + { + if (SHObj.Objects != null) + { + + + if (SHObj.Objects.Count > 0) + { + foreach (SightHoundVehicleObject DSObj in SHObj.Objects) + { + //Get the vehicle and plate as 2 separate predictions + ClsPrediction predv = new ClsPrediction(ObjectType.Object, cam, DSObj, CurImg, AiUrl, SHObj.Image, false); + if (!string.IsNullOrEmpty(predv.Label)) + ret.Predictions.Add(predv); + ClsPrediction predp = new ClsPrediction(ObjectType.Object, cam, DSObj, CurImg, AiUrl, SHObj.Image, true); + if (!string.IsNullOrEmpty(predp.Label)) + ret.Predictions.Add(predp); + + } + } + + ret.Success = true; + AiUrl.LastResultMessage = $"{ret.Predictions.Count} Vehicle predictions found."; + } + else + { + ret.Error = $"ERROR: No Vehicle predictions? JSON: '{cleanjsonString}')"; + AiUrl.IncrementError(); + AiUrl.LastResultMessage = ret.Error; + } + + + } + else if (string.IsNullOrEmpty(ret.Error)) + { + //deserialization did not cause exception, it just gave a null response in the object? + //probably wont happen but just making sure + ret.Error = $"ERROR: Deserialization of 'Response' from DeepStack failed. response is null. JSON: '{cleanjsonString}'"; + AiUrl.IncrementError(); + AiUrl.LastResultMessage = ret.Error; + } + } + else if (AiUrl.Type == URLTypeEnum.SightHound_Person) + { + //face/person detection + + //This can throw an exception + SightHoundPersonRoot SHObj = JsonConvert.DeserializeObject(ret.JsonString); + + if (SHObj != null) + { + if (SHObj.Objects != null) + { + if (SHObj.Objects.Count > 0) + { + foreach (SightHoundPersonObject DSObj in SHObj.Objects) + { + ClsPrediction pred = new ClsPrediction(ObjectType.Object, cam, DSObj, CurImg, AiUrl, SHObj.Image); + ret.Predictions.Add(pred); + } + } + + ret.Success = true; + AiUrl.LastResultMessage = $"{ret.Predictions.Count} Person predictions found."; + + } + else + { + ret.Error = $"ERROR: No Person predictions? JSON: '{cleanjsonString}')"; + AiUrl.IncrementError(); + AiUrl.LastResultMessage = ret.Error; + } + + + } + else if (string.IsNullOrEmpty(ret.Error)) + { + //deserialization did not cause exception, it just gave a null response in the object? + //probably wont happen but just making sure + ret.Error = $"ERROR: Deserialization of 'Response' from DeepStack failed. response is null. JSON: '{cleanjsonString}'"; + AiUrl.IncrementError(); + AiUrl.LastResultMessage = ret.Error; + } + } + + + } + catch (Exception ex) + { + ret.Error = $"ERROR: Deserialization of 'Response' from '{AiUrl.Type.ToString()}' failed: {ex.Msg()}, JSON: '{cleanjsonString}'"; + AiUrl.IncrementError(); + AiUrl.LastResultMessage = ret.Error; + } + } + else + { + ret.Error = $"ERROR: Empty string returned from HTTP post."; + AiUrl.IncrementError(); + AiUrl.LastResultMessage = ret.Error; + } + + + } + else + { + //try to get the error response: + //{ + // "error": "ERROR MESSAGE" + // "reason": "reason for error", + // "reasonCode": 00000, + // "details": { + // "statusCode": 000, + // "statusMessage": "reason for error", + // "body": "description of error" + // } + // } + + //{ + // "error": "ERROR_MEDIA_DATA", + // "reason": "Cannot identify image format", + // "reasonCode": 40012, + // "details": { + // "statusCode": 406, + // "statusMessage": "Cannot identify image format", + // "body": "image error cannot identify image file (-6)" + // } + //} + + + string error = ""; + swposttime.Stop(); + if (ret.JsonString != null && !string.IsNullOrWhiteSpace(ret.JsonString)) + { + SightHoundError SHErr = JsonConvert.DeserializeObject(ret.JsonString); + if (!string.IsNullOrEmpty(SHErr.Details.Body)) + error = $"{SHErr.Error} (ReasonCode={SHErr.ReasonCode}): {SHErr.Details.Body}"; + else + error = $"{SHErr.Error} (ReasonCode={SHErr.ReasonCode}): {SHErr.Reason}"; + } + else + { + error = "(EmptyJsonResponse?)"; + } + + + swposttime.Stop(); + ret.Error = $"ERROR: Got http status code '{output.StatusCode}' ({Convert.ToInt32(output.StatusCode)}) - '{error}' in {swposttime.ElapsedMilliseconds}ms: {output.ReasonPhrase}"; + AiUrl.IncrementError(); + AiUrl.LastResultMessage = ret.Error; + } + + } + catch (Exception ex) + { + + string error = ""; + + swposttime.Stop(); + long seconds = swposttime.ElapsedMilliseconds / 1000; + if (seconds >= AiUrl.GetTimeout().TotalSeconds) + { + ret.Error = $"ERROR: HTTPClient timeout at {seconds} seconds (Max={AiUrl.GetTimeout().TotalSeconds} set in 'HTTPClientTimeoutSeconds' in aitool.settings.json): - '{error}': {ex.Msg()}"; + } + else + { + ret.Error = $"ERROR: '{error}': {ex.Msg()}"; + } + AiUrl.IncrementError(); + AiUrl.LastResultMessage = ret.Error; + } + finally + { + ret.SWPostTime = swposttime.ElapsedMilliseconds; + AiUrl.LastTimeMS = (int)swposttime.ElapsedMilliseconds; + AiUrl.AITimeCalcs.AddToCalc(AiUrl.LastTimeMS); + if (request != null) + request.Dispose(); + } + } + + //============================================================================================================== + //============================================================================================================== + //============================================================================================================== + else if (AiUrl.Type == URLTypeEnum.DOODS) + { + Stopwatch swposttime = new Stopwatch(); + + try + { + ClsDoodsRequest cdr = new ClsDoodsRequest(); + + //We could prevent doods from giving back ALL of its results but then we couldnt fully see how it was working: + //So many things come back from Doods, cluttering up the db, we really need to limit at the source + + if (AppSettings.Settings.HistoryRestrictMinThresholdAtSource && !OverrideThreshold) + cdr.Detect.MinPercentMatch = cam.threshold_lower; + + if (AppSettings.Settings.HistoryRestrictMinThresholdAtSource && OverrideThreshold) + cdr.Detect.MinPercentMatch = AiUrl.Threshold_Lower; + + cdr.DetectorName = AppSettings.Settings.DOODSDetectorName; + + //string testjson = JsonConvert.SerializeObject(cdr); + + long FileSize = new FileInfo(CurImg.image_path).Length; + + cdr.Data = CurImg.ToMemStream().ConvertToBase64(); + + using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, AiUrl.ToString())) + { + + using HttpContent httpContent = Global.CreateHttpContentString(cdr); + + request.Content = httpContent; + + Log($"Debug: (1/6) Uploading a {Global.FormatBytes(FileSize)} image to '{AiUrl.Type}' AI Server at {AiUrl}", AiUrl.CurSrv, cam, CurImg); + + + // Got http status code 'RequestEntityTooLarge' (413) in 42ms: Request Entity Too Large + swposttime.Restart(); + + using HttpResponseMessage output = await AiUrl.HttpClient.SendAsync(request, HttpCompletionOption.ResponseContentRead, MasterCTS.Token); + + swposttime.Stop(); + ret.StatusCode = output.StatusCode; + + + if (output.IsSuccessStatusCode) + { + ret.JsonString = await output.Content.ReadAsStringAsync(); + + if (ret.JsonString != null && !string.IsNullOrWhiteSpace(ret.JsonString)) + { + string cleanjsonString = ret.JsonString.CleanString(); + + ClsDoodsResponse response = null; + + try + { + //This can throw an exception + response = JsonConvert.DeserializeObject(ret.JsonString); + + if (response != null) + { + if (response.Detections != null) + { + if (response.Detections.Count > 0) + { + + foreach (ClsDoodsDetection DSObj in response.Detections) + { + ClsPrediction pred = new ClsPrediction(ObjectType.Object, cam, DSObj, CurImg, AiUrl); + + ret.Predictions.Add(pred); + + } + + + } + + ret.Success = true; + AiUrl.LastResultMessage = $"{ret.Predictions.Count} predictions found."; + + + } + else + { + ret.Error = $"ERROR: No predictions? JSON: '{cleanjsonString}')"; + AiUrl.IncrementError(); + AiUrl.LastResultMessage = ret.Error; + } + + + } + else if (string.IsNullOrEmpty(ret.Error)) + { + //deserialization did not cause exception, it just gave a null response in the object? + //probably wont happen but just making sure + ret.Error = $"ERROR: Deserialization of 'Response' from DeepStack failed. response is null. JSON: '{cleanjsonString}'"; + AiUrl.IncrementError(); + AiUrl.LastResultMessage = ret.Error; + } + } + catch (Exception ex) + { + ret.Error = $"ERROR: Deserialization of 'Response' from '{AiUrl.Type.ToString()}' failed: {ex.Msg()}, JSON: '{cleanjsonString}'"; + AiUrl.IncrementError(); + AiUrl.LastResultMessage = ret.Error; + } + } + else + { + ret.Error = $"ERROR: Empty string returned from HTTP post."; + AiUrl.IncrementError(); + AiUrl.LastResultMessage = ret.Error; + } + + + } + else + { + ret.Error = $"ERROR: Got http status code '{output.StatusCode}' ({Convert.ToInt32(output.StatusCode)}) in {swposttime.ElapsedMilliseconds}ms: {output.ReasonPhrase}"; + AiUrl.IncrementError(); + AiUrl.LastResultMessage = ret.Error; + } + + } + + + + } + catch (Exception ex) + { + swposttime.Stop(); + + long seconds = swposttime.ElapsedMilliseconds / 1000; + if (seconds >= AiUrl.GetTimeout().TotalSeconds) + { + ret.Error = $"ERROR: HTTPClient timeout at {seconds} seconds (Max={AiUrl.GetTimeout().TotalSeconds} set in 'HTTPClientTimeoutSeconds' in aitool.settings.json): {ex.Msg()}"; + } + else + { + ret.Error = $"ERROR: {ex.Msg()}"; + } + + ret.Error = $"ERROR: {ex.Msg()}"; + AiUrl.IncrementError(); + AiUrl.LastResultMessage = ret.Error; + } + finally + { + ret.SWPostTime = swposttime.ElapsedMilliseconds; + AiUrl.LastTimeMS = (int)swposttime.ElapsedMilliseconds; + AiUrl.AITimeCalcs.AddToCalc(AiUrl.LastTimeMS); + + } + + } + //============================================================================================================== + //============================================================================================================== + //============================================================================================================== + else if (AiUrl.Type == URLTypeEnum.AWSRekognition_Objects) + { + Stopwatch swposttime = new Stopwatch(); + + try + { + + //string testjson = JsonConvert.SerializeObject(cdr); + + long FileSize = new FileInfo(CurImg.image_path).Length; + //https://docs.aws.amazon.com/general/latest/gr/rande.html + + + RegionEndpoint endpoint = RegionEndpoint.GetBySystemName(AppSettings.Settings.AmazonRegionEndpoint); + AmazonRekognitionClient rekognitionClient = new AmazonRekognitionClient(new BasicAWSCredentials(AppSettings.Settings.AmazonAccessKeyId, AppSettings.Settings.AmazonSecretKey), endpoint); + + DetectLabelsRequest dlr = new DetectLabelsRequest(); + + dlr.MaxLabels = AppSettings.Settings.AmazonMaxLabels; + dlr.MinConfidence = AppSettings.Settings.AmazonMinConfidence; + + if (AppSettings.Settings.HistoryRestrictMinThresholdAtSource && !OverrideThreshold) + dlr.MinConfidence = cam.threshold_lower; + + if (AppSettings.Settings.HistoryRestrictMinThresholdAtSource && OverrideThreshold) + dlr.MinConfidence = AiUrl.Threshold_Lower; + + Amazon.Rekognition.Model.Image rekognitionImage = new Amazon.Rekognition.Model.Image(); + + //byte[] data = null; + + //using (FileStream fileStream = new FileStream(CurImg.image_path, FileMode.Open, FileAccess.Read)) + //{ + // data = new byte[fileStream.Length]; + // await fileStream.ReadAsync(data, 0, (int)fileStream.Length); + //} + + rekognitionImage.Bytes = CurImg.ToMemStream(); + + dlr.Image = rekognitionImage; + + + Log($"Debug: (1/6) Uploading a {Global.FormatBytes(FileSize)} image to '{AiUrl.Type}' AI Server at {AiUrl}", AiUrl.CurSrv, cam, CurImg); + + swposttime.Restart(); + + DetectLabelsResponse response = await rekognitionClient.DetectLabelsAsync(dlr); + + swposttime.Stop(); + + if (response != null) + { + ret.StatusCode = response.HttpStatusCode; + if (response.HttpStatusCode == System.Net.HttpStatusCode.OK) + { + if (response.Labels.Count > 0) + { + + foreach (Amazon.Rekognition.Model.Label lbl in response.Labels) + { + //not sure if there will ever be more than one instance + for (int i = 0; i < lbl.Instances.Count; i++) + { + ClsPrediction pred = new ClsPrediction(ObjectType.Object, cam, lbl, i, CurImg, AiUrl); + + ret.Predictions.Add(pred); + + } + + } + + + } + + ret.Success = true; + AiUrl.LastResultMessage = $"{ret.Predictions.Count} predictions found."; + } + else + { + ret.Error = $"ERROR: Amazon Rekognition 'HttpStatusCode' is '{response.HttpStatusCode}' ({Convert.ToInt32(response.HttpStatusCode)})."; + AiUrl.IncrementError(); + AiUrl.LastResultMessage = ret.Error; + } + } + else + { + ret.Error = $"ERROR: Amazon Rekognition 'Response' is null."; + AiUrl.IncrementError(); + AiUrl.LastResultMessage = ret.Error; + } + + + } + catch (Exception ex) + { + swposttime.Stop(); + + ret.Error = $"ERROR: {ex.Msg()}"; + AiUrl.IncrementError(); + AiUrl.LastResultMessage = ret.Error; + } + finally + { + ret.SWPostTime = swposttime.ElapsedMilliseconds; + AiUrl.LastTimeMS = (int)swposttime.ElapsedMilliseconds; + AiUrl.AITimeCalcs.AddToCalc(AiUrl.LastTimeMS); + } + + } + else if (AiUrl.Type == URLTypeEnum.AWSRekognition_Faces) + { + Stopwatch swposttime = new Stopwatch(); + + try + { + + //string testjson = JsonConvert.SerializeObject(cdr); + + long FileSize = new FileInfo(CurImg.image_path).Length; + //https://docs.aws.amazon.com/general/latest/gr/rande.html + + + RegionEndpoint endpoint = RegionEndpoint.GetBySystemName(AppSettings.Settings.AmazonRegionEndpoint); + AmazonRekognitionClient rekognitionClient = new AmazonRekognitionClient(new BasicAWSCredentials(AppSettings.Settings.AmazonAccessKeyId, AppSettings.Settings.AmazonSecretKey), endpoint); + + DetectFacesRequest dlr = new DetectFacesRequest(); + + //dlr.MaxLabels = AppSettings.Settings.AmazonMaxLabels; + //dlr.MinConfidence = AppSettings.Settings.AmazonMinConfidence; + + //if (AppSettings.Settings.HistoryRestrictMinThresholdAtSource && !OverrideThreshold) + // dlr.MinConfidence = cam.threshold_lower; + + //if (AppSettings.Settings.HistoryRestrictMinThresholdAtSource && OverrideThreshold) + // dlr.MinConfidence = AiUrl.Threshold_Lower; + + Amazon.Rekognition.Model.Image rekognitionImage = new Amazon.Rekognition.Model.Image(); + + //byte[] data = null; + + //using (FileStream fileStream = new FileStream(CurImg.image_path, FileMode.Open, FileAccess.Read)) + //{ + // data = new byte[fileStream.Length]; + // await fileStream.ReadAsync(data, 0, (int)fileStream.Length); + //} + + rekognitionImage.Bytes = CurImg.ToMemStream(); + + dlr.Image = rekognitionImage; + dlr.Attributes.Add("ALL"); + + Log($"Debug: (1/6) Uploading a {Global.FormatBytes(FileSize)} image to '{AiUrl.Type}' AI Server at {AiUrl}", AiUrl.CurSrv, cam, CurImg); + + swposttime.Restart(); + + DetectFacesResponse response = await rekognitionClient.DetectFacesAsync(dlr); + + swposttime.Stop(); + + if (response != null) + { + ret.StatusCode = response.HttpStatusCode; + if (response.HttpStatusCode == System.Net.HttpStatusCode.OK) + { + if (response.FaceDetails.Count > 0) + { + + foreach (Amazon.Rekognition.Model.FaceDetail face in response.FaceDetails) + { + ClsPrediction pred = new ClsPrediction(ObjectType.Object, cam, face, CurImg, AiUrl); + + ret.Predictions.Add(pred); + + } + + + } + + ret.Success = true; + AiUrl.LastResultMessage = $"{ret.Predictions.Count} predictions found."; + } + else + { + ret.Error = $"ERROR: Amazon Rekognition 'HttpStatusCode' is '{response.HttpStatusCode}' ({Convert.ToInt32(response.HttpStatusCode)})."; + AiUrl.IncrementError(); + AiUrl.LastResultMessage = ret.Error; + } + } + else + { + ret.Error = $"ERROR: Amazon Rekognition 'Response' is null."; + AiUrl.IncrementError(); + AiUrl.LastResultMessage = ret.Error; + } + + + } + catch (Exception ex) + { + swposttime.Stop(); + + ret.Error = $"ERROR: {ex.Msg()}"; + AiUrl.IncrementError(); + AiUrl.LastResultMessage = ret.Error; + } + finally + { + ret.SWPostTime = swposttime.ElapsedMilliseconds; + AiUrl.LastTimeMS = (int)swposttime.ElapsedMilliseconds; + AiUrl.AITimeCalcs.AddToCalc(AiUrl.LastTimeMS); + } + + } + else + { + ret.Error = $"Error: URL type not supported yet: '{AiUrl.Type}'"; + AiUrl.LastResultMessage = ret.Error; + } + + + AiUrl.LastResultSuccess = ret.Success || !AiUrl.LastResultMessage.Has("error"); + + return ret; + + } + + + public static List RemovePredictionDuplicates(List items, Camera cam) + { + List result = new List(); + for (int i = 0; i < items.Count; i++) + { + // Assume not duplicate. + bool duplicate = false; + for (int z = 0; z < i; z++) + { + if (items[z] == items[i] && items[z].ConfidenceString() == items[i].ConfidenceString()) + { + ObjectPosition op1 = new ObjectPosition(items[z].XMin, items[z].XMax, items[z].YMin, items[z].YMax, items[z].Label, items[z].ImageWidth, items[z].ImageHeight, items[z].Camera, items[z].Filename); + op1.ScaleConfig = cam.maskManager.ScaleConfig; + op1.PercentMatch = cam.maskManager.PercentMatch; + ObjectPosition op2 = new ObjectPosition(items[i].XMin, items[i].XMax, items[i].YMin, items[i].YMax, items[i].Label, items[i].ImageWidth, items[i].ImageHeight, items[i].Camera, items[i].Filename); + op2.ScaleConfig = cam.maskManager.ScaleConfig; + op2.PercentMatch = cam.maskManager.PercentMatch; + + if (op1 == op2) + { + // This is a duplicate. + duplicate = true; + break; + } + } + } + // If not duplicate, add to result. + if (!duplicate) + { + result.Add(items[i]); + } + } + return result; + } + + public class ClsPredMatch + { + public int Idx = -1; + public double MatchPercent = 0; + public List preds = new List(); + + + public ClsPredMatch(int idx, double matchPercent) + { + Idx = idx; + MatchPercent = matchPercent; + + } + public ClsPredMatch() { } + } + //search for position based on object position + public static ClsPredMatch ContainsPrediction(ClsPrediction pred, List predictions, Camera cam, bool ObjTypeMustMatch, bool TrueIfInsideOrPartiallyInside) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + + + ClsPredMatch ret = new ClsPredMatch(); + + if (predictions.Count == 0) + return ret; + + ObjectPosition op1 = new ObjectPosition(pred.XMin, pred.XMax, pred.YMin, pred.YMax, pred.Label, pred.ImageWidth, pred.ImageHeight, pred.Camera, pred.Filename); + op1.ScaleConfig = cam.maskManager.ScaleConfig; + op1.PercentMatch = cam.maskManager.PercentMatch; + + for (int i = 0; i < predictions.Count; i++) + { + //nope out so we dont include ourself? + //if (predictions[i].GetHashCode() == pred.GetHashCode()) + // break; + + if (TrueIfInsideOrPartiallyInside) + { + //for face matching, we dont care if it is not a closely matching rectangle size, in fact it should be smaller but mostly within + if (RectangleMatches(predictions[i].GetRectangle(), pred.GetRectangle(), cam.MergePredictionsMinMatchPercent, out double matched, TrueIfInsideOrPartiallyInside)) + { + if (!ObjTypeMustMatch || ObjTypeMustMatch && (predictions[i].ObjType == pred.ObjType || predictions[i].Label.IndexOf(pred.Label, StringComparison.OrdinalIgnoreCase) >= 0)) + { + predictions[i].PercentMatchRefinement = matched; + ret.preds.Add(predictions[i]); + } + } + + } + else // to see if we can find a similar sized rectangle using the maskmanager's technique of matching roughly the same size + { + ObjectPosition op2 = new ObjectPosition(predictions[i].XMin, predictions[i].XMax, predictions[i].YMin, predictions[i].YMax, predictions[i].Label, predictions[i].ImageWidth, predictions[i].ImageHeight, predictions[i].Camera, predictions[i].Filename); + op2.ScaleConfig = cam.maskManager.ScaleConfig; + op2.PercentMatch = cam.MergePredictionsMinMatchPercent; //cam.maskManager.PercentMatch; + if (op1 == op2 && (!ObjTypeMustMatch || ObjTypeMustMatch && (predictions[i].ObjType == pred.ObjType || predictions[i].Label.IndexOf(pred.Label, StringComparison.OrdinalIgnoreCase) >= 0))) + { + predictions[i].PercentMatchRefinement = op1.LastPercentMatch; + ret.preds.Add(predictions[i]); + } + + } + } + + //send only best match + if (ret.preds.Count > 0) + { + //sort so highest match is first: + ret.preds = ret.preds.OrderByDescending(p => p.PercentMatchRefinement).ToList(); + ret.MatchPercent = ret.preds[0].PercentMatchRefinement; + pred.PercentMatchRefinement = ret.MatchPercent; + } + + + return ret; + } + + public static bool RectangleMatches(Rectangle MasterRect, Rectangle CompareRect, double PercentMatch, out double MatchedPercent, bool TrueIfInsideOrPartiallyInside) + { + + MatchedPercent = MasterRect.IntersectPercent(CompareRect); + + if (TrueIfInsideOrPartiallyInside) + { + if (MasterRect.IntersectsWith(CompareRect) || MasterRect.Contains(CompareRect)) + { + + //trying to match a face in a person rectangle. The face rectangle can be partially outside the person rectangle so just using Contains doesnt always work. May need to tweak lower and upper + if (MatchedPercent >= 5 && MatchedPercent <= 95) + return true; + } + + } + else + { + if (MatchedPercent >= PercentMatch) + return true; + } + + return false; + } + + + + + + public class DetectObjectsResult + { + public bool Success = false; + public string Error = ""; + public string Message = ""; + public List OutURLs = new List(); + public List OutPredictions = new List(); + public int TimeMS = 0; + } + //analyze image with AI + public static async Task DetectObjects(ClsImageQueueItem CurImg, List InAiUrls, Camera cam = null) + { + + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + DetectObjectsResult ret = new DetectObjectsResult(); + ret.OutURLs = InAiUrls; + + foreach (var url in ret.OutURLs) + { + url.IncrementQueue(); + } + + //Only set error when there IS an error... + + string filename = Path.GetFileName(CurImg.image_path); + + CurImg.QueueWaitMS = (long)(DateTime.Now - CurImg.TimeAdded).TotalMilliseconds; + + Stopwatch sw = Stopwatch.StartNew(); + + long TotalSWPostTime = 0; + + if (cam == null) + cam = AITOOL.GetCamera(CurImg.image_path); + + cam.last_image_file = CurImg.image_path; + + History hist = null; + + // check if camera is still in the first half of the cooldown. If yes, don't analyze to minimize cpu load. + //only analyze if 50% of the cameras cooldown time since last detection has passed + double secs = (DateTime.Now - cam.last_trigger_time).TotalSeconds; + double halfcool = cam.cooldown_time_seconds / 2; + + //ClsAIServerResponse asr = new ClsAIServerResponse(); + ClsAIServerResponse[] asrs = new ClsAIServerResponse[] { }; + string AISRV = ""; + + foreach (var url in ret.OutURLs) + AISRV += url.CurSrv + ";"; + + AISRV = AISRV.Trim(";".ToCharArray()); + + ClsURLItem AiUrl = ret.OutURLs[0]; //default to first one just to have something set + + if (secs >= halfcool) + { + try + { + + Log($"Debug: Starting analysis of {CurImg.image_path}...", AISRV, cam, CurImg); + + if (CurImg.IsValid()) //Waits for access and loads into memory if not already loaded + { + cam.UpdateImageResolutions(CurImg); + + Log($"Debug: (Image resolution={CurImg.Width}x{CurImg.Height} @ {CurImg.DPI} DPI and {Global.FormatBytes(CurImg.FileSize)})", AISRV, cam, CurImg); + + string fldr = Path.Combine(Path.GetDirectoryName(AppSettings.Settings.SettingsFileName), "LastCamImages"); + string file = Path.Combine(fldr, $"{cam.Name}-Last.jpg"); + //Create a copy of the current image for use in mask manager when the original image was deleted + if ((DateTime.Now - LastImageBackupTime).TotalMinutes >= 60 || !File.Exists(file)) + { + //File.Copy(CurImg.image_path, file, true); + CurImg.CopyFileTo(file); + LastImageBackupTime = DateTime.Now; + } + + + ///==================================================================================================================== + ///Get the initial predictions========================================================================================= + ///==================================================================================================================== + + //Start processing all urls that may be linked at the same time in separate threads + List> urltasks = new List>(); + + bool HasLinked = false; + foreach (ClsURLItem url in ret.OutURLs) + { + if (url.LinkServerResults && !url.LinkedResultsServerList.IsEmpty()) + HasLinked = true; + + urltasks.Add(Task.Run(() => GetDetectionsFromAIServer(CurImg, url, cam))); + } + + asrs = await Task.WhenAll(urltasks); + + List initialpredictions = new List(); + + int order = 0; + int RelevantPredictionCount = 0; + + foreach (ClsAIServerResponse asr in asrs) + { + + TotalSWPostTime += asr.SWPostTime; + + bool IsPrimary = (urltasks.Count == 1) || !HasLinked || (HasLinked && !asr.AIURL.LinkedResultsServerList.IsEmpty()); + string primdet = ""; + if (IsPrimary) + primdet = "[Primary]"; + else + primdet = "[Linked]"; + + Log($"Debug: (2/6) {primdet} Posted in {asr.SWPostTime}ms, StatusCode='{asr.StatusCode}', Received a {asr.JsonString.Length} byte JSON response: '{asr.JsonString.Truncate()}'", AiUrl.CurSrv, cam, CurImg); + Log($"Debug: (3/6) {primdet} Processing {asr.Predictions.Count} results...", AiUrl.CurSrv, cam, CurImg); + + + if (asr.Success) + { + + foreach (ClsPrediction pred in asr.Predictions) + { + order++; + pred.AnalyzePrediction(SkipDynamicMaskCheck: true); + if (pred.Result == ResultType.Relevant) + RelevantPredictionCount++; + pred.ServerType = IsPrimary ? ServerType.Primary : ServerType.Linked; + AiUrl = asr.AIURL; + AiUrl.LastResultSuccess = asr.Success; + pred.OriginalOrder = order; + initialpredictions.Add(pred); + } + } + else + { + ret.Error = asr.Error; + //AiUrl.IncrementError(); + //AiUrl.LastResultMessage = ret.Error; + //Only send error if MaxWaitForAIServerTimeoutError is true + string ew = "Debug:"; + if (AppSettings.Settings.MaxWaitForAIServerTimeoutError) + ew = "Error:"; + if (ret.Error.Has("request timed out")) + { + Log(ew + ret.Error, AiUrl.CurSrv, cam, CurImg); + } + else + { + Log(ret.Error, AiUrl.CurSrv, cam, CurImg); + } + } + } + + + ///==================================================================================================================== + ///Get the refinement predictions====================================================================================== + ///==================================================================================================================== + + List refinepredictions = new List(); + + + //First check and see if the main or linked servers contained any refinement objects, if so, add them to the list: + foreach (ClsPrediction pred in initialpredictions) + { + if (pred.ObjType == ObjectType.Face || pred.ObjType == ObjectType.LicensePlate) + refinepredictions.Add(pred); + } + + //see if there are any refinement servers we need to ask for info... + if (AIURLListAvailableRefineServerCount > 0 && RelevantPredictionCount > 0) + { + List RefineURLs = await WaitForNextURL(cam, true, initialpredictions, "", ret.OutURLs); + if (RefineURLs.Count > 0) + { + //Start processing all refinement urls + urltasks = new List>(); + + foreach (ClsURLItem url in RefineURLs) + urltasks.Add(Task.Run(() => GetDetectionsFromAIServer(CurImg, url, cam))); + + asrs = await Task.WhenAll(urltasks); + + int refineorder = 0; + + foreach (ClsAIServerResponse asr in asrs) + { + AiUrl = asr.AIURL; + AiUrl.LastResultSuccess = asr.Success; + TotalSWPostTime += asr.SWPostTime; + + //add to the list of url's we called + if (!ret.OutURLs.Contains(AiUrl)) + ret.OutURLs.Add(AiUrl); + + Log($"Debug: (2.1/6) [Refinement Server] Posted in {asr.SWPostTime}ms, StatusCode='{asr.StatusCode}', Received a {asr.JsonString.Length} byte JSON response: '{asr.JsonString.Truncate()}'", AiUrl.CurSrv, cam, CurImg); + Log($"Debug: (3.1/6) [Refinement Server] Processing {asr.Predictions.Count} results...", AiUrl.CurSrv, cam, CurImg); + + + if (asr.Success) + { + + foreach (ClsPrediction pred in asr.Predictions) + { + refineorder++; + pred.AnalyzePrediction(SkipDynamicMaskCheck: true); + pred.ServerType = ServerType.Refine; + pred.OriginalOrder = order; + refinepredictions.Add(pred); + } + } + else + { + ret.Error = asr.Error; + //AiUrl.IncrementError(); + //AiUrl.LastResultMessage = ret.Error; + Log(ret.Error, AiUrl.CurSrv, cam, CurImg); + } + } + + + } + } + + ///==================================================================================================================== + ///merge the refinement predictions==================================================================================== + ///==================================================================================================================== + + + for (int i = 0; i < refinepredictions.Count; i++) + { + ClsPrediction RefinePred = refinepredictions[i]; + + Log($"Debug: [Refinement] Processing prediction #{i + 1} of {refinepredictions.Count} - {RefinePred.ToString()} [{RefinePred.Result}]...", AiUrl.CurSrv, cam, CurImg); + + //See if there are any existing predictions that can be refined... + for (int r = 0; r < initialpredictions.Count; r++) + { + ClsPrediction ExistingPred = initialpredictions[r]; + //sighthound and deepstack face detection is a rectangle around the face inside the person rectangle so it wont be an exact match. Try to combine the face detail with person + if (RefinePred.ObjType == ObjectType.Face && ExistingPred.ObjType == ObjectType.Person) + { + Rectangle personRect = ExistingPred.GetRectangle(); + Rectangle faceRect = RefinePred.GetRectangle(); + + //check for at least an 80% match since various ai servers create different size rectangles + if (RectangleMatches(personRect, faceRect, cam.MergePredictionsMinMatchPercent, out double matched, true)) + { + Log($"Debug: [Refinement] Added face detail '{ExistingPred.Detail}' (r={r}) from {RefinePred.Server} for original {ExistingPred.Server} detection: {ExistingPred.ToString()} [{ExistingPred.Result}], Rectangle Match={matched.ToPercent()}"); + ExistingPred.PercentMatchRefinement = matched; + ExistingPred.RefineMergedCount++; + ExistingPred.Detail = ExistingPred.Detail.Append(RefinePred.Detail, "; "); + //RefinePred.Result = ResultType.RefinementObject; + } + else + { + Log($"Debug: [Refinement] Did *NOT* match face (r={r}) from {RefinePred.Server} for original {ExistingPred.Server} detection: {ExistingPred.ToString()} [{ExistingPred.Result}], Rectangle Match={matched}%, required={cam.MergePredictionsMinMatchPercent.ToPercent()} (Json CAM setting='MergePredictionsMinMatchPercent')"); + } + RefinePred.PercentMatchRefinement = matched; + + } + //try to match a license plate to a vehicle: + else if (RefinePred.ObjType == ObjectType.LicensePlate && ExistingPred.ObjType == ObjectType.Vehicle) + { + Rectangle VehicleRect = ExistingPred.GetRectangle(); + Rectangle PlateRect = RefinePred.GetRectangle(); + + //check for at least an 80% match since various ai servers create different size rectangles + if (RectangleMatches(VehicleRect, PlateRect, cam.MergePredictionsMinMatchPercent, out double matched, true)) + { + Log($"Debug: [Refinement] Added license plate detail '{ExistingPred.Detail}' (r={r}) from {RefinePred.Server} for original {ExistingPred.Server} detection: {ExistingPred.ToString()} [{ExistingPred.Result}], Rectangle Match={matched.ToPercent()}"); + ExistingPred.PercentMatchRefinement = matched; + ExistingPred.RefineMergedCount++; + ExistingPred.Detail = ExistingPred.Detail.Append(RefinePred.Detail, "; "); + } + else + { + Log($"Debug: [Refinement] Did *NOT* match license plate (r={r}) from {RefinePred.Server} for original {ExistingPred.Server} detection: {ExistingPred.ToString()} [{ExistingPred.Result}], Rectangle Match={matched.ToPercent()}, required={cam.MergePredictionsMinMatchPercent.ToPercent()} (Json CAM setting='MergePredictionsMinMatchPercent')"); + } + RefinePred.PercentMatchRefinement = matched; + } + else + { + //lets just add it for now (new people, trucks, etc from refinement servers) + } + + } + + if (RefinePred.Result == ResultType.Relevant) + RelevantPredictionCount++; + + RefinePred.OriginalOrder = initialpredictions.Count + 1; + initialpredictions.Add(RefinePred); + + + } + + + ///==================================================================================================================== + ///De-duplicate + merge================================================================================================= + ///==================================================================================================================== + + ////lets sort the predictions so that lowest confidence are processed first so they are replaced with duplicates of higher confidence: + initialpredictions = initialpredictions.OrderBy(p => p.Result == ResultType.Relevant ? 1 : 999) + .ThenBy(p => p.ObjectPriority) + .ThenByDescending(p => p.Confidence).ToList(); + + + List predictions = new List(); + + if (AppSettings.Settings.HistoryMergeDuplicatePredictions) + { + //take duplicates out of the queue as we go... + while (initialpredictions.Count > 0) + { + ClsPrediction TestPred = initialpredictions[0]; + ClsPredMatch pm = ContainsPrediction(TestPred, initialpredictions, cam, ObjTypeMustMatch: true, TrueIfInsideOrPartiallyInside: false); + if (pm.preds.Count > 1) //if only 1, then it is itself + { + //We only want to keep the first one of any that look alike, it will already be sorted with high confidence + for (int i = 0; i < pm.preds.Count; i++) + { + if (i > 0) + { + pm.preds[i].Result = pm.preds[i].Result == ResultType.Relevant || pm.preds[i].Result == ResultType.RelevantDuplicateObject ? ResultType.RelevantDuplicateObject : ResultType.DuplicateObject; + pm.preds[i].DupeCount++; + pm.preds[0].Detail = pm.preds[0].Detail.Append(pm.preds[i].Detail, "; "); + if (!pm.preds[0].Label.EqualsIgnoreCase(pm.preds[i].Label)) + { + //add the dupe object name into the details column + pm.preds[0].Detail = pm.preds[0].Detail.Append(pm.preds[i].Label, "; "); + } + } + predictions.Add(pm.preds[i]); + initialpredictions.Remove(pm.preds[i]); + } + + } + else + { + predictions.Add(TestPred); + initialpredictions.Remove(TestPred); + } + } + + + } + else + { + predictions.AddRange(initialpredictions); + } + + ///==================================================================================================================== + ///Run dynamic mask check last so that it does not increase mask counts of duplicate objects=========================== + ///==================================================================================================================== + + foreach (ClsPrediction pred in predictions) + { + if (pred.Result == ResultType.Relevant) + pred.AnalyzePrediction(SkipDynamicMaskCheck: false); //this may increase things like hitcount for relevant objects since it is run twice, may want to figure this out later + } + + + //sort predictions so most important are at the top + predictions = predictions.Distinct().OrderBy(p => p.Result == ResultType.Relevant ? 1 : 999) + .ThenBy(p => p.ObjectPriority) + .ThenByDescending(p => p.Confidence).ToList(); + + //save any images with faces + foreach (ClsPrediction pred in predictions) + { + if (pred.ObjType == ObjectType.Face) + FaceMan.TryAddFaceFile(CurImg, pred.Label); + } + + string PredictionsJSON = Global.GetJSONString(predictions); + + ret.OutPredictions = predictions; + + ///==================================================================================================================== + ///==================================================================================================================== + ///==================================================================================================================== + + int cancelactions = 0; + if (cam.Action_mqtt_enabled && !cam.Action_mqtt_payload_cancel.IsEmpty()) + cancelactions++; + + if (cam.Action_CancelURL_Enabled && cam.cancel_urls.Length > 0) + cancelactions++; + + //process the combined predictions + if (predictions.Count > 0) + { + List relevant_objects = new List(); //list that will be filled with all objects that were detected and are triggering_objects for the camera + List objects_confidence = new List(); //list containing ai confidence value of object at same position in List objects + List objects_details = new List(); //list containing ai confidence value of object at same position in List objects + List objects_position = new List(); //list containing object positions (xmin, ymin, xmax, ymax) + + List irrelevant_objects = new List(); //list that will be filled with all irrelevant objects + List irrelevant_objects_confidence = new List(); //list containing ai confidence value of irrelevant object at same position in List objects + List irrelevant_objects_details = new List(); //list containing ai confidence value of irrelevant object at same position in List objects + List irrelevant_objects_position = new List(); //list containing irrelevant object positions (xmin, ymin, xmax, ymax) + + + int masked_counter = 0; //this value is incremented if an object is in a masked area + int threshold_counter = 0; // this value is incremented if an object does not satisfy the confidence limit requirements + int irrelevant_counter = 0; // this value is incremented if an irrelevant (but not masked or out of range) object is detected + int error_counter = 0; + + //if we are not using the local deepstack windows version, this means nothing: + DeepStackServerControl.IsActivated = true; + + bool HasIgnore = false; + //Find out if there is even one ignored object to prevent the trigger later + foreach (ClsPrediction pred in predictions) + { + if (pred.Result == ResultType.IgnoredObject) + HasIgnore = true; + } + + //print every detected object with the according confidence-level + if (HasIgnore) + Log($"Debug: Detected objects ('Ignored' object found):", AISRV, cam, CurImg); + else + Log($"Debug: Detected objects:", AISRV, cam, CurImg); + + foreach (ClsPrediction pred in predictions) + { + + string clr = ""; + if (pred.Result != ResultType.Error) + DeepStackServerControl.VisionDetectionRunning = true; + + if (pred.Result == ResultType.Relevant && !HasIgnore) + { + relevant_objects.Add(pred.Label); + objects_confidence.Add(pred.Confidence); + objects_details.Add(pred.Detail); + objects_position.Add($"{pred.XMin.Round(0)},{pred.YMin.Round(0)},{pred.XMax.Round(0)},{pred.YMax.Round(0)}"); + clr = "{" + AppSettings.Settings.RectRelevantColor.Name + "}"; + } + else + { + clr = "{" + AppSettings.Settings.RectIrrelevantColor.Name + "}"; + irrelevant_objects.Add(pred.Label); + irrelevant_objects_details.Add(pred.Detail); + irrelevant_objects_confidence.Add(pred.Confidence); + string position = $"{pred.XMin.Round(0)},{pred.YMin.Round(0)},{pred.XMax.Round(0)},{pred.YMax.Round(0)}"; + irrelevant_objects_position.Add(position); + + if (pred.Result == ResultType.NoConfidence) + { + threshold_counter++; + } + else if (pred.Result == ResultType.ImageMasked || pred.Result == ResultType.DynamicMasked || pred.Result == ResultType.StaticMasked) + { + clr = "{" + AppSettings.Settings.RectMaskedColor.Name + "}"; + masked_counter++; + } + else if (pred.Result == ResultType.Error) + { + clr = "{red}"; + error_counter++; + } + else + { + irrelevant_counter++; + } + } + + if (pred.Result == ResultType.Relevant || pred.Result == ResultType.Error) + Log($" {clr}Result='{pred.Result}', Detail='{pred.ToString()}', ObjType='{pred.ObjType}', ObjectResult={pred.ObjectResult}, DynMaskResult='{pred.DynMaskResult}', DynMaskType='{pred.DynMaskType}', ImgMaskResult='{pred.ImgMaskResult}', ImgMaskType='{pred.ImgMaskType}, PercentOfImage={pred.PercentOfImage.ToPercent()}", pred.Server, cam, CurImg); + else + Log($"Debug: {clr}Result='{pred.Result}', Detail='{pred.ToString()}', ObjType='{pred.ObjType}', ObjectResult={pred.ObjectResult}, DynMaskResult='{pred.DynMaskResult}', DynMaskType='{pred.DynMaskType}', ImgMaskResult='{pred.ImgMaskResult}', ImgMaskType='{pred.ImgMaskType}'", pred.Server, cam, CurImg); + + } + + + //mark the end of AI detection for the current image + cam.maskManager.LastDetectionDate = DateTime.Now; + + + //if one or more objects were detected, that are 1. relevant, 2. within confidence limits and 3. outside of masked areas + if (relevant_objects.Count > 0) + { + //store these last detections for the specific camera + cam.last_detections = relevant_objects; + cam.last_confidences = objects_confidence; + cam.last_details = objects_details; + cam.last_positions = objects_position; + cam.last_image_file_with_detections = CurImg.image_path; + + //the new way + + + //create summary string for this detection + StringBuilder detectionsTextSb = new StringBuilder(); + for (int i = 0; i < relevant_objects.Count; i++) + { + detectionsTextSb.Append($"{relevant_objects[i]} {String.Format(AppSettings.Settings.DisplayPercentageFormat, objects_confidence[i])}; "); // String.Format("{0} ({1}%) | ", objects[i], Math.Round((objects_confidence[i] * 100), 2))); + } + + cam.last_detections_summary = detectionsTextSb.ToString().Trim("; ".ToCharArray()); + + //create text string objects and confidences + string objects_and_confidences = ""; + string object_positions_as_string = ""; + //for (int i = 0; i < objects.Count; i++) + //{ + // objects_and_confidences += $"{objects[i]} {String.Format(AppSettings.Settings.DisplayPercentageFormat, objects_confidence[i])}; "; + // object_positions_as_string += $"{objects_position[i]};"; + //} + + foreach (ClsPrediction pred in predictions) + { + if (pred.Result != ResultType.Relevant && AppSettings.Settings.HistoryOnlyDisplayRelevantObjects) + continue; + + objects_and_confidences += $"{pred.ToString()}; "; + object_positions_as_string += $"{pred.PositionString()};"; + } + + objects_and_confidences = objects_and_confidences.Trim("; ".ToCharArray()); + + Log($"Debug: The summary:" + cam.last_detections_summary, AISRV, cam, CurImg); + + Log($"Debug: (5/6) Performing alert actions:", AISRV, cam, CurImg); + + + hist = new History().Create(CurImg.image_path, DateTime.Now, cam.Name, objects_and_confidences, object_positions_as_string, true, PredictionsJSON, AISRV, TotalSWPostTime, true); + + await TriggerActionQueue.AddTriggerActionAsync(TriggerType.All, cam, CurImg, hist, true, !cam.Action_queued, AISRV, ""); //make TRIGGER + + cam.IncrementAlerts(); //stats update + Log($"Debug: (6/6) SUCCESS.", AISRV, cam, CurImg); + + //add to history list + //Log($"Debug: Adding detection to history list.", AiUrl.CurSrv, cam.name); + Global.CreateHistoryItem(hist); + + } + //if no object fulfills all 3 requirements but there are other objects: + else if (irrelevant_objects.Count > 0) + { + //IRRELEVANT ALERT + + //retrieve confidences and positions + string objects_and_confidences = ""; + string object_positions_as_string = ""; + + //for (int i = 0; i < irrelevant_objects.Count; i++) + //{ + // objects_and_confidences += $"{irrelevant_objects[i]} {String.Format(AppSettings.Settings.DisplayPercentageFormat, irrelevant_objects_confidence[i])}; "; // ({Math.Round((irrelevant_objects_confidence[i] * 100), 0)}%); "; + // object_positions_as_string += $"{irrelevant_objects_position[i]};"; + //} + + foreach (ClsPrediction pred in predictions) + { + //if (pred.Result != ResultType.Relevant) + //{ + objects_and_confidences += $"{pred.ToString()}; "; + object_positions_as_string += $"{pred.PositionString()};"; + //} + } + + + objects_and_confidences = objects_and_confidences.Trim("; ".ToCharArray()); + + //string text contains what is written in the log and in the history list + string text = ""; + if (masked_counter > 0)//if masked objects, add them + { + text += $"{masked_counter}x masked; "; + } + if (threshold_counter > 0)//if objects out of confidence range, add them + { + text += $"{threshold_counter}x not in confidence range; "; + } + if (irrelevant_counter > 0) //if other irrelevant objects, add them + { + text += $"{irrelevant_counter}x irrelevant; "; + } + if (error_counter > 0) //if other irrelevant objects, add them + { + text += $"{error_counter}x errors; "; + } + + if (text != "") //remove last ";" + { + text = text.Remove(text.Length - 2); + } + + + Log($"Debug: {text}, so it's an irrelevant alert.", AISRV, cam, CurImg); + + + hist = new History().Create(CurImg.image_path, DateTime.Now, cam.Name, $"{text} : {objects_and_confidences}", object_positions_as_string, false, PredictionsJSON, AISRV, TotalSWPostTime, false); + + if (cancelactions > 0) + { + Log($"Debug: (5/6) Performing {cancelactions} CANCEL actions:", AISRV, cam, CurImg); + await TriggerActionQueue.AddTriggerActionAsync(TriggerType.Cancel, cam, CurImg, hist, false, !cam.Action_queued, AISRV, ""); //make TRIGGER + } + else + Log($"Debug: (5/6) NO CANCEL actions defined, skipping.", AISRV, cam, CurImg); + + + cam.IncrementIrrelevantAlerts(); //stats update + Log($"Debug: (6/6) Camera {cam.Name} caused an irrelevant alert.", AISRV, cam, CurImg); + + //add to history list + Global.CreateHistoryItem(hist); + } + } + else + { + Log($"Debug: ((NO DETECTED OBJECTS))", AISRV, cam, CurImg); + // FALSE ALERT + + cam.IncrementFalseAlerts(); //stats update + + hist = new History().Create(CurImg.image_path, DateTime.Now, cam.Name, "false alert", "", false, "", AISRV, TotalSWPostTime, false); + + if (cancelactions > 0) + { + Log($"Debug: (5/6) Performing {cancelactions} CANCEL actions:", AISRV, cam, CurImg); + await TriggerActionQueue.AddTriggerActionAsync(TriggerType.Cancel, cam, CurImg, hist, false, !cam.Action_queued, AISRV, ""); //make TRIGGER + } + else + Log($"Debug: (5/6) NO CANCEL actions defined, skipping.", AISRV, cam, CurImg); + + + Log($"Debug: (6/6) Camera {cam.Name} caused a false alert, nothing detected.", AISRV, cam, CurImg); + + //add to history list + Global.CreateHistoryItem(hist); + } + + + } + else + { + //could not access the file for 30 seconds?? Or unexpected error + ret.Error = $"Error: Last Image message: '{CurImg.LastError}'. ({CurImg.FileLockMS}ms, with {CurImg.FileLockErrRetryCnt} retries)"; + CurImg.ErrCount++; + CurImg.ResultMessage = ret.Error; + Log(ret.Error, AISRV, cam, CurImg); + } + + } + catch (Exception ex) + { + + ret.Error = $"ERROR: {ex.Msg()}"; + AiUrl.IncrementError(); + AiUrl.LastResultMessage = ret.Error; + AiUrl.LastResultSuccess = false; + Log(ret.Error, AISRV, cam, CurImg); + } + //exitfunction: + if (!string.IsNullOrEmpty(ret.Error) && AppSettings.Settings.send_telegram_errors && cam.telegram_enabled && !(cam.Paused && cam.PauseTelegram)) + { + //bool success = await TelegramUpload(CurImg, "Error"); + if (hist == null) + { + hist = new History().Create(CurImg.image_path, DateTime.Now, cam.Name, "error", "", false, "", AiUrl.CurSrv, TotalSWPostTime, false); + } + await TriggerActionQueue.AddTriggerActionAsync(TriggerType.TelegramImageUpload, cam, CurImg, hist, false, !cam.Action_queued, AiUrl.CurSrv, "Error"); //make TRIGGER + } + + + //I notice deepstack takes a lot longer the very first run? + + CurImg.TotalTimeMS = (long)(DateTime.Now - CurImg.TimeAdded).TotalMilliseconds; //sw.ElapsedMilliseconds + CurImg.QueueWaitMS + CurImg.FileLockMS; + CurImg.DeepStackTimeMS = TotalSWPostTime; + + CurImg.LifeTimeMS = (long)(DateTime.Now - CurImg.TimeCreated).TotalMilliseconds; + tcalc.AddToCalc(CurImg.TotalTimeMS); + qcalc.AddToCalc(CurImg.QueueWaitMS); + fcalc.AddToCalc(CurImg.FileLockMS); + lcalc.AddToCalc(CurImg.FileLoadMS); + icalc.AddToCalc(CurImg.LifeTimeMS); + + Log($"Debug: Total Time: {CurImg.TotalTimeMS.ToString().PadLeft(6)} ms (Count={tcalc.Count.ToString().PadLeft(6)}, Min={tcalc.MinS.PadLeft(6)} ms, Max={tcalc.MaxS.PadLeft(6)} ms, Avg={tcalc.AvgS.PadLeft(6)} ms)", AiUrl.CurSrv, cam, CurImg); + + foreach (ClsURLItem url in ret.OutURLs) + { + Log($"Debug: AI (URL) Time: {url.LastTimeMS.ToString().PadLeft(6)} ms (Count={url.AITimeCalcs.Count.ToString().PadLeft(6)}, Min={url.AITimeCalcs.MinS.PadLeft(6)} ms, Max={url.AITimeCalcs.MaxS.PadLeft(6)} ms, Avg={url.AITimeCalcs.AvgS.PadLeft(6)} ms)", url.CurSrv, cam, CurImg); + } + + Log($"Debug: File lock Time: {CurImg.FileLockMS.ToString().PadLeft(6)} ms (Count={fcalc.Count.ToString().PadLeft(6)}, Min={fcalc.MinS.PadLeft(6)} ms, Max={fcalc.MaxS.PadLeft(6)} ms, Avg={fcalc.AvgS.PadLeft(6)} ms)", AiUrl.CurSrv, cam, CurImg); + Log($"Debug: File load Time: {CurImg.FileLoadMS.ToString().PadLeft(6)} ms (Count={lcalc.Count.ToString().PadLeft(6)}, Min={lcalc.MinS.PadLeft(6)} ms, Max={lcalc.MaxS.PadLeft(6)} ms, Avg={lcalc.AvgS.PadLeft(6)} ms)", AiUrl.CurSrv, cam, CurImg); + Log($"Debug: Image Queue Time: {CurImg.QueueWaitMS.ToString().PadLeft(6)} ms (Count={qcalc.Count.ToString().PadLeft(6)}, Min={qcalc.MinS.PadLeft(6)} ms, Max={qcalc.MaxS.PadLeft(6)} ms, Avg={qcalc.AvgS.PadLeft(6)} ms)", AiUrl.CurSrv, cam, CurImg); + Log($"Debug: Image Life Time: {CurImg.LifeTimeMS.ToString().PadLeft(6)} ms (Count={icalc.Count.ToString().PadLeft(6)}, Min={icalc.MinS.PadLeft(6)} ms, Max={icalc.MaxS.PadLeft(6)} ms, Avg={icalc.AvgS.PadLeft(6)} ms)", AiUrl.CurSrv, cam, CurImg); + Log($"Debug: Image Queue Depth: {CurImg.CurQueueSize.ToString().PadLeft(6)} (Count={scalc.Count.ToString().PadLeft(6)}, Min={scalc.MinS.PadLeft(6)}, Max={scalc.MaxS.PadLeft(6)}, Avg={scalc.AvgS.PadLeft(6)})", AiUrl.CurSrv, cam, CurImg); + + } + else + { + cam.stats_skipped_images++; + cam.stats_skipped_images_session++; + Log($"Skipping detection for '{filename}' because cooldown has not been met for camera '{cam.Name}': '{secs.Round()}' of '{halfcool.Round()}' seconds (half of trigger cooldown time), Session Skip Count={cam.stats_skipped_images_session}", AiUrl.CurSrv, cam, CurImg); + Global.CreateHistoryItem(new History().Create(CurImg.image_path, DateTime.Now, cam.Name, $"Skipped image, cooldown was '{secs.Round()}' of '{halfcool.Round()}' seconds.", "", false, "", AiUrl.CurSrv, TotalSWPostTime, false)); + } + + foreach (var url in ret.OutURLs) + { + url.DecrementQueue(); + } + + ret.Success = (ret.Error == ""); + ret.TimeMS = (int)TotalSWPostTime; + + return ret; + + } + + + public static string GetMaskFile(string cameraname) + { + Camera cam = GetCamera(cameraname, false); + if (cam != null) + return cam.GetMaskFile(true); + else + return ""; + } + + + + public static MaskResultInfo Outsidemask(Camera cam, double xmin, double xmax, double ymin, double ymax, int width, int height) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + //Log($" Checking if object is outside privacy mask of {cameraname}:"); + //Log(" Loading mask file..."); + MaskResultInfo ret = new MaskResultInfo(); + string fileType = ""; + string foundfile = ""; + try + { + + foundfile = cam.GetMaskFile(true); + + if (!string.IsNullOrEmpty(foundfile) && System.IO.File.Exists(foundfile)) + { + Log($"Trace: ->Using found mask file {foundfile}..."); + fileType = Path.GetExtension(foundfile).ToLower(); + } + else + { + Log($"Trace: ->Camera has no mask file yet"); + ret.IsMasked = false; + ret.MaskType = MaskType.None; + ret.Result = MaskResult.NoMaskImageFile; + return ret; + } + + //load mask file (in the image all places that have color (transparency > 9 [0-255 scale]) are masked) + using (var mask_img = new Bitmap(foundfile)) + { + //if any coordinates of the object are outside of the mask image, th mask image must be too small. + if (mask_img.Width != width || mask_img.Height != height) + { + Log($"ERROR: The resolution of the mask '{foundfile}' does not equal the resolution of the processed image. Skipping privacy mask feature. Image: {width}x{height}, Mask: {mask_img.Width}x{mask_img.Height}"); + ret.IsMasked = false; + ret.MaskType = MaskType.Image; + ret.Result = MaskResult.Error; + return ret; + } + + //relative x and y locations of the 9 detection points + double[] x_factor = new double[] { 0.25, 0.5, 0.75, 0.25, 0.5, 0.75, 0.25, 0.5, 0.75 }; + double[] y_factor = new double[] { 0.25, 0.25, 0.25, 0.5, 0.5, 0.5, 0.75, 0.75, 0.75 }; + + //int result = 0; //counts how many of the 9 points are outside of masked area(s) + + //check the transparency of the mask image in all 9 detection points + for (int i = 0; i < 9; i++) + { + //get image point coordinates (and converting double to int) + double x = xmin + (xmax - xmin) * x_factor[i]; + double y = ymin + (ymax - ymin) * y_factor[i]; + + // Get the color of the pixel + System.Drawing.Color pixelColor = mask_img.GetPixel(x.ToInt(), y.ToInt()); + + if (fileType == ".png") + { + //if the pixel is transparent (A refers to the alpha channel), the point is outside of masked area(s) + if (pixelColor.A < 10) + { + ret.ImagePointsOutsideMask++; + } + } + else + { + if (pixelColor.A == 0) // object is in a transparent section of the image (not masked) + { + ret.ImagePointsOutsideMask++; + } + } + + } + + if (ret.ImagePointsOutsideMask > 4) //if 5 or more of the 9 detection points are outside of masked areas, the majority of the object is outside of masked area(s) + { + if (ret.ImagePointsOutsideMask == 9) + { + Log($"Trace: ->ALL of the object is OUTSIDE of masked area(s). ({ret.ImagePointsOutsideMask} of 9 points)"); + ret.IsMasked = false; + ret.MaskType = MaskType.Image; + ret.Result = MaskResult.MajorityOutsideMask; + return ret; + } + else + { + Log($"Trace: ->Most of the object is OUTSIDE of masked area(s). ({ret.ImagePointsOutsideMask} of 9 points)"); + ret.IsMasked = false; + ret.MaskType = MaskType.Image; + ret.Result = MaskResult.CompletlyOutsideMask; + return ret; + } + } + + else //if 4 or less of 9 detection points are outside, then 5 or more points are in masked areas and the majority of the object is so too + { + if (ret.ImagePointsOutsideMask == 0) + { + Log($"Trace: ->All of the object is INSIDE a masked area. ({ret.ImagePointsOutsideMask} of 9 points were outside)"); + ret.IsMasked = true; + ret.MaskType = MaskType.Image; + ret.Result = MaskResult.CompletlyInsideMask; + return ret; + + } + else + { + Log($"Trace: ->Most of the object is INSIDE a masked area. ({ret.ImagePointsOutsideMask} of 9 points were outside)"); + ret.IsMasked = true; + ret.MaskType = MaskType.Image; + ret.Result = MaskResult.MajorityInsideMask; + return ret; + } + } + + } + + } + catch (Exception ex) + { + Log($"ERROR: While loading the mask file {foundfile}: {ex.Msg()}"); + ret.IsMasked = false; + ret.MaskType = MaskType.Image; + ret.Result = MaskResult.Error; + return ret; + } + + } + + public static string ReplaceParams(Camera cam, History hist, ClsImageQueueItem CurImg, string instr, Global.IPType iptype, ClsPrediction curpred = null) + { + string ret = instr; + + try + { + string camname = "TESTCAMERANAME"; + string prefix = "TESTCAMERANAMEPREFIX"; + string imgpath = "C:\\TESTFILE.jpg"; + string caminputfolder = "C:\\TESTFOLDER"; + + if (cam != null) + { + camname = cam.BICamName; + prefix = cam.Prefix; + caminputfolder = cam.input_path; + } + + if (CurImg != null) + { + imgpath = CurImg.image_path; + } + else if (hist != null) + { + imgpath = hist.Filename; + } + else if (cam != null) + { + imgpath = cam.last_image_file; + } + + //handle environment variables too: + ret = Environment.ExpandEnvironmentVariables(ret); + + //handle date and time: + ret = Global.ReplaceCaseInsensitive(ret, "%date%", DateTime.Now.ToString("d")); + ret = Global.ReplaceCaseInsensitive(ret, "%time%", DateTime.Now.ToString("t")); + ret = Global.ReplaceCaseInsensitive(ret, "%datetime%", DateTime.Now.ToString(AppSettings.Settings.DateFormat)); + + + //handle custom variables + ret = Global.ReplaceCaseInsensitive(ret, "[camera]", camname); + ret = Global.ReplaceCaseInsensitive(ret, "[prefix]", prefix); + ret = Global.ReplaceCaseInsensitive(ret, "[caminputfolder]", caminputfolder); + ret = Global.ReplaceCaseInsensitive(ret, "[inputfolder]", AppSettings.Settings.input_path); + ret = Global.ReplaceCaseInsensitive(ret, "[imagepath]", imgpath); //gives the full path of the image that caused the trigger + ret = Global.ReplaceCaseInsensitive(ret, "[imagepathescaped]", Uri.EscapeUriString(imgpath)); //gives the full path of the image that caused the trigger + ret = Global.ReplaceCaseInsensitive(ret, "[imagefilename]", Path.GetFileName(imgpath)); //gives the image name of the image that caused the trigger + ret = Global.ReplaceCaseInsensitive(ret, "[imagefilenamenoext]", Path.GetFileNameWithoutExtension(imgpath)); //gives the image name of the image that caused the trigger + + if (!AppSettings.Settings.DefaultUserName.IsEmpty()) + ret = Global.ReplaceCaseInsensitive(ret, "[username]", AppSettings.Settings.DefaultUserName); //gives the image name of the image that caused the trigger + + string pw = AppSettings.Settings.DefaultPasswordEncrypted.Decrypt(); + if (!pw.IsEmpty()) + ret = Global.ReplaceCaseInsensitive(ret, "[password]", pw); //gives the image name of the image that caused the trigger + + if (BlueIrisInfo.Result == BlueIrisResult.Valid) + { + ret = Global.ReplaceCaseInsensitive(ret, "[blueirisserverip]", Global.IP2Str(AppSettings.Settings.BlueIrisServer.Trim(), iptype)); //gives the image name of the image that caused the trigger + ret = Global.ReplaceCaseInsensitive(ret, "[blueirisurl]", BlueIrisInfo.URL); //gives the image name of the image that caused the trigger + } + + + if (hist != null || curpred != null) + { + List preds = new List(); + + if (hist != null) + preds = hist.Predictions(); + else if (curpred != null) + preds.Add(curpred); + + List detectionslst = new List(); + + if (preds != null && preds.Count > 0) + { + string detections = ""; + string confidences = ""; + + foreach (ClsPrediction pred in preds) + { + if (pred.Result != ResultType.Relevant && AppSettings.Settings.HistoryOnlyDisplayRelevantObjects) + continue; + + detectionslst.Add(pred.ToDeepstackDetection()); + + confidences += pred.ConfidenceString() + ","; + detections += pred.ToString() + ","; + } + ret = Global.ReplaceCaseInsensitive(ret, "[summarynonescaped]", hist.Detections); //summary text including all detections and confidences, p.e."person (91,53%)" + ret = Global.ReplaceCaseInsensitive(ret, "[summary]", Uri.EscapeUriString(hist.Detections)); //summary text including all detections and confidences, p.e."person (91,53%)" + ret = Global.ReplaceCaseInsensitive(ret, "[detection]", preds[0].ToString()); //only gives first detection + ret = Global.ReplaceCaseInsensitive(ret, "[label]", preds[0].Label); //only gives first detection + ret = Global.ReplaceCaseInsensitive(ret, "[detail]", preds[0].Detail); + ret = Global.ReplaceCaseInsensitive(ret, "[detailescaped]", Uri.EscapeUriString(preds[0].Detail)); + ret = Global.ReplaceCaseInsensitive(ret, "[result]", preds[0].Result.ToString()); + ret = Global.ReplaceCaseInsensitive(ret, "[percentofimage]", preds[0].PercentOfImage.Round().ToString()); + ret = Global.ReplaceCaseInsensitive(ret, "[position]", preds[0].PositionString()); + ret = Global.ReplaceCaseInsensitive(ret, "[confidence]", preds[0].ConfidenceString()); + ret = Global.ReplaceCaseInsensitive(ret, "[detections]", detections.Trim(",".ToCharArray())); + ret = Global.ReplaceCaseInsensitive(ret, "[confidences]", confidences.Trim(",".ToCharArray())); + + } + else + { + ret = Global.ReplaceCaseInsensitive(ret, "[summarynonescaped]", "Test Summary"); + ret = Global.ReplaceCaseInsensitive(ret, "[summary]", "Test Summary"); //summary text including all detections and confidences, p.e."person (91,53%)" + ret = Global.ReplaceCaseInsensitive(ret, "[detection]", "Test Detection"); + ret = Global.ReplaceCaseInsensitive(ret, "[label]", "Person"); + ret = Global.ReplaceCaseInsensitive(ret, "[detail]", "Test Detail"); + ret = Global.ReplaceCaseInsensitive(ret, "[detailescaped]", "Test Detail"); + ret = Global.ReplaceCaseInsensitive(ret, "[result]", "Relevant"); + ret = Global.ReplaceCaseInsensitive(ret, "[position]", "0,0,0,0"); + ret = Global.ReplaceCaseInsensitive(ret, "[percentofimage]", "50.123"); + ret = Global.ReplaceCaseInsensitive(ret, "[confidence]", string.Format(AppSettings.Settings.DisplayPercentageFormat, 99.123)); + ret = Global.ReplaceCaseInsensitive(ret, "[detections]", "Detection1, Detection2"); + ret = Global.ReplaceCaseInsensitive(ret, "[confidences]", string.Format(AppSettings.Settings.DisplayPercentageFormat, 99.123) + ", " + string.Format(AppSettings.Settings.DisplayPercentageFormat, 90.01)); + } + + if (ret.IndexOf("[Summaryjson]", StringComparison.OrdinalIgnoreCase) >= 0) + { + ret = Global.ReplaceCaseInsensitive(ret, "[SummaryJson]", "{\"summary\": \"" + Uri.EscapeUriString(hist.Detections) + "\"}"); + } + + if (ret.IndexOf("[Detectionsjson]", StringComparison.OrdinalIgnoreCase) >= 0) + { + string jsonstr = GetJSONString(detectionslst.ToArray(), Formatting.None).CleanString(); + ret = Global.ReplaceCaseInsensitive(ret, "[DetectionsJson]", "{\"detections\": \"" + jsonstr + "\"}"); + } + + if (ret.IndexOf("[alljson]", StringComparison.OrdinalIgnoreCase) >= 0) + { + AllJson json = new AllJson(); + json.Time = hist.Date; + json.Camera = cam.Name; + json.analysisDurationMS = hist.analysisDurationMS; + json.fileName = hist.Filename; + json.baseName = Path.GetFileName(hist.Filename); + json.summary = hist.Detections; + json.state = hist.state; + json.predictions = detectionslst.ToArray(); + string jsonstr = GetJSONString(json, Formatting.None, TypeNameHandling.None, PreserveReferencesHandling.None).CleanString(); + ret = Global.ReplaceCaseInsensitive(ret, "[alljson]", jsonstr); + } + + } + else if (cam != null) + { + if (cam.last_detections != null && cam.last_detections.Count > 0) + { + ret = Global.ReplaceCaseInsensitive(ret, "[summarynonescaped]", cam.last_detections_summary); //summary text including all detections and confidences, p.e."person (91,53%)" + ret = Global.ReplaceCaseInsensitive(ret, "[summary]", Uri.EscapeUriString(cam.last_detections_summary)); //summary text including all detections and confidences, p.e."person (91,53%)" + ret = Global.ReplaceCaseInsensitive(ret, "[detection]", cam.last_detections.ElementAt(0)); //only gives first detection (maybe not most relevant one) + ret = Global.ReplaceCaseInsensitive(ret, "[label]", cam.last_detections.ElementAt(0)); //only gives first detection (maybe not most relevant one) + ret = Global.ReplaceCaseInsensitive(ret, "[detail]", cam.last_details.ElementAt(0)); //only gives first detection (maybe not most relevant one) + ret = Global.ReplaceCaseInsensitive(ret, "[detailescaped]", Uri.EscapeUriString(cam.last_details.ElementAt(0))); //only gives first detection (maybe not most relevant one) + ret = Global.ReplaceCaseInsensitive(ret, "[position]", cam.last_positions.ElementAt(0)); + ret = Global.ReplaceCaseInsensitive(ret, "[confidence]", string.Format(AppSettings.Settings.DisplayPercentageFormat, cam.last_confidences.ElementAt(0))); + ret = Global.ReplaceCaseInsensitive(ret, "[result]", "Unknown"); + ret = Global.ReplaceCaseInsensitive(ret, "[percentofimage]", "50.123"); + ret = Global.ReplaceCaseInsensitive(ret, "[detections]", string.Join(",", cam.last_detections)); + ret = Global.ReplaceCaseInsensitive(ret, "[confidences]", string.Join(",", cam.last_confidences.Select(x => x.ToString(AppSettings.Settings.DisplayPercentageFormat)))); + } + else + { + ret = Global.ReplaceCaseInsensitive(ret, "[summarynonescaped]", "Test Summary"); + ret = Global.ReplaceCaseInsensitive(ret, "[summary]", "Test Summary"); //summary text including all detections and confidences, p.e."person (91,53%)" + ret = Global.ReplaceCaseInsensitive(ret, "[detection]", "Test Detection"); //only gives first detection (maybe not most relevant one) + ret = Global.ReplaceCaseInsensitive(ret, "[detail]", "Test Detail"); + ret = Global.ReplaceCaseInsensitive(ret, "[detailescaped]", "Test Detail"); + ret = Global.ReplaceCaseInsensitive(ret, "[label]", "Person"); + ret = Global.ReplaceCaseInsensitive(ret, "[result]", "Relevant"); + ret = Global.ReplaceCaseInsensitive(ret, "[percentofimage]", "50.123"); + ret = Global.ReplaceCaseInsensitive(ret, "[position]", "0,0,0,0"); + ret = Global.ReplaceCaseInsensitive(ret, "[confidence]", string.Format(AppSettings.Settings.DisplayPercentageFormat, 99.123)); + ret = Global.ReplaceCaseInsensitive(ret, "[detections]", "Detection1, Detection2"); + ret = Global.ReplaceCaseInsensitive(ret, "[confidences]", string.Format(AppSettings.Settings.DisplayPercentageFormat, 99.123) + ", " + string.Format(AppSettings.Settings.DisplayPercentageFormat, 90.01)); + } + + if (ret.IndexOf("[Summaryjson]", StringComparison.OrdinalIgnoreCase) >= 0) + { + ret = Global.ReplaceCaseInsensitive(ret, "[SummaryJson]", "{\"summary\": \"Test Summary\"}"); + } + + if (ret.IndexOf("[Detectionsjson]", StringComparison.OrdinalIgnoreCase) >= 0) + { + ret = Global.ReplaceCaseInsensitive(ret, "[DetectionsJson]", "{\"detections\": \"[]\"}"); + } + + + if (ret.EqualsIgnoreCase("[alljson]")) + { + AllJson json = new AllJson(); + json.analysisDurationMS = 999; + json.fileName = imgpath; + json.Camera = cam.Name; + json.baseName = Path.GetFileName(imgpath); + if (cam.last_detections_summary.IsEmpty()) + cam.last_detections_summary = "Test Detection"; + json.summary = Uri.EscapeUriString(cam.last_detections_summary); + json.state = "on"; + json.predictions = new List().ToArray(); + json.Time = DateTime.Now; + string jsonstr = GetJSONString(json, Formatting.None, TypeNameHandling.None, PreserveReferencesHandling.None).CleanString(); + ret = Global.ReplaceCaseInsensitive(ret, "[alljson]", jsonstr); + } + + + + } + + } + catch (Exception ex) + { + + Log($"Error: {ex.Msg()}"); + } + + return ret; + + } + + //public static ClsURLItem GetURL(string urlstring) + //{ + // ClsURLItem ret = null; + + // if (!urlstring.Contains("//")) + // { + // foreach (var url in AppSettings.Settings.AIURLList) + // { + // if (urlstring.IndexOf(url.CurSrv, StringComparison.OrdinalIgnoreCase) >= 0) + // { + // return url; + // } + // } + // return ret; + // } + + // //first look for exact match - where more than just the URL should match + // ClsURLItem test = new ClsURLItem(urlstring, 0, URLTypeEnum.Unknown); + + // int fnd = AppSettings.Settings.AIURLList.IndexOf(test); + + // if (fnd > -1) + // { + // ret = AppSettings.Settings.AIURLList[fnd]; + // } + // else //lets do a loose search for just the URL string + // { + // for (int i = 0; i < AppSettings.Settings.AIURLList.Count; i++) + // { + // if (!AppSettings.Settings.AIURLList[i].Enabled) + // continue; + + // if (string.Equals(AppSettings.Settings.AIURLList[i].ToString(), test.ToString(), StringComparison.OrdinalIgnoreCase)) + // { + // ret = AppSettings.Settings.AIURLList[i]; + // break; + // } + // } + // } + + // return ret; + + //} + + public static ClsURLItem GetURL(string NameOrURLOrPartialString, bool ReturnDefault, bool OnlyEnabled) + { + NameOrURLOrPartialString = NameOrURLOrPartialString.Trim(); + + if (AppSettings.Settings.AIURLList.IsEmpty()) + return null; + + List urls = new List(); + + //first look for exact match - where more than just the URL should match + foreach (ClsURLItem url in AppSettings.Settings.AIURLList) + { + if ((!OnlyEnabled || OnlyEnabled && url.Enabled) && url.Name.EqualsIgnoreCase(NameOrURLOrPartialString)) + urls.Add(url); + } + foreach (ClsURLItem url in AppSettings.Settings.AIURLList) + { + if ((!OnlyEnabled || OnlyEnabled && url.Enabled) && url.url.EqualsIgnoreCase(NameOrURLOrPartialString)) + urls.Add(url); + } + // lets look for a partial match in the URL (like if we are looking for a hostname or ip) + foreach (ClsURLItem url in AppSettings.Settings.AIURLList) + { + if ((!OnlyEnabled || OnlyEnabled && url.Enabled) && url.url.Has(NameOrURLOrPartialString)) + urls.Add(url); + } + + //ability to search by type + foreach (ClsURLItem url in AppSettings.Settings.AIURLList) + { + if ((!OnlyEnabled || OnlyEnabled && url.Enabled) && url.Type.ToString().EqualsIgnoreCase(NameOrURLOrPartialString)) + urls.Add(url); + } + + if (urls.IsEmpty()) + { + Log($"Trace: No enabled AI URL with the same name or partial URL match found for '{NameOrURLOrPartialString}'."); + //check if there is a default camera which accepts any prefix, select it + if (ReturnDefault) + { + //return the first enabled url + foreach (ClsURLItem url in AppSettings.Settings.AIURLList) + { + if (!OnlyEnabled || OnlyEnabled && url.Enabled) + urls.Add(url); + } + } + } + + if (urls.IsEmpty()) + return null; + + if (urls.Count > 1) + { + Log($"Trace: Multiple enabled AI URLs found for '{NameOrURLOrPartialString}'. Using the first one found: {urls[0].Name}: {urls[0]}"); + } + + return urls[0]; + + } + public static Camera GetCamera(String ImageOrNameOrPrefix, bool ReturnDefault = true) + { + //using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + + //we got here too early, no warning messages + if (AppSettings.Settings.CameraList.Count == 0) + return null; + + List cams = new List(); + + try + { + ImageOrNameOrPrefix = ImageOrNameOrPrefix.Trim(); + + //search by path or filename prefix if we are passed a full path to image file + if (ImageOrNameOrPrefix.Contains("\\")) + { + string pth = Path.GetDirectoryName(ImageOrNameOrPrefix); + string fname = Path.GetFileNameWithoutExtension(ImageOrNameOrPrefix); + + //&CAM.%Y%m%d_%H%M%S + //AIFOSCAMDRIVEWAY.20200827_131840312.jpg + //sgrtgrdg - Kopie (2).jpg + + //Dont try to break the filename apart, just look at any characters matching the first part of the filename + + //first look for . or - at end of prefix if not specified. So CAM1 prefix wont match CAM12 + foreach (Camera ccam in AppSettings.Settings.CameraList) + { + //if (!ccam.enabled) + // continue; + + if (ccam.Prefix.Contains("*") || ccam.Prefix.Contains("?")) + { + if (Regex.IsMatch(Global.WildCardToRegular(ccam.Prefix), ImageOrNameOrPrefix, RegexOptions.IgnoreCase) || Regex.IsMatch(Global.WildCardToRegular(ccam.Prefix), fname, RegexOptions.IgnoreCase)) + { + if (!cams.Contains(ccam)) + { + ccam.LastGetCameraMatchResult = "(Regex)"; + cams.Add(ccam); + } + } + } + else if (ccam.Prefix.EndsWith("-") || ccam.Prefix.EndsWith(".")) + { + if (fname.StartsWith(ccam.Prefix.Trim(), StringComparison.OrdinalIgnoreCase)) + { + { + if (!cams.Contains(ccam)) + { + ccam.LastGetCameraMatchResult = "(StartsWith-Prefix-Dot)"; + cams.Add(ccam); + } + } + } + } + else if (!string.IsNullOrWhiteSpace(ccam.Prefix)) + { + //&CAM.A.%Y%m%d_%H%M%S + //AIFOSCAMDRIVEWAY.A.20200827_131840312 + + //loop through so that we always check a match with the LONGEST prefix (between 2 dots), before the shorter ones + string tmpfname = fname; + int meno = 0; + do + { + meno = tmpfname.LastIndexOf("."); + if (meno > 0) + { + tmpfname = fname.Substring(0, meno); + if (tmpfname.Equals(ccam.Prefix.Trim(), StringComparison.OrdinalIgnoreCase)) + { + if (!cams.Contains(ccam)) + { + ccam.LastGetCameraMatchResult = "(Equals-Prefix-AnyDot)"; + cams.Add(ccam); + } + } + + } + + } while (meno > 0); + } + } + + //regular search + foreach (Camera ccam in AppSettings.Settings.CameraList) + { + //if (!ccam.enabled) + // continue; + + if (!string.IsNullOrWhiteSpace(ccam.Prefix) && fname.StartsWith(ccam.Prefix.Trim(), StringComparison.OrdinalIgnoreCase)) + { + if (!cams.Contains(ccam)) + { + ccam.LastGetCameraMatchResult = "(StartsWith-Prefix)"; + cams.Add(ccam); + } + } + } + + + //if it is not found, search by the camera input path + foreach (Camera ccam in AppSettings.Settings.CameraList) + { + //if (!ccam.enabled) + // continue; + + //If the watched path is c:\bi\cameraname but the full path of found file is + // c:\bi\cameraname\date\time\randomefilename.jpg + //we just check the beginning of the path + if (!String.IsNullOrWhiteSpace(ccam.input_path) && ccam.input_path.Trim().StartsWith(pth, StringComparison.OrdinalIgnoreCase)) + { + if (!cams.Contains(ccam)) + { + ccam.LastGetCameraMatchResult = "(StartsWith-InputPath)"; + cams.Add(ccam); + } + } + } + + + } + else + { + //find by name - we dont care if the camera is disabled + foreach (Camera ccam in AppSettings.Settings.CameraList) + { + if (ImageOrNameOrPrefix.Equals(ccam.Name, StringComparison.OrdinalIgnoreCase)) + { + if (!cams.Contains(ccam)) + { + ccam.LastGetCameraMatchResult = "(ByAICamName)"; + cams.Add(ccam); + } + } + } + ////find by actual cam name if we have to + //foreach (Camera ccam in AppSettings.Settings.CameraList) + //{ + // if (ImageOrNameOrPrefix.Equals(ccam.BICamName, StringComparison.OrdinalIgnoreCase)) + // { + // if (!cams.Contains(ccam)) + // { + // ccam.LastGetCameraMatchResult = "(ByBICamName?)"; + // cams.Add(ccam); + // } + // } + //} + + } + + + //if we didnt find a camera see if there is a default camera name we can use without a prefix + if (cams.Count == 0) + { + Log($"Debug: No enabled camera with the same filename, cameraname, or prefix found for '{ImageOrNameOrPrefix}'"); + //check if there is a default camera which accepts any prefix, select it + if (ReturnDefault) + { + + int i = AppSettings.Settings.CameraList.FindIndex(x => string.Equals(x.Name.Trim(), "default", StringComparison.OrdinalIgnoreCase)); + + if (i == -1) + i = AppSettings.Settings.CameraList.FindIndex(x => x.Prefix.Trim() == ""); + + if (i > -1) + { + if (!cams.Contains(AppSettings.Settings.CameraList[i])) + cams.Add(AppSettings.Settings.CameraList[i]); + + if (!ImageOrNameOrPrefix.EqualsIgnoreCase("none")) + Log($"Trace:(Found a DEFAULT camera for '{ImageOrNameOrPrefix}': '{AppSettings.Settings.CameraList[i].Name}')"); + } + else + { + Log($"Debug: No default camera found. Aborting. (Out of {AppSettings.Settings.CameraList.Count} cameras.)"); + } + } + + } + + } + catch (Exception ex) + { + + Log(ex.Msg()); + } + + Camera cam = null; + + if (cams.Count == 0) + { + Log($"Debug: Cannot match '{ImageOrNameOrPrefix}' to an existing camera. (Out of {AppSettings.Settings.CameraList.Count} cameras.)"); + } + else + { + cam = cams[0]; + if (cams.Count > 1) + { + Log($"Trace: *** Note: More than one configured camera matched '{ImageOrNameOrPrefix}', using the first one matched: '{cams[0].Name}' {cams[0].LastGetCameraMatchResult} ***"); + for (int i = 0; i < cams.Count; i++) + { + Log($"Trace: ----{i + 1}: Name='{cams[i].Name}', MatchResult={cams[i].LastGetCameraMatchResult}, BICamName={cams[i].BICamName}, Prefix='{cams[i].Prefix}', InputPath='{cams[i].input_path}'"); + } + } + } + + return cam; + + } + + + + } +} diff --git a/src/UI/AITOOL.ico b/src/UI/AITOOL.ico new file mode 100644 index 00000000..931a76f5 Binary files /dev/null and b/src/UI/AITOOL.ico differ diff --git a/src/UI/AITOOL.png b/src/UI/AITOOL.png new file mode 100644 index 00000000..33f5d52b Binary files /dev/null and b/src/UI/AITOOL.png differ diff --git a/src/UI/AWSRekognition.png b/src/UI/AWSRekognition.png new file mode 100644 index 00000000..f6b38341 Binary files /dev/null and b/src/UI/AWSRekognition.png differ diff --git a/src/UI/AllJson.cs b/src/UI/AllJson.cs new file mode 100644 index 00000000..b7a37fea --- /dev/null +++ b/src/UI/AllJson.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AITool +{ + public class AllJson + { + public string fileName { get; set; } = ""; + public string baseName { get; set; } = ""; + public string summary { get; set; } = ""; + public string state { get; set; } = ""; + public long analysisDurationMS { get; set; } = 0; + public DateTime Time { get; set; } = DateTime.MinValue; + public string Camera { get; set; } = ""; + + public ClsDeepstackDetection[] predictions { get; set; } + + } +} diff --git a/src/UI/App.config b/src/UI/App.config index 06516e70..5ff3fa91 100644 --- a/src/UI/App.config +++ b/src/UI/App.config @@ -1,61 +1,99 @@  - - -
- - - - - + + + + + + + + + + + - - - - - - - - - - - - - 127.0.0.1:81 - - - False - - - True - - - -1 - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - \ No newline at end of file + diff --git a/src/UI/AssemblyInfo1.cs b/src/UI/AssemblyInfo1.cs new file mode 100644 index 00000000..a79eb5c7 --- /dev/null +++ b/src/UI/AssemblyInfo1.cs @@ -0,0 +1,33 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("AITool")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("AITool")] +[assembly: AssemblyCopyright("Copyright © 2024")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("e9bf4960-77f7-4e6f-86ef-265aa10f2f2c")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +[assembly: AssemblyVersion("2.6.95.8918")] +[assembly: AssemblyFileVersion("2.6.95.8918")] diff --git a/src/UI/BlueIrisControl.cs b/src/UI/BlueIrisControl.cs new file mode 100644 index 00000000..96f22e6c --- /dev/null +++ b/src/UI/BlueIrisControl.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Reflection; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace AITool +{ + public class BlueIrisControl + { + HttpClient triggerHttpClient = null; + BlueIrisInfo BiInfo = null; + string session; + + + public BlueIrisInfo(BlueIrisInfo biInfo) + { + BiInfo = biInfo; + } + + public bool Login() + { + string response = wc.UploadString(url, "{\"cmd\":\"login\"}"); + + if (triggerHttpClient == null) + { + triggerHttpClient = new System.Net.Http.HttpClient(); + triggerHttpClient.Timeout = TimeSpan.FromSeconds(AppSettings.Settings.HTTPClientLocalTimeoutSeconds); + + //lets give it a user agent unique to this machine and product version... + AssemblyName ASN = Assembly.GetExecutingAssembly().GetName(); + string Version = ASN.Version.ToString(); + ProductInfoHeaderValue PIH = new ProductInfoHeaderValue("AI-Tool-MANBAT-" + Global.GetMacAddress(), Version); + triggerHttpClient.DefaultRequestHeaders.UserAgent.Add(PIH); + } + + var r1 = JSON.ParseJson(response); + session = r1.session; + + // Why Blue Iris uses MD5 for security is beyond me. + MD5 md5 = System.Security.Cryptography.MD5.Create(); + byte[] input = System.Text.Encoding.ASCII.GetBytes(user + ":" + session + ":" + password); + byte[] hash = md5.ComputeHash(input); + var sbAuth = new StringBuilder(); + for (int i = 0; i < hash.Length; i++) + sbAuth.Append(hash[i].ToString("X2")); + + response = wc.UploadString(url, "{\"cmd\":\"login\",\"session\":\"" + session + "\",\"response\":\"" + sbAuth + "\"}"); + + var r2 = JSON.ParseJson(response); + return r2.result == "success"; + } + + public bool Logout() + { + string response = wc.UploadString(url, "{\"cmd\":\"logout\",\"session\":\"" + session + "\"}"); + var r1 = JSON.ParseJson(response); + return r1.result == "success"; + } + + public bool Trigger(string cameraShortName) + { + string response = wc.UploadString(url, "{\"cmd\":\"trigger\",\"camera\":\"" + cameraShortName + "\",\"session\":\"" + session + "\"}"); + var r1 = JSON.ParseJson(response); + return r1.result == "success"; + } + + public static bool Trigger(string cameraShortName, int blueIrisHttpPort, string blueIrisUser, string blueIrisPassword) + { + BlueIris bi = new BlueIris(blueIrisHttpPort, blueIrisUser, blueIrisPassword); + if (bi.Login()) + { + bool success = bi.Trigger(cameraShortName); + bi.Logout(); + return success; + } + return false; + } + } +} diff --git a/src/UI/BlueIrisInfo.cs b/src/UI/BlueIrisInfo.cs new file mode 100644 index 00000000..aa76d39f --- /dev/null +++ b/src/UI/BlueIrisInfo.cs @@ -0,0 +1,372 @@ +using Microsoft.Win32; + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Net; +using System.Text; +using System.Threading.Tasks; + +using static AITool.AITOOL; + +namespace AITool + + +{ + public enum BlueIrisResult + { + Valid, + NeedsRemoteRegistryServiceEnabled, + NeedsPermission, + NeedsAdminSharesEnabled, //https://www.repairwin.com/enable-admin-shares-windows-10-8-7/ + InvalidHostOrIP, + HasFirewall, + CouldNotOpenRemoteRegistry, + NotInstalled, + Unknown + } + + public class BICamera + { + + } + + public class BIUser + { + public string Name = ""; + public string Password = ""; + public bool IsAdmin = false; + public bool IsEnabled = false; + } + public class BlueIrisInfo + { + public List ClipPaths = new List(); + public List Cameras = new List(); + public List Users = new List(); + public string AppPath = ""; + public string URL = ""; + public string ServerName = "127.0.0.1"; + public string ServerPort = "81"; + public BlueIrisResult Result = BlueIrisResult.Unknown; + public bool IsHTTPS = false; + public bool IsLocalhost = false; + public string VersionStr = ""; + public double Latitude = 39.809734; + public double Longitude = -98.555620; + + public BlueIrisInfo() + { + + } + + public async Task UpdateRemotePathAsync(string InPath) + { + if (this.IsLocalhost) + return InPath; + + // d:\blueiris\clips\pathname + //\\server\d$\blueiris\clips\pathname + //return $"\\\\{this.ServerName}\\{InPath.Replace(":", "$")}"; + //https://www.repairwin.com/enable-admin-shares-windows-10-8-7/ + + return await Global.GetBestRemotePathAsync(InPath, this.ServerName); + + } + + + + public async Task RefreshBIInfoAsync(string ServernameOrIP) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + this.Result = BlueIrisResult.Unknown; + this.URL = ""; + this.ServerName = ServernameOrIP; + this.ClipPaths.Clear(); + + RegistryKey RemoteKey = null; + + try + { + if (ServernameOrIP.IsEmpty()) + { + this.Result = BlueIrisResult.NotInstalled; + Log($"Debug: No BlueIris server specified."); + return this.Result; + } + + Log($"Debug: Reading BlueIris settings from registry from '{ServernameOrIP}'..."); + + + this.IsLocalhost = await Global.IsLocalHostAsync(ServernameOrIP); + + if (this.IsLocalhost) + { + RemoteKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Default); + } + else + { + Global.UpdateProgressBar($"Reading BlueIris Registry info on '{ServernameOrIP}'...", 1, 1, 1); + //quick ping to validate first + Global.ClsPingOut cpo = await Global.IsConnected(ServernameOrIP, 500, 1); + if (!cpo.Success) + { + this.Result = BlueIrisResult.InvalidHostOrIP; + Log($"Error: Could not PING BlueIris server '{ServernameOrIP}': {cpo.PingError}"); + return this.Result; + } + + //string fixip = ServernameOrIP; + + //we have to get the hostname if it is an ipv6 ip address: + //fixip = await Global.GetHostNameAsync(fixip); + + Log($"Debug: Ping response={cpo.TotalTimeMS}ms, opening remote registry on '{ServernameOrIP}'..."); + + Stopwatch sw = Stopwatch.StartNew(); + + //When the remote registry service is not running we get System.IO.IOException: 'The network path was not found. + RemoteKey = await Task.Run(() => RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, ServernameOrIP)); + + if (RemoteKey != null) + Log($"Debug: Successfully connected to remote registry on {ServernameOrIP} in {sw.ElapsedMilliseconds}ms."); + else + { + this.Result = BlueIrisResult.CouldNotOpenRemoteRegistry; + Log($"Error: Could not open remote registry on {ServernameOrIP} ({sw.ElapsedMilliseconds}ms.)"); + return this.Result; + } + } + + using (RegistryKey key = RemoteKey.OpenSubKey("Software\\Perspective Software\\Blue Iris\\Install", false)) + { + if (key != null) + { + string ap = Convert.ToString(key.GetValue("AppPath4")); + if (!string.IsNullOrWhiteSpace(ap)) + { + this.AppPath = await this.UpdateRemotePathAsync(ap); + + var versionInfo = FileVersionInfo.GetVersionInfo(Path.Combine(this.AppPath, "BlueIris.exe")); + this.VersionStr = versionInfo.FileVersion; + + Log($"Debug: BlueIris found. Version '{this.VersionStr}', app path '{this.AppPath}'"); + + //Test the remote connection... + if (!this.IsLocalhost && !Directory.Exists(this.AppPath)) + { + Log($"Error: Cannot access remote BlueIris install path '{this.AppPath}' - Make sure you have permissions to remotely access and MAP A DRIVE to your remote blue iris CLIPS folder. (Or make sure 'Administrative shares' have been enabled (c$, d$, etc)): https://www.repairwin.com/enable-admin-shares-windows-10-8-7/"); + this.Result = BlueIrisResult.NeedsAdminSharesEnabled; + return this.Result; + } + else if (this.IsLocalhost && !Directory.Exists(this.AppPath)) + { + Log($"Error: Cannot access BlueIris install path '{this.AppPath}'."); + } + + } + } + else + { + this.Result = BlueIrisResult.NotInstalled; + Log("Debug: Could not find BlueIris INSTALL info in the registry."); + return this.Result; + + } + + + } + + if (!string.IsNullOrEmpty(this.AppPath)) + { + + using (RegistryKey key = RemoteKey.OpenSubKey("Software\\Perspective Software\\Blue Iris\\server\\users", false)) + { + if (key != null) + { + foreach (string sk in key.GetSubKeyNames()) + { + + using (RegistryKey curkey = key.OpenSubKey(sk)) + { + if (curkey != null) + { + bool enabled = curkey.GetValue("enabled", 0).ToString().ToInt() == 1; + bool admin = curkey.GetValue("admin", 0).ToString().ToInt() == 1; + + if (!enabled || !admin) // || string.Equals(sk, "local_console", StringComparison.OrdinalIgnoreCase)) + continue; + + BIUser user = new BIUser(); + user.Name = sk; + + //The password can now be encrypted rather than just base64 encoded + string pass = Convert.ToString(curkey.GetValue("password", "")); + if (!pass.IsEmpty()) + try { user.Password = Encoding.UTF8.GetString(Convert.FromBase64String(pass)); } catch (Exception) { }; + + this.Users.Add(user); + Log($"Debug: Found user '{user.Name}', password '{pass.ReplaceChars('*')}'"); + + } + + } + } + } + + } + } + + using (RegistryKey key = RemoteKey.OpenSubKey("Software\\Perspective Software\\Blue Iris\\server", false)) + { + if (key != null) + { + this.IsHTTPS = (Convert.ToInt32(key.GetValue("httpslan")) == 1); + this.ServerName = Convert.ToString(key.GetValue("lanip")).Trim(); + this.ServerPort = Convert.ToString(key.GetValue("port")).Trim(); + if (!string.IsNullOrWhiteSpace(this.ServerName)) + { + if (this.IsHTTPS) //maybe need to check secureonly setting also?? + { + this.URL = "https://" + Global.IP2Str(this.ServerName, Global.IPType.URL) + ":" + this.ServerPort; + } + else + { + this.URL = "http://" + Global.IP2Str(this.ServerName, Global.IPType.URL) + ":" + this.ServerPort; + } + Log("Debug: BlueIris URL found: " + this.URL); + + } + else + { + Log("Error: BlueIris LANIP registry key not found?"); + } + } + else + { + Log("Debug: Could not find BlueIris SERVER info in the registry."); + } + + } + + + using (RegistryKey key = RemoteKey.OpenSubKey("Software\\Perspective Software\\Blue Iris\\clips\\folders", false)) + { + if (key != null) + { + foreach (var sk in key.GetSubKeyNames()) + { + + using (RegistryKey curkey = key.OpenSubKey(sk)) + { + if (curkey != null) + { + string path = Convert.ToString(curkey.GetValue("path")); + if (!string.IsNullOrWhiteSpace(path)) + { + string pth = await this.UpdateRemotePathAsync(path.Trim()); + this.ClipPaths.Add(pth); + if (this.IsLocalhost) + Log($"Debug: BlueIris clip path found: {pth}"); + else + Log($"Debug: BlueIris app path found: {path} (Remote={pth})"); + + } + } + + } + } + } + else + { + Log("Debug: Could not find BlueIris CLIPS info in the registry."); + } + + + } + + using (RegistryKey key = RemoteKey.OpenSubKey("Software\\Perspective Software\\Blue Iris\\Cameras", false)) + { + if (key != null) + { + foreach (var sk in key.GetSubKeyNames()) + { + + using (RegistryKey curkey = key.OpenSubKey(sk)) + { + if (curkey != null) + { + bool enabled = (Convert.ToInt32(curkey.GetValue("enabled")) == 1); + if (enabled) + { + string shortname = Convert.ToString(curkey.GetValue("shortname")); + if (!string.IsNullOrWhiteSpace(shortname)) + { + this.Cameras.Add(shortname); + Log("Debug: BlueIris camera found: " + shortname); + + } + } + } + + } + } + } + else + { + Log("Debug: Could not find BlueIris CAMERAS info in the registry."); + } + + } + + using (RegistryKey key = RemoteKey.OpenSubKey("Software\\Perspective Software\\Blue Iris\\Options", false)) + { + if (key != null) + { + this.Latitude = key.GetValue("latitude", "39.809734").ToString().ToDouble(); + this.Longitude = key.GetValue("longitude", "-98.555620").ToString().ToDouble(); + } + else + { + Log("Debug: Could not find BlueIris OPTIONS info in the registry."); + } + + } + + bool IsValid = (this.ClipPaths.Count > 0 && !String.IsNullOrWhiteSpace(this.AppPath) && !string.IsNullOrWhiteSpace(this.ServerName) && Directory.Exists(this.AppPath)); + + if (IsValid) + this.Result = BlueIrisResult.Valid; + + } + catch (Exception ex) + { + if (ex.Message.IndexOf("The network path was not found", StringComparison.OrdinalIgnoreCase) >= 0) + { + Log($"Error: In order to connect to the remote Windows machine, it needs the 'Remote Registry' service enabled (automatic) + started on '{ServernameOrIP}': " + ex.Msg()); + this.Result = BlueIrisResult.NeedsRemoteRegistryServiceEnabled; + } + //System.UnauthorizedAccessException: 'Attempted to perform an unauthorized operation.' + else if (ex.Message.IndexOf("unauthorized operation", StringComparison.OrdinalIgnoreCase) >= 0) + { + string userinfo = Environment.GetEnvironmentVariable("USERDOMAIN") + @"\" + Environment.GetEnvironmentVariable("USERNAME"); + Log($"Error: Give the current user ({userinfo}) access to '{ServernameOrIP}': " + ex.Msg()); + this.Result = BlueIrisResult.NeedsPermission; + } + else + { + Log("Error: Got error while reading BlueIris registry: " + ex.Msg()); + this.Result = BlueIrisResult.Unknown; + } + } + finally + { + if (RemoteKey != null) + RemoteKey.Dispose(); + } + + return this.Result; + } + } +} diff --git a/src/UI/Camera.cs b/src/UI/Camera.cs index 0373e0fc..68af0f76 100644 --- a/src/UI/Camera.cs +++ b/src/UI/Camera.cs @@ -1,143 +1,795 @@ -using System; +using Newtonsoft.Json; + +using System; using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.IO; using System.Linq; -using System.Text; using System.Threading.Tasks; +using System.Timers; +using System.Windows.Forms; -using System.IO; +using static AITool.MaskManager; -namespace WindowsFormsApp2 +namespace AITool { - class Camera + + public enum TriggerType { - public string name; - public string prefix; - public string triggering_objects_as_string; - public string[] triggering_objects; - public string trigger_urls_as_string; - public string[] trigger_urls; - public bool telegram_enabled; - public bool enabled; - public DateTime last_trigger_time; - public double cooldown_time; - public int threshold_lower; - public int threshold_upper; - - public List last_detections = new List(); //stores objects that were detected last - public List last_confidences = new List(); //stores last objects confidences - public List last_positions = new List(); //stores last objects positions - public String last_detections_summary; //summary text of last detection + Unknown, + All, + DownloadURL, + PostURL, + TelegramImageUpload, + TelegramText, + Sound, + Run, + MQTT, + Pushover, + Cancel + } + public class CameraTriggerAction + { + public TriggerType Type = TriggerType.Unknown; + public string Description = ""; + public string LastResponse = ""; + } + + public class ImageResItem:IEquatable + { + public int Width = 0; + public int Height = 0; + public long Count = 0; + public string LastFileName = ""; + public long LastFileSize = 0; + public float LastFileDPI = 0; + public DateTime LastSeenDate = DateTime.MinValue; + + public ImageResItem() { } + public ImageResItem(ClsImageQueueItem CurImg) + { + this.Count = 1; + this.Height = CurImg.Height; + this.Width = CurImg.Width; + this.LastFileName = CurImg.image_path; + this.LastSeenDate = CurImg.TimeCreated; + this.LastFileSize = CurImg.FileSize; + this.LastFileDPI = CurImg.DPI; + + } + + public override bool Equals(object obj) + { + return Equals(obj as ImageResItem); + } + public bool Equals(ImageResItem other) + { + return other != null && + Width == other.Width && + Height == other.Height; + } + + public override int GetHashCode() + { + int hashCode = 859600377; + hashCode = hashCode * -1521134295 + Width.GetHashCode(); + hashCode = hashCode * -1521134295 + Height.GetHashCode(); + return hashCode; + } + + public override string ToString() + { + return $"{this.Width}x{this.Height}"; + } + + public static bool operator ==(ImageResItem left, ImageResItem right) + { + return EqualityComparer.Default.Equals(left, right); + } + + public static bool operator !=(ImageResItem left, ImageResItem right) + { + return !(left == right); + } + } + public class Camera:IEquatable + { + public string Name { get; set; } = ""; + public string Prefix { get; set; } = ""; + public string BICamName { get; set; } = ""; + public string MaskFileName { get; set; } = ""; + public string triggering_objects_as_string { get; set; } = ""; //"person, people, face, bear, elephant, car, truck, pickup truck, SUV, van, bicycle, motorcycle, motorbike, bus, dog, horse, boat, train, airplane, zebra, giraffe, cow, sheep, cat, bird"; + + public string additional_triggering_objects_as_string { get; set; } = ""; //"SUV, VAN, Pickup Truck, Meat Popsicle"; + public ClsRelevantObjectManager DefaultTriggeringObjects { get; set; } = null; + public ClsRelevantObjectManager TelegramTriggeringObjects { get; set; } = null; + public ClsRelevantObjectManager PushoverTriggeringObjects { get; set; } = null; + + public ClsRelevantObjectManager MQTTTriggeringObjects { get; set; } = null; + public string[] triggering_objects { get; set; } = new string[0]; + + public string trigger_urls_as_string { get; set; } = ""; + public string[] trigger_urls { get; set; } = new string[0]; + public string cancel_urls_as_string { get; set; } = ""; + public string[] cancel_urls { get; set; } = new string[0]; + //public List trigger_action_list { get; set; } = new List(); + public bool trigger_url_cancels { get; set; } = false; + public bool Action_TriggerURL_Enabled { get; set; } = false; + public bool Action_CancelURL_Enabled { get; set; } = false; + public bool UpdatedURLs = false; + + public bool telegram_enabled { get; set; } = false; + public string telegram_caption { get; set; } = "[camera] - [SummaryNonEscaped]"; //cam.name + " - " + cam.last_detections_summary + public string telegram_triggering_objects { get; set; } = ""; + + public string telegram_chatid { get; set; } = ""; + //public string telegram_token { get; set; } = ""; //It would take some work to have each camera use a unique token because the bot has to be fully re-initialized to change the token. + public string telegram_active_time_range { get; set; } = "00:00:00-23:59:59"; + public bool enabled { get; set; } = true; + public double cooldown_time { get; set; } = -1; + public int cooldown_time_seconds { get; set; } = 10; + public int sound_cooldown_time_seconds { get; set; } = 10; + public int threshold_lower { get; set; } = 40; + public int threshold_upper { get; set; } = 100; + + //watch folder for each camera + public string input_path { get; set; } = ""; + public bool input_path_includesubfolders { get; set; } = false; + + public bool Action_image_copy_enabled { get; set; } = false; + public bool Action_image_merge_detections { get; set; } = false; + public bool Action_image_merge_detections_makecopy { get; set; } = true; + public long Action_image_merge_jpegquality { get; set; } = 90; + public string Action_network_folder { get; set; } = "C:\\StoredAlerts"; + public string Action_network_folder_filename { get; set; } = "[ImageFilenameNoExt]"; + public int Action_network_folder_purge_older_than_days { get; set; } = 30; + public bool Action_RunProgram { get; set; } = false; + public string Action_RunProgramString { get; set; } = "C:\\TOOLS\\SomeTool.exe"; + public string Action_RunProgramArgsString { get; set; } = "/switch1 /description=[Summary]"; + public bool Action_PlaySounds { get; set; } = false; + public string Action_Sounds { get; set; } = @"doorbell.wav | talk:There is a [Label] at the door"; + + public bool Action_mqtt_enabled { get; set; } = false; + public string Action_mqtt_topic { get; set; } = "ai/[camera]/motion"; + public string Action_mqtt_payload { get; set; } = "[detections]"; + public string Action_mqtt_topic_cancel { get; set; } = "ai/[camera]/motioncancel"; + public string Action_mqtt_payload_cancel { get; set; } = "cancel"; + public bool Action_mqtt_retain_message { get; set; } = false; + public bool Action_mqtt_send_image { get; set; } = false; + public bool Action_queued { get; set; } = false; + public bool Action_ActivateBlueIrisWindow { get; set; } = false; + public bool Action_pushover_enabled { get; set; } = false; + public string Action_pushover_title { get; set; } = "AI Detection - [camera]"; + public string Action_pushover_message { get; set; } = "[SummaryNonEscaped]"; + public string Action_pushover_device { get; set; } = ""; + public string Action_pushover_triggering_objects { get; set; } = ""; + + public string Action_pushover_Priority { get; set; } = "Normal"; + public string Action_pushover_Sound { get; set; } = "pushover"; + public int Action_pushover_retry_seconds { get; set; } = 60; + public int Action_pushover_expire_seconds { get; set; } = 10800; //3 hours + public string Action_pushover_retrycallback_url { get; set; } = ""; + public string Action_pushover_SupplementaryUrl { get; set; } = ""; + public string Action_pushover_active_time_range { get; set; } = "00:00:00-23:59:59"; + + [JsonIgnore] + public ThreadSafe.Boolean Action_Cancel_Timer_Enabled { get; set; } = new ThreadSafe.Boolean(false); + [JsonIgnore] + public ThreadSafe.DateTime Action_Cancel_Start_Time { get; set; } = new ThreadSafe.DateTime(DateTime.MinValue, AppSettings.Settings.DateFormat); + + public MaskManager maskManager { get; set; } = new MaskManager(); + public int mask_brush_size { get; set; } = 35; //stats - public int stats_alerts; //alert image contained relevant object counter - public int stats_false_alerts; //alert image contained no object counter - public int stats_irrelevant_alerts; //alert image contained irrelevant object counter + public int stats_alerts { get; set; } = 0; //alert image contained relevant object counter + public int stats_false_alerts { get; set; } = 0; //alert image contained no object counter + public int stats_irrelevant_alerts { get; set; } = 0; //alert image contained irrelevant object counter - //write config to file - public void WriteConfig(string _name, string _prefix, string _triggering_objects_as_string, string _trigger_urls_as_string, bool _telegram_enabled, bool _enabled, double _cooldown_time, int _threshold_lower, int _threshold_upper) + public int stats_skipped_images { get; set; } = 0; //Images that were skipped due to cooldown or retry count + + [JsonIgnore] + public int stats_skipped_images_session { get; set; } = 0; //Images that were skipped due to cooldown or retry count + + + public string last_image_file { get; set; } = ""; + public string last_image_file_with_detections { get; set; } = ""; + + public int XOffset { get; set; } = 0; //these are for when deepstack is having a problem with detection rectangle being in the wrong location + public int YOffset { get; set; } = 0; // Can be negative numbers + + public string ImageAdjustProfile { get; set; } = "Default"; + + public string DetectionDisplayFormat { get; set; } = "[Label] [[Detail]] [confidence]"; + + //Keep a list of image resolutions and the last image file name with that resolution. This will help us keep track of which image mask to use + public List ImageResolutions { get; set; } = new List(); + + public double PredSizeMinPercentOfImage { get; set; } = 0; //prediction must be at least this % of the image + public double PredSizeMaxPercentOfImage { get; set; } = 95; + public double PredSizeMinHeight { get; set; } = 0; + public double PredSizeMinWidth { get; set; } = 0; + public double PredSizeMaxHeight { get; set; } = 0; + public double PredSizeMaxWidth { get; set; } = 0; + + public double MergePredictionsMinMatchPercent { get; set; } = 85; //when combining predictions from multiple sources (deepstack/aws for example) the two objects have to match at least this much to be considered the same + + public int LastJPGCleanDay { get; set; } = 0; + + [JsonIgnore] + public bool Paused { get; set; } = false; + [JsonIgnore] + public DateTime ResumeTime { get; set; } = DateTime.MinValue; + + public double PauseMinutes { get; set; } = 30; + public bool PauseFileMon { get; set; } = false; + public bool PauseTelegram { get; set; } = false; + public bool PausePushover { get; set; } = false; + public bool PauseMQTT { get; set; } = false; + public bool PauseURL { get; set; } = false; + public string CameraGroup { get; set; } = ""; + + [JsonIgnore] + public ThreadSafe.DateTime last_trigger_time { get; set; } = new ThreadSafe.DateTime(DateTime.MinValue, AppSettings.Settings.DateFormat); + [JsonIgnore] + public ThreadSafe.DateTime last_sound_time { get; set; } = new ThreadSafe.DateTime(DateTime.MinValue, AppSettings.Settings.DateFormat); + + [JsonIgnore] + public string LastGetCameraMatchResult { get; set; } = ""; + + [JsonIgnore] + public List last_detections { get; set; } = new List(); //stores objects that were detected last + [JsonIgnore] + public List last_details { get; set; } = new List(); //stores objects that were detected last + [JsonIgnore] + public List last_confidences { get; set; } = new List(); //stores last objects confidences + [JsonIgnore] + public List last_positions { get; set; } = new List(); //stores last objects positions + [JsonIgnore] + public String last_detections_summary; //summary text of last detection + [JsonIgnore] + private object CamLock { get; set; } = new object(); + private System.Timers.Timer _pauseTimer = new System.Timers.Timer(); + private ElapsedEventHandler ev; + [JsonConstructor] + public Camera() { } + + public Camera(string Name = "") + { + + this.Name = Name; + this.BICamName = Name; + this.Prefix = Name; + this.UpdateCamera(); + } + + + public void Pause() + { + + this.Paused = true; + this.ResumeTime = DateTime.Now.AddMinutes(this.PauseMinutes); + this._pauseTimer.Interval = 1000; + //register event handler to run clean history every minute + ev = new System.Timers.ElapsedEventHandler(this.PauseEvent); + this._pauseTimer.Elapsed += ev; + this._pauseTimer.Start(); + AITOOL.Log($"Debug: Camera '{this.Name}' paused for '{this.PauseMinutes}' minutes until '{this.ResumeTime}'."); + + } + + public void Resume() + { + this.Paused = false; + this.ResumeTime = DateTime.MinValue; + this._pauseTimer.Elapsed -= ev; + this._pauseTimer.Stop(); + AITOOL.Log($"Debug: Resuming paused camera '{this.Name}' after '{this.PauseMinutes}' minutes. "); + + } + + private void PauseEvent(object sender, EventArgs e) + { + if (this.Paused) + { + if (DateTime.Now >= this.ResumeTime) + this.Resume(); + } + } + public void UpdateCamera() { - //if camera name (= settings file name) changed, the old settings file must be deleted - if(name != _name) + lock (CamLock) { - System.IO.File.Delete(AppDomain.CurrentDomain.BaseDirectory + $"/cameras/{ name }.txt"); + + try + { + + //check to see if camera needs to be resumed + this.PauseEvent(null, new EventArgs()); + + this.UpdateTriggeringObjects(); + + if (string.IsNullOrEmpty(this.DetectionDisplayFormat)) + this.DetectionDisplayFormat = "[Label] [[Detail]] [confidence]"; + + if (string.IsNullOrEmpty(this.BICamName)) + this.BICamName = this.Name; + + if (string.IsNullOrEmpty(this.MaskFileName)) + this.MaskFileName = $"{this.Name}.bmp"; + + if (this.ImageResolutions.Count == 0) + this.ScanImages(10, 500, -1);//run a quick scan to get resolutions + + if (this.cooldown_time > -1) + { + this.cooldown_time_seconds = TimeSpan.FromMinutes(this.cooldown_time).TotalSeconds.ToInt(); + this.cooldown_time = -1; + } + + if (this.maskManager == null) + { + this.maskManager = new MaskManager(); + AITOOL.Log("Debug: Had to reset MaskManager for camera " + this.Name); + } + + //update threshold in all masks if changed during session + this.maskManager.Update(this); + + ///this was an old setting we dont want to use any longer, but pull it over if someone enabled it before + if (this.trigger_url_cancels && !string.IsNullOrWhiteSpace(this.cancel_urls_as_string)) + { + this.cancel_urls_as_string = this.trigger_urls_as_string; + this.trigger_url_cancels = false; + } + + //this is just a flag to see if we have updated old settings file to support an 'enabled' property. + //before, the existence of a URL indicated 'enabled', now we have an actual flag + if (!this.UpdatedURLs) + { + //if there was a URL set then it was 'enabled' + this.Action_TriggerURL_Enabled = !this.trigger_urls_as_string.IsEmpty(); + this.Action_CancelURL_Enabled = !this.cancel_urls_as_string.IsEmpty(); + this.UpdatedURLs = true; + } + + //set the default url's if nothing configured + ///admin?camera=x&flagalert=x&memo=text&jpeg=path&flagclip x = 0 mark the most recent alert as cancelled (if not previously confirmed). x = 1 mark the most recent alert as flagged. x = 2 mark the most recent alert as confirmed. x = 3 mark the most recent alert as flagged and confirmed. x = -1 reset the flagged, confirmed, and cancelled states + + + if (!AITOOL.BlueIrisInfo.IsNull() && AITOOL.BlueIrisInfo.Result == BlueIrisResult.Valid) + { + if (this.trigger_urls_as_string.IsEmpty()) + this.trigger_urls_as_string = "[BlueIrisURL]/admin?camera=[camera]&trigger&user=[Username]&pw=[Password]&flagalert=2&memo=[summary]&jpeg=[ImagePathEscaped]"; + if (this.cancel_urls_as_string.IsEmpty()) + this.cancel_urls_as_string = "[BlueIrisURL]/admin?&camera=[camera]&user=[Username]&pw=[Password]&flagalert=0&memo=(Canceled)"; + } + else + { + if (this.trigger_urls_as_string.IsEmpty()) + this.trigger_urls_as_string = "http://127.0.0.1:81/admin?camera=[camera]&trigger&user=[Username]&pw=[Password]&flagalert=2&memo=[summary]&jpeg=[ImagePathEscaped]"; + if (this.cancel_urls_as_string.IsEmpty()) + this.cancel_urls_as_string = "http://127.0.0.1:81/admin?&camera=[camera]&user=[Username]&pw=[Password]&flagalert=0&memo=(Canceled)"; + + } + + this.trigger_urls = this.trigger_urls_as_string.SplitStr("\r\n|").ToArray(); + this.cancel_urls = this.cancel_urls_as_string.SplitStr("\r\n|").ToArray(); + + this.CleanActionNetworkFolder(); + + if (this.input_path.IsNotNull() && this.Action_network_folder.IsNotNull() && this.input_path.TrimEnd("\\".ToCharArray()).EqualsIgnoreCase(this.Action_network_folder.TrimEnd("\\".ToCharArray()))) + { + //You dont want to watch, then copy the same file back to the same folder + AITOOL.Log($"WARNING: Input path ({this.input_path}) & 'Copy alert images to folder' path may not be the same for camera '{this.Name}'.", ""); + } + + + } + catch (Exception ex) + { + + AITOOL.Log($"Error: While updating camera '{this.Name}', got error: {ex.Msg()}"); + } + } - //write config file - using (StreamWriter sw = System.IO.File.CreateText(AppDomain.CurrentDomain.BaseDirectory + $"/cameras/{ _name }.txt")) + } + + public void CleanActionNetworkFolder() + { + if (this.Action_image_copy_enabled && + !string.IsNullOrWhiteSpace(this.Action_network_folder) && + this.Action_network_folder_purge_older_than_days > 0 && + LastJPGCleanDay != DateTime.Now.DayOfYear && + Directory.Exists(this.Action_network_folder)) { - name = _name; - prefix = _prefix; - triggering_objects_as_string = _triggering_objects_as_string; - trigger_urls_as_string = _trigger_urls_as_string; - telegram_enabled = _telegram_enabled; - enabled = _enabled; - cooldown_time = _cooldown_time; - threshold_lower = _threshold_lower; - threshold_upper = _threshold_upper; + AITOOL.Log($"Debug: Cleaning out jpg files older than '{this.Action_network_folder_purge_older_than_days}' days in '{this.Action_network_folder}'..."); + List filist = new List(Global.GetFiles(this.Action_network_folder, "*.jpg")); + int deleted = 0; + int errs = 0; + foreach (FileInfo fi in filist) + { + if ((DateTime.Now - fi.LastWriteTime).TotalDays > this.Action_network_folder_purge_older_than_days) + { + try { fi.Delete(); deleted++; } + catch { errs++; } + } + } + if (errs == 0) + AITOOL.Log($"Debug: ...Deleted {deleted} out of {filist.Count} files"); + else + AITOOL.Log($"Debug: ...Deleted {deleted} out of {filist.Count} files with {errs} errors."); - triggering_objects = triggering_objects_as_string.Split(','); //split the row of triggering objects between every ',' + LastJPGCleanDay = DateTime.Now.DayOfYear; - trigger_urls = trigger_urls_as_string.Replace(" ", "").Split(','); //all trigger urls in an array - trigger_urls = trigger_urls.Except(new string[] { "" }).ToArray(); //remove empty entries - //rewrite trigger_urls_as_string without possible empty entires - int i = 0; - trigger_urls_as_string = ""; - foreach (string c in trigger_urls) + } + } + public void UpdateTriggeringObjects() + { + try + { + //Convert string Triggering objects to RelevantObjectManager instances + if (this.DefaultTriggeringObjects == null || this.DefaultTriggeringObjects.ObjectList.IsEmpty() || !this.triggering_objects_as_string.IsEmpty() || !this.additional_triggering_objects_as_string.IsEmpty()) + { + this.DefaultTriggeringObjects = new ClsRelevantObjectManager(this.triggering_objects_as_string + "," + this.additional_triggering_objects_as_string, "Default", this); + this.triggering_objects_as_string = ""; + this.additional_triggering_objects_as_string = ""; + } + else //force the camera name to stay correct if renamed + { + this.DefaultTriggeringObjects.Init(this); + } + + if (this.TelegramTriggeringObjects == null || this.TelegramTriggeringObjects.ObjectList.IsEmpty() || !this.telegram_triggering_objects.IsEmpty()) + { + this.TelegramTriggeringObjects = new ClsRelevantObjectManager(this.telegram_triggering_objects, "Telegram", this); + this.telegram_triggering_objects = ""; + } + else //force the camera name to stay correct if renamed { - trigger_urls_as_string += c; - if(i < (trigger_urls.Length - 1)) + this.TelegramTriggeringObjects.Init(this); + } + + if (this.PushoverTriggeringObjects == null || this.PushoverTriggeringObjects.ObjectList.IsEmpty() || !this.telegram_triggering_objects.IsEmpty()) + { + this.PushoverTriggeringObjects = new ClsRelevantObjectManager(this.Action_pushover_triggering_objects, "Pushover", this); + this.Action_pushover_triggering_objects = ""; + } + else //force the camera name to stay correct if renamed + { + this.PushoverTriggeringObjects.Init(this); + } + + if (this.MQTTTriggeringObjects == null || this.MQTTTriggeringObjects.ObjectList.IsEmpty()) + { + this.MQTTTriggeringObjects = new ClsRelevantObjectManager(AppSettings.Settings.ObjectPriority, "MQTT", this); + } + else //force the camera name to stay correct if renamed + { + this.MQTTTriggeringObjects.Init(this); + } + + } + catch (Exception ex) + { + AITOOL.Log($"Error: While updating camera '{this.Name}', got error: {ex.Msg()}"); + } + + } + + + public string GetMaskFile(bool MustExist, ClsImageQueueItem CurImg = null, ImageResItem ir = null) + { + string ret = ""; + + lock (CamLock) + { + try + { + String resstr = ""; + + if (CurImg != null) { - trigger_urls_as_string += ", "; + resstr = $"_{CurImg.Width}x{CurImg.Height}"; + } + else if (ir == null && this.ImageResolutions.Count > 0) + { + ir = this.ImageResolutions[0]; //the first one should be the most recent image processed because of the sort. + resstr = $"_{ir.Width}x{ir.Height}"; + } + else if (ir != null) + { + resstr = $"_{ir.Width}x{ir.Height}"; + } + + string CamMaskFile = ""; + if (!string.IsNullOrEmpty(this.MaskFileName)) + { + if (this.MaskFileName.Contains("\\") && this.MaskFileName.Contains(".")) + CamMaskFile = this.MaskFileName; + else if (this.MaskFileName.Contains(".")) + CamMaskFile = Path.Combine(Path.GetDirectoryName(AppSettings.Settings.SettingsFileName), $"{this.MaskFileName}"); + else + CamMaskFile = Path.Combine(Path.GetDirectoryName(AppSettings.Settings.SettingsFileName), $"{this.MaskFileName}.bmp"); + + //Add WidthxHeight to filename + string ResFile = Path.Combine(Path.GetDirectoryName(CamMaskFile), $"{Path.GetFileNameWithoutExtension(CamMaskFile)}{resstr}.bmp"); + + bool resempty = string.IsNullOrEmpty(resstr); + bool cammaskexist = File.Exists(CamMaskFile); + bool resexist = File.Exists(ResFile); + if (!resempty && cammaskexist && !Path.GetFileName(CamMaskFile).Contains("_") && !resexist) + { + //lets rename it to appropriate ResFile name + string tmpresstr = ""; + using (FileStream fileStream = new FileStream(CamMaskFile, FileMode.Open, FileAccess.Read)) + { + using Image img = Image.FromStream(fileStream, false, false); + tmpresstr = $"_{img.Width}x{img.Height}"; + } + if (tmpresstr == resstr) + { + AITOOL.Log($"Debug: Renaming mask file from '{CamMaskFile}' to '{ResFile}'..."); + File.Move(CamMaskFile, ResFile); + } + else + { + AITOOL.Log($"Debug: Cannot rename mask file because it does not match the current image resolution of '{resstr}' (!={tmpresstr}): MaskFile='{CamMaskFile}'..."); + } + } + else + { + //AITOOL.Log($"Debug: ResEmpty={resempty}, CamMaskExist={cammaskexist}, ResMaskFileExist={resexist}, CurRes={resstr}, MaskRes={resstr}, CamMaskFile='{CamMaskFile}', ResMaskFile={ResFile}..."); + } + + if (MustExist) + { + if (File.Exists(ResFile)) + { + return ResFile; + } + else + { + return ""; + } + } + else + { + return ResFile; + } + } - i++; + + } + catch (Exception ex) + { + + AITOOL.Log("Error: " + ex.Msg()); } - sw.WriteLine($"Trigger URL(s): \"{trigger_urls_as_string.Replace(", ,", "")}\" (input one or multiple urls, leave empty to disable; format: \"url, url, url\", example: \"http://192.168.1.133:80/admin?trigger&camera=frontyard&user=admin&pw=secretpassword, http://google.com\")"); - sw.WriteLine($"Relevant objects: \"{triggering_objects_as_string}\" (format: \"object, object, ...\", options: see below, example: \"person, bicycle, car\")"); - sw.WriteLine($"Input file begins with: \"{prefix}\" (only analyze images which names start with this text, leave empty to disable the feature, example: \"backyardcam\")"); - if (telegram_enabled == true) + } + + return ret; + + } + public bool UpdateImageResolutions(ClsImageQueueItem CurImg) + { + + bool ret = false; + + if (!CurImg.IsValid()) + return ret; + + lock (CamLock) + { + ImageResItem newri = new ImageResItem(CurImg); + + int idx = this.ImageResolutions.IndexOf(newri); + bool updated = false; + + if (idx > -1) { - sw.WriteLine("Send images to Telegram: \"yes\"(options: yes, no)"); + if (CurImg.TimeCreated > this.ImageResolutions[idx].LastSeenDate) + { + updated = true; + this.ImageResolutions[idx].LastFileName = CurImg.image_path; + this.ImageResolutions[idx].LastSeenDate = CurImg.TimeCreated; + this.ImageResolutions[idx].LastFileSize = CurImg.FileSize; + this.ImageResolutions[idx].LastFileDPI = CurImg.DPI; + this.ImageResolutions[idx].Count++; + } } else { - sw.WriteLine("Send images to Telegram: \"no\"(options: yes, no)"); + ret = true; + this.ImageResolutions.Add(newri); } - if (enabled == true) + //sort so most recent is at top of list + + if (ret || updated) + this.ImageResolutions = this.ImageResolutions.OrderByDescending(x => x.LastSeenDate).ToList(); + + } + + return ret; + + } + public async Task ScanImagesAsync(int MaxFiles = 3000, int MaxTimeScanningMS = 60000, int MaxDaysOld = -4) + { + await Task.Run(() => ScanImages(MaxFiles, MaxTimeScanningMS, MaxDaysOld)); + } + public void ScanImages(int MaxFiles = 3000, int MaxTimeScanningMS = 60000, int MaxDaysOld = -4) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + List files = new List(); + + Stopwatch fscansw = Stopwatch.StartNew(); + + if (!string.IsNullOrEmpty(this.input_path) && Directory.Exists(this.input_path)) + { + List newfiles = Global.GetFiles(this.input_path, $"{this.Prefix}*.jpg", this.input_path_includesubfolders ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly, DateTime.Now.AddDays(MaxDaysOld), DateTime.Now, MaxFiles); + files.AddRange(newfiles); + AITOOL.Log($"Debug: Found {newfiles.Count} {this.Prefix}*.jpg files in {this.input_path}"); + } + + if (files.Count < MaxFiles && !string.IsNullOrEmpty(AppSettings.Settings.input_path) && AppSettings.Settings.input_path != this.input_path && Directory.Exists(AppSettings.Settings.input_path)) + { + List newfiles = Global.GetFiles(AppSettings.Settings.input_path, $"{this.Prefix}*.jpg", AppSettings.Settings.input_path_includesubfolders ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly, DateTime.Now.AddDays(MaxDaysOld), DateTime.Now, MaxFiles); + files.AddRange(newfiles); + AITOOL.Log($"Debug: Found {newfiles.Count} {this.Prefix}*.jpg files in {AppSettings.Settings.input_path}"); + } + + if (files.Count < MaxFiles && this.Action_image_copy_enabled && !string.IsNullOrEmpty(this.Action_network_folder) && Directory.Exists(this.Action_network_folder)) + { + List newfiles = Global.GetFiles(this.Action_network_folder, $"{this.Prefix}*.jpg", SearchOption.TopDirectoryOnly, DateTime.Now.AddDays(MaxDaysOld), DateTime.Now, MaxFiles); + files.AddRange(newfiles); + AITOOL.Log($"Debug: Found {newfiles.Count} {this.Prefix}*.jpg files in {this.Action_network_folder}"); + } + + if (!string.IsNullOrEmpty(this.last_image_file) && File.Exists(this.last_image_file)) + { + if (!files.Any(x => string.Equals(x.FullName, this.last_image_file, StringComparison.OrdinalIgnoreCase))) { - sw.WriteLine("ai detection enabled?: \"yes\"(options: yes, no)"); + files.Add(new FileInfo(this.last_image_file)); } - else + } + + files = files.OrderByDescending(t => t.CreationTime).ToList(); + + fscansw.Stop(); + + + int cnt = 0; + int updated = 0; + int invalid = 0; + + AITOOL.Log($"Debug: Found {files.Count} images in {fscansw.ElapsedMilliseconds} ms. Scanning images..."); + + Stopwatch sw = Stopwatch.StartNew(); + + foreach (FileInfo fi in files) + { + + try { - sw.WriteLine("ai detection enabled?: \"no\"(options: yes, no)"); + ClsImageQueueItem img = new ClsImageQueueItem(fi.FullName, 0, true); + if (img.IsValid()) + { + if (this.UpdateImageResolutions(img)) + updated++; + + cnt++; + } + else + { + invalid++; + } } - sw.WriteLine($"Cooldown time: \"{cooldown_time}\" minutes (How many minutes must have passed since the last detection. Used to separate event to ensure that every event only causes one alert.)"); - sw.WriteLine($"Certainty threshold: \"{threshold_lower},{threshold_upper}\" (format: \"lower % limit, upper % limit\")"); - sw.WriteLine($"STATS: alerts,irrelevant alerts,false alerts: \"{stats_alerts.ToString()}, {stats_irrelevant_alerts.ToString()}, {stats_false_alerts.ToString()}\" "); + catch (Exception ex) + { + invalid++; + AITOOL.Log($"Debug: {fi.Name}: {ex.Message}"); + } + + if (sw.ElapsedMilliseconds >= MaxTimeScanningMS) + { + AITOOL.Log($"Debug: Max search time exceeded: {sw.ElapsedMilliseconds} ms >= {MaxTimeScanningMS} ms"); + break; + } + + } + sw.Stop(); + string reseseses = ""; + foreach (ImageResItem res in this.ImageResolutions) + { + reseseses += $"{res.ToString()};"; } + + if (string.IsNullOrEmpty(this.last_image_file) && files.Count > 0) + this.last_image_file = files[0].FullName; + + AITOOL.Log($"Debug: {cnt} of {files.Count} image files processed, {updated} new resolutions found ({invalid} invalid) in {sw.ElapsedMilliseconds} ms (Max={MaxTimeScanningMS} ms), {this.ImageResolutions.Count} different image resolutions found: {reseseses}"); + } - //delete config file - public void Delete() + //public ResultType IsRelevantObject(ClsPrediction pred) + //{ + // if (Global.IsInList(pred.Label, this.triggering_objects_as_string, TrueIfEmpty: false) || Global.IsInList(pred.Label, this.additional_triggering_objects_as_string, TrueIfEmpty: false)) + // return ResultType.Relevant; + // else + // return ResultType.UnwantedObject; + + //} + public ResultType IsRelevantSize(ClsPrediction pred) { - System.IO.File.Delete(AppDomain.CurrentDomain.BaseDirectory + $"/cameras/{ this.name }.txt"); - } + ResultType ret = ResultType.Relevant; + + if (pred.RectWidth == 0 || pred.RectHeight == 0 || pred.ImageHeight == 0 || pred.ImageWidth == 0) + return ResultType.Unknown; + + //public int PredSizeMinPercentOfImage = 1; //prediction must be at least this % of the image + //public int PredSizeMaxPercentOfImage = 95; + //public int PredSizeMinHeight = 0; + //public int PredSizeMinWidth = 0; + //public int PredSizeMaxHeight = 0; + //public int PredSizeMaxWidth = 0; - //read config from file + if (this.PredSizeMinPercentOfImage > 0 && pred.PercentOfImage.Round() < this.PredSizeMinPercentOfImage) + ret = ResultType.TooSmallPercent; + else if (this.PredSizeMaxPercentOfImage > 0 && pred.PercentOfImage.Round() > this.PredSizeMaxPercentOfImage) + ret = ResultType.TooLargePercent; + + if (ret == ResultType.Relevant) + { + if (pred.RectWidth < this.PredSizeMinWidth) + ret = ResultType.TooSmallWidth; + else if (pred.RectHeight < this.PredSizeMinHeight) + ret = ResultType.TooSmallHeight; + else if (this.PredSizeMaxWidth > 0 && pred.RectWidth > this.PredSizeMaxWidth) + ret = ResultType.TooLargeWidth; + else if (this.PredSizeMaxHeight > 0 && pred.RectHeight > this.PredSizeMaxHeight) + ret = ResultType.TooLargeHeight; + } + + return ret; + } public void ReadConfig(string config_path) { //retrieve whole config file content string[] content = System.IO.File.ReadAllLines(config_path); //import config data into variables, cut out relevant data between " " - name = Path.GetFileNameWithoutExtension(config_path); - prefix = content[2].Split('"')[1]; + this.Name = Path.GetFileNameWithoutExtension(config_path); + this.Prefix = content[2].Split('"')[1]; //read triggering objects - triggering_objects_as_string = content[1].Split('"')[1].Replace(" ", ""); //take the second line, split it between every ", take the part after the first ", remove every " " in this part - triggering_objects = triggering_objects_as_string.Split(','); //split the row of triggering objects between every ',' - + this.triggering_objects_as_string = content[1].Split('"')[1].Replace(" ", ""); //take the second line, split it between every ", take the part after the first ", remove every " " in this part + this.triggering_objects = this.triggering_objects_as_string.SplitStr(",").ToArray(); //split the row of triggering objects between every ',' + + //input_path = AppSettings.Settings.input_path; //read trigger urls - trigger_urls_as_string = content[0].Split('"')[1]; //takes the first line, cuts out everything between the first and the second " marker; all trigger urls in one string, ! still contains possible spaces etc. - trigger_urls = trigger_urls_as_string.Replace(" ", "").Split(','); //all trigger urls in an array - trigger_urls = trigger_urls.Except(new string[] { "" }).ToArray(); //remove empty entries + this.trigger_urls_as_string = content[0].Split('"')[1]; //takes the first line, cuts out everything between the first and the second " marker; all trigger urls in one string, ! still contains possible spaces etc. + this.trigger_urls = this.trigger_urls_as_string.Replace(" ", "").Split(','); //all trigger urls in an array + this.trigger_urls = this.trigger_urls.Except(new string[] { "" }).ToArray(); //remove empty entries //rewrite trigger_urls_as_string without possible empty entires int i = 0; - trigger_urls_as_string = ""; - foreach (string c in trigger_urls) + this.trigger_urls_as_string = ""; + foreach (string c in this.trigger_urls) { - trigger_urls_as_string += c; - if (i < (trigger_urls.Length - 1)) + this.trigger_urls_as_string += c; + if (i < (this.trigger_urls.Length - 1)) { - trigger_urls_as_string += ", "; + this.trigger_urls_as_string += " | "; } i++; } @@ -145,66 +797,100 @@ public void ReadConfig(string config_path) //read telegram enabled if (content[3].Split('"')[1].Replace(" ", "") == "yes") { - telegram_enabled = true; + this.telegram_enabled = true; } else { - telegram_enabled = false; + this.telegram_enabled = false; } //read enabled if (content[4].Split('"')[1].Replace(" ", "") == "yes") { - enabled = true; + this.enabled = true; } else { - enabled = false; + this.enabled = false; } - - Double.TryParse(content[5].Split('"')[1], out cooldown_time); //read cooldown time - + double outvaldbl = 0; + int outvalint = 0; + Double.TryParse(content[5].Split('"')[1], out outvaldbl); //read cooldown time + this.cooldown_time = outvaldbl; //read lower and upper threshold. Only load if line containing threshold values already exists (>version 1.58). if (content[6] != "") { - Int32.TryParse(content[6].Split('"')[1].Split(',')[0], out threshold_lower); //read lower threshold - Int32.TryParse(content[6].Split('"')[1].Split(',')[1], out threshold_upper); //read upper threshold + Int32.TryParse(content[6].Split('"')[1].Split(',')[0], out outvalint); //read lower threshold + this.threshold_lower = outvalint; + Int32.TryParse(content[6].Split('"')[1].Split(',')[1], out outvalint); //read upper threshold + this.threshold_upper = outvalint; } else //if config file from older version, set values to 0% and 100% { - threshold_lower = 0; - threshold_upper = 100; + this.threshold_lower = 30; + this.threshold_upper = 100; } - + //read stats - Int32.TryParse(content[7].Split('"')[1].Split(',')[0], out stats_alerts); //bedeutet: Zeile 7 (6+1), aufgetrennt an ", 2tes (1+1) Resultat, aufgeteilt an ',', davon 1. Resultat - Int32.TryParse(content[7].Split('"')[1].Split(',')[1], out stats_irrelevant_alerts); - Int32.TryParse(content[7].Split('"')[1].Split(',')[2], out stats_false_alerts); + + Int32.TryParse(content[7].Split('"')[1].Split(',')[0], out outvalint); //bedeutet: Zeile 7 (6+1), aufgetrennt an ", 2tes (1+1) Resultat, aufgeteilt an ',', davon 1. Resultat + this.stats_alerts = outvalint; + Int32.TryParse(content[7].Split('"')[1].Split(',')[1], out outvalint); + this.stats_irrelevant_alerts = outvalint; + Int32.TryParse(content[7].Split('"')[1].Split(',')[2], out outvalint); + this.stats_false_alerts = outvalint; } //one correct alarm counter public void IncrementAlerts() { - stats_alerts++; - WriteConfig(name, prefix, triggering_objects_as_string, trigger_urls_as_string, telegram_enabled, enabled, cooldown_time, threshold_lower, threshold_upper); + this.stats_alerts++; + AppSettings.SaveAsync(); + //WriteConfig(name, prefix, triggering_objects_as_string, trigger_urls_as_string, telegram_enabled, enabled, cooldown_time, threshold_lower, threshold_upper); } //one alarm that contained no objects counter public void IncrementFalseAlerts() { - stats_false_alerts++; - WriteConfig(name, prefix, triggering_objects_as_string, trigger_urls_as_string, telegram_enabled, enabled, cooldown_time, threshold_lower, threshold_upper); + this.stats_false_alerts++; + AppSettings.SaveAsync(); + //WriteConfig(name, prefix, triggering_objects_as_string, trigger_urls_as_string, telegram_enabled, enabled, cooldown_time, threshold_lower, threshold_upper); } //one alarm that contained irrelevant objects counter public void IncrementIrrelevantAlerts() { - stats_irrelevant_alerts++; - WriteConfig(name, prefix, triggering_objects_as_string, trigger_urls_as_string, telegram_enabled, enabled, cooldown_time, threshold_lower, threshold_upper); + this.stats_irrelevant_alerts++; + AppSettings.SaveAsync(); + //WriteConfig(name, prefix, triggering_objects_as_string, trigger_urls_as_string, telegram_enabled, enabled, cooldown_time, threshold_lower, threshold_upper); + } + + public override string ToString() + { + return this.Name; } + public override bool Equals(object obj) + { + return Equals(obj as Camera); + } + public bool Equals(Camera other) + { + return other != null && + string.Equals(Name, other.Name, StringComparison.OrdinalIgnoreCase); + } + + public static bool operator ==(Camera left, Camera right) + { + return EqualityComparer.Default.Equals(left, right); + } + + public static bool operator !=(Camera left, Camera right) + { + return !(left == right); + } } } diff --git a/src/UI/CheckedComboBox.cs b/src/UI/CheckedComboBox.cs new file mode 100644 index 00000000..49f298fe --- /dev/null +++ b/src/UI/CheckedComboBox.cs @@ -0,0 +1,417 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows.Forms; +using System.Drawing; +using System.Diagnostics; + +namespace CheckComboBoxTest { + public class CheckedComboBox : ComboBox { + /// + /// Internal class to represent the dropdown list of the CheckedComboBox + /// + internal class Dropdown : Form { + // ---------------------------------- internal class CCBoxEventArgs -------------------------------------------- + /// + /// Custom EventArgs encapsulating value as to whether the combo box value(s) should be assignd to or not. + /// + internal class CCBoxEventArgs : EventArgs { + private bool assignValues; + public bool AssignValues { + get { return assignValues; } + set { assignValues = value; } + } + private EventArgs e; + public EventArgs EventArgs { + get { return e; } + set { e = value; } + } + public CCBoxEventArgs(EventArgs e, bool assignValues) : base() { + this.e = e; + this.assignValues = assignValues; + } + } + + // ---------------------------------- internal class CustomCheckedListBox -------------------------------------------- + + /// + /// A custom CheckedListBox being shown within the dropdown form representing the dropdown list of the CheckedComboBox. + /// + internal class CustomCheckedListBox : CheckedListBox { + private int curSelIndex = -1; + + public CustomCheckedListBox() : base() { + this.SelectionMode = SelectionMode.One; + this.HorizontalScrollbar = true; + } + + /// + /// Intercepts the keyboard input, [Enter] confirms a selection and [Esc] cancels it. + /// + /// The Key event arguments + protected override void OnKeyDown(KeyEventArgs e) { + if (e.KeyCode == Keys.Enter) { + // Enact selection. + ((CheckedComboBox.Dropdown) Parent).OnDeactivate(new CCBoxEventArgs(null, true)); + e.Handled = true; + + } else if (e.KeyCode == Keys.Escape) { + // Cancel selection. + ((CheckedComboBox.Dropdown) Parent).OnDeactivate(new CCBoxEventArgs(null, false)); + e.Handled = true; + + } else if (e.KeyCode == Keys.Delete) { + // Delete unckecks all, [Shift + Delete] checks all. + for (int i = 0; i < Items.Count; i++) { + SetItemChecked(i, e.Shift); + } + e.Handled = true; + } + // If no Enter or Esc keys presses, let the base class handle it. + base.OnKeyDown(e); + } + + protected override void OnMouseMove(MouseEventArgs e) { + base.OnMouseMove(e); + int index = IndexFromPoint(e.Location); + Debug.WriteLine("Mouse over item: " + (index >= 0 ? GetItemText(Items[index]) : "None")); + if ((index >= 0) && (index != curSelIndex)) { + curSelIndex = index; + SetSelected(index, true); + } + } + + } // end internal class CustomCheckedListBox + + // -------------------------------------------------------------------------------------------------------- + + // ********************************************* Data ********************************************* + + private CheckedComboBox ccbParent; + + // Keeps track of whether checked item(s) changed, hence the value of the CheckedComboBox as a whole changed. + // This is simply done via maintaining the old string-representation of the value(s) and the new one and comparing them! + private string oldStrValue = ""; + public bool ValueChanged { + get { + string newStrValue = ccbParent.Text; + if ((oldStrValue.Length > 0) && (newStrValue.Length > 0)) { + return (oldStrValue.CompareTo(newStrValue) != 0); + } else { + return (oldStrValue.Length != newStrValue.Length); + } + } + } + + // Array holding the checked states of the items. This will be used to reverse any changes if user cancels selection. + bool[] checkedStateArr; + + // Whether the dropdown is closed. + private bool dropdownClosed = true; + + private CustomCheckedListBox cclb; + public CustomCheckedListBox List { + get { return cclb; } + set { cclb = value; } + } + + // ********************************************* Construction ********************************************* + + public Dropdown(CheckedComboBox ccbParent) { + this.ccbParent = ccbParent; + InitializeComponent(); + this.ShowInTaskbar = false; + // Add a handler to notify our parent of ItemCheck events. + this.cclb.ItemCheck += new System.Windows.Forms.ItemCheckEventHandler(this.cclb_ItemCheck); + } + + // ********************************************* Methods ********************************************* + + // Create a CustomCheckedListBox which fills up the entire form area. + private void InitializeComponent() { + this.cclb = new CustomCheckedListBox(); + this.SuspendLayout(); + // + // cclb + // + this.cclb.BorderStyle = System.Windows.Forms.BorderStyle.None; + this.cclb.Dock = System.Windows.Forms.DockStyle.Fill; + this.cclb.FormattingEnabled = true; + this.cclb.Location = new System.Drawing.Point(0, 0); + this.cclb.Name = "cclb"; + this.cclb.Size = new System.Drawing.Size(47, 15); + this.cclb.TabIndex = 0; + // + // Dropdown + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.BackColor = System.Drawing.SystemColors.Menu; + this.ClientSize = new System.Drawing.Size(47, 16); + this.ControlBox = false; + this.Controls.Add(this.cclb); + this.ForeColor = System.Drawing.SystemColors.ControlText; + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow; + this.MinimizeBox = false; + this.Name = "ccbParent"; + this.StartPosition = System.Windows.Forms.FormStartPosition.Manual; + this.ResumeLayout(false); + } + + public string GetCheckedItemsStringValue() { + StringBuilder sb = new StringBuilder(""); + for (int i = 0; i < cclb.CheckedItems.Count; i++) { + sb.Append(cclb.GetItemText(cclb.CheckedItems[i])).Append(ccbParent.ValueSeparator); + } + if (sb.Length > 0) { + sb.Remove(sb.Length - ccbParent.ValueSeparator.Length, ccbParent.ValueSeparator.Length); + } + return sb.ToString(); + } + + /// + /// Closes the dropdown portion and enacts any changes according to the specified boolean parameter. + /// NOTE: even though the caller might ask for changes to be enacted, this doesn't necessarily mean + /// that any changes have occurred as such. Caller should check the ValueChanged property of the + /// CheckedComboBox (after the dropdown has closed) to determine any actual value changes. + /// + /// + public void CloseDropdown(bool enactChanges) { + if (dropdownClosed) { + return; + } + Debug.WriteLine("CloseDropdown"); + // Perform the actual selection and display of checked items. + if (enactChanges) { + ccbParent.SelectedIndex = -1; + // Set the text portion equal to the string comprising all checked items (if any, otherwise empty!). + ccbParent.Text = GetCheckedItemsStringValue(); + + } else { + // Caller cancelled selection - need to restore the checked items to their original state. + for (int i = 0; i < cclb.Items.Count; i++) { + cclb.SetItemChecked(i, checkedStateArr[i]); + } + } + // From now on the dropdown is considered closed. We set the flag here to prevent OnDeactivate() calling + // this method once again after hiding this window. + dropdownClosed = true; + // Set the focus to our parent CheckedComboBox and hide the dropdown check list. + ccbParent.Focus(); + this.Hide(); + // Notify CheckedComboBox that its dropdown is closed. (NOTE: it does not matter which parameters we pass to + // OnDropDownClosed() as long as the argument is CCBoxEventArgs so that the method knows the notification has + // come from our code and not from the framework). + ccbParent.OnDropDownClosed(new CCBoxEventArgs(null, false)); + } + + protected override void OnActivated(EventArgs e) { + Debug.WriteLine("OnActivated"); + base.OnActivated(e); + dropdownClosed = false; + // Assign the old string value to compare with the new value for any changes. + oldStrValue = ccbParent.Text; + // Make a copy of the checked state of each item, in cace caller cancels selection. + checkedStateArr = new bool[cclb.Items.Count]; + for (int i = 0; i < cclb.Items.Count; i++) { + checkedStateArr[i] = cclb.GetItemChecked(i); + } + } + + protected override void OnDeactivate(EventArgs e) { + Debug.WriteLine("OnDeactivate"); + base.OnDeactivate(e); + CCBoxEventArgs ce = e as CCBoxEventArgs; + if (ce != null) { + CloseDropdown(ce.AssignValues); + + } else { + // If not custom event arguments passed, means that this method was called from the + // framework. We assume that the checked values should be registered regardless. + CloseDropdown(true); + } + } + + private void cclb_ItemCheck(object sender, ItemCheckEventArgs e) { + if (ccbParent.ItemCheck != null) { + ccbParent.ItemCheck(sender, e); + } + } + + } // end internal class Dropdown + + // ******************************** Data ******************************** + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + // A form-derived object representing the drop-down list of the checked combo box. + private Dropdown dropdown; + private BrightIdeasSoftware.CheckStateRenderer checkStateRenderer1; + + // The valueSeparator character(s) between the ticked elements as they appear in the + // text portion of the CheckedComboBox. + private string valueSeparator; + public string ValueSeparator { + get { return valueSeparator; } + set { valueSeparator = value; } + } + + public bool CheckOnClick { + get { return dropdown.List.CheckOnClick; } + set { dropdown.List.CheckOnClick = value; } + } + + public new string DisplayMember { + get { return dropdown.List.DisplayMember; } + set { dropdown.List.DisplayMember = value; } + } + + public new CheckedListBox.ObjectCollection Items { + get { return dropdown.List.Items; } + } + + public CheckedListBox.CheckedItemCollection CheckedItems { + get { return dropdown.List.CheckedItems; } + } + + public CheckedListBox.CheckedIndexCollection CheckedIndices { + get { return dropdown.List.CheckedIndices; } + } + + public bool ValueChanged { + get { return dropdown.ValueChanged; } + } + + // Event handler for when an item check state changes. + public event ItemCheckEventHandler ItemCheck; + + // ******************************** Construction ******************************** + + public CheckedComboBox() : base() { + // We want to do the drawing of the dropdown. + this.DrawMode = DrawMode.OwnerDrawVariable; + // Default value separator. + this.valueSeparator = ", "; + // This prevents the actual ComboBox dropdown to show, although it's not strickly-speaking necessary. + // But including this remove a slight flickering just before our dropdown appears (which is caused by + // the empty-dropdown list of the ComboBox which is displayed for fractions of a second). + this.DropDownHeight = 1; + // This is the default setting - text portion is editable and user must click the arrow button + // to see the list portion. Although we don't want to allow the user to edit the text portion + // the DropDownList style is not being used because for some reason it wouldn't allow the text + // portion to be programmatically set. Hence we set it as editable but disable keyboard input (see below). + this.DropDownStyle = ComboBoxStyle.DropDown; + this.dropdown = new Dropdown(this); + // CheckOnClick style for the dropdown (NOTE: must be set after dropdown is created). + this.CheckOnClick = true; + } + + // ******************************** Operations ******************************** + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) { + if (disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + protected override void OnDropDown(EventArgs e) { + base.OnDropDown(e); + DoDropDown(); + } + + private void DoDropDown() { + if (!dropdown.Visible) { + Rectangle rect = RectangleToScreen(this.ClientRectangle); + dropdown.Location = new Point(rect.X, rect.Y + this.Size.Height); + int count = dropdown.List.Items.Count; + if (count > this.MaxDropDownItems) { + count = this.MaxDropDownItems; + } else if (count == 0) { + count = 1; + } + dropdown.Size = new Size(this.Size.Width, (dropdown.List.ItemHeight) * count + 2); + dropdown.Show(this); + } + } + + protected override void OnDropDownClosed(EventArgs e) { + // Call the handlers for this event only if the call comes from our code - NOT the framework's! + // NOTE: that is because the events were being fired in a wrong order, due to the actual dropdown list + // of the ComboBox which lies underneath our dropdown and gets involved every time. + if (e is Dropdown.CCBoxEventArgs) { + base.OnDropDownClosed(e); + } + } + + protected override void OnKeyDown(KeyEventArgs e) { + if (e.KeyCode == Keys.Down) { + // Signal that the dropdown is "down". This is required so that the behaviour of the dropdown is the same + // when it is a result of user pressing the Down_Arrow (which we handle and the framework wouldn't know that + // the list portion is down unless we tell it so). + // NOTE: all that so the DropDownClosed event fires correctly! + OnDropDown(null); + } + // Make sure that certain keys or combinations are not blocked. + e.Handled = !e.Alt && !(e.KeyCode == Keys.Tab) && + !((e.KeyCode == Keys.Left) || (e.KeyCode == Keys.Right) || (e.KeyCode == Keys.Home) || (e.KeyCode == Keys.End)); + + base.OnKeyDown(e); + } + + protected override void OnKeyPress(KeyPressEventArgs e) { + e.Handled = true; + base.OnKeyPress(e); + } + + public bool GetItemChecked(int index) { + if (index < 0 || index > Items.Count) { + throw new ArgumentOutOfRangeException("index", "value out of range"); + } else { + return dropdown.List.GetItemChecked(index); + } + } + + public void SetItemChecked(int index, bool isChecked) { + if (index < 0 || index > Items.Count) { + throw new ArgumentOutOfRangeException("index", "value out of range"); + } else { + dropdown.List.SetItemChecked(index, isChecked); + // Need to update the Text. + this.Text = dropdown.GetCheckedItemsStringValue(); + } + } + + public CheckState GetItemCheckState(int index) { + if (index < 0 || index > Items.Count) { + throw new ArgumentOutOfRangeException("index", "value out of range"); + } else { + return dropdown.List.GetItemCheckState(index); + } + } + + public void SetItemCheckState(int index, CheckState state) { + if (index < 0 || index > Items.Count) { + throw new ArgumentOutOfRangeException("index", "value out of range"); + } else { + dropdown.List.SetItemCheckState(index, state); + // Need to update the Text. + this.Text = dropdown.GetCheckedItemsStringValue(); + } + } + + private void InitializeComponent() + { + this.checkStateRenderer1 = new BrightIdeasSoftware.CheckStateRenderer(); + this.SuspendLayout(); + this.ResumeLayout(false); + + } + } // end public class CheckedComboBox + +} diff --git a/src/UI/CheckedComboBox.resx b/src/UI/CheckedComboBox.resx new file mode 100644 index 00000000..a78fb2be --- /dev/null +++ b/src/UI/CheckedComboBox.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + False + + \ No newline at end of file diff --git a/src/UI/CleanOldInstalls.bat b/src/UI/CleanOldInstalls.bat new file mode 100644 index 00000000..983c6067 --- /dev/null +++ b/src/UI/CleanOldInstalls.bat @@ -0,0 +1,9 @@ +@echo off +echo Deleting old installer files +IF NOT EXIST "%~dp0Installer" MD "%~dp0Installer" +CD "%~dp0Installer" +DEL /f AITOOLSetup*.exe +::FOR /F "usebackq tokens=* skip=0 delims=" %%A IN (`DIR AIToolSetup*.exe /B /O:-D /A:-D`) DO ( +:: ECHO Processing: %%~nA +:: DEL "%%A" +::) diff --git a/src/UI/ClsDeepStackResponse.cs b/src/UI/ClsDeepStackResponse.cs new file mode 100644 index 00000000..2c3fb524 --- /dev/null +++ b/src/UI/ClsDeepStackResponse.cs @@ -0,0 +1,47 @@ +namespace AITool +{ + //classes for AI analysis + + public class ClsDeepStackResponse + { + //output = { + // "success": False, + // "error": "invalid image file", + // "code": 400, + // } + + + //output = {"success": True, "predictions": outputs} + + + //output = { + // "success": False, + // "error": "error occured on the server", + // "code": 500, + // } + + //https://www.codeproject.com/AI/docs/api/api_reference.html + + public bool success { get; set; } + public string error { get; set; } + public string message { get; set; } + public string imageBase64 { get; set; } + public int code { get; set; } + public int count { get; set; } + public int inferenceMs { get; set; } + public int processMs { get; set; } + public int analysisRoundTripMs { get; set; } + public string moduleId { get; set; } + public string moduleName { get; set; } + public string command { get; set; } + public string executionProvider { get; set; } + public bool canUseGPU { get; set; } + public string label { get; set; } //this is used for scene detection {'success': True, 'confidence': 0.7373981, 'label': 'conference_room' + public string plate { get; set; } + public float confidence { get; set; } //this is used for scene detection {'success': True, 'confidence': 0.7373981, 'label': 'conference_room' + public ClsDeepstackDetection[] predictions { get; set; } + + } +} + + diff --git a/src/UI/ClsDeepstackDetection.cs b/src/UI/ClsDeepstackDetection.cs new file mode 100644 index 00000000..0a1a9aea --- /dev/null +++ b/src/UI/ClsDeepstackDetection.cs @@ -0,0 +1,19 @@ +namespace AITool +{ + public class ClsDeepstackDetection + { + + public string label { get; set; } = ""; + public string Detail { get; set; } = ""; //only used internally, deepstack does not ever send this + public string UserID { get; set; } = ""; //only for face detection + public double confidence { get; set; } //only for face detection or scene detection + public double y_min { get; set; } = 0; + public double x_min { get; set; } = 0; + public double y_max { get; set; } = 0; + public double x_max { get; set; } = 0; + public string Server { get; set; } = ""; + + } +} + + diff --git a/src/UI/ClsDoodsDetection.cs b/src/UI/ClsDoodsDetection.cs new file mode 100644 index 00000000..fb116185 --- /dev/null +++ b/src/UI/ClsDoodsDetection.cs @@ -0,0 +1,25 @@ +using Newtonsoft.Json; + +namespace AITool +{ + public class ClsDoodsDetection + { + [JsonProperty("top")] + public double Top { get; set; } = 0; + + [JsonProperty("left")] + public double Left { get; set; } = 0; + + [JsonProperty("bottom")] + public double Bottom { get; set; } = 0; + + [JsonProperty("right")] + public double Right { get; set; } = 0; + + [JsonProperty("label")] + public string Label { get; set; } = ""; + + [JsonProperty("confidence")] + public double Confidence { get; set; } = 0; + } +} diff --git a/src/UI/ClsDoodsRequest.cs b/src/UI/ClsDoodsRequest.cs new file mode 100644 index 00000000..22653040 --- /dev/null +++ b/src/UI/ClsDoodsRequest.cs @@ -0,0 +1,48 @@ +using Newtonsoft.Json; + +namespace AITool +{ + public class ClsDoodsRequest + { + [JsonProperty("detector_name")] + public string DetectorName { get; set; } = "default"; + + [JsonProperty("data")] + public string Data { get; set; } = ""; + + [JsonProperty("file")] + public string File { get; set; } = ""; + + [JsonProperty("detect")] + public Detect Detect { get; set; } = new Detect { MinPercentMatch = 0 }; + } + + + //public partial class Region + //{ + // [JsonProperty("top")] + // public double Top { get; set; } = 0; + + // [JsonProperty("left")] + // public double Left { get; set; } = 0; + + // [JsonProperty("bottom")] + // public double Bottom { get; set; } = 0; + + // [JsonProperty("right")] + // public double Right { get; set; } = 0; + + // [JsonProperty("detect")] + // public Detect[] Detect { get; set; } = new Detect[] { new Detect { MinPercentMatch = 0 } }; + + // [JsonProperty("covers")] + // public bool Covers { get; set; } + //} + + public partial class Detect + { + [JsonProperty("*")] + public long MinPercentMatch { get; set; } = 0; + + } +} diff --git a/src/UI/ClsDoodsResponse.cs b/src/UI/ClsDoodsResponse.cs new file mode 100644 index 00000000..64e9c64b --- /dev/null +++ b/src/UI/ClsDoodsResponse.cs @@ -0,0 +1,15 @@ +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace AITool +{ + // Deserialization of 'Response' from 'DOODS' failed: Input string '0.09833781' is not a valid integer. Path 'detections[0].top', line 1, position 32. [JsonReaderException] Mod: d__35 Line:1147:37, JSON: '{"detections":[{"top":0.09833781,"left":0.62415826,"bottom":0.14295554,"right":0.7029755,"label":"car","confidence":63.28125},{"top":0.05073979,"left":0.040147513,"bottom":0.08883451,"right":0.07647807,"label":"car","confidence":53.90625},{"top":0.106752336,"left":0.6479672,"bottom":0.15137008,"right":0.73192835,"label":"car","confidence":51.171875},{"top":0.047032133,"left":0.11679672,"bottom":0.08336268,"right":0.14591543,"label":"car","confidence":46.09375},{"top":0.8483164,"left":0.59002864,"bottom":0.87565106,"right":0.6246767,"label":"kite","confidence":46.09375},{"top":0.03276477,"left":0.8568243,"bottom":0.13587794,"right":0.8809123,"label":"traffic light","confidence":42.578125},{"top":0.039902665,"left":0.013500711,"bottom":0.08743232,"right":0.064132325,"label":"car","confidence":41.40625},{"top":0.043502297,"left":0.78453726,"bottom":0.11749081,"right":0.8064459,"label":"traffic light","confidence":39.0625},{"top":0.03285755,"left":0.7472904,"bottom":0.12813556,"right":0.78787124,"label":"traffic light","confidence":39.0625},{"top":0.0127313435,"left":0.16018775,"bottom":0.058782034,"right":0.18043163,"label":"traffic light","confidence":36.71875}]}' + public class ClsDoodsResponse + { + //[JsonProperty("id")] + //public string Id { get; set; } = ""; + + [JsonProperty("detections")] + public List Detections { get; set; } = new List(); + } +} diff --git a/src/UI/ClsFaceManager.cs b/src/UI/ClsFaceManager.cs new file mode 100644 index 00000000..00471f62 --- /dev/null +++ b/src/UI/ClsFaceManager.cs @@ -0,0 +1,431 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using static AITool.Global; +using static AITool.AITOOL; +using Amazon.Rekognition.Model; +using Newtonsoft.Json; +using AITool.Properties; +using System.Diagnostics; +using System.Collections.Concurrent; + +namespace AITool +{ + public class ClsFace:IEquatable + { + public string Name { get; set; } = ""; + public long Hits { get; set; } = 0; + public string FaceStoragePath { get; set; } = ""; + public List Files { get; set; } = new List(); + //[JsonIgnore] + public ConcurrentDictionary FilesDic { get; set; } = new ConcurrentDictionary(); + //public List Files = new List(); + + [JsonConstructor] + public ClsFace() + { + if (Files.Count > 0) + Files.Clear(); //this is the old list, recreate new list + + this.Update(); + } + + public ClsFace(string name) + { + Name = name; + this.Update(); + } + + public void Update() + { + this.FaceStoragePath = Path.Combine(AppSettings.Settings.FacesPath, this.Name); + if (!Directory.Exists(this.FaceStoragePath)) + Directory.CreateDirectory(this.FaceStoragePath); + + } + + + public bool TryAddFaceFile(ClsImageQueueItem CurImg, out string Newfilename, int MaxFilesPerFace, int MaxFileAgeDays) + { + Newfilename = ""; + + if (!CurImg.IsValid()) + return false; + + string fname = Path.GetFileName(CurImg.image_path).ToLower(); + Newfilename = Path.Combine(this.FaceStoragePath, fname); + + if (!this.FilesDic.ContainsKey(fname)) + { + if (this.FilesDic.Count > 1) + { + //Get the oldest one + ClsFaceFile oldest = this.FilesDic.Values.OrderBy(f => f.DateFileModified).First(); + + if (this.FilesDic.Count > MaxFilesPerFace) + { + string firstname = Path.GetFileName(oldest.FilePath).ToLower(); + bool removed = this.FilesDic.TryRemove(firstname, out ClsFaceFile value); + if (removed && oldest.Exists) + File.Delete(oldest.FilePath); + } + + if ((DateTime.Now - oldest.DateFileModified).TotalDays > MaxFileAgeDays) + { + //remove the first one + string firstname = Path.GetFileName(oldest.FilePath).ToLower(); + bool removed = this.FilesDic.TryRemove(firstname, out ClsFaceFile value); + if (removed && oldest.Exists) + File.Delete(oldest.FilePath); + + } + + } + + //if in different location, copy it in + if (!CurImg.image_path.EqualsIgnoreCase(Newfilename)) + { + if (!CurImg.CopyFileTo(Newfilename)) + return false; + } + + ClsFaceFile ff = new ClsFaceFile(Newfilename, this); + + ff.OriginalPath = CurImg.image_path; + + ff.OriginalCamera = GetCamera(ff.OriginalPath, true).Name; + + this.FilesDic.TryAdd(fname, ff); + + return true; + } + + return false; + + } + public override bool Equals(object obj) + { + return Equals(obj as ClsFace); + } + + public bool Equals(ClsFace other) + { + return other != null && + Name.EqualsIgnoreCase(other.Name); + } + + public override int GetHashCode() + { + return 539060726 + EqualityComparer.Default.GetHashCode(Name); + } + + public static bool operator ==(ClsFace left, ClsFace right) + { + return EqualityComparer.Default.Equals(left, right); + } + + public static bool operator !=(ClsFace left, ClsFace right) + { + return !(left == right); + } + } + + public class ClsFaceFile:IEquatable + { + [JsonConstructor] + public ClsFaceFile() + { + + } + public ClsFaceFile(string filepath, ClsFace face) + { + this.FilePath = filepath; + this.Name = Path.GetFileName(filepath); + this.DateAdded = DateTime.Now; + this.Update(face); + } + + public void Update(ClsFace face) + { + //first make sure path is correct + string curpath = Path.GetDirectoryName(this.FilePath); + if (!curpath.EqualsIgnoreCase(face.FaceStoragePath)) + { + string filename = Path.GetFileName(this.FilePath); + this.FilePath = Path.Combine(face.FaceStoragePath, filename); + } + + if (File.Exists(this.FilePath)) + { + this.Exists = true; + this.DateFileModified = new FileInfo(this.FilePath).LastWriteTime; + } + else + { + this.Exists = false; + } + } + + public override bool Equals(object obj) + { + return Equals(obj as ClsFaceFile); + } + + public bool Equals(ClsFaceFile other) + { + return other != null && + Name.EqualsIgnoreCase(other.Name); + } + + public override int GetHashCode() + { + return 539060726 + EqualityComparer.Default.GetHashCode(Name); + } + + public string Name { get; set; } = ""; + public int RegisterTimeMS { get; set; } = 0; + public string FilePath { get; set; } = ""; + public bool Exists { get; set; } = false; + public bool Keep { get; set; } = false; + public DateTime DateRegistered { get; set; } = DateTime.MinValue; + public DateTime DateAdded { get; set; } = DateTime.MinValue; + public DateTime DateFileModified { get; set; } = DateTime.MinValue; + public string OriginalPath { get; set; } = ""; + public string OriginalCamera { get; set; } = "Unknown"; + + public static bool operator ==(ClsFaceFile left, ClsFaceFile right) + { + return EqualityComparer.Default.Equals(left, right); + } + + public static bool operator !=(ClsFaceFile left, ClsFaceFile right) + { + return !(left == right); + } + } + public class ClsFaceManager + { + public int MaxFilesPerFace { get; set; } = 1000; + public int MaxFileAgeDays { get; set; } = 182; + public bool SaveUnknownFaces { get; set; } = true; + public bool SaveKnownFaces { get; set; } = true; + public string FaceFile { get; set; } = ""; + public List Faces { get; set; } = new List(); + public ConcurrentDictionary FacesDic { get; set; } = new ConcurrentDictionary(); + private ThreadSafe.Boolean NeedsSaving { get; set; } = new ThreadSafe.Boolean(false); + //private object FaceLock = new object(); + public ClsFaceManager() + { + if (this.Faces.Count > 0) + { + this.Faces.Clear(); + NeedsSaving = true; + } + + this.FaceFile = Path.Combine(AppSettings.Settings.FacesPath, "Faces.JSON"); + //update in background thread + Task.Run(this.UpdateFaces); + } + public void SaveFaces() + { + using var Trace = new Trace(); + + + + if (NeedsSaving) // && this.FacesDic.IsNotEmpty()) + { + //lock (FaceLock) + this.Faces.Clear(); //not used any longer + Global.WriteToJsonFile(this.FaceFile, this); + + NeedsSaving = false; + } + } + public void UpdateFaces() + { + using var Trace = new Trace(); + + int missingfiles = 0; + int oldfiles = 0; + int addedfiles = 0; + int totalfiles = 0; + int missingfaces = 0; + int errors = 0; + Stopwatch sw = Stopwatch.StartNew(); + + try + { + + //lock (FaceLock) + //{ + + Log("Debug: Updating faces..."); + + + //if (!Directory.Exists(AppSettings.Settings.FacesPath)) + // Directory.CreateDirectory(AppSettings.Settings.FacesPath); + + this.TryAddFace("Unknown"); + + //Add any existing subfolders as new faces if not already in the list + string[] facedirs = Directory.GetDirectories(AppSettings.Settings.FacesPath, "*", SearchOption.TopDirectoryOnly); + foreach (var facedir in facedirs) + this.TryAddFace(facedir); + + //delete any faces that dont have a folder + foreach (ClsFace face in this.FacesDic.Values) + { + if (!face.Name.EqualsIgnoreCase("unknown") && !Directory.Exists(face.FaceStoragePath)) + { + missingfaces++; + + //lock (FaceLock) + if (!this.FacesDic.TryRemove(face.Name.ToLower(), out ClsFace removedvalue)) + errors++; + + } + } + //for (int i = this.Faces.Count - 1; i >= 0; i--) + //{ + // if (!this.Faces[i].Name.EqualsIgnoreCase("unknown") && !Directory.Exists(this.Faces[i].FaceStoragePath)) + // { + // missingfaces++; + + // lock (FaceLock) + // this.Faces.RemoveAt(i); + // } + //} + + + foreach (ClsFace face in this.FacesDic.Values) + { + + foreach (var file in face.FilesDic.Values) + file.Update(face); + + //remove any missing files + var itemsToRemove = face.FilesDic.Values.Where(f => !f.Exists).ToArray(); + foreach (var ff in itemsToRemove) + { + string firstname = Path.GetFileName(ff.FilePath).ToLower(); + if (!face.FilesDic.TryRemove(firstname, out ClsFaceFile removedvalue)) + errors++; + + + } + missingfiles += itemsToRemove.Length; + + //remove old files + itemsToRemove = face.FilesDic.Values.Where(f => (DateTime.Now - f.DateFileModified).TotalDays > this.MaxFileAgeDays && !f.Keep).ToArray(); + foreach (var ff in itemsToRemove) + { + string firstname = Path.GetFileName(ff.FilePath).ToLower(); + if (!face.FilesDic.TryRemove(firstname, out ClsFaceFile removedvalue)) + errors++; + //actually delete the file: + if (!Global.SafeFileDelete(ff.FilePath, "UpdateFaces-TooOld1", 250, true)) + errors++; + + } + oldfiles += itemsToRemove.Length; + + totalfiles = face.FilesDic.Count; + + //scan the folder for new files + List newfiles = Global.GetFiles(face.FaceStoragePath, "*.jpg;*.jpeg;*.bmp;*.png", SearchOption.TopDirectoryOnly); + foreach (FileInfo fi in newfiles) + { + if ((DateTime.Now - fi.CreationTime).TotalDays > this.MaxFileAgeDays) + { + if (!Global.SafeFileDelete(fi.FullName, "UpdateFaces-TooOld2", 250, true)) + errors++; + } + else if (face.TryAddFaceFile(new ClsImageQueueItem(fi.FullName, 0), out string newfilename, this.MaxFilesPerFace, this.MaxFileAgeDays)) + { + addedfiles++; + } + } + + } + + //} + + + + } + catch (Exception ex) + { + + Log($"Error: {ex.Msg()}"); + } + + int tot = missingfaces + oldfiles + addedfiles + missingfiles; + + NeedsSaving = tot > 0; + + Log($"Updated {this.FacesDic.Count} faces in {sw.ElapsedMilliseconds}ms. {errors} errors. {missingfaces} faces removed. {totalfiles} files. Added {addedfiles} new files, Removed {missingfiles} missing files and {oldfiles} files that were too old."); + + + } + + public bool TryAddFaceFile(ClsImageQueueItem CurImg, string face = "") + { + if (!CurImg.IsValid()) + return false; + + if (face.IsEmpty()) + face = "Unknown"; + + if (face.EqualsIgnoreCase("face")) + face = "Unknown"; + + if (face.EqualsIgnoreCase("unknown")) + { + if (!this.SaveUnknownFaces) + return false; + } + else + { + if (!this.SaveKnownFaces) + return false; + } + + ClsFace FoundFace = this.TryAddFace(face); + + bool added = FoundFace.TryAddFaceFile(CurImg, out string Outfilename, this.MaxFilesPerFace, this.MaxFileAgeDays); + + //delete from unknown folder if it was originally from there and it moved + if (added) + { + this.NeedsSaving = true; + + if (CurImg.image_path.Has("\\unknown\\") && !Outfilename.Has("\\unknown\\") && + CurImg.image_path.Has("\\face\\") && !Outfilename.Has("\\face\\")) + Global.SafeFileDelete(CurImg.image_path, "TryAddFaceFile"); + + } + + return added; + } + + public ClsFace TryAddFace(string face) + { + + if (face.Contains("\\")) + face = Path.GetFileName(face); + + ClsFace FoundFace = new ClsFace(face); + + bool added = this.FacesDic.TryAdd(face.ToLower(), FoundFace); + + if (added) + this.NeedsSaving = true; + + return FoundFace; + } + + } +} diff --git a/src/UI/ClsFileSystemWatcher.cs b/src/UI/ClsFileSystemWatcher.cs new file mode 100644 index 00000000..d648385c --- /dev/null +++ b/src/UI/ClsFileSystemWatcher.cs @@ -0,0 +1,21 @@ +using System.IO; + +namespace AITool +{ + public class ClsFileSystemWatcher + { + public string Name = ""; + public string Path = ""; + public bool HasError = false; + public bool IncludeSubdirectories = false; + public FileSystemWatcher watcher = null; + public ClsFileSystemWatcher(string Name, string Path, FileSystemWatcher Watcher, bool IncludeSubFolders) + { + this.Name = Name; + this.Path = Path; + this.watcher = Watcher; + this.IncludeSubdirectories = IncludeSubFolders; + this.watcher.InternalBufferSize = 65536; //defaults to 8k, we are going max it out to try to prevent "too many changes at once in directory" + } + } +} diff --git a/src/UI/ClsImageAdjust.cs b/src/UI/ClsImageAdjust.cs new file mode 100644 index 00000000..5274f30f --- /dev/null +++ b/src/UI/ClsImageAdjust.cs @@ -0,0 +1,100 @@ +using Newtonsoft.Json; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AITool +{ + public class ClsImageAdjust : IEquatable + { + public string Name { get; set; } = ""; + public int JPEGQualityPercent { get; set; } = 90; + public int ImageSizePercent { get; set; } = 100; + public int ImageWidth { get; set; } = -1; //-1=unchanged + public int ImageHeight { get; set; } = -1; //-1=unchanged + public int Brightness { get; set; } = 1; //1=unchanged + public int Contrast { get; set; } = 1; //1=unchanged + + [JsonConstructor] + public ClsImageAdjust() + { + + } + + public ClsImageAdjust(string name) + { + this.Name = name.Trim(); + } + public ClsImageAdjust(string name, string jpegqualitypercent, string imagesizepercent, string imagewidth, string imageheight, string brightness, string contrast) + { + this.Update(name.Trim(), jpegqualitypercent.Trim(), imagesizepercent.Trim(), imagewidth.Trim(), imageheight.Trim(), brightness.Trim(), contrast.Trim()); + } + + public void Update(string name, string jpegqualitypercent, string imagesizepercent, string imagewidth, string imageheight, string brightness, string contrast) + { + this.Name = name.Trim(); + + if (string.IsNullOrWhiteSpace(jpegqualitypercent)) + jpegqualitypercent = this.JPEGQualityPercent.ToString(); + + if (string.IsNullOrWhiteSpace(imagesizepercent)) + imagesizepercent = this.ImageSizePercent.ToString(); + + if (string.IsNullOrWhiteSpace(imagewidth)) + imagewidth = this.ImageWidth.ToString(); + + if (string.IsNullOrWhiteSpace(imageheight)) + imageheight = this.ImageHeight.ToString(); + + if (string.IsNullOrWhiteSpace(brightness)) + brightness = this.Brightness.ToString(); + + if (string.IsNullOrWhiteSpace(contrast)) + contrast = this.Contrast.ToString(); + + this.JPEGQualityPercent = Convert.ToInt32(jpegqualitypercent); + this.ImageSizePercent = Convert.ToInt32(imagesizepercent); + this.ImageWidth = Convert.ToInt32(imagewidth); + this.ImageHeight = Convert.ToInt32(imageheight); + this.Brightness = Convert.ToInt32(brightness); + this.Contrast = Convert.ToInt32(contrast); + + + } + + public override bool Equals(object obj) + { + return Equals(obj as ClsImageAdjust); + } + + public bool Equals(ClsImageAdjust other) + { + return other != null && + string.Equals(this.Name, other.Name, StringComparison.OrdinalIgnoreCase); + } + + public override int GetHashCode() + { + unchecked // Overflow is fine, just wrap + { + int hash = 59; + // Suitable nullity checks etc, of course :) + hash = hash * 61 + this.Name.GetHashCode(); + return hash; + } + } + + public static bool operator ==(ClsImageAdjust left, ClsImageAdjust right) + { + return EqualityComparer.Default.Equals(left, right); + } + + public static bool operator !=(ClsImageAdjust left, ClsImageAdjust right) + { + return !(left == right); + } + } +} diff --git a/src/UI/ClsImageQueueItem.cs b/src/UI/ClsImageQueueItem.cs new file mode 100644 index 00000000..d64562f8 --- /dev/null +++ b/src/UI/ClsImageQueueItem.cs @@ -0,0 +1,343 @@ +using System; +using System.Diagnostics; +using System.Drawing; +using System.IO; +using System.Threading; + +namespace AITool +{ + + public class ClsImageQueueItem:IDisposable + { + private bool disposedValue; + + public string image_path { get; set; } = ""; + public DateTime TimeAdded { get; set; } = DateTime.MinValue; + public DateTime TimeCreated { get; set; } = DateTime.MinValue; + public DateTime TimeCreatedUTC { get; set; } = DateTime.MinValue; + public long QueueWaitMS { get; set; } = 0; + public long TotalTimeMS { get; set; } = 0; + public long LifeTimeMS { get; set; } = 0; + public long DeepStackTimeMS { get; set; } = 0; + public long FileLockMS { get; set; } = 0; + public long FileLoadMS { get; set; } = 0; + public long FileLockErrRetryCnt { get; set; } = 0; + public long CurQueueSize { get; set; } = 0; + public ThreadSafe.Integer ErrCount { get; set; } = new ThreadSafe.Integer(0); + public ThreadSafe.Integer RetryCount { get; set; } = new ThreadSafe.Integer(0); + public string ResultMessage { get; set; } = ""; + public string LastError { get; set; } = ""; + public int Width { get; set; } = 0; + public int Height { get; set; } = 0; + public float DPI { get; set; } = 0; + public long FileSize { get; set; } = 0; + private byte[] _imageByteArray = null; + private bool _valid { get; set; } = false; + private bool _loaded { get; set; } = false; + private bool _Temp { get; set; } = false; + public bool IsValid() + { + if (!this._loaded) + this.LoadImage(); + return this._valid; + } + + public bool CopyFileTo(string outputFilePath) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + bool ret = false; + + int bufferSize = 1024 * 1024; + string copydir = ""; + + try + { + if (!outputFilePath.Contains("\\")) + { + AITOOL.Log($"Error: Must specify a full path: {outputFilePath}"); + return false; + } + + if (this.IsValid()) //loads into memory if not already loaded + { + + copydir = Path.GetDirectoryName(outputFilePath); + + DirectoryInfo d = new DirectoryInfo(copydir); + if (d.Root != null && !d.Exists) + { + //dont try to create if working off root drive + d.Create(); + } + + + //If the destination file exists, wait for exclusive access + Global.WaitFileAccessResult result2 = new Global.WaitFileAccessResult(); + if (File.Exists(outputFilePath)) + { + result2 = Global.WaitForFileAccess(outputFilePath, FileAccess.ReadWrite, FileShare.None, 3000, MinFileSize: 0); + if (result2.Success) + File.Delete(outputFilePath); + } + else + result2.Success = true; + + if (result2.Success) + { + //using (FileStream fileStream = new FileStream(outputFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite)) + //{ + // this._imageMemStream.Position = 0; + // fileStream.SetLength(this._imageMemStream.Length); + // int bytesRead = -1; + // byte[] bytes = new byte[bufferSize]; + + // while ((bytesRead = this._imageMemStream.Read(bytes, 0, bufferSize)) > 0) + // { + // fileStream.Write(bytes, 0, bytesRead); + // } + //} + + using (FileStream fileStream = new FileStream(outputFilePath, FileMode.Create, FileAccess.Write, FileShare.None)) + { + fileStream.Write(_imageByteArray, 0, _imageByteArray.Length); + } + + ret = true; + } + else + { + AITOOL.Log($"Error: Could not gain access to destination file ({result2.TimeMS}ms, '{result2.ResultString}') {outputFilePath}"); + } + } + else + { + AITOOL.Log($"Error: File not valid: {this.image_path}"); + } + + } + catch (Exception ex) + { + AITOOL.Log($"Error: Copying to {outputFilePath}: {ex.Msg()}"); + } + + return ret; + + } + public ClsImageQueueItem(String FileName, long CurQueueSize, bool Temp = false) + { + this.image_path = FileName; + this.TimeAdded = DateTime.Now; + this.CurQueueSize = CurQueueSize; + this._Temp = Temp; + FileInfo fi = new FileInfo(this.image_path); + if (fi.Exists) + { + this.TimeCreated = fi.CreationTime; + this.TimeCreatedUTC = fi.CreationTimeUtc; + this.FileSize = fi.Length; + } + + } + public Bitmap ToBitmap() + { + return new Bitmap(this.ToMemStream()); + } + + public Image ToImage() + { + return Image.FromStream(this.ToMemStream()); + } + + public MemoryStream ToMemStream() + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + if (this.IsValid()) + { + try + { + return new MemoryStream(this._imageByteArray); + } + catch (Exception ex) + { + AITOOL.Log($"Error: Cannot convert to MemoryStream: {ex.Message}"); + } + } + else + { + AITOOL.Log($"Error: Cannot convert to MemoryStream because image is not valid."); + } + return new MemoryStream(); + } + public void LoadImage() + { + //using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + //since having a lot of trouble with image access problems, try to wait for image to become available, validate the image and load + //a single time rather than multiple + Global.WaitFileAccessResult result = new Global.WaitFileAccessResult(); + this._valid = false; + bool validate = !this._Temp; + + try + { + if (!string.IsNullOrEmpty(this.image_path) && File.Exists(this.image_path)) + { + Stopwatch sw = Stopwatch.StartNew(); + do + { + int MaxWaitMS = 0; + int MaxRetries = 0; + if (this._Temp) + { + MaxWaitMS = 500; + MaxRetries = 2; + } + else + { + MaxWaitMS = 10000; + MaxRetries = 100; + } + + result = Global.WaitForFileAccess(this.image_path, FileAccess.Read, FileShare.None, MaxWaitMS, AppSettings.Settings.loop_delay_ms, true, 4096, MaxRetries); + + this.FileLockMS = sw.ElapsedMilliseconds; + this.FileLockErrRetryCnt += result.ErrRetryCnt; + + if (result.Success) + { + + try + { + sw.Restart(); + // Open a FileStream object using the passed in safe file handle. + using (FileStream fileStream = new FileStream(result.Handle, FileAccess.Read)) + { + + using System.Drawing.Image img = System.Drawing.Image.FromStream(fileStream, true, validate); + + this._valid = img != null && img.RawFormat.Equals(System.Drawing.Imaging.ImageFormat.Jpeg) && img.Width > 0 && img.Height > 0; + + this.FileLoadMS = sw.ElapsedMilliseconds; + + if (!this._valid) + { + LastError = $"Error: Image file is not jpeg? {this.image_path}"; + AITOOL.Log(LastError); + break; + } + else + { + this.Width = img.Width; + this.Height = img.Height; + this.DPI = img.HorizontalResolution; + if (this._Temp) + { + this.FileLoadMS = sw.ElapsedMilliseconds; + } + else + { + //using MemoryStream ms = new MemoryStream(); + //fileStream.CopyTo(ms); + using MemoryStream ms = new MemoryStream(); + img.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg); + this._imageByteArray = ms.ToArray(); + this.FileSize = this._imageByteArray.Length; + this.FileLoadMS = sw.ElapsedMilliseconds; + this._valid = true; + AITOOL.Log($"Trace: Image file is valid. Resolution={this.Width}x{this.Height}, LockMS={this.FileLockMS}ms (max={MaxWaitMS}ms), retries={this.FileLockErrRetryCnt}, size={Global.FormatBytes(this._imageByteArray.Length)}: {Path.GetFileName(this.image_path)}"); + } + break; + } + } + + } + catch (Exception ex) + { + this._valid = false; + LastError = $"Error: Image is corrupt. LockMS={this.FileLockMS}ms (max={MaxWaitMS}ms), retries={this.FileLockErrRetryCnt}: {Path.GetFileName(this.image_path)} - {ex.Msg()}"; + } + finally + { + this._loaded = true; + + if (!result.Handle.IsClosed) + { + result.Handle.Close(); + result.Handle.Dispose(); + } + } + + } + else + { + if (this._Temp) + LastError = $"Debug: Could not gain access to the image in {result.TimeMS}ms, retries={result.ErrRetryCnt}: {Path.GetFileName(this.image_path)}"; + else + LastError = $"Error: Could not gain access to the image in {result.TimeMS}ms, retries={result.ErrRetryCnt}: {Path.GetFileName(this.image_path)}"; + } + + if (this._Temp) //only one loop + break; + + } while ((!result.Success || !this._valid) && sw.ElapsedMilliseconds < 30000); + + } + else + { + LastError = "Debug: File has been deleted: " + this.image_path; + } + } + catch (Exception ex) + { + + LastError = $"Error: {ex.Msg()}"; + } + finally + { + if (result.Handle != null && !result.Handle.IsInvalid && !result.Handle.IsClosed) + { + result.Handle.Close(); + result.Handle.Dispose(); + } + if (!this._valid && !string.IsNullOrEmpty(LastError)) + AITOOL.Log(LastError); + } + } + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + // TODO: dispose managed state (managed objects) + if (this._imageByteArray != null) + { + this._imageByteArray = null; + AITOOL.Log($"Trace: {Path.GetFileName(this.image_path)} Lifetime was {(DateTime.Now - this.TimeCreated).TotalMilliseconds}"); + } + } + + // TODO: free unmanaged resources (unmanaged objects) and override finalizer + // TODO: set large fields to null + disposedValue = true; + } + } + + // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources + // ~ClsImageQueueItem() + // { + // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + // Dispose(disposing: false); + // } + + void IDisposable.Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/UI/ClsLogItm.cs b/src/UI/ClsLogItm.cs new file mode 100644 index 00000000..c0753efe --- /dev/null +++ b/src/UI/ClsLogItm.cs @@ -0,0 +1,206 @@ +using NLog; +using System; +using System.Collections.Generic; + +namespace AITool +{ + //public enum EnumLogLevel + //{ + // Trace = 0, + // Debug = 1, + // Info = 2, + // Warn = 3, + // Error = 4, + // Fatal = 5, + // Off = 6, + // Unknown = 7 + //} + + //From NLOG - just trying to mimic this: + + //Each log entry has a level. And each logger is configured to include or ignore certain levels. + //A common configuration is to specify the minimum level where that level and higher levels are + //included. For example, if the minimum level is Info, then Info, Warn, Error and Fatal are + //logged, but Debug and Trace are ignored. + + //Level Typical Use + //Fatal Something bad happened; application is going down + //Error Something failed; application may or may not continue + //Warn Something unexpected; application will continue + //Info Normal behavior like mail sent, user updated profile etc. + //Debug For debugging; executed query, user authenticated, session expired + //Trace For trace debugging; begin method X, end method X + + + public class ClsLogItm : IEquatable + { + public ClsLogItm(LogLevel level, DateTime time, string source, string func, string aiserver, string camera, string image, string detail, int idx, int depth, string color, int threadid) + { + // "Date|Level|Source|Func|AIServer|Camera|Detail|Idx|Depth|Color|threadid" + this.Level = level; + this.Date = time; + this.Source = source; + this.Func = func; + this.AIServer = aiserver; + this.Camera = camera; + this.Image = image; + this.Detail = detail; + this.Idx = idx; + this.Depth = depth; + this.Color = color; + this.ThreadID = threadid; + } + public ClsLogItm() + { + } + public ClsLogItm(string LogEntry) + { + + if (string.IsNullOrEmpty(LogEntry)) + return; + + if (LogEntry.StartsWith("[")) //old log format, ignore + return; + + //some log entries have a | which they shouldnt + LogEntry = LogEntry.Replace("|Create|", ";Create;"); + + List splt = LogEntry.SplitStr("|", false, false); + // "Date|Level|Source|Func|AIServer|Camera|Image|Detail|Idx|Depth|Color|ThreadID" + // 0 1 2 3 4 5 6 7 8 9 10 11 + + if (splt.Count == 12) + { + try + { + DateTime tdate = DateTime.MinValue; + if (DateTime.TryParse(splt[0], out tdate)) + this.Date = tdate; + + if (string.Equals(splt[1], "level", StringComparison.OrdinalIgnoreCase)) //this must be a NEW header written part way down the file? + return; + + this.Level = LogLevel.FromString(splt[1]); + this.Source = splt[2]; + this.Func = splt[3]; + this.AIServer = splt[4]; + this.Camera = splt[5]; + this.Image = splt[6]; + this.Detail = splt[7]; + this.Idx = Convert.ToInt32(splt[8]); + this.Depth = Convert.ToInt32(splt[9]); + this.Color = splt[10]; + this.ThreadID = Convert.ToInt32(splt[11]); + + } + catch + { + this.Level = LogLevel.Off; + return; + } + + } + else if (splt.Count == 11) + { + try + { + DateTime tdate = DateTime.MinValue; + if (DateTime.TryParse(splt[0], out tdate)) + this.Date = tdate; + + if (splt[1].ToLower() == "level") //this must be a NEW header written part way down the file? + return; + this.Level = LogLevel.FromString(splt[1]); + this.Source = splt[2]; + this.Func = splt[3]; + this.AIServer = splt[4]; + this.Camera = splt[5]; + this.Detail = splt[6]; + this.Idx = Convert.ToInt32(splt[7]); + this.Depth = Convert.ToInt32(splt[8]); + this.Color = splt[9]; + this.ThreadID = Convert.ToInt32(splt[10]); + + } + catch + { + this.Level = LogLevel.Off; + return; + } + + } + else + { + return; + } + + } + + public DateTime Date { get; set; } = DateTime.MinValue; + public string Func { get; set; } = ""; + public string Detail { get; set; } = ""; + public NLog.LogLevel Level { get; set; } = NLog.LogLevel.Off; + public string Source { get; set; } = ""; + public string AIServer { get; set; } = ""; + public string Camera { get; set; } = ""; + public string Image { get; set; } = ""; + public int Idx { get; set; } = 0; + public int Depth { get; set; } = 0; + public string Color { get; set; } = ""; + public int ThreadID { get; set; } = 0; + public bool FromFile { get; set; } = false; + public string Filename { get; set; } = ""; + //public bool Displayed = false; + public override string ToString() + { + //This is mainly meant for log output + // "Date|Level|Source|Func|AIServer|Camera|Detail|Idx|Depth|Color" + + string str = $"{this.Date.ToString("yyyy-MM-dd HH:mm:ss.ffffff")}|{this.Level.ToString()}|{this.Source}|{this.Func}|{this.AIServer}|{this.Camera}|{this.Image}|{this.Detail}|{this.Idx}|{this.Depth}|{this.Color}|{this.ThreadID}"; + return str; + } + public string ToDetailString() + { + //This is mainly meant for my application RTF log or normal log not output log + string str = this.Level.ToString().ToUpper() + "> " + this.Detail.Trim(); + //Displayed = true; + return str; + } + + public override bool Equals(object obj) + { + return this.Equals(obj as ClsLogItm); + } + + public bool Equals(ClsLogItm other) + { + return other != null && + this.Date == other.Date && + this.Idx == other.Idx && + this.ThreadID == other.ThreadID; + } + + public override int GetHashCode() + { + unchecked // Overflow is fine, just wrap + { + int hash = 59; + // Suitable nullity checks etc, of course :) + hash = hash * 61 + this.Date.GetHashCode(); + hash = hash * 61 + this.Idx.GetHashCode(); + hash = hash * 61 + this.ThreadID.GetHashCode(); + return hash; + } + } + + public static bool operator ==(ClsLogItm left, ClsLogItm right) + { + return EqualityComparer.Default.Equals(left, right); + } + + public static bool operator !=(ClsLogItm left, ClsLogItm right) + { + return !(left == right); + } + } +} diff --git a/src/UI/ClsLogManager.cs b/src/UI/ClsLogManager.cs new file mode 100644 index 00000000..43a92ae4 --- /dev/null +++ b/src/UI/ClsLogManager.cs @@ -0,0 +1,674 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +using NLog; +using NLog.Targets; +using NLog.Targets.Wrappers; + +namespace AITool +{ + + //this is for UI logging only, not file logging directly + public class ClsLogManager:IDisposable + { + public bool Enabled { get; set; } = true; + public List Values { get; set; } = new List(); + public ConcurrentQueue RecentlyAdded { get; set; } = new ConcurrentQueue(); + public ConcurrentQueue RecentlyDeleted { get; set; } = new ConcurrentQueue(); + public ThreadSafe.Integer ErrorCount { get; set; } = new ThreadSafe.Integer(0); + public ClsLogItm LastLogItm = new ClsLogItm(); + public int MaxGUILogItems { get; set; } = 10000; + public long LastLoadTimeMS { get; set; } = 0; + //public List LastLoadMessages = new List(); + private ThreadSafe.Integer _LastIDX { get; set; } = new ThreadSafe.Integer(0); + private bool _Store; + private ThreadSafe.Integer _CurDepth = new ThreadSafe.Integer(0); + private object _LockObj = new object(); + + private LogLevel MinLevel = LogLevel.Debug; + private string _Filename = ""; + private long _MaxSize = 0; + private int _MaxAgeDays = 0; + private string _LastSource = "None"; + private string _LastCamera = "None"; + private string _LastAIServer = "None"; + private string _LastImage = "None"; + + private NLog.Logger NLogFileWriter = null; + AsyncTargetWrapper NLogAsyncWrapper = null; + + + public ClsLogManager(bool Store, string DefaultSource) + { + this._Store = Store; //we wont store log entries when running as a service, its only for the GUI + this._LastSource = DefaultSource; + //this.UpdateNLog(MinLevel, Filename, MaxSize, MaxAgeDays, MaxGUILogItems); + } + + public string GetCurrentLogFileName() + { + string fileName = null; + + if (LogManager.Configuration != null && LogManager.Configuration.ConfiguredNamedTargets.Count != 0) + { + Target target = LogManager.Configuration.FindTargetByName("NLogAsyncWrapper"); + if (target == null) + { + throw new Exception("Could not find target named: " + "NLogAsyncWrapper"); + } + + FileTarget fileTarget = null; + WrapperTargetBase wrapperTarget = target as WrapperTargetBase; + + // Unwrap the target if necessary. + if (wrapperTarget == null) + { + fileTarget = target as FileTarget; + } + else + { + fileTarget = wrapperTarget.WrappedTarget as FileTarget; + } + + if (fileTarget == null) + { + throw new Exception("Could not get a FileTarget from " + target.GetType()); + } + + var logEventInfo = new LogEventInfo { TimeStamp = DateTime.Now }; + fileName = fileTarget.FileName.Render(logEventInfo); + } + else + { + throw new Exception("LogManager contains no Configuration or there are no named targets"); + } + + this._Filename = fileName; //refresh + + return fileName; + } + + public async Task UpdateNLog(LogLevel MinLevel, string Filename, long MaxSize, int MaxAgeDays, int MaxGUILogItems) + { + + try + { + this.MaxGUILogItems = MaxGUILogItems; + + bool needsupdating = this.NLogFileWriter == null || this.NLogAsyncWrapper == null || MinLevel != this.MinLevel || Filename != this._Filename || MaxSize != this._MaxSize || MaxAgeDays != this._MaxAgeDays; + + if (!needsupdating) + return; + + //if we change the logging level only, I dont want to re-initialize everything else... + + bool onlylevel = this.NLogFileWriter != null && this.NLogAsyncWrapper != null && MinLevel != this.MinLevel && Filename == this._Filename && MaxSize == this._MaxSize && MaxAgeDays == this._MaxAgeDays; + + if (onlylevel) + { + this.MinLevel = MinLevel; + foreach (var rule in LogManager.Configuration.LoggingRules) + { + rule.EnableLoggingForLevel(this.MinLevel); + } + + //Call to update existing Loggers created with GetLogger() or + //GetCurrentClassLogger() + LogManager.ReconfigExistingLoggers(); + + this._Filename = this.GetCurrentLogFileName(); + + return; + } + + if (this.NLogAsyncWrapper == null) + this.NLogAsyncWrapper = new AsyncTargetWrapper(); + + this.MinLevel = MinLevel; + this._MaxAgeDays = MaxAgeDays; + this._MaxSize = MaxSize; + + // Targets where to log to: File and Console + var FileTarget = new NLog.Targets.FileTarget("logfile"); // { FileName = AppSettings.Settings.LogFileName }; + string dir = Path.GetDirectoryName(Filename); + string justfile = Path.GetFileNameWithoutExtension(Filename); + //${basedir}/${shortdate}.log + + FileTarget.FileName = dir + "\\" + justfile + ".[${shortdate}].log"; + + FileTarget.ArchiveAboveSize = MaxSize; + FileTarget.ArchiveEvery = NLog.Targets.FileArchivePeriod.Day; + FileTarget.MaxArchiveDays = MaxAgeDays; + FileTarget.ArchiveNumbering = NLog.Targets.ArchiveNumberingMode.DateAndSequence; + FileTarget.ArchiveOldFileOnStartup = false; + FileTarget.ArchiveDateFormat = "yyyy-MM-dd"; + FileTarget.ArchiveFileName = dir + "\\" + justfile + ".[{#}].log.zip"; + + + FileTarget.KeepFileOpen = false; + FileTarget.CreateDirs = true; + FileTarget.Header = "Date|Level|Source|Func|AIServer|Camera|Image|Detail|Idx|Depth|Color|ThreadID"; + FileTarget.EnableArchiveFileCompression = true; + FileTarget.Layout = "${message}"; //nothing fancy we are doing it ourselves + + this.NLogAsyncWrapper.WrappedTarget = FileTarget; + this.NLogAsyncWrapper.QueueLimit = 100; + this.NLogAsyncWrapper.OverflowAction = AsyncTargetWrapperOverflowAction.Discard; + this.NLogAsyncWrapper.Name = "NLogAsyncWrapper"; + + // Rules for mapping loggers to targets + NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(this.NLogAsyncWrapper, this.MinLevel); + NLog.LogManager.AutoShutdown = true; + + this.NLogFileWriter = NLog.LogManager.GetCurrentClassLogger(); + + //this.NLogAsyncWrapper.EventQueueGrow + //this.NLogAsyncWrapper.LogEventDropped + + if (this.Values.Count == 0) + this.GetCurrentLogFileName(); + + //load the current log file into memory + //await this.LoadLogFileAsync(this._Filename, true, false); + + } + catch (Exception ex) + { + Console.WriteLine("Error: " + ex.Msg()); + } + + } + + public List GetRecentlyDeleted() + { + List ret = new List(); + while (!this.RecentlyDeleted.IsEmpty) + { + if (this.RecentlyDeleted.TryDequeue(out ClsLogItm CLI)) + ret.Add(CLI); + } + return ret; + } + public List GetRecentlyAdded() + { + List ret = new List(); + while (!this.RecentlyAdded.IsEmpty) + { + if (this.RecentlyAdded.TryDequeue(out ClsLogItm CLI)) + ret.Add(CLI); + } + return ret; + } + public void Clear() + { + lock (this._LockObj) + { + NLog.LogManager.Flush(); + this.Values.Clear(); + this.LastLogItm = new ClsLogItm(); + this._LastIDX = 0; + this._LastAIServer = ""; + this._LastCamera = ""; + this._LastImage = ""; + this.RecentlyAdded = new ConcurrentQueue(); + this.RecentlyDeleted = new ConcurrentQueue(); + this.ErrorCount = 0; + this._Filename = this.GetCurrentLogFileName(); + + } + } + + + public void Add(ClsLogItm CDI) + { + lock (this._LockObj) + { + this._LastIDX = CDI.Idx; + this.LastLogItm = CDI; + this.Values.Add(this.LastLogItm); + } + } + + public void AddRange(List CDIList) + { + lock (this._LockObj) + { + foreach (ClsLogItm CDI in CDIList) + { + this._LastIDX = CDI.Idx; + this.LastLogItm = CDI; + this.Values.Add(this.LastLogItm); + } + } + } + + public void Enter([CallerMemberName()] string memberName = null) + { + this._CurDepth++; + + if (this._CurDepth > 10) + this._CurDepth = 10; //just in case something weird is going on + + if (this.MinLevel == LogLevel.Trace) + this.Log($"---->ENTER {memberName}, Depth={this._CurDepth}", "Trace-Enter", "Trace-Enter", "", "", 0, LogLevel.Trace, DateTime.Now, memberName); + + } + public void Exit([CallerMemberName()] string memberName = null, long timems = 0) + { + this._CurDepth.Decrement(0); + if (this._CurDepth < 0) + this._CurDepth = 0; + + if (this.MinLevel == LogLevel.Trace) + this.Log($"---- Time = default(DateTime?), [CallerMemberName()] string memberName = null) + { + if (!this.Enabled) + return null; + + lock (this._LockObj) + { + this._LastIDX++; + this.LastLogItm = new ClsLogItm(); + this.LastLogItm.Detail = Detail.Replace("|", ";"); + this.LastLogItm.Filename = Path.GetFileName(this._Filename); + + this.LastLogItm.ThreadID = Thread.CurrentThread.ManagedThreadId; + + if (Source == null || string.IsNullOrWhiteSpace(Source)) + this.LastLogItm.Source = this._LastSource; + else + this.LastLogItm.Source = Source; + + if (Camera == null || string.IsNullOrWhiteSpace(Camera)) + this.LastLogItm.Camera = this._LastCamera; + else + { + this.LastLogItm.Camera = Camera; + if (!Camera.StartsWith("Trace-")) + this._LastCamera = Camera; + } + + if (AIServer == null || string.IsNullOrWhiteSpace(AIServer)) + this.LastLogItm.AIServer = this._LastAIServer; + else + { + this.LastLogItm.AIServer = AIServer; + if (!AIServer.StartsWith("Trace-")) + this._LastAIServer = AIServer; + } + + if (Image == null || string.IsNullOrWhiteSpace(Image)) + this.LastLogItm.Image = this._LastImage; + else + { + this.LastLogItm.Image = Path.GetFileName(Image); + } + + if (Time.HasValue) + this.LastLogItm.Date = Time.Value; + else + this.LastLogItm.Date = DateTime.Now; + + if (memberName == ".ctor") + memberName = "Constructor"; + + this.LastLogItm.Func = memberName.Replace("AITool.", ""); + + //deepstack messages spam us... + bool DeepstackDebug = !string.Equals(this.LastLogItm.Func, "handleredisprocmsg", StringComparison.OrdinalIgnoreCase) || string.Equals(this.LastLogItm.Func, "handleredisprocmsg", StringComparison.OrdinalIgnoreCase) && AppSettings.Settings.deepstack_debug; + + if (!DeepstackDebug) + return this.LastLogItm; + + if (Level == null) + { + if (this.LastLogItm.Detail.Within("fatal:", 6)) + { + Level = LogLevel.Fatal; + } + else if (this.LastLogItm.Detail.Within("error:", 6)) + Level = LogLevel.Error; + else if (this.LastLogItm.Detail.Within("warning:", 6) || this.LastLogItm.Detail.Within("warn:", 6)) + Level = LogLevel.Warn; + else if (this.LastLogItm.Detail.Within("info:", 6)) + Level = LogLevel.Info; + else if (this.LastLogItm.Detail.Within("debug:", 6)) + Level = LogLevel.Debug; + else if (this.LastLogItm.Detail.Within("trace:", 6)) + Level = LogLevel.Trace; + else + Level = LogLevel.Info; + } + + bool HasError = false; + + //remove tags + if (Level == LogLevel.Error) + { + this.ErrorCount++; + HasError = true; + this.LastLogItm.Detail = Global.ReplaceCaseInsensitive(this.LastLogItm.Detail, "error:", ""); + } + else if (Level == LogLevel.Fatal) + { + this.ErrorCount++; + HasError = true; + this.LastLogItm.Detail = Global.ReplaceCaseInsensitive(this.LastLogItm.Detail, "fatal:", ""); + } + else if (Level == LogLevel.Warn) + { + this.ErrorCount++; + HasError = true; + this.LastLogItm.Detail = Global.ReplaceCaseInsensitive(this.LastLogItm.Detail, "warn:", ""); + this.LastLogItm.Detail = Global.ReplaceCaseInsensitive(this.LastLogItm.Detail, "warning:", ""); + } + else if (Level == LogLevel.Info) + { + this.LastLogItm.Detail = Global.ReplaceCaseInsensitive(this.LastLogItm.Detail, "info:", ""); + } + else if (Level == LogLevel.Trace) + { + this.LastLogItm.Detail = Global.ReplaceCaseInsensitive(this.LastLogItm.Detail, "trace:", ""); + } + else if (Level == LogLevel.Debug) + { + this.LastLogItm.Detail = Global.ReplaceCaseInsensitive(this.LastLogItm.Detail, "debug:", ""); + } + + if (this.LastLogItm.Detail.TrimStart().StartsWith("{")) + { + this.LastLogItm.Color = this.LastLogItm.Detail.GetWord("{", "}"); + //strip out the color definition + this.LastLogItm.Detail = Global.ReplaceCaseInsensitive(this.LastLogItm.Detail, "{" + this.LastLogItm.Color + "}", ""); + } + + //clean out any whitespace + //this.LastLogItm.Detail = this.LastLogItm.Detail.TrimStart(); + + if (this._CurDepth + Depth > 0) + this.LastLogItm.Detail = new string(' ', (this._CurDepth + Depth * 2)) + this.LastLogItm.Detail; + + this.LastLogItm.Depth = this._CurDepth + Depth; + this.LastLogItm.Level = Level; + this.LastLogItm.Idx = this._LastIDX; + + + if (this._Store) + { + if (Level >= this.MinLevel) + { + this.Values.Add(this.LastLogItm); + this.RecentlyAdded.Enqueue(this.LastLogItm); + //keep the log list size down + if (this.Values.Count > this.MaxGUILogItems) + { + this.RecentlyDeleted.Enqueue(this.Values[0]); + this.Values.RemoveAt(0); + } + } + } + + this.NLogFileWriter.Log(Level, this.LastLogItm.ToString()); + + if (Debugger.IsAttached) + Debug.WriteLine(this.LastLogItm.ToDetailString()); //change from Console. to Debug. so it would appear in Immediate window? + + string itm = this.LastLogItm.ToString(); + + if (AppSettings.Settings.IsNotNull()) + { + //Send telegram error message + if (AppSettings.Settings.send_telegram_errors && + (HasError) && + AppSettings.Settings.telegram_chatids.Count > 0 && + AITOOL.TriggerActionQueue != null && + !(itm.IndexOf("telegram", StringComparison.OrdinalIgnoreCase) >= 0) && + !(itm.IndexOf("addtriggeraction", StringComparison.OrdinalIgnoreCase) >= 0)) + + { + //await TelegramText($"[{time}]: {text}"); //upload text to Telegram + Camera cam = AITOOL.GetCamera(this._LastCamera); + AITOOL.TriggerActionQueue.AddTriggerActionAsync(TriggerType.TelegramText, cam, null, null, true, false, null, this.LastLogItm.ToDetailString()); + } + + //Send pushover error message + if (AppSettings.Settings.send_pushover_errors && + (HasError) && + !string.IsNullOrEmpty(AppSettings.Settings.pushover_APIKey) && + !string.IsNullOrEmpty(AppSettings.Settings.pushover_UserKey) && + AITOOL.TriggerActionQueue != null && + !(itm.IndexOf("pushover", StringComparison.OrdinalIgnoreCase) >= 0) && + !(itm.IndexOf("addtriggeraction", StringComparison.OrdinalIgnoreCase) >= 0)) + { + Camera cam = AITOOL.GetCamera(this._LastCamera); + AITOOL.TriggerActionQueue.AddTriggerActionAsync(TriggerType.Pushover, cam, null, null, true, false, null, this.LastLogItm.ToDetailString()); + } + + } + + if (HasError) + NLog.LogManager.Flush(); + + } + + return this.LastLogItm; + } + public void Sort() + { + lock (this._LockObj) + this.Values = this.Values.OrderBy(c => c.Date).ThenBy(c => c.Idx).ToList(); + } + + public async Task> LoadLogFileAsync(string Filename, bool Import, bool LimitEntries) + { + return await Task.Run(async () => this.LoadLogFile(Filename, Import, LimitEntries)); + } + + private List LoadLogFile(string Filename, bool Import, bool LimitEntries) + { + List ret = new List(); + + //this.LastLoadMessages.Clear(); + + if (Import) + this.Enabled = false; //disable while we import + + string ExtractZipPath = ""; + string file = Path.GetFileName(Filename); + + Stopwatch sw = Stopwatch.StartNew(); + + if (File.Exists(Filename)) + { + + try + { + Global.UpdateProgressBar($"Loading {Path.GetFileName(Filename)}...", 1, 1, 1); + + Global.WaitFileAccessResult result = Global.WaitForFileAccess(Filename, FileAccess.Read, FileShare.Read, 30000); + if (result.Success) + { + //if its a zip file, extract that puppy... + string NewFilename = ""; + + if (Filename.EndsWith("zip", StringComparison.OrdinalIgnoreCase)) + { + ExtractZipPath = Path.Combine(Global.GetTempFolder(), "_" + file); + if (!Directory.Exists(ExtractZipPath)) + Directory.CreateDirectory(ExtractZipPath); + + //just extract the first file in the archive + using (ZipArchive archive = ZipFile.OpenRead(Filename)) + { + foreach (ZipArchiveEntry entry in archive.Entries) + { + string destinationPath = Path.GetFullPath(Path.Combine(ExtractZipPath, entry.FullName)); + entry.ExtractToFile(destinationPath, true); + NewFilename = destinationPath; + break; + } + } + + } + else + { + NewFilename = Filename; + } + + lock (this._LockObj) + { + file = Path.GetFileName(NewFilename); + int Invalid = 0; + bool OldFile = false; + using (StreamReader sr = new StreamReader(NewFilename)) + { + int cnt = 0; + while (!sr.EndOfStream) + { + cnt++; + if (cnt > 1) + { + string line = sr.ReadLine(); + + if (!OldFile && line.TrimStart().StartsWith("[")) //old log format, ignore + { + OldFile = true; + this._LastIDX = 0; + break; + } + + if (!Import) + { + //just spit out a list of log lines + ClsLogItm CLI = new ClsLogItm(line); + if (CLI.Level != LogLevel.Off) //off indicates invalid - for example the old log format + { + CLI.FromFile = true; + CLI.Filename = file; + ret.Add(CLI); + } + else + { + Invalid++; + if (Invalid > 50) + { + this.Log($"Error: Too many invalid lines ({Invalid}) stopping load."); + ret.Clear(); + break; + } + else + { + this.Log($"Debug: {Invalid} line(s) in log file '{line}'"); + } + } + } + else + { + //load into current log manager + if (this._Store) + { + ClsLogItm CLI = new ClsLogItm(line); + if (CLI.Level != LogLevel.Off) //off indicates invalid - for example the old log format + { + this.LastLogItm = CLI; + if (this.LastLogItm.Level >= this.MinLevel) + { + CLI.FromFile = true; + CLI.Filename = file; + this.Values.Add(this.LastLogItm); + this.RecentlyAdded.Enqueue(this.LastLogItm); + //keep the log list size down + if (LimitEntries && this.Values.Count > this.MaxGUILogItems) + { + this.RecentlyDeleted.Enqueue(this.Values[0]); + this.Values.RemoveAt(0); + } + } + + } + else + { + Invalid++; + if (Invalid > 50) + { + this.Log($"Error: Too many invalid lines ({Invalid}) stopping load."); + ret.Clear(); + this._LastIDX = 0; + break; + } + else + { + this.Log($"Debug: {Invalid} line(s) in log file '{line}'"); + } + + } + } + } + } + } + + } + + if (OldFile) + { + //rename it to keep it out of our way next time + try + { + this.Log($"Debug: File was in the old log format, renaming to OLDLOGFORMAT. {NewFilename}"); + File.Move(NewFilename, NewFilename + ".OLDLOGFORMAT"); + } + catch (Exception ex) + { + this.Log($"Error: While renaming log to OLDLOGFORMAT, got: {ex.Message}"); + } + } + } + } + else + { + this.Log($"Error: Gave up waiting for exclusive file access after {result.TimeMS}ms with {result.ErrRetryCnt} retries for {Filename}"); + } + + if (Directory.Exists(ExtractZipPath)) + Directory.Delete(ExtractZipPath, true); + + + + } + catch (Exception ex) + { + + this.Log($"Error: {ex.Msg()}"); + } + + + } + + if (Import) + this.Enabled = true; //enable after we import + + Global.UpdateProgressBar($"", 0, 0, 0); + + this.LastLoadTimeMS = sw.ElapsedMilliseconds; + + return ret; + } + + public void Dispose() + { + NLog.LogManager.Flush(); + ((IDisposable)this.NLogAsyncWrapper).Dispose(); + } + } + +} diff --git a/src/UI/ClsMessage.cs b/src/UI/ClsMessage.cs new file mode 100644 index 00000000..d376b21d --- /dev/null +++ b/src/UI/ClsMessage.cs @@ -0,0 +1,61 @@ +using System; + +namespace AITool +{ + public enum MessageType + { + LogEntry, + UpdateLabel, + UpdateStatus, + UpdateDeepstackStatus, + UpdateProgressBar, + ImageAddedToQueue, + CreateHistoryItem, + DeleteHistoryItem, + BeginProcessImage, + EndProcessImage, + SettingsSaved, + SettingsLoaded, + DatabaseInitialized + + } + + //This will be sent between processes or modules - will be used for service eventually + public class ClsMessage + { + private string description = ""; + private string jsonpayload = ""; + private DateTime messageCreateDate = DateTime.Now; + private MessageType messageType = MessageType.LogEntry; + private string memberName = ""; + private int curval = 0; + private int maxval = 0; + private int minval = 0; + public string Description { get => this.description; set => this.description = value; } + public string JSONPayload { get => this.jsonpayload; set => this.jsonpayload = value; } + public MessageType MessageType { get => this.messageType; set => this.messageType = value; } + public DateTime MessageCreateDate { get => this.messageCreateDate; set => this.messageCreateDate = value; } + public string MemberName { get => this.memberName; set => this.memberName = value; } + public int CurVal { get => this.curval; set => this.curval = value; } + public int MaxVal { get => this.maxval; set => this.maxval = value; } + public int MinVal { get => this.minval; set => this.minval = value; } + + //pass a class object or string in payloadobject, gets converted to json string + public ClsMessage(MessageType MT = MessageType.LogEntry, string Descript = "", object PayloadObject = null, string MemberName = "", int CurVal = -1, int MinVal = -1, int MaxVal = -1) + { + this.description = Descript; + this.messageType = MT; + this.memberName = MemberName; + this.messageCreateDate = DateTime.Now; + this.curval = CurVal; + this.maxval = MaxVal; + this.minval = MinVal; + + if (PayloadObject != null) + { + this.jsonpayload = Global.GetJSONString(PayloadObject); + } + } + } + +} diff --git a/src/UI/ClsPrediction.cs b/src/UI/ClsPrediction.cs new file mode 100644 index 00000000..a8c516a9 --- /dev/null +++ b/src/UI/ClsPrediction.cs @@ -0,0 +1,1223 @@ +using Amazon.Rekognition.Model; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using static AITool.AITOOL; +using Newtonsoft.Json; + +namespace AITool +{ + public enum ObjectType + { + Object, + Person, + Animal, + Vehicle, + Face, + Unknown, + LicensePlate + } + public enum ServerType + { + Primary, + Linked, + Refine, + Unknown + } + + public enum ResultType + { + Relevant = 1, + DynamicMasked = 2, + StaticMasked = 3, + ImageMasked = 4, + NoMask = 5, + NoConfidence = 6, + UnwantedObject = 7, + DuplicateObject = 8, + RefinementObject = 9, + CameraNotEnabled = 10, + Error = 11, + Unknown = 12, + TooSmallPercent = 13, + TooLargePercent = 14, + TooSmallWidth = 15, + TooSmallHeight = 16, + TooLargeWidth = 17, + TooLargeHeight = 18, + IgnoredObject = 19, + RelevantDuplicateObject = 20, + NotInTimeRange = 21, + NotEnabled = 22, + UnknownObject = 23, + + } + public class ClsPrediction:IEquatable + { + private ObjectType _defaultObjType; + private Camera _cam; + private ClsImageQueueItem _curimg; + private ClsURLItem _cururl; + + public string Label { get; set; } = ""; + public string Detail { get; set; } = ""; + public ResultType Result { get; set; } = ResultType.Unknown; + public ResultType ObjectResult { get; set; } = ResultType.Unknown; + public double Confidence { get; set; } = 0; + public double PercentOfImage { get; set; } = 0; + + public ObjectType ObjType { get; set; } = ObjectType.Unknown; + public ServerType ServerType { get; set; } = ServerType.Unknown; + public string Server { get; set; } = ""; + public MaskType DynMaskType { get; set; } = MaskType.Unknown; + public MaskResult DynMaskResult { get; set; } = MaskResult.Unknown; + public double PercentMatch { get; set; } = 0; + public double PercentMatchRefinement { get; set; } = 0; + public MaskType ImgMaskType { get; set; } = MaskType.Unknown; + public MaskResult ImgMaskResult { get; set; } = MaskResult.Unknown; + public int DynamicThresholdCount { get; set; } = 0; + public int ImagePointsOutsideMask { get; set; } = 0; + public double YMin { get; set; } = 0; + public double XMin { get; set; } = 0; + public double YMax { get; set; } = 0; + public double XMax { get; set; } = 0; + public string Camera { get; set; } = ""; + public string BICamName { get; set; } = ""; + public double RectWidth { get; set; } = 0; + public double RectHeight { get; set; } = 0; + public double RectArea { get; set; } = 0; + public double AIWidth { get; set; } = 0; + public double AIHeight { get; set; } = 0; + public double ImageWidth { get; set; } = 0; + public double ImageHeight { get; set; } = 0; + public double ImageArea { get; set; } = 0; + public int ObjectPriority { get; set; } = 0; + public string Filename { get; set; } = ""; + public int OriginalOrder { get; set; } = 0; + public int DupeCount { get; set; } = 0; + public int RefineMergedCount { get; set; } = 0; + public DateTime Time { get; set; } = DateTime.MinValue; + public ClsDeepstackDetection ToDeepstackDetection() + { + ClsDeepstackDetection ret = new ClsDeepstackDetection(); + ret.label = this.Label; + ret.confidence = this.Confidence; + ret.x_min = this.XMin; + ret.y_min = this.YMin; + ret.x_max = this.XMax; + ret.y_max = this.YMax; + ret.Detail = this.Detail; + ret.Server = this.Server; + return ret; + } + private void UpdateImageInfo(ClsImageQueueItem curImg) + { + this._curimg = curImg; + this.ImageHeight = curImg.Height; + this.ImageWidth = curImg.Width; + this.Filename = curImg.image_path; + this.UpdatePercent(); + } + + public bool IsValid() + { + return !this.Label.IsEmpty() && this.XMax > 0 && this.YMax > 0 && this.ImageHeight > 0 && this.ImageWidth > 0; + } + public void UpdatePercent() + { + if (this.IsValid()) + { + + //calculate the percentage of the size of the prediction compared to the image size + + //First update width and height due to past miscalculation + Rectangle PredRect = this.GetRectangle(); + + this.RectWidth = PredRect.Width; + this.RectHeight = PredRect.Height; + this.RectArea = PredRect.Area(); + + Rectangle img = new Rectangle(0, 0, this.ImageWidth.ToInt(), this.ImageHeight.ToInt()); + this.ImageArea = img.Area(); + + this.PercentOfImage = img.PercentOfSize(PredRect); + + if (this.PercentOfImage <= 0) + Log($"Error: PercentOfImage is {this.PercentOfImage.ToPercent()}? ImageArea={ImageArea}, PredArea={this.RectArea}, pred.width={this.RectWidth}, pred.height={this.RectHeight}, pred.Xmin={this.XMin}, pred.Ymin={this.YMin}, pred.Xmax={this.XMax}, pred.Ymax={this.YMax}, ImageWidth={this.ImageWidth}, ImageHeight={this.ImageHeight}"); + + } + else + { + //Log($"Error: Updated prediction PercentOfImage too soon? Xmax={this.XMax}, Ymax={this.YMax}, ImageWidth={this.ImageWidth}, ImageHeight={this.ImageHeight}"); + } + + } + public Rectangle GetRectangle() + { + return Rectangle.FromLTRB(this.XMin.ToInt(true), this.YMin.ToInt(true), this.XMax.ToInt(true), this.YMax.ToInt(true)); + } + public RectangleF GetRectangleF() + { + return RectangleF.FromLTRB(this.XMin.ToFloat(), this.YMin.ToFloat(true), this.XMax.ToFloat(true), this.YMax.ToFloat(true)); + } + [JsonConstructor] + public ClsPrediction() + { + this.UpdatePercent(); + } + public ClsPrediction(ObjectType defaultObjType, Camera cam, Amazon.Rekognition.Model.FaceDetail AiDetectionObject, ClsImageQueueItem curImg, ClsURLItem curURL) + { + this._defaultObjType = defaultObjType; + this._cam = cam; + this._cururl = curURL; + this.Camera = cam.Name; + this.BICamName = cam.BICamName; + this.Server = curURL.CurSrv; + this.Time = DateTime.Now; + this.UpdateImageInfo(curImg); + + if (AiDetectionObject == null || cam == null) + { + Log("Error: Prediction or Camera was null?", "", this._cam.Name); + this.Result = ResultType.Error; + return; + } + + this.AIWidth = AiDetectionObject.BoundingBox.Width; + this.AIHeight = AiDetectionObject.BoundingBox.Height; + + //aws returns a percentage of the image width and height rather than actual pixels + this.RectHeight = curImg.Height * AiDetectionObject.BoundingBox.Height; + this.RectWidth = curImg.Width * AiDetectionObject.BoundingBox.Width; + + + double right = (curImg.Width * AiDetectionObject.BoundingBox.Left) + this.RectWidth; + double left = curImg.Width * AiDetectionObject.BoundingBox.Left; + + double top = curImg.Height * AiDetectionObject.BoundingBox.Top; + double bottom = (curImg.Height * AiDetectionObject.BoundingBox.Top) + this.RectHeight; + + this.XMin = left; + this.YMin = top; + + this.XMax = right; + this.YMax = bottom; + + //[{Global.UpperFirst(AiDetectionObject.Attributes.Gender)}, {AiDetectionObject.Attributes.Age}, {Global.UpperFirst(AiDetectionObject.Attributes.Emotion)}] + string emotions = ""; + List emotionslist = new List(); + + if (AiDetectionObject.Emotions != null && AiDetectionObject.Emotions.Count > 0) + { + foreach (Emotion em in AiDetectionObject.Emotions) + { + if (em.Confidence >= 25) + emotionslist.Add(em); + } + if (emotionslist.Count > 0) + { + //sort so highest conf is first + emotionslist = emotionslist.OrderByDescending(a => a.Confidence).ToList(); + emotions = ", "; + int cnt = 0; + foreach (Emotion em in emotionslist) + { + emotions += $"{em.Type.ToString().ToLower().UpperFirst()};"; + cnt++; + if (cnt > 3) + break; + } + emotions = emotions.Trim("; ".ToCharArray()); + } + } + + string gender = ""; + if (AiDetectionObject.Gender != null) + gender = $", {AiDetectionObject.Gender.Value}"; + + string age = ""; + if (AiDetectionObject.AgeRange != null) + age = $", {AiDetectionObject.AgeRange.Low}-{AiDetectionObject.AgeRange.High}"; + + string smile = ""; + if (AiDetectionObject.Smile.Value) + smile = ", Smile"; + + string eyeglasses = ""; + if (AiDetectionObject.Eyeglasses.Value) + eyeglasses = ", Eyeglasses"; + + string sunglasses = ""; + if (AiDetectionObject.Sunglasses.Value) + sunglasses = ", Sunglasses"; + + string beard = ""; + if (AiDetectionObject.Beard.Value) + beard = ", Beard"; + + string mustache = ""; + if (AiDetectionObject.Mustache.Value) + mustache = ", Mustache"; + + string mouthopen = ""; + if (AiDetectionObject.MouthOpen.Value) + mouthopen = ", MouthOpen"; + + + this.Label = "Face"; + + this.Detail = $"{gender}{age}{emotions}{smile}{eyeglasses}{sunglasses}{beard}{mustache}{mouthopen}".Trim(", ".ToCharArray()); + + this.Confidence = AiDetectionObject.Confidence; + + this.GetObjectType(); + this.UpdateImageInfo(curImg); + } + + public ClsPrediction(ObjectType defaultObjType, Camera cam, Amazon.Rekognition.Model.Label AiDetectionObject, int InstanceIdx, ClsImageQueueItem curImg, ClsURLItem curURL) + { + this._defaultObjType = defaultObjType; + this._cam = cam; + this._cururl = curURL; + this.Camera = cam.Name; + this.BICamName = cam.BICamName; + this.Server = curURL.CurSrv; + this.Time = DateTime.Now; + this.UpdateImageInfo(curImg); + + if (AiDetectionObject == null || cam == null || string.IsNullOrWhiteSpace(AiDetectionObject.Name)) + { + Log("Error: Prediction or Camera was null?", "", this._cam.Name); + this.Result = ResultType.Error; + return; + } + //"Name": "Car", + // "Confidence": 98.87621307373047, + // "Instances": [ + // { + // "BoundingBox": { + // "Width": 0.10527367144823074, + // "Height": 0.18472492694854736, + // "Left": 0.0042892382480204105, + // "Top": 0.5051581859588623 + // }, + // "Confidence": 98.87621307373047 + // }, + + //Rectangle(xmin, ymin, xmax - xmin, ymax - ymin) + // x, y Width, Height + + this.AIWidth = AiDetectionObject.Instances[InstanceIdx].BoundingBox.Width; + this.AIHeight = AiDetectionObject.Instances[InstanceIdx].BoundingBox.Height; + + this.RectHeight = curImg.Height * AiDetectionObject.Instances[InstanceIdx].BoundingBox.Height; + this.RectWidth = curImg.Width * AiDetectionObject.Instances[InstanceIdx].BoundingBox.Width; + + double right = (curImg.Width * AiDetectionObject.Instances[InstanceIdx].BoundingBox.Left) + this.RectWidth; + double left = curImg.Width * AiDetectionObject.Instances[InstanceIdx].BoundingBox.Left; + + double top = curImg.Height * AiDetectionObject.Instances[InstanceIdx].BoundingBox.Top; + double bottom = (curImg.Height * AiDetectionObject.Instances[InstanceIdx].BoundingBox.Top) + this.RectHeight; + + this.XMin = left; + this.YMin = top; + + this.XMax = right; + this.YMax = bottom; + + //force first letter to always be capitalized + this.Label = AiDetectionObject.Name.UpperFirst(); + //if (AiDetectionObject.Parents != null && AiDetectionObject.Parents.Count > 0) + //{ + // foreach (var parent in AiDetectionObject.Parents) + // { + // this.Detail += $", {parent.Name.UpperFirst()}"; + // } + // this.Detail = this.Detail.Trim(", ".ToCharArray()); + //} + + this.Confidence = AiDetectionObject.Confidence; //AiDetectionObject.Instances[InstanceIdx].Confidence; + + this.GetObjectType(); + this.UpdateImageInfo(curImg); + + } + + public ClsPrediction(ObjectType defaultObjType, Camera cam, SightHoundVehicleObject AiDetectionObject, ClsImageQueueItem curImg, ClsURLItem curURL, SightHoundImage SHImg, bool GetPlate) + { + //https://docs.sighthound.com/cloud/recognition/ + + this._defaultObjType = defaultObjType; + this._cam = cam; + this._cururl = curURL; + this.Camera = cam.Name; + this.BICamName = cam.BICamName; + this.Server = curURL.CurSrv; + this.Time = DateTime.Now; + this.UpdateImageInfo(curImg); + + if (AiDetectionObject == null || cam == null || string.IsNullOrWhiteSpace(AiDetectionObject.ObjectType)) + { + Log("Error: Prediction or Camera was null?", "", this._cam.Name); + this.Result = ResultType.Error; + return; + } + + this.ImageHeight = SHImg.Height; //curImg.Height; + this.ImageWidth = SHImg.Width; //curImg.Width; + + if (curImg.Height != SHImg.Height) + { + Log($"Debug: Original image Height does not match returned height: Original={curImg.Height}, Returned={SHImg.Height}"); + } + if (curImg.Width != SHImg.Width) + { + Log($"Debug: Original image Width does not match returned Width: Original={curImg.Width}, Returned={SHImg.Width}"); + } + + //{ + // "image": { + // "width":2016, + // "orientation":1, + // "height":1512 + //}, + //"objects":[ + // { + // "vehicleAnnotation":{ + // "bounding":{ + // "vertices":[ + // { "x":430, "y":286 }, + // { "x":835, "y":286 }, + // { "x":835, "y":559 }, + // { "x":430, "y":559 } + // ] + // }, + // "attributes":{ + // "system":{ + // "color":{ + // "confidence":0.9968, + // "name":"silver/grey" + // }, + // "make":{ + // "confidence":0.8508, + // "name":"BMW" + // }, + // "model":{ + // "confidence":0.8508, + // "name":"3 Series" + // }, + // "vehicleType":"car" + // } + // }, + // "recognitionConfidence":0.8508 + // "licenseplate":{ + // "bounding":{ + // "vertices":[ + // { "x":617, "y":452 }, + // { "x":743, "y":452 }, + // { "x":743, "y":482 }, + // { "x":617, "y":482 } + // ] + // }, + // "attributes":{ + // "system":{ + // "region":{ + // "name":"Florida", + // "confidence":0.9994 + // }, + // "string":{ + // "name":"RTB2", + // "confidence":0.999 + // }, + // "characters":[ + // { + // "bounding":{ + // "vertices":[ + // { "y":455, "x":637 }, + // { "y":455, "x":649 }, + // { "y":473, "x":649 }, + // { "y":473, "x":637 } + // ] + // }, + // "index":0, + // "confidence":0.9999, + // "character":"R" + // }, + // { + // "bounding":{ + // "vertices":[ + // { "y":455, "x":648 }, + // { "y":455, "x":661 }, + // { "y":473, "x":661 }, + // { "y":473, "x":648 } + // ] + // }, + // "index":1, + // "confidence":0.9999, + // "character":"T" + // }, + // { + // "bounding":{ + // "vertices":[ + // { "y":455, "x":671 }, + // { "y":455, "x":684 }, + // { "y":474, "x":684 }, + // { "y":474, "x":671 } + // ] + // }, + // "index":2, + // "confidence":0.9996, + // "character":"B" + // }, + // { + // "bounding":{ + // "vertices":[ + // { "y":456, "x":683 }, + // { "y":456, "x":696 }, + // { "y":474, "x":696 }, + // { "y":474, "x":683 } + // ] + // }, + // "index":3, + // "confidence":0.9995, + // "character":"2" + // } + // ] + // } + // } + // }, + // }, + // "objectId":"_vehicle_f3c3d26b-c568-4d98-b6db-b96659fd7766", + // "objectType":"vehicle" + // } + //], + //"requestId":"d25b5e5d22f6431498065e9a25134d59" + //} + + + + //Rectangle(xmin, ymin, xmax - xmin, ymax - ymin) + // x, y Width, Height + + + // + // vertices (array): A list of objects that define coordinates of the vertices that surround the Object + // x (integer): Horizontal pixel position of the vertex + // y (integer): Vertical pixel position of the vertex + + + if (!GetPlate) + { + // get the bounding box from vertices's + List pts = new List(); + foreach (var pt in AiDetectionObject.VehicleAnnotation.Bounding.Vertices) + pts.Add(new System.Drawing.Point(pt.X, pt.Y)); + + Rectangle rect = new Rectangle().FromVertices(pts); + + this.AIHeight = rect.Height; + this.AIWidth = rect.Width; + + this.RectHeight = rect.Height; + this.RectWidth = rect.Width; + + double right = rect.X + this.RectWidth; + double left = rect.X; + + double top = rect.Y; + double bottom = rect.Y + this.RectHeight; + + this.XMin = left; + this.YMin = top; + + this.XMax = right; + this.YMax = bottom; + + if (AiDetectionObject.VehicleAnnotation != null && !string.IsNullOrEmpty(AiDetectionObject.VehicleAnnotation.Attributes.System.VehicleType)) + { + string type = AiDetectionObject.VehicleAnnotation.Attributes.System.VehicleType.UpperFirst(); + string color = AiDetectionObject.VehicleAnnotation.Attributes.System.Color.Name.UpperFirst(); + string make = AiDetectionObject.VehicleAnnotation.Attributes.System.Make.Name.UpperFirst(); + string model = AiDetectionObject.VehicleAnnotation.Attributes.System.Model.Name.UpperFirst(); + + this.Label = type; + this.Detail = $"{color}, {make}, {model}"; + this.ObjType = ObjectType.Vehicle; + + //this isnt exactly right, but sighthound doesnt give confidence for person/face, only gender, age + this.Confidence = AiDetectionObject.VehicleAnnotation.RecognitionConfidence * 100; + } + else + { + this.Confidence = 100; + } + + } + else if (AiDetectionObject.VehicleAnnotation.Licenseplate != null && !string.IsNullOrEmpty(AiDetectionObject.VehicleAnnotation.Licenseplate.Attributes.System.String.Name)) + { + //get the license plate object + // get the bounding box from vertices's + List pts = new List(); + foreach (var pt in AiDetectionObject.VehicleAnnotation.Licenseplate.Bounding.Vertices) + pts.Add(new System.Drawing.Point(pt.X, pt.Y)); + + Rectangle rect = new Rectangle().FromVertices(pts); //Global.RectFromVertices(pts); + + this.AIHeight = rect.Height; + this.AIWidth = rect.Width; + + this.RectHeight = rect.Height; + this.RectWidth = rect.Width; + + double right = rect.X + this.RectWidth; + double left = rect.X; + + double top = rect.Y; + double bottom = rect.Y + this.RectHeight; + + this.XMin = left; + this.YMin = top; + this.XMax = right; + this.YMax = bottom; + + this.Label = "License Plate"; + this.ObjType = ObjectType.LicensePlate; + this.Detail = $"Plate={AiDetectionObject.VehicleAnnotation.Licenseplate.Attributes.System.Region.Name} {AiDetectionObject.VehicleAnnotation.Licenseplate.Attributes.System.String.Name}"; + + //this isnt exactly right, but sighthound doesnt give confidence for person/face, only gender, age + this.Confidence = AiDetectionObject.VehicleAnnotation.Licenseplate.Attributes.System.String.Confidence * 100; + + } + + + this.GetObjectType(); + this.UpdateImageInfo(curImg); + + } + public ClsPrediction(ObjectType defaultObjType, Camera cam, SightHoundPersonObject AiDetectionObject, ClsImageQueueItem curImg, ClsURLItem curURL, SightHoundImage SHImg) + { + this._defaultObjType = defaultObjType; + this._cam = cam; + this._cururl = curURL; + this.Camera = cam.Name; + this.BICamName = cam.BICamName; + this.Server = curURL.CurSrv; + this.Time = DateTime.Now; + this.UpdateImageInfo(curImg); + + if (AiDetectionObject == null || cam == null || string.IsNullOrWhiteSpace(AiDetectionObject.Type)) + { + Log("Error: Prediction or Camera was null?", "", this._cam.Name); + this.Result = ResultType.Error; + return; + } + + this.ImageHeight = SHImg.Height; //curImg.Height; + this.ImageWidth = SHImg.Width; //curImg.Width; + + if (curImg.Height != SHImg.Height) + { + Log($"Debug: Original image Height does not match returned height: Original={curImg.Height}, Returned={SHImg.Height}"); + } + if (curImg.Width != SHImg.Width) + { + Log($"Debug: Original image Width does not match returned Width: Original={curImg.Width}, Returned={SHImg.Width}"); + } + + //{ + // "image": { + // "width": 1280, + // "height": 960, + // "orientation": 1 + // }, + + // "objects": [ + + // { + // "type": "person", + // "boundingBox": { + // "x": 363, + // "y": 182, + // "height": 778, + // "width": 723 + // } + // }, + + // { + // "type": "face", + // "boundingBox": { + // "x": 508, + // "y": 305, + // "height": 406, + // "width": 406 + // }, + // "attributes": { + // "gender": "male", + // "genderConfidence": 0.9883, + // "age":25, + // "ageConfidence": 0.2599, + // "emotion": "happiness", + // "emotionConfidence": 0.9943, + // "emotionsAll": { + // "neutral": 0.0018, + // "sadness": 0.0009, + // "disgust": 0.0002, + // "anger": 0.0003, + // "surprise": 0, + // "fear": 0.0022, + // "happiness": 0.9943 + // }, + // "pose":{ + // "pitch":-18.4849, + // "roll":0.854, + // "yaw":-4.2123 + // }, + // "frontal": true + // }, + // "landmarks": { + // "faceContour": [[515,447],[517,491]...[872,436]], + // "noseBridge": [[710,419],[711,441]...[712,487]], + // "noseBall": [[680,519],[696,522]...[742,518]], + // "eyebrowRight": [[736,387],[768,376]...[854,394]], + // "eyebrowLeft": [[555,413],[578,391]...[679,391]], + // "eyeRight": [[753,428],[774,414]...[777,432]], + // "eyeRightCenter": [[786,423]], + // "eyeLeft": [[597,435],[617,423]...[619,442]], + // "eyeLeftCenter": [[630,432]], + // "mouthOuter": [[650,590],[674,572]...[675,600]], + // "mouthInner": [[661,587],[697,580]...[697,584]] + // } + // } + // ] + //} + + + //Rectangle(xmin, ymin, xmax - xmin, ymax - ymin) + // x, y Width, Height + + + //boundingBox (object): An object containing x, y, width, and height values + //defining the location of the object in the image. The top left pixel of + //the image represents coordinate (0,0). + + this.AIHeight = AiDetectionObject.BoundingBox.Height; + this.AIWidth = AiDetectionObject.BoundingBox.Width; + + this.RectHeight = AiDetectionObject.BoundingBox.Height; + this.RectWidth = AiDetectionObject.BoundingBox.Width; + + double right = AiDetectionObject.BoundingBox.X + this.RectWidth; + double left = AiDetectionObject.BoundingBox.X; + + double top = AiDetectionObject.BoundingBox.Y; + double bottom = AiDetectionObject.BoundingBox.Y + this.RectHeight; + + this.XMin = left; + this.YMin = top; + this.XMax = right; + this.YMax = bottom; + + //force first letter to always be capitalized + this.Label = AiDetectionObject.Type.UpperFirst(); + + if (AiDetectionObject.Attributes != null && !string.IsNullOrEmpty(AiDetectionObject.Attributes.Gender)) + { + string emotions = AiDetectionObject.Attributes.Emotion.UpperFirst(); //this is the highest confidence emotion + + if (AiDetectionObject.Attributes.EmotionsAll != null) + { + List emotionslist = new List(); + if (AiDetectionObject.Attributes.EmotionsAll.Anger > .25) + emotionslist.Add(new SightHoundEmotionItem("Anger", AiDetectionObject.Attributes.EmotionsAll.Anger)); + if (AiDetectionObject.Attributes.EmotionsAll.Disgust > .25) + emotionslist.Add(new SightHoundEmotionItem("Disgust", AiDetectionObject.Attributes.EmotionsAll.Disgust)); + if (AiDetectionObject.Attributes.EmotionsAll.Fear > .25) + emotionslist.Add(new SightHoundEmotionItem("Fear", AiDetectionObject.Attributes.EmotionsAll.Fear)); + if (AiDetectionObject.Attributes.EmotionsAll.Happiness > .25) + emotionslist.Add(new SightHoundEmotionItem("Happiness", AiDetectionObject.Attributes.EmotionsAll.Happiness)); + if (AiDetectionObject.Attributes.EmotionsAll.Neutral > .25) + emotionslist.Add(new SightHoundEmotionItem("Neutral", AiDetectionObject.Attributes.EmotionsAll.Neutral)); + if (AiDetectionObject.Attributes.EmotionsAll.Sadness > .25) + emotionslist.Add(new SightHoundEmotionItem("Sadness", AiDetectionObject.Attributes.EmotionsAll.Sadness)); + if (AiDetectionObject.Attributes.EmotionsAll.Surprise > .25) + emotionslist.Add(new SightHoundEmotionItem("Surprise", AiDetectionObject.Attributes.EmotionsAll.Surprise)); + + //sort so highest conf is first + emotionslist = emotionslist.OrderByDescending(a => a.Confidence).ToList(); + emotions = ""; + int cnt = 0; + foreach (SightHoundEmotionItem em in emotionslist) + { + emotions += $"{em.ToString()};"; + cnt++; + if (cnt > 3) + break; + } + emotions = emotions.Trim("; ".ToCharArray()); + if (string.IsNullOrEmpty(emotions)) + emotions = "UnknownEmotions"; + } + + string frontal = ""; + if (AiDetectionObject.Attributes.Frontal) + frontal = ", Frontal"; + + this.Label = AiDetectionObject.Type.UpperFirst(); + this.Detail = $"{AiDetectionObject.Attributes.Gender.UpperFirst()}, {AiDetectionObject.Attributes.Age}, {emotions}{frontal}"; + + //this isnt exactly right, but sighthound doesnt give confidence for person/face, only gender, age + this.Confidence = AiDetectionObject.Attributes.GenderConfidence * 100; + } + else + { + this.Confidence = 100; + } + + this.GetObjectType(); + this.UpdateImageInfo(curImg); + + } + public ClsPrediction(ObjectType defaultObjType, Camera cam, ClsDeepstackDetection AiDetectionObject, ClsImageQueueItem curImg, ClsURLItem curURL) + { + this._defaultObjType = defaultObjType; + this._cam = cam; + this._cururl = curURL; + this.Server = curURL.CurSrv; + this.Time = DateTime.Now; + this.Camera = cam.Name; + this.BICamName = cam.BICamName; + + this.UpdateImageInfo(curImg); + + + if (AiDetectionObject == null || cam == null || (string.IsNullOrWhiteSpace(AiDetectionObject.label) && string.IsNullOrWhiteSpace(AiDetectionObject.UserID))) + { + Log("Error: Prediction or Camera was null?", "", this._cam.Name); + this.Result = ResultType.Error; + return; + } + + //if running face detection: + if (!string.IsNullOrWhiteSpace(AiDetectionObject.UserID)) + { + this.ObjType = ObjectType.Face; + this.Label = AiDetectionObject.UserID.UpperFirst(); + this.Detail = ""; //"Face, " + Global.UpperFirst(AiDetectionObject.UserID); + } + else + { + //force first letter to always be capitalized + this.Label = AiDetectionObject.label.UpperFirst(); + } + + if (!string.IsNullOrEmpty(AiDetectionObject.Detail)) + this.Detail = AiDetectionObject.Detail.UpperFirst(); + + this.XMax = AiDetectionObject.x_max; + this.YMax = AiDetectionObject.y_max; + this.XMin = AiDetectionObject.x_min; + this.YMin = AiDetectionObject.y_min; + this.Confidence = AiDetectionObject.confidence * 100; //store as whole number percent + this.Filename = curImg.image_path; + + this.AIWidth = this.XMax - this.XMin; + this.AIHeight = this.YMax - this.YMin; //TODO: Bug? Deepstack returning higher YMIN than YMAX sometimes. + + this.RectWidth = this.AIWidth; + this.RectHeight = this.AIHeight; + + this.GetObjectType(); + this.UpdateImageInfo(curImg); + + } + + public ClsPrediction(ObjectType defaultObjType, Camera cam, ClsDoodsDetection AiDetectionObject, ClsImageQueueItem curImg, ClsURLItem curURL) + { + this._defaultObjType = defaultObjType; + this._cam = cam; + this._cururl = curURL; + this.Camera = cam.Name; + this.BICamName = cam.BICamName; + this.Server = curURL.CurSrv; + this.Time = DateTime.Now; + this.UpdateImageInfo(curImg); + + + if (AiDetectionObject == null || cam == null || string.IsNullOrWhiteSpace(AiDetectionObject.Label)) + { + Log("Error: Prediction or Camera was null?", "", this._cam.Name); + this.Result = ResultType.Error; + return; + } + + //force first letter to always be capitalized + this.Label = AiDetectionObject.Label.UpperFirst(); + + //{ + // "top":0.09833781, + // "left":0.62415826, + // "bottom":0.14295554, + // "right":0.7029755, + // "label":"car", + // "confidence":63.28125 + // } + + // convert percentage values from doods to pixels + + //System.Drawing.Rectangle rect = new System.Drawing.Rectangle(xmin, ymin, xmax - xmin, ymax - ymin); + //System.Drawing.Rectangle rect = new System.Drawing.Rectangle(x, y, width, height); + + //x, y = - coordinate of the UPPER LEFT corner of the rectangle + //xmin, ymin + + double right = curImg.Width * AiDetectionObject.Right; + double left = curImg.Width * AiDetectionObject.Left; + + double top = curImg.Height * AiDetectionObject.Top; + double bottom = curImg.Height * AiDetectionObject.Bottom; + + this.XMin = left; + this.YMin = top; + + this.XMax = right; + this.YMax = bottom; + + this.AIWidth = AiDetectionObject.Right - AiDetectionObject.Left; + this.AIHeight = AiDetectionObject.Bottom - AiDetectionObject.Top; + + this.RectWidth = this.XMax - this.XMin; + this.RectHeight = this.YMax - this.YMin; + + this.Confidence = AiDetectionObject.Confidence; + this.Filename = curImg.image_path; + + this.GetObjectType(); + this.UpdateImageInfo(curImg); + + } + + public void AnalyzePrediction(bool SkipDynamicMaskCheck) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + MaskResultInfo result = new MaskResultInfo(); + + try + { + if (this.ObjectPriority == 0) + this.GetObjectType(); + + if (!this.IsValid()) + { + Log($"Error: prediction has not been processed yet? Label={this.Label}"); + this.UpdateImageInfo(this._curimg); + + } + + if (this._cam.enabled) + { + //this.Result = AITOOL.ArePredictionObjectsRelevant(this._cam.triggering_objects_as_string + "," + this._cam.additional_triggering_objects_as_string, "NEW", this, true); + this.Result = this._cam.DefaultTriggeringObjects.IsRelevant(this, true, out bool IgnoreImageMask, out bool IgnoreDynamicMask, "NEW"); + if (this.Result == ResultType.Relevant) + { + this.Result = this._cam.IsRelevantSize(this); + if (this.Result == ResultType.Relevant) + { + // -> OBJECT IS RELEVANT + + //if confidence limits are satisfied + bool OverrideThreshold = this._cururl != null && (this._cururl.Threshold_Lower > 0 || (this._cururl.Threshold_Upper > 0 && this._cururl.Threshold_Upper < 100)); + + if ((!OverrideThreshold && this.Confidence.Round() >= this._cam.threshold_lower && this.Confidence.Round() <= this._cam.threshold_upper) || + (OverrideThreshold && this.Confidence.Round() >= this._cururl.Threshold_Lower && this.Confidence.Round() <= this._cururl.Threshold_Upper)) + { + // -> OBJECT IS WITHIN CONFIDENCE LIMITS + + if (!IgnoreImageMask) + { + //only if the object is outside of the masked area + result = AITOOL.Outsidemask(this._cam, this.XMin, this.XMax, this.YMin, this.YMax, this._curimg.Width, this._curimg.Height); + this.ImgMaskResult = result.Result; + this.ImgMaskType = result.MaskType; + this.ImagePointsOutsideMask = result.ImagePointsOutsideMask; + } + else + { + //a relevant object was flagged to ignore masks + result.IsMasked = false; + result.Result = MaskResult.ObjectIgnoredMask; + result.MaskType = MaskType.Unknown; + this.ImgMaskResult = result.Result; + this.ImgMaskType = result.MaskType; + + } + + if (!result.IsMasked) + { + this.Result = ResultType.Relevant; + } + else if (result.MaskType == MaskType.None) //if the object is in a masked area + { + this.Result = ResultType.NoMask; + } + else if (result.MaskType == MaskType.Image) //if the object is in a masked area + { + this.Result = ResultType.ImageMasked; + } + + //check the mask manager if the image mask didnt flag anything + if (!result.IsMasked && !IgnoreDynamicMask) + { + //check the dynamic or static masks + if (this._cam.maskManager.MaskingEnabled && !SkipDynamicMaskCheck) + { + ObjectPosition currentObject = new ObjectPosition(this.XMin, this.XMax, this.YMin, this.YMax, this.Label, + this._curimg.Width, this._curimg.Height, this._cam.Name, this._curimg.image_path); + //creates history and masked lists for objects returned + result = this._cam.maskManager.CreateDynamicMask(currentObject); + this.DynMaskResult = result.Result; + this.DynMaskType = result.MaskType; + this.DynamicThresholdCount = result.DynamicThresholdCount; + this.PercentMatch = result.PercentMatch; + //there is a dynamic or static mask + if (result.MaskType == MaskType.Dynamic) + this.Result = ResultType.DynamicMasked; + else if (result.MaskType == MaskType.Static) + this.Result = ResultType.StaticMasked; + } + else if (SkipDynamicMaskCheck) + { + this.DynMaskResult = MaskResult.SkippedDynamicMaskCheck; + this.DynMaskType = MaskType.None; + + } + else + { + this.DynMaskResult = MaskResult.NotEnabled; + } + + } + + if (result.Result == MaskResult.Error || result.Result == MaskResult.Unknown) + { + this.Result = ResultType.Error; + Log($"Error: Masking error? '{this._cam.Name}' ('{this.Label}') - DynMaskResult={this.DynMaskResult}, ImgMaskResult={this.ImgMaskResult}", "", this._cam.Name, this._curimg.image_path); + } + + } + else //if confidence limits are not satisfied + { + this.Result = ResultType.NoConfidence; + } + + + } + } + + + } + else + { + Log($"Debug: Camera not enabled '{this._cam.Name}' ('{this.Label}')", "", this._cam.Name, this._curimg.image_path); + this.Result = ResultType.CameraNotEnabled; + } + + } + catch (Exception ex) + { + Log($"Error: Label '{this.Label}', Camera '{this._cam.Name}': {ex.Msg()}", "", this._cam.Name, this._curimg.image_path); + this.Result = ResultType.Error; + } + + } + + public override string ToString() + { + //LabelDisplayFormat = "[Detection] [[Detail]] ({0:0}%)" + + if (this._cam == null && !string.IsNullOrEmpty(this.Camera)) + this._cam = AITOOL.GetCamera(this.Camera); + + string DetectionDisplayFormat = "[label] [confidence]"; + + if (this._cam == null) + { + Log($"Could not match camera '{this.Camera}' to an existing camera. Has it been renamed?"); + } + else + { + if (!string.Equals(this._cam.Name, this.Camera, StringComparison.OrdinalIgnoreCase)) + { + Log($"Prediction camera '{this.Camera}' does not match found camera '{this._cam.Name}'. Has it been renamed?"); + } + + DetectionDisplayFormat = this._cam.DetectionDisplayFormat; + } + //Dont call this or we get a stack overflow! + //string ret = AITOOL.ReplaceParams(this._cam, null, this._curimg, this._cam.DetectionDisplayFormat, this); + + string ret = DetectionDisplayFormat; + + ret = Global.ReplaceCaseInsensitive(ret, "[label]", this.Label); //only gives first detection + ret = Global.ReplaceCaseInsensitive(ret, "[detail]", this.Detail); + ret = Global.ReplaceCaseInsensitive(ret, "[result]", this.Result.ToString()); + ret = Global.ReplaceCaseInsensitive(ret, "[position]", this.PositionString()); + ret = Global.ReplaceCaseInsensitive(ret, "[confidence]", this.ConfidenceString()); + ret = Global.ReplaceCaseInsensitive(ret, "[percentofimage]", this.PercentOfImage.Round().ToString()); + //if there was no detail string, clean up: + ret = ret.Replace("[]", "").Replace("()", "").Replace(" ", " ").Replace(" ", " "); + + return ret; + + } + public string PositionString() + { + return $"{this.XMin},{this.YMin},{this.XMax},{this.YMax}"; + } + public string ConfidenceString() + { + return $"{String.Format(AppSettings.Settings.DisplayPercentageFormat, this.Confidence)}"; + } + + private void GetObjectType() + { + //just to make it so truck and pickup truck are the same object priority + //eventually we may have to allow for more than one object with the same priority but this is a quick fix + if (this.Label.EqualsIgnoreCase("pickup truck")) + { + this.Label = "Truck"; + } + + string tmp = this.Label.Trim().ToLower(); + + //person, bicycle, car, motorcycle, airplane, + //bus, train, truck, boat, traffic light, fire hydrant, stop_sign, + //parking meter, bench, bird, cat, dog, horse, sheep, cow, elephant, + //bear, zebra, giraffe, backpack, umbrella, handbag, tie, suitcase, + //frisbee, skis, snowboard, sports ball, kite, baseball bat, baseball glove, + //skateboard, surfboard, tennis racket, bottle, wine glass, cup, fork, + //knife, spoon, bowl, banana, apple, sandwich, orange, broccoli, carrot, + //hot dog, pizza, donot, cake, chair, couch, potted plant, bed, dining table, + //toilet, tv, laptop, mouse, remote, keyboard, cell phone, microwave, + //oven, toaster, sink, refrigerator, book, clock, vase, scissors, teddy bear, + //hair dryer, toothbrush. + + + if (this.ObjType != ObjectType.Unknown) //because we can set this in deepstack face detection for example + { + this.GetPriority(); + return; + } + + if (tmp.Equals("person") || tmp.Equals("people") || tmp.Equals("male") || tmp.Equals("female")) + this.ObjType = ObjectType.Person; + else if (tmp.Equals("face") || this.Detail.IndexOf("face", StringComparison.OrdinalIgnoreCase) >= 0) + this.ObjType = ObjectType.Face; + else if (tmp.Contains("license") || tmp.Contains("plate")) + { + this.ObjType = ObjectType.LicensePlate; + //Plate: ddddddddd + if (tmp.StartsWith("plate:")) + { + this.Detail = this.Label.GetWord("plate:", ""); + this.Label = "License Plate"; + } + } + else if (tmp.Equals("dog") || + tmp.Equals("cat") || + tmp.Equals("bird") || + tmp.Equals("bear") || + tmp.Equals("sheep") || + tmp.Equals("kangaroo") || + tmp.Equals("deer") || + tmp.Equals("cow") || + tmp.Equals("rabbit") || + tmp.Equals("raccoon") || + tmp.Equals("racoon") || + tmp.Equals("fox") || + tmp.Equals("skunk") || + tmp.Equals("squirrel") || + tmp.Equals("pig") || + tmp.Equals("horse") || + tmp.Equals("elephant") || + tmp.Equals("zebra") || + tmp.Equals("giraffe")) + this.ObjType = ObjectType.Animal; + else if (tmp.Equals("car") || + tmp.Contains("Ambulance") || + tmp.Contains("truck") || + tmp.Equals("bus") || + tmp.Equals("van") || + tmp.Equals("suv") || + tmp.Equals("bicycle") || + tmp.Equals("motorcycle") || + tmp.Equals("boat") || + tmp.Equals("train") || + tmp.Equals("airplane")) + { + this.ObjType = ObjectType.Vehicle; + } + else + this.ObjType = this._defaultObjType; + + this.GetPriority(); + + } + + public void GetPriority() + { + //List pris = AppSettings.Settings.ObjectPriority.Split(",", ToLower: true); + ClsRelevantObject ro = this._cam.DefaultTriggeringObjects.Get(this.Label, AllowEverything: false, out int FoundIDX); + if (ro != null && ro.Priority > 0) + this.ObjectPriority = ro.Priority; //pris.IndexOf(tmp); + else + this.ObjectPriority = 999; + } + + public override bool Equals(object obj) + { + return Equals(obj as ClsPrediction); + } + + public bool Equals(ClsPrediction other) + { + if (other == null) + return false; + + //string tmp1 = this.Label.Trim().ToLower(); + //string tmp2 = other.Label.Trim().ToLower(); + + //if (tmp1.Contains("[")) + // tmp1 = Global.GetWordBetween(tmp1, "", "[").Trim(); + + //if (tmp2.Contains("[")) + // tmp2 = Global.GetWordBetween(tmp2, "", "[").Trim(); + + return this.GetHashCode() == other.GetHashCode(); + } + + public override int GetHashCode() + { + int hashCode = 1877525906; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Label); + hashCode = hashCode * -1521134295 + Confidence.GetHashCode(); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Server); + hashCode = hashCode * -1521134295 + YMin.GetHashCode(); + hashCode = hashCode * -1521134295 + XMin.GetHashCode(); + hashCode = hashCode * -1521134295 + YMax.GetHashCode(); + hashCode = hashCode * -1521134295 + XMax.GetHashCode(); + return hashCode; + } + + public static bool operator ==(ClsPrediction left, ClsPrediction right) + { + return EqualityComparer.Default.Equals(left, right); + } + + public static bool operator !=(ClsPrediction left, ClsPrediction right) + { + return !(left == right); + } + } +} diff --git a/src/UI/ClsProp.cs b/src/UI/ClsProp.cs new file mode 100644 index 00000000..82a3b8f3 --- /dev/null +++ b/src/UI/ClsProp.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AITool +{ + public class ClsProp + { + public ClsProp(string name, string value) + { + Name = name; + Value = value; + } + + public string Name { get; set; } = ""; + public string Value { get; set; } = ""; + + } +} diff --git a/src/UI/ClsRelevantObjectManager.cs b/src/UI/ClsRelevantObjectManager.cs new file mode 100644 index 00000000..cd1bff0e --- /dev/null +++ b/src/UI/ClsRelevantObjectManager.cs @@ -0,0 +1,921 @@ +using Newtonsoft.Json; + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AITool +{ + public class ClsRelevantObject : IEquatable + { + + public string Name { get; set; } = ""; + public bool Enabled { get; set; } = true; + public bool Trigger { get; set; } = true; + public int Priority { get; set; } = 999; + public double Threshold_lower { get; set; } = 40; + public double Threshold_upper { get; set; } = 100; + public string ActiveTimeRange { get; set; } = "00:00:00-23:59:59"; + [JsonProperty("IgnoreMask")] + public bool IgnoreImageMask { get; set; } = false; + public bool? IgnoreDynamicMask { get; set; } = null; + public double PredSizeMinPercentOfImage { get; set; } = 0; //prediction must be at least this % of the image + public double PredSizeMaxPercentOfImage { get; set; } = 95; + public MovingCalcs PercentSizeStats { get; set; } = new MovingCalcs(50, "RelevantObject.PercentSize", false, "##0.00"); + public MovingCalcs WidthStats { get; set; } = new MovingCalcs(50, "RelevantObject.Width", false); + public MovingCalcs HeightStats { get; set; } = new MovingCalcs(50, "RelevantObject.Height", false); + public long Hits { get; set; } = 0; + public DateTime LastHitTime { get; set; } = DateTime.MinValue; + public DateTime CreatedTime { get; set; } = DateTime.MinValue; + public int LastHashCode = 0; + + [JsonConstructor] + public ClsRelevantObject() + { + this.Update(); + } + public ClsRelevantObject(string Name) + { + if (Name.TrimStart().StartsWith("-")) + this.Trigger = false; + else + this.Trigger = true; + + if (Name.Equals("*")) + Name = "Everything"; + + this.Name = Name.Trim(" -".ToCharArray()).UpperFirst(); + this.Priority = Priority; + this.CreatedTime = DateTime.Now; + this.Update(); + + } + + public void Update(double MinLowerThreshold = 0, double MaxUpperThreshold = 0, bool? IgnoreDynamicMask = null, bool? IgnoreImageMask = null) + { + //We originally only had IgnoreMask, now we have split into two so we use the original value as the default for the new value + //wait for the json to be initialized and only set the default of the new value after the first run + if (this.LastHashCode != 0) + { + if (!this.IgnoreDynamicMask.HasValue) + this.IgnoreDynamicMask = this.IgnoreImageMask; + + if (MinLowerThreshold > 0 && this.Threshold_lower < MinLowerThreshold) + this.Threshold_lower = MinLowerThreshold; + + if (MaxUpperThreshold > 0 && this.Threshold_upper > MaxUpperThreshold) + this.Threshold_upper = MaxUpperThreshold; + + if (IgnoreDynamicMask.HasValue) + this.IgnoreDynamicMask = IgnoreDynamicMask; + + if (IgnoreImageMask.HasValue) + this.IgnoreImageMask = IgnoreImageMask.Value; + } + + + this.LastHashCode = this.GetHashCode(); + } + public override string ToString() + { + string ret = this.Name; + if (!this.Trigger) + ret = "-" + ret; + + if (!this.Enabled) + ret = "(" + ret + ")"; + + return ret; + } + + public override bool Equals(object obj) + { + return Equals(obj as ClsRelevantObject); + } + + public bool Equals(ClsRelevantObject other) + { + return other != null && + this.GetHashCode() == other.GetHashCode(); + } + + public override int GetHashCode() + { + int hashCode = 349655125; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Name.ToLower()); + hashCode = hashCode * -1521134295 + Threshold_lower.GetHashCode(); + hashCode = hashCode * -1521134295 + Threshold_upper.GetHashCode(); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(ActiveTimeRange.ToLower()); + return hashCode; + } + + public static bool operator ==(ClsRelevantObject left, ClsRelevantObject right) + { + return EqualityComparer.Default.Equals(left, right); + } + + public static bool operator !=(ClsRelevantObject left, ClsRelevantObject right) + { + return !(left == right); + } + } + + //================================================================================================================================================ + //================================================================================================================================================ + + public class ClsRelevantObjectManager + { + public OrderedDictionary ObjectDict { get; set; } = null; + public List ObjectList { get; set; } = new List(); + public string TypeName { get; set; } = ""; + public int EnabledCount { get; set; } = 0; + public string CameraName { get; set; } = ""; + + [JsonIgnore] + private Camera cam = null; + [JsonIgnore] + private Camera defaultcam = null; + [JsonIgnore] + private bool Initialized = false; + private List _DefaultObjectsList = new List(); + private object ROLockObject = new object(); + [JsonConstructor] + public ClsRelevantObjectManager() + { + //this.Init(); + ExcludeObjects(); + + } + + public ClsRelevantObjectManager(ClsRelevantObjectManager manager) + { + this.TypeName = manager.TypeName; + this.CameraName = manager.CameraName; + this.Init(manager.cam); + this.ObjectList = this.FromList(manager.ObjectList, true, ExactMatchOnly: true); + this.Update(); + } + public ClsRelevantObjectManager(string Objects, string TypeName, Camera cam) + { + this.TypeName = TypeName; + + this.Init(cam); + + if (!Objects.Trim(", ".ToCharArray()).IsEmpty()) + this.ObjectList = this.FromString(Objects, true, true); + else + this.Reset(); + + this.Update(); + + } + + public void Init(Camera cam = null) + { + if (!cam.IsNull()) + this.cam = cam; + + if (this.cam.IsNull() && this.CameraName.IsNotEmpty()) + this.cam = AITOOL.GetCamera(this.CameraName); + + if (!this.cam.IsNull()) + this.CameraName = this.cam.Name; + + //dont initialize until we have a list of cameras available + if (!this.Initialized || !this.ObjectDict.IsNull() && !AppSettings.Settings.CameraList.IsNull() && AppSettings.Settings.CameraList.Count > 0) + { + + lock (ROLockObject) + { + //migrate from the dictionary to the list - dictionary no longer used + if (!this.ObjectDict.IsNull()) + { + if (this.ObjectDict.Count > 0) + { + ObjectList.Clear(); + foreach (Object item in ObjectDict.Values) + { + if (item is DictionaryEntry) + { + ClsRelevantObject ro = (ClsRelevantObject)((DictionaryEntry)item).Value; + ObjectList.Add(ro.CloneJson()); + } + else if (item is ClsRelevantObject) + { + ClsRelevantObject ro = (ClsRelevantObject)item; + ObjectList.Add(ro.CloneJson()); + } + else + { + AITOOL.Log($"Warn: Old object is {item.GetType().FullName}??"); + } + } + + } + + this.ObjectDict = null; + } + + //Add default settings + this.Initialized = true; + + Update(); + } + } + + + } + + public void AddDefaults() + { + //Add defaults if missing + this.ObjectList = this.FromList(this.GetDefaultObjectList(false), false, ExactMatchOnly: true); + + } + public void Update(bool ResetIfNeeded = true) + { + + //sort + //this.ObjectList = this.ObjectList.OrderByDescending(ro => ro.Enabled).ThenBy(ro => ro.Priority).ThenBy(ro => ro.CreatedTime).ThenBy(ro => ro.Name).ToList(); + + if (this.cam.IsNull()) + this.Init(); + + if (ResetIfNeeded && this.ObjectList.Count == 0 && !this.CameraName.EqualsIgnoreCase("default")) + this.Reset(); + + bool restrict = !this.cam.IsNull() && !this.cam.DefaultTriggeringObjects.IsNull() && this.cam.DefaultTriggeringObjects.TypeName != this.TypeName; + + //sort ObjectList so that enabled objects are first, then by priority + this.ObjectList = this.ObjectList.OrderByDescending(ro => ro.Enabled).ToList(); + + //make sure no priority is in order and the minimum is not less than the main cameras list + for (int i = 0; i < this.ObjectList.Count; i++) + { + if (restrict) + { + ClsRelevantObject ro = this.Get(this.ObjectList[i].Name, false, out int FoundIDX, UseMainCamList: true); + if (!ro.IsNull()) + this.ObjectList[i].Update(ro.Threshold_lower, ro.Threshold_upper, ro.IgnoreDynamicMask, ro.IgnoreImageMask); + else + this.ObjectList[i].Update(); + + } + else + { + this.ObjectList[i].Update(); + } + this.ObjectList[i].Priority = i + 1; + + } + + + ExcludeObjects(); + + } + + void ExcludeObjects() + { + if (this.ObjectList.IsEmpty() || AppSettings.Settings.ObjectsExcludedDict.IsEmpty()) + return; + + //erase anything in ObjectList that is in AppSettings.Settings.ObjectsExcludedDic + for (int i = this.ObjectList.Count - 1; i >= 0; i--) + { + if (AppSettings.Settings.ObjectsExcludedDict.ContainsKey(this.ObjectList[i].Name)) + this.ObjectList.RemoveAt(i); + } + + } + + public ClsRelevantObject Delete(ClsRelevantObject ro, out int NewIDX) + { + ClsRelevantObject ret = ro; + + NewIDX = 0; + + if (ro == null) + return ro; + + ClsRelevantObject rofound = this.Get(ro, false, out int FoundIDX, true); + if (!rofound.IsNull()) + { + this.ObjectList.RemoveAt(FoundIDX); + NewIDX = FoundIDX - 1; + + this.Update(false); + + if (NewIDX > -1) + { + ret = this.ObjectList[NewIDX]; + } + } + + return ret; + + } + + public ClsRelevantObject MoveUp(ClsRelevantObject ro, out int NewIDX) + { + ClsRelevantObject ret = ro; + NewIDX = 0; + + if (ro == null) + return ro; + + ClsRelevantObject rofound = this.Get(ro, false, out int FoundIDX, true); + if (!rofound.IsNull()) + { + NewIDX = FoundIDX - 1; + + if (NewIDX > -1) + { + this.ObjectList.Move(FoundIDX, NewIDX); + this.Update(false); + ret = this.ObjectList[NewIDX]; + } + } + + return ret; + + } + public ClsRelevantObject MoveDown(ClsRelevantObject ro, out int NewIDX) + { + ClsRelevantObject ret = ro; + NewIDX = 0; + + if (ro == null) + return ro; + + ClsRelevantObject rofound = this.Get(ro, false, out int FoundIDX, true); + if (!rofound.IsNull()) + { + NewIDX = FoundIDX + 1; + + if (NewIDX <= this.ObjectList.Count - 1) + { + this.ObjectList.Move(FoundIDX, NewIDX); + this.Update(false); + ret = this.ObjectList[NewIDX]; + } + } + + return ret; + + } + public void Reset() + { + AITOOL.Log($"Using Relevant Objects list from the 'Default' camera for {this.TypeName} RelevantObjectManager."); + this.ObjectList = this.GetDefaultObjectList(true); + this.Update(); + } + + public List GetDefaultObjectList(bool Clear) + { + List ret = this._DefaultObjectsList; + //get the default camera list + + try + { + if (this.defaultcam.IsNull()) + this.defaultcam = AITOOL.GetCamera("Default", true); + + if (!this.defaultcam.IsNull()) //probably here to soon + { + + if (this.defaultcam.DefaultTriggeringObjects.ObjectList.Count > 0 && !this.CameraName.EqualsIgnoreCase(this.defaultcam.Name)) + { + this._DefaultObjectsList = this.defaultcam.DefaultTriggeringObjects.CloneObjectList(); + } + else + { + this._DefaultObjectsList = this.FromString(AppSettings.Settings.ObjectPriority, Clear, false); + } + + ret = this._DefaultObjectsList; + + } + + } + catch (Exception ex) + { + AITOOL.Log($"Error: ({this.TypeName}) {ex.Msg()}"); + } + + return ret; + } + + public List CloneObjectList() + { + List ret = new List(); + foreach (var ro in this.ObjectList) + { + if (!AppSettings.Settings.ObjectsExcludedDict.ContainsKey(ro.Name)) + ret.Add(ro.CloneJson()); //cloning so that when we add default settings from another object manager instance we dont change the original + } + return ret; + } + + public int GetHashCode() + { + int ret = 0; + foreach (var ro in this.ObjectList) + ret += ro.GetHashCode(); + return ret; + } + + public List FromList(List InList, bool Clear, bool ExactMatchOnly) + { + + List ret = new List(); + + if (InList.Count == 0) + return ret; + + this.Init(); + if (!this.Initialized) + return ret; + + if (Clear) + { + this.ObjectList.Clear(); + this.EnabledCount = 0; + } + + ret.AddRange(this.ObjectList); + + int order = ret.Count - 1; + + bool AlreadyHasItems = ret.Count > 0; + int dupes = 0; + + foreach (var ro in InList) + { + if (AppSettings.Settings.ObjectsExcludedDict.ContainsKey(ro.Name)) + continue; + + ClsRelevantObject rofound = this.Get(ro, false, out int FoundIDX, ExactMatchOnly, ret); + + if (rofound.IsNull()) + { + //Only add items as enabled if we started out from an empty list + if (AlreadyHasItems) + ro.Enabled = false; + + order++; + this.EnabledCount++; + ro.Priority = order; + ro.Update(); + ret.Add(ro.CloneJson()); + } + else + { + dupes++; + } + } + + ////force disabled items to be lower priority + //foreach (var ro in InList) + //{ + // ClsRelevantObject rofound = this.Get(ro, false, out int FoundIDX); + // if (rofound.IsNull()) + // { + // //Only add items as enabled if we started out from an empty list + // if (!AlreadyHasItems) + // ro.Enabled = true; + // else + // ro.Enabled = false; + + // if (!ro.Enabled) + // { + // order++; + // ro.Priority = order; + // ro.Update(); + // ret.Add(ro); + // } + // } + //} + + return ret; + } + public List FromString(string Objects, bool Clear, bool ExactMatchonly) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + List ret = new List(); + + this.Init(); + if (!this.Initialized) + return ret; + + + try + { + + if (Clear) + { + this.ObjectList.Clear(); + this.EnabledCount = 0; + } + //take anything before the first semicolon: + if (Objects.Contains(";")) + Objects = Objects.GetWord("", ";"); + + bool AlreadyHasItems = ObjectList.Count > 0; + + List lst = Objects.SplitStr(","); + List DefaultObjectList = new List(); + + if (!this.CameraName.EqualsIgnoreCase("default")) + DefaultObjectList = this.GetDefaultObjectList(false); + + foreach (var obj in lst) + { + + if (AppSettings.Settings.ObjectsExcludedDict.ContainsKey(obj)) + continue; + + ClsRelevantObject ro = new ClsRelevantObject(obj); + + ClsRelevantObject rofound = this.Get(ro, false, out int FoundIDX, ExactMatchonly, ret); + + if (rofound.IsNull()) + { + //Only add items as enabled if we started out from an empty list + if (AlreadyHasItems) + ro.Enabled = false; + + if (ro.Enabled) + this.EnabledCount++; + + //set the order if found in the default list + ClsRelevantObject defRo = this.Get(ro, false, out int DefFoundIDX, false, DefaultObjectList); + + if (DefFoundIDX > -1) + ro.Priority = DefFoundIDX + 1; + else + ro.Priority = ret.Count + 1; + + ret.Add(ro); + } + } + + + } + catch (Exception ex) + { + AITOOL.Log("Error: " + ex.Msg()); + } + + return ret; + } + + public bool TryAdd(ClsRelevantObject ro, bool Enable, out int AddedIDX) + { + bool ret = false; + AddedIDX = -1; + + if (ro.IsNull()) + return false; + + if (AppSettings.Settings.ObjectsExcludedDict.ContainsKey(ro.Name)) + return false; + + ClsRelevantObject rofound = this.Get(ro, false, out int FoundIDX, true); + + if (rofound.IsNull()) + { + ro.Priority = this.ObjectList.Count + 1; + ro.Enabled = Enable; + + if (ro.Enabled) + this.EnabledCount++; + + ro.Update(); + this.ObjectList.Add(ro); + AddedIDX = this.ObjectList.Count - 1; + ret = true; + } + + return ret; + + } + public bool TryAdd(string objname, bool Enable, out int AddedIDX) + { + AddedIDX = -1; + + if (objname.IsEmpty() || this.TypeName.IsEmpty()) + return false; + + return this.TryAdd(new ClsRelevantObject(objname), Enable, out AddedIDX); + } + + public override string ToString() + { + string ret = ""; + foreach (var ro in this.ObjectList) + { + if (ro.Enabled) + ret += ro.ToString() + ", "; + } + return ret.Trim(" ,".ToCharArray()); + } + + public ClsRelevantObject Get(ClsRelevantObject roobj, bool AllowEverything, out int FoundIDX, bool ExactMatchOnly, List ObjList = null) + { + + if (ObjList.IsNull()) + ObjList = this.ObjectList; + + //look for exact hashcode first + FoundIDX = ObjList.IndexOf(roobj); + + if (FoundIDX > -1) + return ObjList[FoundIDX]; + + //search for the last hashcode in case the object has changed + for (int i = 0; i < ObjList.Count; i++) + { + if (ObjList[i].LastHashCode != 0 && ObjList[i].LastHashCode == roobj.LastHashCode && !roobj.Name.EqualsIgnoreCase("new object")) + { + FoundIDX = i; + return ObjList[i]; + } + + } + + //fall back to name only search + if (!ExactMatchOnly) + return this.Get(roobj.Name, AllowEverything, out FoundIDX, ObjList); + + return null; + + + } + public ClsRelevantObject Get(string objname, bool AllowEverything, out int FoundIDX, List ObjList = null, bool UseMainCamList = false) + { + + if (ObjList.IsNull()) + { + if (!UseMainCamList) + ObjList = this.ObjectList; + else + ObjList = this.cam.DefaultTriggeringObjects.ObjectList; + } + + FoundIDX = -1; + //Get only by label name + for (int i = 0; i < ObjList.Count; i++) + { + if (ObjList[i].Name.EqualsIgnoreCase(objname)) + { + FoundIDX = i; + return ObjList[i]; + } + } + + if (AllowEverything) + { + for (int i = 0; i < ObjList.Count; i++) + { + if (ObjList[i].Name.EqualsIgnoreCase("everything")) + { + FoundIDX = i; + return ObjList[i]; + } + } + } + + return null; + + } + + public ResultType IsRelevant(string Label, out bool IgnoreImageMask, out bool IgnoreDynamicMask, string DbgDetail = "") + { + ClsPrediction pred = new ClsPrediction(); + pred.Label = Label; + return IsRelevant(new List() { pred }, true, out IgnoreImageMask, out IgnoreDynamicMask, DbgDetail); + } + public ResultType IsRelevant(ClsPrediction pred, bool IsNew, out bool IgnoreImageMask, out bool IgnoreDynamicMask, string DbgDetail = "") + { + return IsRelevant(new List() { pred }, IsNew, out IgnoreImageMask, out IgnoreDynamicMask, DbgDetail); + } + + public ResultType IsRelevant(List preds, bool IsNew, out bool IgnoreImageMask, out bool IgnoreDynamicMask, string DbgDetail = "") + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + ResultType ret = ResultType.UnwantedObject; + IgnoreImageMask = false; + IgnoreDynamicMask = false; + + this.Init(); + if (!this.Initialized) + return ret; + + + //if nothing is 'enabled' assume everything should be let through to be on the safe side (As if they passed an empty list) + if (this.ObjectList.Count == 0 || this.EnabledCount == 0) //assume if no list is provided to always return relevant + { + AITOOL.Log($"Debug: RelevantObjectManager: Allowing '{this.TypeName}{DbgDetail}' because no relevant objects were enabled. {preds.Count} predictions(s), Enabled={this.EnabledCount} of {this.ObjectList.Count}"); + return ResultType.Relevant; + } + + //if fred is found, the whole prediction will be ignored + //triggering_objects = person, car, -FRED + //found objects = person, fred + + if (preds != null && preds.Count > 0) + { + //find at least one thing in the triggered objects list in order to send + string notrelevant = ""; + string relevant = ""; + string ignored = ""; + string unknownobject = ""; + string notenabled = ""; + string nottime = ""; + string nothreshold = ""; + string percentsizewrong = ""; + bool ignore = false; + + foreach (ClsPrediction pred in preds) + { + string label = pred.Label; + + + if (pred.Result == ResultType.Relevant || IsNew) + { + ClsRelevantObject ro = this.Get(label, AllowEverything: true, out int FoundIDX); + + if (!ro.IsNull()) + { + //Add the PercentOfImage size stats in any case: + if (pred.Confidence == 0 || pred.Confidence.Round() >= ro.Threshold_lower) + { + ro.PercentSizeStats.AddToCalc(pred.PercentOfImage); + ro.HeightStats.AddToCalc(pred.RectHeight); + ro.WidthStats.AddToCalc(pred.RectWidth); + } + + if (ro.Enabled) + { + if (Global.IsTimeBetween(DateTime.Now, ro.ActiveTimeRange)) + { + //assume if confidence is 0 it has not been set yet (dynamic masking routine, etc) + if (pred.Confidence == 0 || pred.Confidence.Round() >= ro.Threshold_lower && pred.Confidence.Round() <= ro.Threshold_upper) + { + if (pred.PercentOfImage.Round() >= ro.PredSizeMinPercentOfImage && pred.PercentOfImage.Round() <= ro.PredSizeMaxPercentOfImage) + { + ro.LastHitTime = DateTime.Now; + ro.Hits++; + if (!ro.Trigger) + { + ignore = true; + if (!ignored.Contains(label)) + ignored += label + ","; + } + else + { + ret = ResultType.Relevant; + pred.ObjectResult = ResultType.Relevant; + IgnoreImageMask = ro.IgnoreImageMask; + if (ro.IgnoreDynamicMask.HasValue) + IgnoreDynamicMask = ro.IgnoreDynamicMask.Value; + if (!relevant.Contains(label)) + relevant += label + ","; + } + + } + else + { + if (pred.Confidence.Round() < ro.Threshold_lower) + pred.ObjectResult = ResultType.TooSmallPercent; + else if (pred.Confidence.Round() > ro.Threshold_upper) + pred.ObjectResult = ResultType.TooLargePercent; + + if (!percentsizewrong.Contains(label)) + percentsizewrong += label + $" ({pred.PercentOfImage.Round()}%),"; + } + + } + else + { + pred.ObjectResult = ResultType.NoConfidence; + if (!nothreshold.Contains(label)) + nothreshold += label + $" ({pred.Confidence.Round()}%),"; + } + + } + else + { + pred.ObjectResult = ResultType.NotInTimeRange; + if (!nottime.Contains(label)) + nottime += label + ","; + } + + } + else + { + pred.ObjectResult = ResultType.NotEnabled; + if (!notenabled.Contains(label)) + notenabled += label + ","; + } + } + else + { + pred.ObjectResult = ResultType.UnknownObject; + if (!unknownobject.Contains(label)) + unknownobject += label + ","; + } + } + else + { + pred.ObjectResult = pred.Result; + if (!notrelevant.Contains(label)) + notrelevant += label + ","; + } + + } + + //Add to the main list + if (this.cam == null) + this.cam = AITOOL.GetCamera(this.CameraName); + + if (this.defaultcam == null) + this.defaultcam = AITOOL.GetCamera("Default", true); + + //always try to add the current prediction to the list (disabled) to give them the option of enabling later + foreach (ClsPrediction pred in preds) + { + this.TryAdd(pred.Label, false, out int AddedIDX); + + + //add it to the Current camera list (disabled) + this.cam.DefaultTriggeringObjects.TryAdd(pred.Label, false, out int AddedIDX2); + + //add it to the default camera list (disabled) + if (!this.defaultcam.IsNull()) + this.defaultcam.DefaultTriggeringObjects.TryAdd(pred.Label, false, out int AddedIDX3); + + } + + + if (ignore) + ret = ResultType.IgnoredObject; + + if (!DbgDetail.IsEmpty()) + DbgDetail = $" ({DbgDetail})"; + + if (!relevant.IsEmpty()) + relevant = $"Relevant='{relevant.Trim(", ".ToCharArray())}',"; + + if (!notrelevant.IsEmpty()) + notrelevant = $"Irrelevant='{notrelevant.Trim(", ".ToCharArray())}',"; + + if (!ignored.IsEmpty()) + ignored = $"Caused ignore='{ignored.Trim(", ".ToCharArray())}',"; + + if (!notenabled.IsEmpty()) + notenabled = $"Not Enabled={notenabled.Trim(" ,".ToCharArray())},"; + + if (!nottime.IsEmpty()) + nottime = $"Not Time={nottime.Trim(" ,".ToCharArray())},"; + + if (!nothreshold.IsEmpty()) + nothreshold = $"No Threshold Match='{nothreshold.Trim(" ,".ToCharArray())}',"; + + if (!percentsizewrong.IsEmpty()) + percentsizewrong = $"Percent Size wrong='{percentsizewrong.Trim(" ,".ToCharArray())}',"; + + if (!unknownobject.IsEmpty()) + unknownobject = $"Unknown Object='{unknownobject.Trim(" ,".ToCharArray())}',"; ; + + string maskignore = ""; + if (IgnoreImageMask) + maskignore = " (Mask will be ignored)"; + + if (ret != ResultType.Relevant) + { + AITOOL.Log($"Debug: RelevantObjectManager: Skipping '{this.TypeName}{DbgDetail}' because: {notrelevant}{ignored}{notenabled}{nottime}{nothreshold}{percentsizewrong}{unknownobject}{relevant}. {maskignore}, {preds.Count} predictions(s), Enabled={this.EnabledCount} of {this.ObjectList.Count}, IsNew={IsNew}"); + } + else + { + AITOOL.Log($"Debug: RelevantObjectManager: Object is valid for '{this.TypeName}{DbgDetail}' because: {relevant}{notrelevant}{ignored}{notenabled}{nottime}{nothreshold}{percentsizewrong}{unknownobject}. {maskignore}, {preds.Count} predictions(s), Enabled={this.EnabledCount} of {this.ObjectList.Count}, IsNew={IsNew}"); + + } + } + else + { + AITOOL.Log($"Debug: RelevantObjectManager: Skipping '{this.TypeName}{DbgDetail}' because there were no PREDICTIONS."); + } + + return ret; + + } + + } +} diff --git a/src/UI/ClsSightHound.cs b/src/UI/ClsSightHound.cs new file mode 100644 index 00000000..0f7f7419 --- /dev/null +++ b/src/UI/ClsSightHound.cs @@ -0,0 +1,317 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AITool +{ + //https://www.sighthound.com/products/cloud + + + //========================================================================== + // SHARED + //========================================================================== + + + public class SightHoundError + { + [JsonProperty("error")] + public string Error { get; set; } = ""; + + [JsonProperty("reason")] + public string Reason { get; set; } = ""; + + [JsonProperty("reasonCode")] + public int ReasonCode { get; set; } = 0; + + [JsonProperty("details")] + public SightHoundErrorDetails Details { get; set; } = new SightHoundErrorDetails(); + } + + public class SightHoundErrorDetails + { + [JsonProperty("statusCode")] + public int StatusCode { get; set; } = 0; + + [JsonProperty("statusMessage")] + public string StatusMessage { get; set; } = ""; + + [JsonProperty("body")] + public string Body { get; set; } = ""; + } + + public class SightHoundImage + { + [JsonProperty("width")] + public int Width { get; set; } = 0; + [JsonProperty("height")] + public int Height { get; set; } = 0; + [JsonProperty("orientation")] + public int Orientation { get; set; } = 0; + } + + + //========================================================================== + // VEHICLE + //========================================================================== + + public class SightHoundVehicleRoot + { + [JsonProperty("image")] + public SightHoundImage Image { get; set; } + + [JsonProperty("objects")] + + public List Objects { get; set; } + [JsonProperty("requestId")] + public string RequestId { get; set; } = ""; + + } + + + public class SightHoundVehicleObject + { + [JsonProperty("vehicleAnnotation")] + public SightHoundVehicleAnnotation VehicleAnnotation { get; set; } + + [JsonProperty("objectId")] + public string ObjectId { get; set; } + + [JsonProperty("objectType")] + public string ObjectType { get; set; } + } + + public class SightHoundVehicleAnnotation + { + [JsonProperty("bounding")] + public SightHoundBounding Bounding { get; set; } + + [JsonProperty("attributes")] + public SightHoundAttributes Attributes { get; set; } + + [JsonProperty("recognitionConfidence")] + public float RecognitionConfidence { get; set; } + + [JsonProperty("licenseplate")] + public Licenseplate Licenseplate { get; set; } + } + + public class Licenseplate + { + [JsonProperty("bounding")] + public SightHoundBounding Bounding { get; set; } + + [JsonProperty("attributes")] + public SightHoundAttributes Attributes { get; set; } + } + + public class SightHoundAttributes + { + [JsonProperty("system")] + public SightHoundSystem System { get; set; } + } + + public class SightHoundSystem + { + [JsonProperty("color")] + public SightHoundColor Color { get; set; } + + [JsonProperty("make")] + public SightHoundMake Make { get; set; } + + [JsonProperty("model")] + public SightHoundModel Model { get; set; } + + [JsonProperty("vehicleType")] + public string VehicleType { get; set; } + + [JsonProperty("region")] + public SightHoundRegion Region { get; set; } + + [JsonProperty("string")] + public SightHoundString String { get; set; } + + [JsonProperty("characters")] + public List Characters { get; set; } + } + + public class SightHoundString + { + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("confidence")] + public float Confidence { get; set; } + } + + public class SightHoundCharacter + { + [JsonProperty("bounding")] + public SightHoundBounding Bounding { get; set; } + + [JsonProperty("index")] + public int Index { get; set; } + + [JsonProperty("confidence")] + public double Confidence { get; set; } + + [JsonProperty("character")] + public string character { get; set; } + } + public class SightHoundColor + { + [JsonProperty("confidence")] + public double Confidence { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + } + + public class SightHoundMake + { + [JsonProperty("confidence")] + public double Confidence { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + } + + public class SightHoundModel + { + [JsonProperty("confidence")] + public double Confidence { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + } + + public class SightHoundRegion + { + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("confidence")] + public double Confidence { get; set; } + } + + public class SightHoundVertex + { + [JsonProperty("x")] + public int X { get; set; } + + [JsonProperty("y")] + public int Y { get; set; } + } + + public class SightHoundBounding + { + [JsonProperty("vertices")] + public List Vertices { get; set; } + } + + //========================================================================== + // PERSON + //========================================================================== + public class SightHoundPersonRoot + { + [JsonProperty("image")] + public SightHoundImage Image { get; set; } + + [JsonProperty("objects")] + + public List Objects { get; set; } + [JsonProperty("requestId")] + public string RequestId { get; set; } = ""; + + } + + public class SightHoundPersonObject + { + [JsonProperty("type")] + public string Type { get; set; } = ""; + + [JsonProperty("boundingBox")] + public SightHoundPersonBoundingBox BoundingBox { get; set; } + + [JsonProperty("attributes")] + public SightHoundPersonAttributes? Attributes { get; set; } + [JsonProperty("landmarks")] + public Dictionary? Landmarks { get; set; } + + } + + public class SightHoundPersonBoundingBox + { + [JsonProperty("x")] + public int X { get; set; } = 0; + [JsonProperty("y")] + public int Y { get; set; } = 0; + [JsonProperty("height")] + public int Height { get; set; } = 0; + [JsonProperty("width")] + public int Width { get; set; } = 0; + + } + public class SightHoundPersonAttributes + { + [JsonProperty("gender")] + public string Gender { get; set; } = ""; + [JsonProperty("genderConfidence")] + public float GenderConfidence { get; set; } = 0; + [JsonProperty("age")] + public int Age { get; set; } = 0; + [JsonProperty("emotion")] + public string Emotion { get; set; } = ""; + [JsonProperty("emotionsAll")] + public SightHoundEmotionsAll EmotionsAll { get; set; } + [JsonProperty("frontal")] + public bool Frontal { get; set; } = false; + + } + + public class SightHoundEmotionsAll + { + [JsonProperty("neutral")] + public double Neutral { get; set; } + + [JsonProperty("sadness")] + public double Sadness { get; set; } + + [JsonProperty("disgust")] + public double Disgust { get; set; } + + [JsonProperty("anger")] + public double Anger { get; set; } + + [JsonProperty("surprise")] + public double Surprise { get; set; } + + [JsonProperty("fear")] + public double Fear { get; set; } + + [JsonProperty("happiness")] + public double Happiness { get; set; } + + } + + public class SightHoundEmotionItem + { + public SightHoundEmotionItem(string name, double confidence) + { + Name = name; + Confidence = confidence; + } + + public string Name { get; set; } = ""; + public double Confidence { get; set; } = 0; + + public override string ToString() + { + return Name; + } + } + +} diff --git a/src/UI/ClsTelegramMessages.cs b/src/UI/ClsTelegramMessages.cs new file mode 100644 index 00000000..14f64ede --- /dev/null +++ b/src/UI/ClsTelegramMessages.cs @@ -0,0 +1,360 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; + +using Amazon.Rekognition.Model; + +using Newtonsoft.Json.Linq; + +using Telegram.Bot; +using Telegram.Bot.Exceptions; +using Telegram.Bot.Polling; +//using Telegram.Bot.Extensions.Polling; +using Telegram.Bot.Types; +using Telegram.Bot.Types.Enums; +//using Telegram.Bot.Types.InputFiles; + +using static System.Windows.Forms.VisualStyles.VisualStyleElement.Window; +using static AITool.AITOOL; + +using User = Telegram.Bot.Types.User; + +namespace AITool +{ + public class ClsTelegramMessages:IDisposable + { + public TelegramBotClient telegramBot { get; set; } = null; + + private HttpClient telegramHttpClient = null; + private User BotUser = null; + private ThreadSafe.Boolean Started = new ThreadSafe.Boolean(false); + private ThreadSafe.Boolean StartingReceive = new ThreadSafe.Boolean(false); + private CancellationTokenSource TelegramCTS = new CancellationTokenSource(); + private bool disposedValue; + public ClsTelegramMessages() + { + TryStartTelegram(); + } + + public async Task TryStartTelegram() + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + bool ret = true; + // + try + { + if (AppSettings.Settings.telegram_token.IsNotNull() && AppSettings.Settings.telegram_chatids.IsNotEmpty()) + { + if (!this.Started) + { + Log("Debug: Initializing Telegram Bot..."); + + Stopwatch sw = Stopwatch.StartNew(); + + //try to prevent telegram Could not create SSL/TLS secure channel exception on Win7. + //This may also need TLS 1.2 and 1.3 checked in Internet Options > Advanced tab.... + ServicePointManager.Expect100Continue = true; + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls; //| SecurityProtocolType.Tls13; + + this.telegramHttpClient = new System.Net.Http.HttpClient(); + this.telegramHttpClient.Timeout = TimeSpan.FromSeconds(AppSettings.Settings.HTTPClientRemoteTimeoutSeconds); + + this.telegramBot = new TelegramBotClient(AppSettings.Settings.telegram_token, this.telegramHttpClient); + + //this may be redundant: + this.telegramBot.Timeout = TimeSpan.FromSeconds(AppSettings.Settings.HTTPClientRemoteTimeoutSeconds); + + //for debugging, remove later... + this.telegramBot.OnApiResponseReceived += TelegramBot_OnApiResponseReceived; + this.telegramBot.OnMakingApiRequest += TelegramBot_OnMakingApiRequest; + + //start another thread to initialize listening for commands (said it was non blocking but was having weird issues with it) + if (!this.StartingReceive) + { + //Only start listening if another instance is not running + if (!AppSettings.AlreadyRunning && AppSettings.Settings.telegram_monitor_commands) + Task.Run(TelegramStartReceiving); + } + else + Log($"Debug: Already initializing StartReceiving?"); + + Log($"Debug: ...Done in {sw.ElapsedMilliseconds}ms."); + + this.Started= true; + } + + } + else + { + Log("Debug: Telegram Bot cannot initialize because of missing token or chatid."); + } + + } + catch (Exception ex) + { + ret = false; + this.Started = false; + throw; + } + + return ret; + } + + private async void TelegramStartReceiving() + { + //put this on another thread because it seemed to freeze even though it was supposed to be non blocking? + + try + { + this.StartingReceive= true; + + Log("Debug: (Initializing StartReceiving)..."); + + Stopwatch sw = Stopwatch.StartNew(); + + // StartReceiving does not block the caller thread. Receiving is done on the ThreadPool. + var receiverOptions = new ReceiverOptions { AllowedUpdates = { } }; // receive all update types + + this.telegramBot.StartReceiving( + TelegramHandleUpdateAsync, + TelegramHandleErrorAsync, + receiverOptions, + cancellationToken: this.TelegramCTS.Token); + + Log("Debug: (Getting User Info)..."); + + BotUser = await this.telegramBot.GetMeAsync(); + + if (AppSettings.Settings.telegram_chatids.GetStrAtIndex(0).IsNotEmpty()) + { + Log("Debug: (Sending intro message)..."); + string AssemVer = Assembly.GetExecutingAssembly().GetName().Version.ToString(); + Message message = await this.telegramBot.SendTextMessageAsync(AppSettings.Settings.telegram_chatids.GetStrAtIndex(0), + $"AITOOL {AssemVer} Initialized. " + + $"\nTo send Telegram commands, first ask BotFather to disable '/setprivacy'. " + + $"\nCommand Usage: " + + $"\nPAUSE|STOP|START|RESUME [CAMNAME] [MINUTES]" + + $"\n'PAUSE 1' will pause all cams for 1 min." + + $"\nMUTE, UNMUTE, VOLUMEUP, VOLUMEDOWN, " + + $"\nVOLUMESET Level" + + $"\nSHUTDOWNCOMPUTER\nRESTARTCOMPUTER\nRESTARTAITOOL\nSCREENSHOT"); + } + else + { + Log("Debug: (Skipping intro message because no ChatID configured)..."); + } + + Log($"Debug: Ready to receive control messages. Done in {sw.ElapsedMilliseconds}ms. Listening for Telegram messages from {BotUser.Username} ({BotUser.Id}, {BotUser.FirstName} {BotUser.LastName}), IsBot={BotUser.IsBot}, CanJoinGroups={BotUser.CanJoinGroups}, CanReadAllGroupMessages={BotUser.CanReadAllGroupMessages}..."); + + } + catch (Exception ex) + { + Log($"Error: {ex.Msg()}"); + } + finally + { + this.StartingReceive = false; + } + + } + + private ValueTask TelegramBot_OnMakingApiRequest(ITelegramBotClient botClient, Telegram.Bot.Args.ApiRequestEventArgs args, CancellationToken cancellationToken = default) + { + Log($"Trace: API request: {args.Request.MethodName}"); + return default; + } + + private ValueTask TelegramBot_OnApiResponseReceived(ITelegramBotClient botClient, Telegram.Bot.Args.ApiResponseEventArgs args, CancellationToken cancellationToken = default) + { + Log($"Trace: API response: {args.ResponseMessage.ToString().CleanString(",")}"); + return default; + } + + public async Task SendTextMessageAsync(string ChatID, string Caption) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + Message message = null; + + await this.TryStartTelegram(); + + if (Started) + { + message = await this.telegramBot.SendTextMessageAsync(ChatID, Caption); + } + + return message; + + } + public async Task SendPhotoAsync(string ChatID, Stream FileStream, string file_id, string Filename, string Caption) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + Message message = null; + + await this.TryStartTelegram(); + + if (Started) + { + await this.telegramBot.SendChatActionAsync(ChatID, ChatAction.UploadPhoto); + + if (Filename.Contains("\\")) + Filename = Path.GetFileName(Filename); + + if (file_id.IsNull()) + //message = await this.telegramBot.SendPhotoAsync(ChatID, new InputOnlineFile(FileStream, Filename), Caption); + message = await this.telegramBot.SendPhotoAsync(ChatID, new InputFileStream(FileStream, Filename), caption: Caption); + else + message = await this.telegramBot.SendPhotoAsync(ChatID, InputFile.FromFileId(file_id), caption: Caption); + } + + return message; + + } + + async Task TelegramHandleUpdateAsync(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + try + { + // Only process Message updates: https://core.telegram.org/bots/api#message + if (update.Type != UpdateType.Message) + { + Log($"Debug: Telegram: Received an unsupported update.type '{update.Type}'"); + return; + } + + // Only process text messages + if (update.Message!.Type != Telegram.Bot.Types.Enums.MessageType.Text && update.Message!.Type != Telegram.Bot.Types.Enums.MessageType.Voice) + { + Log($"Debug: Telegram: Received an unsupported update.Message.type '{update.Message!.Type}'"); + return; + } + + + var chatId = update.Message.Chat.Id; + var messageText = update.Message.Text.IsNotEmpty() ? update.Message.Text : ""; + + Log($"Debug: Telegram: Received a '{update.Type}' with content '{messageText}' in chat {chatId}."); + + if (update.Message.Type == Telegram.Bot.Types.Enums.MessageType.Voice) + { + var filePath = Path.Combine(Global.GetTempFolder(), update.Message.Voice.FileId + ".ogg"); + Log($"Debug: Telegram: Downloading a {update.Message.Voice.FileSize} byte Voice recording to {filePath}..."); + + Stopwatch sw = Stopwatch.StartNew(); + using (FileStream fileStream = System.IO.File.OpenWrite(filePath)) + { + Telegram.Bot.Types.File file = await this.telegramBot.GetInfoAndDownloadFileAsync(fileId: update.Message.Voice.FileId, destination: fileStream); + } + Log($"Debug: ...Done in {sw.ElapsedMilliseconds}ms"); + Global.TelegramControlMessage($"play:{filePath}"); + } + else + { + Global.TelegramControlMessage(messageText); + } + + + // Echo received message text + //Telegram.Bot.Types.Message sentMessage = await botClient.SendTextMessageAsync( + // chatId: chatId, + // text: "You said:\n" + messageText, + // cancellationToken: cancellationToken); + } + catch (Exception ex) + { + Log($"Error: {ex.Msg()}"); + } + } + + string lasterr = ""; + DateTime lasterrtime = DateTime.MinValue; + int repeated = 0; + int inuse = 0; + Task TelegramHandleErrorAsync(ITelegramBotClient botClient, Exception exception, CancellationToken cancellationToken) + { + + var ErrorMessage = exception switch + { + ApiRequestException apiRequestException => $"Telegram API Error: [{apiRequestException.ErrorCode}] {apiRequestException.Message}", + _ => exception.Msg() + }; + + //rate limit since I've seen 100 of these in a few seconds? + double lastsecs = Math.Abs((DateTime.Now - lasterrtime).TotalSeconds); + + if (lasterr.Contains("409")) + inuse++; + + if (!ErrorMessage.EqualsIgnoreCase(lasterr) || ErrorMessage.EqualsIgnoreCase(lasterr) && lastsecs >= 120) + { + string rep = ""; + if (this.repeated > 0) + rep = $"(Repeated {this.repeated} times)"; + Log($"Error: {rep}{ErrorMessage}"); + this.lasterr = ErrorMessage; + this.lasterrtime = DateTime.Now; + this.repeated = 0; + } + else + { + repeated++; + } + + //this happens if more than one client is listening at a time + //Telegram API [409] Conflict: terminated by other getUpdates request; make sure that only one bot instance is running + if (inuse >= 6) + { + //give up and stop listening after three repeat errors + Log("Error: Canceling receive Updates because another Telegram client is using the same token."); + this.TelegramCTS.Cancel(); + } + + return Task.CompletedTask; + } + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + // TODO: dispose managed state (managed objects) + if (this.telegramBot.IsNotNull()) + this.telegramBot = null; + + if (this.telegramHttpClient.IsNotNull()) + this.telegramHttpClient.Dispose(); + } + + // TODO: free unmanaged resources (unmanaged objects) and override finalizer + // TODO: set large fields to null + disposedValue = true; + } + } + + // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources + // ~ClsTelegramMessages() + // { + // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + // Dispose(disposing: false); + // } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/UI/ClsTriggerActionQueue.cs b/src/UI/ClsTriggerActionQueue.cs new file mode 100644 index 00000000..49a3d338 --- /dev/null +++ b/src/UI/ClsTriggerActionQueue.cs @@ -0,0 +1,1757 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.IO; +using System.Linq; +using System.Media; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Reflection; +using System.Speech.Synthesis; +using System.Threading.Tasks; + +using MQTTnet.Client; + +using NPushover.RequestObjects; +using NPushover.ResponseObjects; + +using SixLabors.ImageSharp; + +using Telegram.Bot.Exceptions; +//using Telegram.Bot.Types.InputFiles; + +using static AITool.AITOOL; +//using static Microsoft.WindowsAPICodePack.Shell.PropertySystem.SystemProperties.System; + +namespace AITool +{ + public class ClsTriggerActionQueueItem + { + public TriggerType TType { get; set; } = TriggerType.Unknown; + public Camera cam { get; set; } = null; + public string camname { get; set; } = "None"; + public History Hist { get; set; } = null; + public ClsImageQueueItem CurImg { get; set; } = null; + public bool Trigger { get; set; } = true; + public string Text { get; set; } = ""; + public DateTime AddedTime { get; set; } = DateTime.MinValue; + public long QueueCount { get; set; } = 0; + public long QueueWaitMS { get; set; } = 0; + public bool IsQueued { get; set; } = false; + public long ActionTimeMS { get; set; } = 0; + public long TotalTimeMS { get; set; } = 0; + public ClsTriggerActionQueueItem(TriggerType ttype, Camera cam, ClsImageQueueItem CurImg, History hist, bool Trigger, string Text, bool IsQueued) + { + this.cam = cam; + this.Hist = hist; + this.CurImg = CurImg; + + //if they are null must be for testing? + if (this.cam.IsNull()) + this.cam = new Camera("None"); + + if (this.CurImg.IsNull()) + this.CurImg = new ClsImageQueueItem(Path.Combine(Global.GetTempFolder(), "test.jpg"), 0); + + if (this.Hist.IsNull()) + this.Hist = new History().Create(this.CurImg.image_path, DateTime.Now, this.cam.Name, Text, "", Trigger, "", "None", 0, Trigger); + + if (!this.cam.IsNull()) + this.camname = this.cam.Name; + + this.TType = ttype; + this.Trigger = Trigger; + this.AddedTime = DateTime.Now; + this.Text = Text; + this.IsQueued = IsQueued; + } + } + + public class ClsTriggerActionQueue + { + BlockingCollection TriggerActionQueue = new BlockingCollection(); + ConcurrentDictionary CancelActionDict = new ConcurrentDictionary(); + ConcurrentDictionary GroupsLastTriggerDict = new ConcurrentDictionary(); + public ThreadSafe.DateTime last_telegram_trigger_time { get; set; } = new ThreadSafe.DateTime(DateTime.MinValue, AppSettings.Settings.DateFormat); + public ThreadSafe.DateTime last_Pushover_trigger_time { get; set; } = new ThreadSafe.DateTime(DateTime.MinValue, AppSettings.Settings.DateFormat); + public ThreadSafe.DateTime TelegramRetryTime { get; set; } = new ThreadSafe.DateTime(DateTime.MinValue, AppSettings.Settings.DateFormat); + public ThreadSafe.DateTime PushoverRetryTime { get; set; } = new ThreadSafe.DateTime(DateTime.MinValue, AppSettings.Settings.DateFormat); + //public ClsURLItem _url { get; set; } = null; + private String CurSrv = ""; + string ImgPath = "NoImage"; + public ThreadSafe.Integer Count { get; set; } = new ThreadSafe.Integer(0); + public MovingCalcs QCountCalc { get; set; } = new MovingCalcs(250, "Action Queue Sizes", false); + public MovingCalcs QTimeCalc { get; set; } = new MovingCalcs(250, "Action Queue Times", true); + public MovingCalcs ActionTimeCalc { get; set; } = new MovingCalcs(250, "Actions", true); + public MovingCalcs TotalTimeCalc { get; set; } = new MovingCalcs(250, "Actions", true); + public ClsTriggerActionQueue() + { + Task.Run(this.TriggerActionJobQueueLoop); + Task.Run(this.CancelActionJobQueueLoop); + } + + public async Task AddTriggerActionAsync(TriggerType ttype, Camera cam, ClsImageQueueItem CurImg, History hist, bool Trigger, bool Wait, string CurSrv, string Text) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + bool ret = false; + this.CurSrv = CurSrv; + + if (CurImg != null) + { + this.ImgPath = CurImg.image_path; + } + + + ClsTriggerActionQueueItem AQI = new ClsTriggerActionQueueItem(ttype, cam, CurImg, hist, Trigger, Text, !Wait); + + //Make sure not to put cancel items in the queue if no cancel triggers are defined... + + bool HasCancel = ((AQI.cam.Action_mqtt_enabled && AQI.cam.Action_mqtt_payload_cancel.IsNotEmpty()) || + (AQI.cam.Action_CancelURL_Enabled && AQI.cam.cancel_urls.Length > 0)); + + bool IsCancel = ttype == TriggerType.Cancel || !Trigger; + + //bool DoIt = (Trigger || (!Trigger && cam.cancel_urls.Count > 0 || (cam.Action_mqtt_enabled && !string.IsNullOrEmpty(cam.Action_mqtt_payload_cancel)))); + bool DoIt = (Trigger || (IsCancel && HasCancel)); + + if (DoIt) + { + if (Wait) //not queued + { + ret = await this.RunTriggers(AQI); + } + else + { + if (this.TriggerActionQueue.Count <= AppSettings.Settings.MaxActionQueueSize) + { + if (!this.TriggerActionQueue.TryAdd(AQI)) + { + Log($"Error: Action '{AQI.TType}' could not be added? {this.ImgPath}", this.CurSrv, AQI.cam, AQI.CurImg); + } + else + { + this.Count = this.TriggerActionQueue.Count; + AQI.QueueCount = this.Count; + + ret = true; + Log($"Debug: Action '{AQI.TType}' ADDED to queue. Trigger={AQI.Trigger}, Queued={AQI.IsQueued}, Queue Count={AQI.QueueCount}, Image={this.ImgPath}", this.CurSrv, AQI.cam, AQI.CurImg); + + } + } + else + { + Log($"Error: Action '{AQI.TType}' could not be added because queue size is {this.TriggerActionQueue.Count} and the max is {AppSettings.Settings.MaxActionQueueSize} (MaxActionQueueSize) - {this.ImgPath}", this.CurSrv, AQI.cam, AQI.CurImg); + } + + } + } + else + { + Log($"Debug: Action '{AQI.TType}' could not be added because there are no CANCEL actions configured.", this.CurSrv, AQI.cam, AQI.CurImg); + } + + return ret; + } + + private async Task TriggerActionJobQueueLoop() + { + + try + { + //this runs forever and blocks if nothing is in the queue + foreach (ClsTriggerActionQueueItem AQI in this.TriggerActionQueue.GetConsumingEnumerable(MasterCTS.Token)) + { + if (MasterCTS.IsCancellationRequested) + break; + + try + { + await this.RunTriggers(AQI); + await Task.Delay(50); //very short wait between trigger queue events + } + catch (Exception ex) + { + + Log($"Error: " + ex.ToString(), this.CurSrv, AQI.cam, AQI.CurImg); + } + + } + + } + catch (Exception ex) + { + + Log($"Exit ActionQueueLoop: {ex.Message}"); + } + + Log($"Exit ActionQueueLoop?"); + + } + + private async Task CancelActionJobQueueLoop() + { + while (true) + { + if (MasterCTS.IsCancellationRequested) + break; + + if (!this.CancelActionDict.IsEmpty) + { + + foreach (ClsTriggerActionQueueItem AQI in CancelActionDict.Values) + { + + try + { + if (AQI.cam.Action_Cancel_Timer_Enabled) + { + if ((DateTime.Now - AQI.cam.Action_Cancel_Start_Time).TotalSeconds >= AppSettings.Settings.ActionCancelSeconds) + { + Log($"Debug: Running cancel Action '{AQI.TType}' in queue for event '{AQI.Hist.Detections}' for camera '{AQI.camname}', after {(DateTime.Now - AQI.cam.Action_Cancel_Start_Time).TotalSeconds.Round()} seconds...", this.CurSrv, AQI.cam, AQI.CurImg); + await this.RunTriggers(AQI); + AQI.cam.Action_Cancel_Timer_Enabled = false; // will be deleted next time the loop goes through + } + } + else + { + CancelActionDict.TryRemove(AQI.camname.ToLower(), out ClsTriggerActionQueueItem removedItem); + Log($"Debug: Removed cancel Action '{AQI.TType}' in queue for event '{AQI.Hist.Detections}' for camera '{AQI.camname}', after {(DateTime.Now - AQI.cam.Action_Cancel_Start_Time).TotalSeconds.Round()} seconds", this.CurSrv, AQI.cam, AQI.CurImg); + + } + + } + catch (Exception ex) + { + + Log($"Error: " + ex.ToString(), this.CurSrv, AQI.cam, AQI.CurImg); + } + } + + } + + await Task.Delay(1000); //Only check every second + } + + } + + public async Task RunTriggers(ClsTriggerActionQueueItem AQI) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + bool res = false; + + try + { + AQI.QueueWaitMS = Convert.ToInt64((DateTime.Now - AQI.AddedTime).TotalMilliseconds); + this.QTimeCalc.AddToCalc(AQI.QueueWaitMS); + bool WasSkipped = false; + + Stopwatch sw = Stopwatch.StartNew(); + + this.Count = this.TriggerActionQueue.Count; + + this.QCountCalc.AddToCalc(AQI.QueueCount); + + Global.SendMessage(MessageType.UpdateStatus); + + if (AQI.TType == TriggerType.TelegramText) + { + if (AppSettings.Settings.telegram_chatids.Count > 0 && AppSettings.Settings.telegram_token != "" && !(AQI.cam.Paused && AQI.cam.PauseTelegram)) + res = await this.TelegramText(AQI); + else + WasSkipped = true; + } + else if (AQI.TType == TriggerType.TelegramImageUpload) + { + if (AppSettings.Settings.telegram_chatids.Count > 0 && AppSettings.Settings.telegram_token != "" && !(AQI.cam.Paused && AQI.cam.PauseTelegram)) + res = await this.TelegramUpload(AQI); + else + WasSkipped = true; + } + else if (AQI.TType == TriggerType.Pushover) + { + if (!string.IsNullOrEmpty(AppSettings.Settings.pushover_APIKey) && !string.IsNullOrEmpty(AppSettings.Settings.pushover_UserKey) && !(AQI.cam.Paused && AQI.cam.PausePushover)) + res = await this.PushoverUpload(AQI); + else + WasSkipped = true; + } + else + { + res = await this.Trigger(AQI); + } + + + bool HasCancelAction = ((AQI.cam.Action_mqtt_enabled && !AQI.cam.Action_mqtt_payload_cancel.IsEmpty()) || + (AQI.cam.Action_CancelURL_Enabled && AQI.cam.cancel_urls.Length > 0)); + + if (HasCancelAction) + { + if (AQI.TType == TriggerType.Cancel || AQI.Trigger == false) //If this is a CANCEL anyway... + { + //if we already did a cancel, set flag to delete the queued cancel item if exists and log that we are removing from queue + if (AQI.cam.Action_Cancel_Timer_Enabled || this.CancelActionDict.ContainsKey(AQI.camname.ToLower())) + { + AQI.cam.Action_Cancel_Timer_Enabled = false; + if (this.CancelActionDict.ContainsKey(AQI.camname.ToLower())) + this.CancelActionDict.TryRemove(AQI.camname.ToLower(), out ClsTriggerActionQueueItem ignoreme); + + Log($"Debug: Cancel action '{AQI.TType}' has been removed due to event '{AQI.Hist.Detections}' for camera '{AQI.camname}'", this.CurSrv, AQI.cam, AQI.CurImg); + + } + } + else //If this is another TRIGGER... + { + if (this.CancelActionDict.ContainsKey(AQI.camname.ToLower())) + { + //if already in queue, update date + Log($"Debug: EXTENDING cancel action '{AQI.TType}' time due to event '{AQI.Hist.Detections}' for camera '{AQI.camname}', waiting {AppSettings.Settings.ActionCancelSeconds} seconds...", this.CurSrv, AQI.cam, AQI.CurImg); + AQI.cam.Action_Cancel_Start_Time = DateTime.Now; + } + else //add it to the queue + { + Log($"Debug: Cancel action '{AQI.TType}' queued due to event '{AQI.Hist.Detections}' for camera '{AQI.camname}', waiting {AppSettings.Settings.ActionCancelSeconds} seconds...", this.CurSrv, AQI.cam, AQI.CurImg); + AQI.cam.Action_Cancel_Start_Time = DateTime.Now; + AQI.cam.Action_Cancel_Timer_Enabled = true; + AQI.Trigger = false; //set to be a cancel + AQI.TType = TriggerType.Cancel; + this.CancelActionDict.TryAdd(AQI.camname.ToLower(), AQI); + } + } + } + else + { + Log($"Debug: Cancel action '{AQI.TType}' could not be queued because there are no CANCEL actions configured. Event='{AQI.Hist.Detections}' for camera '{AQI.camname}'", this.CurSrv, AQI.cam, AQI.CurImg); + } + + this.Count = this.TriggerActionQueue.Count; + + AQI.ActionTimeMS = sw.ElapsedMilliseconds; + AQI.TotalTimeMS = Convert.ToInt64((DateTime.Now - AQI.AddedTime).TotalMilliseconds); + this.TotalTimeCalc.AddToCalc(AQI.TotalTimeMS); + this.ActionTimeCalc.AddToCalc(AQI.ActionTimeMS); + + if (!WasSkipped) + { + Log($"Debug: Action '{AQI.TType}' done. Succeeded={res}, Trigger={AQI.Trigger}, Queued={AQI.IsQueued}, Queue Count={AQI.QueueCount} (Min={this.QCountCalc.MinS},Max={this.QCountCalc.MaxS},Avg={this.QCountCalc.AvgS}), Total time={AQI.TotalTimeMS}ms (Min={this.TotalTimeCalc.MinS}ms,Max={this.TotalTimeCalc.MaxS}ms,Avg={Convert.ToInt64(this.TotalTimeCalc.AvgS)}ms), Queue time={AQI.QueueWaitMS} (Min={this.QTimeCalc.MinS}ms,Max={this.QTimeCalc.MaxS}ms,Avg={Convert.ToInt64(this.QTimeCalc.AvgS)}ms), Action Time={AQI.ActionTimeMS}ms (Min={this.ActionTimeCalc.MinS}ms,Max={this.ActionTimeCalc.MaxS}ms,Avg={Convert.ToInt64(this.ActionTimeCalc.AvgS)}ms), Image={this.ImgPath}", this.CurSrv, AQI.cam, AQI.CurImg); + } + + Global.SendMessage(MessageType.UpdateStatus); + + } + catch (Exception ex) + { + res = false; + Log("Error: " + ex.Msg(), this.CurSrv, AQI.cam, AQI.CurImg); + } + + return res; + } + + //public bool IsNotInCooldown(Camera cam, out double cooltime) + //{ + // if (cam.CameraGroup.IsEmpty()) + // { + // cooltime = (DateTime.Now - cam.last_trigger_time).TotalSeconds; + // return cooltime >= cam.cooldown_time_seconds; + // } + // else + // { + // ThreadSafe.Datetime last_trigger_time = new ThreadSafe.Datetime(DateTime.MinValue); + + // if (GroupsLastTriggerDict.) + // } + //} + + //trigger actions + public async Task Trigger(ClsTriggerActionQueueItem AQI) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + bool ret = true; + + //mostly for testing when we dont have a current image... + if (AQI.CurImg == null) + { + if (!string.IsNullOrEmpty(AQI.cam.last_image_file_with_detections)) + { + AQI.CurImg = new ClsImageQueueItem(AQI.cam.last_image_file_with_detections, 1); + } + else if (!string.IsNullOrEmpty(AQI.cam.last_image_file)) + { + AQI.CurImg = new ClsImageQueueItem(AQI.cam.last_image_file, 1); + } + else + { + Log($"Error: No image to process?", this.CurSrv, AQI.camname); + return false; + } + } + + try + { + double cooltime = (DateTime.Now - AQI.cam.last_trigger_time).TotalSeconds; + string tmpfile = ""; + + //only trigger if cameras cooldown time since last detection has passed + if (cooltime >= AQI.cam.cooldown_time_seconds) + { + + //merge annotations + if (AQI.cam.Action_image_merge_detections && AQI.Trigger) + { + tmpfile = await this.MergeImageAnnotations(AQI); + + if (!string.Equals(AQI.CurImg.image_path, tmpfile, StringComparison.OrdinalIgnoreCase) && System.IO.File.Exists(tmpfile)) //it wont exist if no detections or failure... + { + AQI.CurImg = new ClsImageQueueItem(tmpfile, 1); + //force the image to load right away to try to avoid BI showing blank images when given a jpg file for the thumbnail + AQI.CurImg.LoadImage(); + } + } + + //copy image + if (AQI.cam.Action_image_copy_enabled && AQI.Trigger) + { + Log($"Debug: Copying image to network folder...", this.CurSrv, AQI.cam, AQI.CurImg); + string newimagepath = ""; + if (!this.CopyImage(AQI, ref newimagepath)) + { + ret = false; + Log($"Warn: -> Warning: Image could not be copied to network folder.", this.CurSrv, AQI.cam, AQI.CurImg); + } + else + { + Log($"Debug: -> Image copied to network folder {newimagepath}", this.CurSrv, AQI.cam, AQI.CurImg); + //set the image path to the new path so all imagename variable works + AQI.CurImg = new ClsImageQueueItem(newimagepath, 1); + //force the image to load right away to try to avoid BI showing blank images when given a jpg file for the thumbnail + AQI.CurImg.LoadImage(); + } + + } + + // Activate BI window + if (AQI.cam.Action_ActivateBlueIrisWindow && AQI.Trigger) + Global.ShowProcessWindow("blueiris.exe", "Blue Iris", Global.ShowWindowEnum.SW_SHOWMAXIMIZED); + + //Play sounds + if (AQI.cam.Action_PlaySounds && AQI.Trigger) + { + double soundcooltime = (DateTime.Now - AQI.cam.last_sound_time).TotalSeconds; + + if (soundcooltime >= AQI.cam.sound_cooldown_time_seconds) + { + try + { + + //Examples: + //Simple sound play: + // C:\BlueIris\sounds\are-you-kidding.wav + // are-you-kidding.wav <-- No need to specify path if in AITOOL, BlueIris folder or Windows Media folder + // are-you-kidding + // C:\Windows\Media\Ring10.wav + // Ring10.wav + //Conditional: + // cat ; catsound.wav + // cat,dog,sheep ; animalsound.wav + // bear ; fuuuuck.wav + //Talk: + // Talk:There is a [Label] outside + // person ; talk:There is a mother f'in person in the driveway + //Combine any with pipe symbols + // Talk:There is a [Label] outside | object1, object2 ; soundfile.wav | object1, object2 ; anotherfile.wav | * ; defaultsound.wav + string snds = AITOOL.ReplaceParams(AQI.cam, AQI.Hist, AQI.CurImg, AQI.cam.Action_Sounds, Global.IPType.Path); + + bool wasplayed = false; + + List prms = snds.SplitStr("|"); + foreach (string prm in prms) + { + if (prm.Contains(";")) + { + //prm0 - object1, object2 + //prm1 - soundfile.wav + List splt = prm.SplitStr(";"); + + ClsRelevantObjectManager rom = new ClsRelevantObjectManager(splt[0], "Sound", AQI.cam); + + if (!AQI.Hist.IsNull() && rom.IsRelevant(AQI.Hist.Predictions(), false, out bool IgnoreImageMask, out bool IgnoreDynamicMask) == ResultType.Relevant) + { + + if (splt[1].StartsWith("talk:", StringComparison.OrdinalIgnoreCase)) + { + string speech = splt[1].GetWord("talk:", ""); + + if (speech.IsNotNull()) + { + //if you would like to change the default voice used, you have to change it in the OLD control panel, not the new Windows 10/11 version? + //Start menu > type 'Control Panel' > Easy of use > Speech Recognition > Advanced speech options > Text to speech tab. + + using var synth = new SpeechSynthesizer(); + + synth.SetOutputToDefaultAudioDevice(); + Log($"Debug: Talking using system default Windows voice '{synth.Voice.Name}': '{speech}'...", this.CurSrv, AQI.cam, AQI.CurImg); + synth.Speak(speech); + wasplayed = true; + } + + } + else + { + string soundfile = Global.FindSoundFile(splt[1].Trim()); + if (soundfile.IsNotNull()) + { + + Log($"Debug: Playing sound: {soundfile}...", this.CurSrv, AQI.cam, AQI.CurImg); + using SoundPlayer sp = new SoundPlayer(soundfile); + sp.PlaySync(); + wasplayed = true; + } + else + { + Log($"Error: Sound file not found: {soundfile}"); + } + + } + } + } + else if (prm.StartsWith("talk:", StringComparison.OrdinalIgnoreCase)) + { + string speech = prm.GetWord("talk:", ""); + + if (speech.IsNotNull()) + { + using var synth = new SpeechSynthesizer(); + + synth.SetOutputToDefaultAudioDevice(); + Log($"Debug: Talking using system default Windows voice '{synth.Voice.Name}': '{speech}'...", this.CurSrv, AQI.cam, AQI.CurImg); + synth.Speak(speech); + wasplayed = true; + } + } + else //assume it is JUST a sound file + { + string soundfile = Global.FindSoundFile(prm); + if (soundfile.IsNotNull()) + { + + Log($"Debug: Playing sound: {soundfile}...", this.CurSrv, AQI.cam, AQI.CurImg); + using SoundPlayer sp = new SoundPlayer(soundfile); + sp.PlaySync(); + wasplayed = true; + } + else + { + Log($"Error: Sound file not found: {prm}"); + } + } + + if (wasplayed && prms.Count > 1) + await Task.Delay(50); //very short wait between sound events + + } + + if (wasplayed) + { + + AQI.cam.last_sound_time = DateTime.Now; + + if (AppSettings.Settings.ActionDelayMS >= 100) //dont show for tiny delays + Log($"Debug: ...Applying 'ActionDelayMS' delay of {AppSettings.Settings.ActionDelayMS}ms."); + + //lets not wait after each sound + //await Task.Delay(AppSettings.Settings.ActionDelayMS); //very short wait between trigger events + + } + else + { + Log($"Debug: No object matched sound to play.", this.CurSrv, AQI.cam, AQI.CurImg); + } + + } + catch (Exception ex) + { + + ret = false; + Log($"Error: while calling sound '{AQI.cam.Action_Sounds}', got: {ex.Msg()}", this.CurSrv, AQI.cam, AQI.CurImg); + } + + } + else + { + Log($" Camera {AQI.camname} is still in SOUND cooldown. Sound was not played. ({soundcooltime} of {AQI.cam.sound_cooldown_time_seconds} seconds - See Cameras 'sound_cooldown_time_seconds' in settings file)", this.CurSrv, AQI.cam, AQI.CurImg); + + } + } + + //run external program + if (AQI.cam.Action_RunProgram && AQI.Trigger) + { + string run = ""; + string param = ""; + try + { + run = AITOOL.ReplaceParams(AQI.cam, AQI.Hist, AQI.CurImg, AQI.cam.Action_RunProgramString, Global.IPType.Path); + param = AITOOL.ReplaceParams(AQI.cam, AQI.Hist, AQI.CurImg, AQI.cam.Action_RunProgramArgsString, Global.IPType.Path); + Log($"Debug: Starting external app - Camera={AQI.camname} run='{run}', param='{param}'", this.CurSrv, AQI.cam, AQI.CurImg); + + Process.Start(run, param); + + if (AppSettings.Settings.ActionDelayMS >= 100) //dont show for tiny delays + Log($"Debug: ...Applying 'ActionDelayMS' delay of {AppSettings.Settings.ActionDelayMS}ms."); + + //await Task.Delay(AppSettings.Settings.ActionDelayMS); //very short wait between trigger events + } + catch (Exception ex) + { + + ret = false; + Log($"Error: while running program '{run}' with params '{param}', got: {ex.Msg()}", this.CurSrv, AQI.cam, AQI.CurImg); + } + } + + //call trigger urls + if (!(AQI.cam.Paused && AQI.cam.PauseURL)) + { + if (AQI.Trigger && AQI.cam.Action_TriggerURL_Enabled && AQI.cam.trigger_urls.Length > 0) + { + //replace url parameters with according values + List urls = new List(); + //call urls + foreach (string url in AQI.cam.trigger_urls) + { + string tmp = AITOOL.ReplaceParams(AQI.cam, AQI.Hist, AQI.CurImg, url, Global.IPType.URL); + urls.Add(tmp); + + } + + bool result = await this.CallTriggerURLs(urls, AQI); + } + else if (!AQI.Trigger && AQI.cam.Action_CancelURL_Enabled && AQI.cam.cancel_urls.Length > 0) + { + //replace url parameters with according values + List urls = new List(); + //call urls + foreach (string url in AQI.cam.cancel_urls) + { + string tmp = AITOOL.ReplaceParams(AQI.cam, AQI.Hist, AQI.CurImg, url, Global.IPType.URL); + urls.Add(tmp); + + } + + bool result = await this.CallTriggerURLs(urls, AQI); + + } + + } + + + if (AQI.cam.Action_mqtt_enabled && !(AQI.cam.Paused && AQI.cam.PauseMQTT)) + { + + //make sure it is a matching object, but call MQTT in any case if it is a canceled event + if (!AQI.Hist.IsNull() && (!AQI.Trigger || AQI.cam.MQTTTriggeringObjects.IsRelevant(AQI.Hist.Predictions(), false, out bool IgnoreImageMask, out bool IgnoreDynamicMask) == ResultType.Relevant)) + { + string topic = ""; + string payload = ""; + + if (AQI.Trigger) + { + topic = AITOOL.ReplaceParams(AQI.cam, AQI.Hist, AQI.CurImg, AQI.cam.Action_mqtt_topic, Global.IPType.URL); + payload = AITOOL.ReplaceParams(AQI.cam, AQI.Hist, AQI.CurImg, AQI.cam.Action_mqtt_payload, Global.IPType.URL); + Log($"Debug: MQTT Trigger event - [SummaryNonEscaped]='{AQI.Hist.Detections}', After replacement Topic='{topic}', Payload='{payload}'"); + } + else + { + topic = AITOOL.ReplaceParams(AQI.cam, AQI.Hist, AQI.CurImg, AQI.cam.Action_mqtt_topic_cancel, Global.IPType.URL); + payload = AITOOL.ReplaceParams(AQI.cam, AQI.Hist, AQI.CurImg, AQI.cam.Action_mqtt_payload_cancel, Global.IPType.URL); + Log($"Debug: MQTT Cancel event - [SummaryNonEscaped]='{AQI.Hist.Detections}', After replacement Topic='{topic}', Payload='{payload}'"); + } + + + List topics = topic.SplitStr("|"); + List payloads = payload.SplitStr("|"); + if (topics.Count == payloads.Count) + { + ClsImageQueueItem ci = null; + + for (int i = 0; i < topics.Count; i++) + { + if (AQI.cam.Action_mqtt_send_image && topics[i].IndexOf("/image", StringComparison.OrdinalIgnoreCase) >= 0) + ci = AQI.CurImg; + else + ci = null; + MqttClientPublishResult pr = await AITOOL.mqttClient.PublishAsync(topics[i], payloads[i], AQI.cam.Action_mqtt_retain_message, ci); + if (pr == null || pr.ReasonCode != MqttClientPublishReasonCode.Success) + ret = false; + + if (AppSettings.Settings.ActionDelayMS >= 100) //dont show for tiny delays + Log($"Debug: ...Applying 'ActionDelayMS' delay of {AppSettings.Settings.ActionDelayMS}ms."); + + await Task.Delay(AppSettings.Settings.ActionDelayMS); //very short wait between trigger events + } + + } + else + { + Log($"Error: You must have an equal number of MQTT topics and payloads. (separated by | pipe symbol). Topics='{topic}', Payloads='{payloads}'"); + ret = false; + } + + } + else + { + Log("Trace: Skipping MQTT call."); + ret = true; //dont return false unless actual error + } + + } + + //upload to pushover + if (AQI.cam.Action_pushover_enabled && AQI.Trigger && !(AQI.cam.Paused && AQI.cam.PausePushover)) + { + + if (!await this.PushoverUpload(AQI)) + { + ret = false; + Log($"Error: -> ERROR sending message or image to Pushover", this.CurSrv, AQI.cam, AQI.CurImg); + } + else + { + Log($"Debug: -> Sent message or image to Pushover.", this.CurSrv, AQI.cam, AQI.CurImg); + } + + if (AppSettings.Settings.ActionDelayMS >= 100) //dont show for tiny delays + Log($"Debug: ...Applying 'ActionDelayMS' delay of {AppSettings.Settings.ActionDelayMS}ms."); + + await Task.Delay(AppSettings.Settings.ActionDelayMS); //very short wait between trigger events + + } + + + //upload to telegram + if (AQI.cam.telegram_enabled && AQI.Trigger && !(AQI.cam.Paused && AQI.cam.PauseTelegram)) + { + + if (!await this.TelegramUpload(AQI)) + { + ret = false; + Log($"Error: -> ERROR sending image to Telegram.", this.CurSrv, AQI.cam, AQI.CurImg); + } + else + { + Log($"Debug: -> Sent image to Telegram.", this.CurSrv, AQI.cam, AQI.CurImg); + } + + if (AppSettings.Settings.ActionDelayMS >= 100) //dont show for tiny delays + Log($"Debug: ...Applying 'ActionDelayMS' delay of {AppSettings.Settings.ActionDelayMS}ms."); + + await Task.Delay(AppSettings.Settings.ActionDelayMS); //very short wait between trigger events + } + + if (AQI.Trigger) + { + AQI.cam.last_trigger_time = DateTime.Now; //reset cooldown time every time an image contains something, even if no trigger was called (still in cooldown time) + Log($"Debug: {AQI.camname} last triggered at {AQI.cam.last_trigger_time}.", this.CurSrv, AQI.cam, AQI.CurImg); + Global.UpdateLabel($"{AQI.camname} last triggered at {AQI.cam.last_trigger_time}.", "lbl_info"); + } + + } + else + { + //log that nothing was done + Log($" Camera {AQI.camname} is still in cooldown. ({cooltime.Round()} of {AQI.cam.cooldown_time_seconds} seconds - See Cameras 'cooldown_time_seconds' in settings file)", this.CurSrv, AQI.cam, AQI.CurImg); + } + + + if (AQI.cam.Action_image_merge_detections && AQI.Trigger && !string.IsNullOrEmpty(tmpfile) && tmpfile.IndexOf(Global.GetTempFolder(), StringComparison.OrdinalIgnoreCase) >= 0 && System.IO.File.Exists(tmpfile)) + { + Global.SafeFileDeleteAsync(tmpfile, "TriggerActionQueue"); + } + + + } + catch (Exception ex) + { + + Log($"Error: " + ex.Msg(), this.CurSrv, AQI.cam, AQI.CurImg); + } + + + return ret; + + } + + + public async Task MergeImageAnnotations(ClsTriggerActionQueueItem AQI) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + int countr = 0; + string detections = ""; + string lasttext = ""; + string lastposition = ""; + string OutputImageFile = ""; + + try + { + Log($"Debug: Merging image annotations: " + AQI.CurImg.image_path, "", "", AQI.CurImg); + + if (AQI.CurImg.IsValid()) + { + AQI.cam.UpdateImageResolutions(AQI.CurImg); + + Stopwatch sw = Stopwatch.StartNew(); + + //using MemoryStream ms = AQI.CurImg.ToStream(); + + using (Bitmap img = AQI.CurImg.ToBitmap()) + { + using (Graphics g = Graphics.FromImage(img)) + { + if (AQI.Hist != null && !string.IsNullOrEmpty(AQI.Hist.PredictionsJSON)) + { + List predictions = AQI.Hist.Predictions(); + + for (int i = predictions.Count - 1; i >= 0; i--) + { + ClsPrediction pred = predictions[i]; + + if (AITOOL.DrawAnnotation(g, + pred, + img.Width, + img.Height)) + { + countr++; + } + + //bool Merge = false; + + //if (AppSettings.Settings.HistoryOnlyDisplayRelevantObjects && pred.Result == ResultType.Relevant) + // Merge = true; + //else if (!AppSettings.Settings.HistoryOnlyDisplayRelevantObjects) + // Merge = true; + + //if (Merge) + //{ + // if (pred.Result == ResultType.Relevant) + // { + // color = System.Drawing.Color.FromArgb(AppSettings.Settings.RectRelevantColorAlpha, AppSettings.Settings.RectRelevantColor); + // } + // else if (pred.Result == ResultType.DynamicMasked || pred.Result == ResultType.ImageMasked || pred.Result == ResultType.StaticMasked) + // { + // color = System.Drawing.Color.FromArgb(AppSettings.Settings.RectMaskedColorAlpha, AppSettings.Settings.RectMaskedColor); + // } + // else + // { + // color = System.Drawing.Color.FromArgb(AppSettings.Settings.RectIrrelevantColorAlpha, AppSettings.Settings.RectIrrelevantColor); + // } + + // double xmin = pred.XMin; + // double ymin = pred.YMin; + // double xmax = pred.XMax; + // double ymax = pred.YMax; + + // System.Drawing.Rectangle rect = pred.GetRectangle(); //new System.Drawing.Rectangle(xmin.ToInt(), ymin.ToInt(), (xmax - xmin).ToInt(), (ymax - ymin).ToInt()); + + // using (Pen pen = new Pen(color, AppSettings.Settings.RectBorderWidth)) + // { + // g.DrawRectangle(pen, rect); //draw rectangle + // } + + // //we need this since people can change the border width in the json file + // double halfbrd = AppSettings.Settings.RectBorderWidth / 2; + + // System.Drawing.SizeF TextSize = g.MeasureString(lasttext, new Font(AppSettings.Settings.RectDetectionTextFont, AppSettings.Settings.RectDetectionTextSize)); //finds size of text to draw the background rectangle + + // double x = xmin - halfbrd; + // double y = ymax + halfbrd; + + + // //adjust the x / width label so it doesnt go off screen + // double EndX = x + TextSize.Width; + // if (EndX > xmax) + // { + // //int diffx = x - sclxmax; + // x = xmax - TextSize.Width + halfbrd; + // } + + // if (x < xmin) + // x = xmin; + + // if (x < 0) + // x = 0; + + // //adjust the y / height label so it doesnt go off screen + // double EndY = y + TextSize.Height; + // if (EndY > ymax) + // { + // //float diffy = EndY - sclymax; + // y = ymax - TextSize.Height - halfbrd; + // } + + + // if (y < 0) + // y = 0; + + // //object name text below rectangle + // rect = new System.Drawing.Rectangle(x.ToInt(), + // y.ToInt(), + // img.Width, + // img.Height); //sets bounding box for drawn text + + // Brush brush = new SolidBrush(color); //sets background rectangle color + // if (AppSettings.Settings.RectDetectionTextBackColor != System.Drawing.Color.Gainsboro) + // brush = new SolidBrush(AppSettings.Settings.RectDetectionTextBackColor); + + // Brush forecolor = Brushes.Black; + // if (AppSettings.Settings.RectDetectionTextForeColor != System.Drawing.Color.Gainsboro) + // forecolor = new SolidBrush(AppSettings.Settings.RectDetectionTextForeColor); + + // lasttext = pred.ToString(); + + // g.FillRectangle(brush, + // x.ToInt(), + // y.ToInt(), + // TextSize.Width, + // TextSize.Height); //draw grey background rectangle for detection text + + // g.DrawString(lasttext, + // new Font(AppSettings.Settings.RectDetectionTextFont, + // AppSettings.Settings.RectDetectionTextSize), + // forecolor, + // rect); //draw detection text + + // g.Flush(); + + // countr++; + //} + + + } + + } + //else + //{ + // //Use the old way -this code really doesnt need to be here but leaving just to make sure + // detections = AQI.cam.last_detections_summary; + // if (string.IsNullOrEmpty(detections)) + // detections = ""; + + // string label = detections.GetWord("", ":"); + + // if (label.IndexOf("irrelevant", StringComparison.OrdinalIgnoreCase) >= 0 || label.IndexOf("confidence", StringComparison.OrdinalIgnoreCase) >= 0 || label.IndexOf("masked", StringComparison.OrdinalIgnoreCase) >= 0 || label.IndexOf("errors", StringComparison.OrdinalIgnoreCase) >= 0) + // { + // detections = detections.SplitStr(":")[1]; //removes the "1x masked, 3x irrelevant:" before the actual detection, otherwise this would be displayed in the detection tags + + // if (label.IndexOf("masked", StringComparison.OrdinalIgnoreCase) >= 0) + // { + // color = System.Drawing.Color.FromArgb(AppSettings.Settings.RectMaskedColorAlpha, AppSettings.Settings.RectMaskedColor); + // } + // else + // { + // color = System.Drawing.Color.FromArgb(AppSettings.Settings.RectIrrelevantColorAlpha, AppSettings.Settings.RectIrrelevantColor); + // } + // } + // else + // { + // color = System.Drawing.Color.FromArgb(AppSettings.Settings.RectRelevantColorAlpha, AppSettings.Settings.RectRelevantColor); + // } + + // //List detectlist = Global.Split(detections, "|;"); + // countr = AQI.cam.last_detections.Count; + + // //display a rectangle around each relevant object + + + // for (int i = 0; i < countr; i++) + // { + // //({ Math.Round((user.confidence * 100), 2).ToString() }%) + // lasttext = $"{AQI.cam.last_detections[i]} {String.Format(AppSettings.Settings.DisplayPercentageFormat, AQI.cam.last_confidences[i])}"; + // lastposition = AQI.cam.last_positions[i]; //load 'xmin,ymin,xmax,ymax' from third column into a string + + // //store xmin, ymin, xmax, ymax in separate variables + // Int32.TryParse(lastposition.Split(',')[0], out int xmin); + // Int32.TryParse(lastposition.Split(',')[1], out int ymin); + // Int32.TryParse(lastposition.Split(',')[2], out int xmax); + // Int32.TryParse(lastposition.Split(',')[3], out int ymax); + + // xmin = xmin + AQI.cam.XOffset; + // ymin = ymin + AQI.cam.YOffset; + + // System.Drawing.Rectangle rect = new System.Drawing.Rectangle(xmin, ymin, xmax - xmin, ymax - ymin); + + + // using (Pen pen = new Pen(color, AppSettings.Settings.RectBorderWidth)) + // { + // g.DrawRectangle(pen, rect); //draw rectangle + // } + + // //we need this since people can change the border width in the json file + // int halfbrd = AppSettings.Settings.RectBorderWidth / 2; + + // //object name text below rectangle + // rect = new System.Drawing.Rectangle(xmin - halfbrd, ymax + halfbrd, img.Width, img.Height); //sets bounding box for drawn text + + + // Brush brush = new SolidBrush(color); //sets background rectangle color + // if (AppSettings.Settings.RectDetectionTextBackColor != System.Drawing.Color.Gainsboro) + // brush = new SolidBrush(AppSettings.Settings.RectDetectionTextBackColor); + + // Brush forecolor = Brushes.Black; + // if (AppSettings.Settings.RectDetectionTextForeColor != System.Drawing.Color.Gainsboro) + // forecolor = new SolidBrush(AppSettings.Settings.RectDetectionTextForeColor); + + // System.Drawing.SizeF size = g.MeasureString(lasttext, new Font(AppSettings.Settings.RectDetectionTextFont, AppSettings.Settings.RectDetectionTextSize)); //finds size of text to draw the background rectangle + // g.FillRectangle(brush, xmin - halfbrd, ymax + halfbrd, size.Width, size.Height); //draw grey background rectangle for detection text + // g.DrawString(lasttext, new Font(AppSettings.Settings.RectDetectionTextFont, AppSettings.Settings.RectDetectionTextSize), forecolor, rect); //draw detection text + + // g.Flush(); + + // //Log($"...{i}, LastText='{lasttext}' - LastPosition='{lastposition}'"); + // } + + //} + + + if (countr > 0) + { + + GraphicsState gs = g.Save(); + + ImageCodecInfo jpgEncoder = this.GetEncoder(ImageFormat.Jpeg); + + // Create an Encoder object based on the GUID + // for the Quality parameter category. + System.Drawing.Imaging.Encoder myEncoder = System.Drawing.Imaging.Encoder.Quality; + + // Create an EncoderParameters object. + // An EncoderParameters object has an array of EncoderParameter + // objects. In this case, there is only one + // EncoderParameter object in the array. + EncoderParameters myEncoderParameters = new EncoderParameters(1); + + EncoderParameter myEncoderParameter = new EncoderParameter(myEncoder, AQI.cam.Action_image_merge_jpegquality); //100=least compression, largest file size, best quality + myEncoderParameters.Param[0] = myEncoderParameter; + + Global.WaitFileAccessResult result = new Global.WaitFileAccessResult(); + result.Success = true; //assume true + string tmpfolder = Global.GetTempFolder(); //Path.GetTempPath(); + if (AQI.cam.Action_image_merge_detections_makecopy && !(AQI.CurImg.image_path.IndexOf(tmpfolder, StringComparison.OrdinalIgnoreCase) >= 0)) + OutputImageFile = Path.Combine(tmpfolder, Path.GetFileName(AQI.CurImg.image_path)); + else + OutputImageFile = AQI.CurImg.image_path; + + if (System.IO.File.Exists(OutputImageFile)) + { + result = await Global.WaitForFileAccessAsync(OutputImageFile, FileAccess.ReadWrite, FileShare.None); + } + + if (result.Success) + { + img.Save(OutputImageFile, jpgEncoder, myEncoderParameters); + Log($"Debug: Merged {countr} detections in {sw.ElapsedMilliseconds}ms into image {OutputImageFile}", this.CurSrv, AQI.cam, AQI.CurImg); + } + else + { + Log($"Error: Could not gain access to write merged file {OutputImageFile}", this.CurSrv, AQI.cam, AQI.CurImg); + } + + } + else + { + Log($"Debug: No detections to merge. Time={sw.ElapsedMilliseconds}ms, {OutputImageFile}", this.CurSrv, AQI.cam, AQI.CurImg); + + } + + } + + } + + } + else + { + Log($"Error: could not find last image with detections: " + AQI.CurImg.image_path, this.CurSrv, AQI.cam, AQI.CurImg); + } + } + catch (Exception ex) + { + + Log($"Error: Detections='{detections}', LastText='{lasttext}', LastPostions='{lastposition}' - " + ex.Msg(), this.CurSrv, AQI.cam, AQI.CurImg); + } + + return OutputImageFile; + } + + private ImageCodecInfo GetEncoder(ImageFormat format) + { + ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders(); + foreach (ImageCodecInfo codec in codecs) + { + if (codec.FormatID == format.Guid) + { + return codec; + } + } + return null; + } + + public bool CopyImage(ClsTriggerActionQueueItem AQI, ref string dest_path) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + bool ret = false; + + try + { + if (string.IsNullOrWhiteSpace(AQI.cam.Action_network_folder) || string.IsNullOrWhiteSpace(AQI.cam.Action_network_folder_filename)) + { + AITOOL.Log($"Error: Camera settings > 'Copy alert images to folder' path or 'Filename' is empty.: {AQI.cam.Action_network_folder} -- {AQI.cam.Action_network_folder_filename}"); + return false; + } + + string netfld = AITOOL.ReplaceParams(AQI.cam, AQI.Hist, AQI.CurImg, AQI.cam.Action_network_folder, Global.IPType.Path); + + if (string.IsNullOrWhiteSpace(netfld) || !netfld.Contains("\\")) + { + AITOOL.Log($"Error: Camera settings > Copy alert images to folder is not a valid path: {netfld}"); + return false; + } + + if (!Directory.Exists(netfld)) + Directory.CreateDirectory(netfld); + + //check to see if we need to clean the network folder out yet: + //It will only check once a day, and will only clean if it has been over cam.Action_network_folder_purge_older_than_days (defaults to 30 days) + AQI.cam.CleanActionNetworkFolder(); + + string ext = Path.GetExtension(AQI.CurImg.image_path); + string filename = AITOOL.ReplaceParams(AQI.cam, AQI.Hist, AQI.CurImg, AQI.cam.Action_network_folder_filename, Global.IPType.Path).Trim().Replace(ext, "") + ext; + + dest_path = System.IO.Path.Combine(netfld, filename); + + Log($"Debug: File copying from {AQI.CurImg.image_path} to {dest_path}", this.CurSrv, AQI.cam, AQI.CurImg); + + + if (AQI.CurImg.CopyFileTo(dest_path)) + { + ret = true; + } + } + catch (Exception ex) + { + ret = false; + Log($"ERROR: Could not copy image {AQI.CurImg.image_path} to network path {dest_path}: {ex.Msg()}", this.CurSrv, AQI.cam, AQI.CurImg); + } + + return ret; + + } + //call trigger urls + public async Task CallTriggerURLs(List trigger_urls, ClsTriggerActionQueueItem AQI) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + bool ret = true; + string url = ""; + string type = "trigger"; + if (!AQI.Trigger) + type = "cancel"; + + if (AITOOL.triggerHttpClient == null) + { + AITOOL.triggerHttpClient = new ThrottledHttpClient(TimeSpan.FromSeconds(AppSettings.Settings.HTTPClientLocalTimeoutSeconds)); //new System.Net.Http.HttpClient(); + AITOOL.triggerHttpClient.Timeout = TimeSpan.FromSeconds(AppSettings.Settings.HTTPClientLocalTimeoutSeconds); + + //lets give it a user agent unique to this machine and product version... + AssemblyName ASN = Assembly.GetExecutingAssembly().GetName(); + string Version = ASN.Version.ToString(); + ProductInfoHeaderValue PIH = new ProductInfoHeaderValue("AI_Tool_MANBAT_" + Global.GetMacAddress(), Version); + AITOOL.triggerHttpClient.DefaultRequestHeaders.UserAgent.Add(PIH); + } + + + for (int i = 0; i < trigger_urls.Count; i++) + { + url = trigger_urls[i]; + + if (url.IsStringBefore(";", ":")) + { + //prm0 - object1, object2 + //prm1 - soundfile.wav or URL + string objects = url.GetWord("", ";"); + url = url.GetWord(";", ""); + //make sure it is a matching object + //if (AITOOL.ArePredictionObjectsRelevant(objects, "TriggerURL", AQI.Hist.Predictions(), false) != ResultType.Relevant) ; + + ClsRelevantObjectManager rom = new ClsRelevantObjectManager(objects, "TriggerURL", AQI.cam); + + if (!AQI.Hist.IsNull() && rom.IsRelevant(AQI.Hist.Predictions(), false, out bool IgnoreImageMask, out bool IgnoreDynamicMask) != ResultType.Relevant) + continue; + + } + else + { + //Log($"Debug: No conditional objects found in URL: {url}"); + } + + Stopwatch sw = Stopwatch.StartNew(); + try + { + Log($"Debug: -> {type} URL is being triggered... {url}"); + + HttpResponseMessage response = await triggerHttpClient.GetAsync(url); + + //If we get a null response it means the host+port was already in use. In that case, we are just going to skip this URL call and call it good. + if (response == null) + { + Log($"Debug: -> {type} URL called in {sw.ElapsedMilliseconds}ms: {url}, response: 'URL was in use, skipping. Turn off 'queue actions' in camera settings to avoid this happening.'"); + } + else + { + if (response.IsSuccessStatusCode) + { + string content = await response.Content.ReadAsStringAsync(); + Log($"Debug: -> {type} URL called in {sw.ElapsedMilliseconds}ms: {url}, response: '{content.CleanString().Truncate()}'"); + } + else + { + ret = false; + Log($"ERROR: {type}: In {sw.ElapsedMilliseconds}ms, got StatusCode='{response.StatusCode}', Reason='{response.ReasonPhrase}: Could not {type} URL '{url}', please check if correct"); + } + } + + } + catch (Exception ex) + { + ret = false; + Log($"ERROR: {type}: In {sw.ElapsedMilliseconds}ms, Could not {type} Error='{ex.Msg()}', URL='{url}'"); + } + + if (AppSettings.Settings.ActionDelayMS >= 100) //dont show for tiny delays + Log($"Debug: ...Applying 'ActionDelayMS' delay of {AppSettings.Settings.ActionDelayMS}ms."); + + await Task.Delay(AppSettings.Settings.ActionDelayMS); //very short wait between trigger events + } + + + return ret; + + + } + + public async Task PushoverUpload(ClsTriggerActionQueueItem AQI) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + bool ret = true; + + if (!string.IsNullOrEmpty(AppSettings.Settings.pushover_APIKey) && !string.IsNullOrEmpty(AppSettings.Settings.pushover_UserKey)) + { + try + { + //make sure it is a matching object + if (!AQI.Hist.IsNull() && AQI.cam.PushoverTriggeringObjects.IsRelevant(AQI.Hist.Predictions(), false, out bool IgnoreImageMask, out bool IgnoreDynamicMask) != ResultType.Relevant) + return true; + + if (AppSettings.Settings.pushover_cooldown_seconds < 2) + AppSettings.Settings.pushover_cooldown_seconds = 2; //force to be at least 2 seconds + + DateTime now = DateTime.Now; + + if (this.PushoverRetryTime == DateTime.MinValue || now >= this.PushoverRetryTime) + { + double cooltime = Math.Round((now - this.last_Pushover_trigger_time).TotalSeconds, 4); + if (cooltime >= AppSettings.Settings.pushover_cooldown_seconds) + { + string title = ""; + string message = ""; + string device = ""; + + if (AQI.Trigger) + { + + if (!string.IsNullOrEmpty(AQI.Text)) + { + if (AQI.Text.IndexOf("error", StringComparison.OrdinalIgnoreCase) >= 0) + title = "Error"; + else + title = AITOOL.ReplaceParams(AQI.cam, AQI.Hist, AQI.CurImg, AQI.cam.Action_pushover_title, Global.IPType.Path); + + message = AITOOL.ReplaceParams(AQI.cam, AQI.Hist, AQI.CurImg, AQI.Text, Global.IPType.Path); + + } + else + { + title = AITOOL.ReplaceParams(AQI.cam, AQI.Hist, AQI.CurImg, AQI.cam.Action_pushover_title, Global.IPType.Path); + message = AITOOL.ReplaceParams(AQI.cam, AQI.Hist, AQI.CurImg, AQI.cam.Action_pushover_message, Global.IPType.Path); + } + + device = AITOOL.ReplaceParams(AQI.cam, AQI.Hist, AQI.CurImg, AQI.cam.Action_pushover_device, Global.IPType.Path); + } + else //TODO: Add cancel if requested + { + title = AITOOL.ReplaceParams(AQI.cam, AQI.Hist, AQI.CurImg, AQI.cam.Action_pushover_title, Global.IPType.Path); + message = AITOOL.ReplaceParams(AQI.cam, AQI.Hist, AQI.CurImg, AQI.cam.Action_pushover_message, Global.IPType.Path); + device = AITOOL.ReplaceParams(AQI.cam, AQI.Hist, AQI.CurImg, AQI.cam.Action_pushover_device, Global.IPType.Path); + } + + + List titles = title.SplitStr("|"); + List messages = message.SplitStr("|"); + List devices = device.SplitStr("|"); + List sounds = AQI.cam.Action_pushover_Sound.SplitStr("|"); + List priorities = AQI.cam.Action_pushover_Priority.SplitStr("|"); + List times = AQI.cam.Action_pushover_active_time_range.SplitStr("|"); + + for (int t = 0; t < times.Count; t++) + { + string time = times.GetStrAtIndex(t); + + if (Global.IsTimeBetween(now, time)) + { + + if (AITOOL.pushoverClient == null) + AITOOL.pushoverClient = new NPushover.Pushover(AppSettings.Settings.pushover_APIKey); //new PushoverClient.Pushover(, AppSettings.Settings.pushover_UserKey); + + string imginfo = ""; + if (AQI.CurImg != null && AQI.CurImg.IsValid()) + { + imginfo = $"Attached Image: {Path.GetFileName(AQI.CurImg.image_path)}"; + } + + PushoverUserResponse response = null; + + Stopwatch sw = Stopwatch.StartNew(); + + try + { + string pushtitle = titles.GetStrAtIndex(t); + string pushmessage = messages.GetStrAtIndex(t); + string pushsound = sounds.GetStrAtIndex(t); + string pushdevice = devices.GetStrAtIndex(t); + + if (times.Count != priorities.Count) + Log($"Warn: You should have the same number of Pushover priorities and times specified."); + + NPushover.RequestObjects.Priority pri = (NPushover.RequestObjects.Priority)Enum.Parse(typeof(NPushover.RequestObjects.Priority), priorities.GetStrAtIndex(t)); + + //fix a bug where pushover expire was set to hours rather than seconds + if (AQI.cam.Action_pushover_expire_seconds >= TimeSpan.FromHours(24).TotalSeconds) + AQI.cam.Action_pushover_expire_seconds = 10800; //3 hours + + NPushover.RequestObjects.Message msg = new NPushover.RequestObjects.Message() + { + Title = pushtitle, + Body = pushmessage, + Timestamp = AQI.CurImg != null ? AQI.CurImg.TimeCreated : DateTime.Now, + Priority = pri, + Sound = pushsound, + + RetryOptions = pri == Priority.Emergency ? new RetryOptions + { + RetryEvery = TimeSpan.FromSeconds(AQI.cam.Action_pushover_retry_seconds), + RetryPeriod = TimeSpan.FromSeconds(AQI.cam.Action_pushover_expire_seconds), + CallBackUrl = !string.IsNullOrEmpty(AQI.cam.Action_pushover_retrycallback_url) ? new Uri(AQI.cam.Action_pushover_retrycallback_url) : null, + } : null, + SupplementaryUrl = !string.IsNullOrEmpty(AQI.cam.Action_pushover_SupplementaryUrl) ? new SupplementaryURL { Uri = new Uri(AQI.cam.Action_pushover_SupplementaryUrl), Title = "42" } : null, + }; + + sw.Restart(); + + List userkeys = AppSettings.Settings.pushover_UserKey.SplitStr("|,;"); + foreach (string userkey in userkeys) + { + Log($"Debug: Sending pushover message '{pushmessage}', priority '{pri.ToString()}', sound '{pushsound}' to user '{userkey}' {imginfo}..."); + response = await AITOOL.pushoverClient.SendPushoverMessageAsync(msg, userkey, pushdevice, AQI.CurImg); + await Task.Delay(AppSettings.Settings.loop_delay_ms); + } + this.last_Pushover_trigger_time = now; + sw.Stop(); + } + catch (Exception ex) + { + + sw.Stop(); + ret = false; + Log($"Error: Pushover: After {sw.ElapsedMilliseconds}ms, got: " + ex.Msg(), this.CurSrv, AQI.cam, AQI.CurImg); + } + + if (response != null) + { + string rateinfo = ""; + if (response.RateLimitInfo != null) + { + rateinfo = $"(Monthly Limit={response.RateLimitInfo.Limit}, Remaining={response.RateLimitInfo.Remaining}, ResetDate={response.RateLimitInfo.Reset})"; + } + + if (response.IsOk) + { + ret = true; + Log($"Debug: ...Pushover success in {sw.ElapsedMilliseconds}ms {rateinfo}"); + } + else + { + string errs = ""; + if (response.HasErrors) + errs = string.Join(";", response.Errors); + ret = false; + Log($"Error: Pushover response code={response.Status} in {sw.ElapsedMilliseconds}ms, Errs='{errs}' {rateinfo}"); + } + } + else + { + ret = false; + Log($"Error: Pushover failed to return a response in {sw.ElapsedMilliseconds}ms?", this.CurSrv, AQI.cam, AQI.CurImg); + } + + if (!ret) + this.PushoverRetryTime = DateTime.Now.AddSeconds(AppSettings.Settings.Pushover_RetryAfterFailSeconds); + else + this.PushoverRetryTime = DateTime.MinValue; + + + + } + else + { + Log($"Debug: Skipping pushover because time is not between {time}"); + } + + } + + + } + else + { + //log that nothing was done + Log($"Debug: Still in PUSHOVER cooldown. No image will be uploaded to Pushover. ({cooltime} of {AppSettings.Settings.pushover_cooldown_seconds} seconds - See 'pushover_cooldown_seconds' in settings file)", this.CurSrv, AQI.cam, AQI.CurImg); + + } + } + else + { + Log($"Debug: Waiting {Math.Round((this.PushoverRetryTime - DateTime.Now).TotalSeconds, 1)} seconds ({this.PushoverRetryTime}) to retry PUSHOVER connection. This is due to a previous pushover send error.", this.CurSrv, AQI.cam, AQI.CurImg); + } + + + } + catch (Exception ex) + { + + ret = false; + Log($"Error: {ex.Msg()}", this.CurSrv, AQI.cam, AQI.CurImg); + } + } + else + { + ret = false; + Log("Error: Pushover API key or User Key not set."); + } + + + return ret; + } + //send image to Telegram + public async Task TelegramUpload(ClsTriggerActionQueueItem AQI) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + bool ret = true; + string lastchatid = ""; + + if ((!string.IsNullOrWhiteSpace(AQI.cam.telegram_chatid) || AppSettings.Settings.telegram_chatids.Count > 0) && AppSettings.Settings.telegram_token != "") + { + //telegram upload sometimes fails + Stopwatch sw = Stopwatch.StartNew(); + try + { + + if (AppSettings.Settings.telegram_cooldown_seconds < 2) + AppSettings.Settings.telegram_cooldown_seconds = 2; //force to be at least 2 seconds + + string Caption = ""; + + if (!string.IsNullOrEmpty(AQI.Text)) + Caption = AITOOL.ReplaceParams(AQI.cam, AQI.Hist, AQI.CurImg, AQI.Text, Global.IPType.Path); + else + Caption = AITOOL.ReplaceParams(AQI.cam, AQI.Hist, AQI.CurImg, AQI.cam.telegram_caption, Global.IPType.Path); + + //make sure it is a matching object + //if (AITOOL.ArePredictionObjectsRelevant(AQI.cam.telegram_triggering_objects, "Telegram", AQI.Hist.Predictions(), false) != ResultType.Relevant) + if (!AQI.Hist.IsNull()) + { + if (AQI.cam.TelegramTriggeringObjects.IsRelevant(AQI.Hist.Predictions(), false, out bool IgnoreImageMask, out bool IgnoreDynamicMask) != ResultType.Relevant) + { + AITOOL.Log("Debug: No relevant objects, skipping TelegramUpload."); + return true; + } + } + else + { + AITOOL.Log("Warn: Hist is null?"); + } + + + DateTime now = DateTime.Now; + + if (this.TelegramRetryTime == DateTime.MinValue || now >= this.TelegramRetryTime) + { + double cooltime = Math.Round((now - this.last_telegram_trigger_time).TotalSeconds, 4); + if (cooltime >= AppSettings.Settings.telegram_cooldown_seconds) + { + //in order to avoid hitting our limits when sending out mass notifications, consider spreading them over longer intervals, e.g. 8-12 hours. The API will not allow bulk notifications to more than ~30 users per second, if you go over that, you'll start getting 429 errors. + + if (Global.IsTimeBetween(now, AQI.cam.telegram_active_time_range)) + { + string chatid = ""; + bool overrideid = (!string.IsNullOrWhiteSpace(AQI.cam.telegram_chatid)); + if (overrideid) + chatid = AQI.cam.telegram_chatid.Trim(); + else + chatid = AppSettings.Settings.telegram_chatids[0]; + + //upload image to Telegram servers and send to first chat + Log($"Debug: uploading image to chat \"{chatid.ReplaceChars('*')}\"", this.CurSrv, AQI.cam, AQI.CurImg); + lastchatid = chatid; + + Telegram.Bot.Types.Message message = await AITOOL.Telegram.SendPhotoAsync(chatid, AQI.CurImg.ToMemStream(), "", AQI.CurImg.image_path, Caption); + + string file_id = message.Photo[0].FileId; //get file_id of uploaded image + + if (!overrideid) + { + //share uploaded image with all remaining telegram chats (if multiple chat_ids given) using file_id + foreach (string curchatid in AppSettings.Settings.telegram_chatids.Skip(1)) + { + Log($"Debug: uploading image to chat \"{curchatid.ReplaceChars('*')}\"...", this.CurSrv, AQI.cam, AQI.CurImg); + lastchatid = curchatid; + message = await AITOOL.Telegram.SendPhotoAsync(curchatid, null, file_id, "", Caption); + } + } + ret = message != null; + + this.last_telegram_trigger_time = DateTime.Now; + this.TelegramRetryTime = DateTime.MinValue; + + if (AQI.IsQueued) + { + //add a minimum delay if we are in a queue to prevent minimum cooldown error + Log($"Debug: Waiting {AppSettings.Settings.telegram_cooldown_seconds} seconds (telegram_cooldown_seconds)...", this.CurSrv, AQI.cam, AQI.CurImg); + await Task.Delay(TimeSpan.FromSeconds(AppSettings.Settings.telegram_cooldown_seconds)); + } + + } + else + { + Log($"Debug: Skipping Telegram because time is not between {AQI.cam.telegram_active_time_range}"); + } + } + else + { + //log that nothing was done + Log($"Debug: Still in TELEGRAM cooldown. No image will be uploaded to Telegram. ({cooltime} of {AppSettings.Settings.telegram_cooldown_seconds} seconds - See 'telegram_cooldown_seconds' in settings file)", this.CurSrv, AQI.cam, AQI.CurImg); + + } + + } + else + { + Log($"Debug: Waiting {Math.Round((this.TelegramRetryTime - DateTime.Now).TotalSeconds, 1)} seconds ({this.TelegramRetryTime}) to retry TELEGRAM connection. This is due to a previous telegram send error.", this.CurSrv, AQI.cam, AQI.CurImg); + } + + + } + catch (ApiRequestException ex) //current version only gives webexception NOT this exception! https://github.com/TelegramBots/Telegram.Bot/issues/891 + { + bool se = AppSettings.Settings.send_telegram_errors; + AppSettings.Settings.send_telegram_errors = false; + Log($"ERROR: Could not upload image {AQI.CurImg.image_path} with chatid '{lastchatid.ReplaceChars('*')}' to Telegram: {ex.Msg()}", this.CurSrv, AQI.cam, AQI.CurImg); + + if (!ex.Parameters.IsNull() && !ex.Parameters.RetryAfter.IsNull()) + { + this.TelegramRetryTime = DateTime.Now.AddSeconds(Convert.ToDouble(ex.Parameters.RetryAfter)); + Log($"...BOT API returned 'RetryAfter' value '{ex.Parameters.RetryAfter} seconds', so not retrying until {this.TelegramRetryTime}", this.CurSrv, AQI.cam, AQI.CurImg); + } + + AppSettings.Settings.send_telegram_errors = se; + //store image that caused an error in ./errors/ + if (!Directory.Exists("./errors/")) //if folder does not exist, create the folder + { + //create folder + DirectoryInfo di = Directory.CreateDirectory("./errors"); + Log($"./errors/" + " dir created."); + } + //save error image + using (var image = await SixLabors.ImageSharp.Image.LoadAsync(AQI.CurImg.image_path)) + { + await image.SaveAsync($"./errors/" + "TELEGRAM-ERROR-" + Path.GetFileName(AQI.CurImg.image_path) + ".jpg"); + } + Global.UpdateLabel($"Can't upload error message to Telegram!", "lbl_errors"); + ret = false; + + } + catch (Exception ex) //As of version + { + bool se = AppSettings.Settings.send_telegram_errors; + AppSettings.Settings.send_telegram_errors = false; + Log($"ERROR: Could not upload image {AQI.CurImg.image_path} to Telegram with chatid '{lastchatid.ReplaceChars('*')}': {ex.Msg()}", this.CurSrv, AQI.cam, AQI.CurImg); + this.TelegramRetryTime = DateTime.Now.AddSeconds(AppSettings.Settings.Telegram_RetryAfterFailSeconds); + Log($"Debug: ...'Default' 'Telegram_RetryAfterFailSeconds' value was set to '{AppSettings.Settings.Telegram_RetryAfterFailSeconds}' seconds, so not retrying until {this.TelegramRetryTime}", this.CurSrv, AQI.cam, AQI.CurImg); + AppSettings.Settings.send_telegram_errors = se; + //store image that caused an error in ./errors/ + if (!Directory.Exists("./errors/")) //if folder does not exist, create the folder + { + //create folder + DirectoryInfo di = Directory.CreateDirectory("./errors"); + Log($"./errors/" + " dir created."); + } + //save error image + using (var image = await SixLabors.ImageSharp.Image.LoadAsync(AQI.CurImg.image_path)) + { + await image.SaveAsync("./errors/" + "TELEGRAM-ERROR-" + Path.GetFileName(AQI.CurImg.image_path) + ".jpg"); + } + Global.UpdateLabel($"Can't upload error message to Telegram!", "lbl_errors"); + ret = false; + + } + + + Log($"Debug: ...Finished in {sw.ElapsedMilliseconds}ms", this.CurSrv, AQI.cam, AQI.CurImg); + + } + else + { + Log($"Error: Telegram settings mis-configured. telegram_chatids.Count={AppSettings.Settings.telegram_chatids.Count} ({string.Join(",", AppSettings.Settings.telegram_chatids).ReplaceChars('*')}), telegram_token='{AppSettings.Settings.telegram_token.ReplaceChars('*')}'", this.CurSrv, AQI.cam, AQI.CurImg); + ret = false; + } + + return ret; + + } + + //send text to Telegram + public async Task TelegramText(ClsTriggerActionQueueItem AQI) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + bool ret = false; + string lastchatid = ""; + if (AppSettings.Settings.telegram_chatids.Count > 0 && AppSettings.Settings.telegram_token != "") + { + try + { + + if (AppSettings.Settings.telegram_cooldown_seconds < 2) + AppSettings.Settings.telegram_cooldown_seconds = 2; //force to be at least 2 second + + string Caption = AITOOL.ReplaceParams(AQI.cam, AQI.Hist, AQI.CurImg, AQI.Text, Global.IPType.Path); + + DateTime now = DateTime.Now; + + if (this.TelegramRetryTime == DateTime.MinValue || now >= this.TelegramRetryTime) + { + double cooltime = Math.Round((now - this.last_telegram_trigger_time).TotalSeconds, 4); + if (cooltime >= AppSettings.Settings.telegram_cooldown_seconds) + { + if (Global.IsTimeBetween(now, AQI.cam.telegram_active_time_range)) + { + string chatid = ""; + bool overrideid = (!string.IsNullOrWhiteSpace(AQI.cam.telegram_chatid)); + if (overrideid) + chatid = AQI.cam.telegram_chatid.Trim(); + else + chatid = AppSettings.Settings.telegram_chatids[0]; + + + if (overrideid) + { + lastchatid = chatid; + Telegram.Bot.Types.Message msg = await AITOOL.Telegram.SendTextMessageAsync(chatid, Caption); + + } + else + { + foreach (string curchatid in AppSettings.Settings.telegram_chatids) + { + lastchatid = curchatid; + Telegram.Bot.Types.Message msg = await AITOOL.Telegram.SendTextMessageAsync(curchatid, Caption); + + } + + } + this.last_telegram_trigger_time = DateTime.Now; + this.TelegramRetryTime = DateTime.MinValue; + + if (AQI.IsQueued) + { + //add a minimum delay if we are in a queue to prevent minimum cooldown error + Log($"Waiting {AppSettings.Settings.telegram_cooldown_seconds} seconds (telegram_cooldown_seconds)...", this.CurSrv, AQI.cam, AQI.CurImg); + await Task.Delay(TimeSpan.FromSeconds(AppSettings.Settings.telegram_cooldown_seconds)); + } + + ret = true; + } + else + { + Log($"Debug: Skipping Telegram because time is not between {AQI.cam.telegram_active_time_range}"); + } + } + else + { + //log that nothing was done + Log($" Still in TELEGRAM cooldown. No image will be uploaded to Telegram. ({cooltime} of {AppSettings.Settings.telegram_cooldown_seconds} seconds - See 'telegram_cooldown_seconds' in settings file)", this.CurSrv, AQI.cam, AQI.CurImg); + + } + + } + else + { + Log($" Waiting {Math.Round((this.TelegramRetryTime - DateTime.Now).TotalSeconds, 1)} seconds ({this.TelegramRetryTime}) to retry TELEGRAM connection. This is due to a previous telegram send error.", this.CurSrv, AQI.cam, AQI.CurImg); + } + + + + } + catch (ApiRequestException ex) //current version only gives webexception NOT this exception! https://github.com/TelegramBots/Telegram.Bot/issues/891 + { + bool se = AppSettings.Settings.send_telegram_errors; + AppSettings.Settings.send_telegram_errors = false; + Log($"ERROR: Could not upload text '{AQI.Text}' with chatid '{lastchatid}' to Telegram: {ex.Msg()}", this.CurSrv, AQI.cam, AQI.CurImg); + this.TelegramRetryTime = DateTime.Now.AddSeconds(Convert.ToDouble(ex.Parameters.RetryAfter)); + Log($"...BOT API returned 'RetryAfter' value '{ex.Parameters.RetryAfter} seconds', so not retrying until {this.TelegramRetryTime}", this.CurSrv, AQI.cam, AQI.CurImg); + AppSettings.Settings.send_telegram_errors = se; + Global.UpdateLabel($"Can't upload error message to Telegram!", "lbl_errors"); + + } + catch (Exception ex) + { + bool se = AppSettings.Settings.send_telegram_errors; + AppSettings.Settings.send_telegram_errors = false; + Log($"ERROR: Could not upload image '{AQI.Text}' with chatid '{lastchatid}' to Telegram: {ex.Msg()}", this.CurSrv, AQI.cam, AQI.CurImg); + this.TelegramRetryTime = DateTime.Now.AddSeconds(AppSettings.Settings.Telegram_RetryAfterFailSeconds); + Log($"...'Default' 'Telegram_RetryAfterFailSeconds' value was set to '{AppSettings.Settings.Telegram_RetryAfterFailSeconds}' seconds, so not retrying until {this.TelegramRetryTime}", this.CurSrv, AQI.cam, AQI.CurImg); + AppSettings.Settings.send_telegram_errors = se; + Global.UpdateLabel($"Can't upload error message to Telegram!", "lbl_errors"); + } + + } + else + { + Log($"Error: Telegram settings misconfigured. telegram_chatids.Count={AppSettings.Settings.telegram_chatids.Count} ({string.Join(",", AppSettings.Settings.telegram_chatids)}), telegram_token='{AppSettings.Settings.telegram_token}'", this.CurSrv, AQI.cam, AQI.CurImg); + } + + return ret; + } + + //Handle telegram return messages... + + + + } +} diff --git a/src/UI/ClsURLItem.cs b/src/UI/ClsURLItem.cs new file mode 100644 index 00000000..b45f1ba0 --- /dev/null +++ b/src/UI/ClsURLItem.cs @@ -0,0 +1,792 @@ +using Amazon; + +using Newtonsoft.Json; + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; + +namespace AITool +{ + public enum URLTypeEnum + { + CodeProject_AI, + CodeProject_AI_Faces, + CodeProject_AI_Custom, + CodeProject_AI_Scene, + CodeProject_AI_Plate, + CodeProject_AI_IPCAM_Animal, + CodeProject_AI_IPCAM_Dark, + CodeProject_AI_IPCAM_General, + CodeProject_AI_IPCAM_Combined, + DeepStack, + DeepStack_Faces, + DeepStack_Custom, + DeepStack_Scene, + DOODS, + AWSRekognition_Objects, + AWSRekognition_Faces, + SightHound_Vehicle, + SightHound_Person, + Other, + Unknown + } + [DebuggerDisplay("{this.Name}: {this.url}")] + public class ClsURLItem:IEquatable + { + + public URLTypeEnum Type { get; set; } = URLTypeEnum.Unknown; + public int Order { get; set; } = 0; + + public string Name { get; set; } = ""; + public string url { get; set; } = ""; + + [JsonIgnore] + public bool IsValid { get; set; } = false; + public bool IsOnline { get; set; } = false; + [JsonIgnore] + public ThreadSafe.Boolean InUse { get; set; } = new ThreadSafe.Boolean(false); + [JsonIgnore] + public ThreadSafe.Integer AIQueueLength { get; set; } = new ThreadSafe.Integer(0); + public int AIQueueTimeMS + { + get + { + if (this.QueueStartTime != DateTime.MinValue) + { + return (int)(DateTime.Now - this.QueueStartTime).TotalMilliseconds; + } + else + { + return 0; + + } + } + } + public ThreadSafe.Integer AIQueueSkippedCount { get; set; } = new ThreadSafe.Integer(0); + public string LastResultMessage { get; set; } = ""; + public string LastSkippedReason { get; set; } = ""; + [JsonIgnore] + public string NotReadyReason { get; set; } = ""; + [JsonIgnore] + public bool LastResultSuccess { get; set; } = false; + [JsonIgnore] + public ThreadSafe.DateTime LastUsedTime { get; set; } = new ThreadSafe.DateTime(DateTime.MinValue, AppSettings.Settings.DateFormat); + [JsonIgnore] + public ThreadSafe.DateTime QueueStartTime { get; set; } = new ThreadSafe.DateTime(DateTime.MinValue, AppSettings.Settings.DateFormat); + + public int LastTimeMS { get; set; } = 0; + + public int AvgTimeMS + { + get { return Convert.ToInt32(this.AITimeCalcs.Avg); } + } + public ThreadSafe.DateTime LastTestedTime { get; set; } = new ThreadSafe.DateTime(DateTime.MinValue, AppSettings.Settings.DateFormat); + + public MovingCalcs AITimeCalcs { get; set; } = new MovingCalcs(500, "TimeCalcs", true); //store deepstack time calc in the url + public MovingCalcs AIQueueLengthCalcs { get; set; } = new MovingCalcs(500, "AIQueueLengthCalcs", false); + public DateTime LastMaxQueueLengthTime + { + get { return this.AIQueueLengthCalcs.LastMaxTime; } + } + public MovingCalcs AIQueueTimeCalcs { get; set; } = new MovingCalcs(500, "AIQueueTimeCalcs", true); + public DateTime LastMaxQueueTimeTime + { + get { return this.AIQueueTimeCalcs.LastMaxTime; } + } + + public ThreadSafe.Boolean Enabled { get; set; } = new ThreadSafe.Boolean(true); + [JsonIgnore] + public ThreadSafe.Integer CurErrCount { get; set; } = new ThreadSafe.Integer(0); + public ThreadSafe.Boolean ErrDisabled { get; set; } = new ThreadSafe.Boolean(false); + public ThreadSafe.Integer ErrCount { get; set; } = new ThreadSafe.Integer(0); + public ThreadSafe.Integer ErrsInRowCount { get; set; } = new ThreadSafe.Integer(0); + + public string Cameras { get; set; } = ""; + public int MaxImagesPerMonth { get; set; } = 0; + public int Threshold_Lower { get; set; } = 0; //override the cameras threshold since different AI servers may need to be tuned to different values + public int Threshold_Upper { get; set; } = 100; + public bool UseAsRefinementServer { get; set; } = false; + public string RefinementObjects { get; set; } = ""; + [JsonIgnore] + public ThreadSafe.Boolean RefinementUseCurrentlyValid { get; set; } = new ThreadSafe.Boolean(false); + [JsonIgnore] + public ThreadSafe.Integer CurOrder { get; set; } = new ThreadSafe.Integer(0); + public bool UseOnlyAsLinkedServer { get; set; } = false; + public bool LinkServerResults { get; set; } = false; + public string LinkedResultsServerList { get; set; } = ""; + public string ActiveTimeRange { get; set; } = "00:00:00-23:59:59"; + [JsonIgnore] + public bool IsLocalHost { get; set; } = false; + public bool IsLocalNetwork { get; set; } = false; + public string CurSrv { get; set; } = ""; + public int Port { get; set; } = 0; + public string Host { get; set; } = ""; + public int HttpClientTimeoutSeconds { get; set; } = 0; + public string DefaultURL { get; set; } = ""; + public string HelpURL { get; set; } = ""; + //[JsonIgnore] + //public Global.ClsProcess Process { get; set; } = null; + [JsonIgnore] + public HttpClient HttpClient { get; set; } = null; + //public int Count { get; set; } = 0; + public bool UrlFixed { get; set; } = false; + public bool ExternalSettingsValid { get; set; } = false; + public bool IgnoreOfflineError { get; set; } = false; //if we cant even ping the server, we can skip it without giving an error. This might be useful for servers that are only available at certain times of the day + public bool AllowAIServerBasedQueue { get; set; } = false; + public int AIMaxQueueLength { get; set; } = 16; + public int SkipIfImgQueueLengthLarger { get; set; } = 8; + public int SkipIfAIQueueTimeOverSecs { get; set; } = 300; //300=5 mins + public override string ToString() + { + return this.url; + } + + public bool IsServerReady(int CurrentImgQueueLength, int ValidServerCnt) + { + if (!Enabled) + { + NotReadyReason = "Disabled"; + return false; + } + if (ErrDisabled) + { + NotReadyReason = "ErrDisabled"; + return false; + } + if (AllowAIServerBasedQueue) + { + //only return false if there is something in the queue and we actually have other AITOOL urls to use + if (this.AIQueueLength > 1 && ValidServerCnt > 1) + { + if (AIQueueLength > AIMaxQueueLength) + { + NotReadyReason = "AIServerQueueFull"; + AIQueueSkippedCount++; + return false; + } + //if the AI server queue has anything in it and our regular image queue is above a certain length, then skip this server to give others a chance + //if there is only 1 server, then we will still use it since nothing to skip to. + if (CurrentImgQueueLength > this.SkipIfImgQueueLengthLarger) + { + NotReadyReason = "ImgQueueToHigh"; + AIQueueSkippedCount++; + return false; + } + + int queueTimeSecs = (int)(this.AIQueueTimeMS / 1000); + if (queueTimeSecs > SkipIfAIQueueTimeOverSecs) + { + NotReadyReason = "AIServerQueueTimeMaxed"; + AIQueueSkippedCount++; + return false; + } + } + } + if (!AllowAIServerBasedQueue && InUse) + { + NotReadyReason = "InUse"; + return false; + } + + NotReadyReason = "[Ready]"; + return true; + + } + /// + /// This will reduce the queue count by 1 until it reaches 0 + /// + /// True if still in use + public bool DecrementQueue() + { + this.AIQueueLength.Decrement(0); + bool ret = this.AIQueueLength == 0; + if (ret) + { + this.AIQueueTimeCalcs.AddToCalc(this.AIQueueTimeMS); + this.QueueStartTime = new ThreadSafe.DateTime(DateTime.MinValue, AppSettings.Settings.DateFormat); + this.InUse = false; + } + return ret; + } + + public void IncrementQueue(DateTime? time = null) + { + this.AIQueueLength++; + this.InUse = true; + if (AIQueueLength == 1) + { + if (time.HasValue) + { + this.LastUsedTime = new ThreadSafe.DateTime(time.Value, AppSettings.Settings.DateFormat); + } + else + { + this.LastUsedTime = new ThreadSafe.DateTime(DateTime.Now, AppSettings.Settings.DateFormat); + } + + this.QueueStartTime = new ThreadSafe.DateTime(DateTime.Now, AppSettings.Settings.DateFormat); + } + + this.AIQueueLengthCalcs.AddToCalc(this.AIQueueLength); + } + public void IncrementError() + { + this.CurErrCount++; + this.ErrCount++; + } + + public TimeSpan GetTimeout() + { + if (this.HttpClientTimeoutSeconds > 0) + return TimeSpan.FromSeconds(this.HttpClientTimeoutSeconds); + else if (this.IsLocalNetwork) + return TimeSpan.FromSeconds(AppSettings.Settings.HTTPClientLocalTimeoutSeconds); + else + return TimeSpan.FromSeconds(AppSettings.Settings.HTTPClientRemoteTimeoutSeconds); + + } + + [JsonConstructor] + public ClsURLItem() { } + + public ClsURLItem(String url, int Order, URLTypeEnum type) + { + //this.Name = name.Trim(); + this.UrlFixed = false; + this.url = url.Trim(); + this.Type = type; + this.Order = Order; + this.Update(true); + } + + public async Task CheckIfOnlineAsync() + { + + bool IsAWS = this.Type == URLTypeEnum.AWSRekognition_Objects || this.Type == URLTypeEnum.AWSRekognition_Faces; + + + if (this.IsValid && this.Host.IsNotEmpty() && !IsAWS) + { + if (this.IsLocalHost) + { + this.IsOnline = true; //assume true always for localhost + } + else + { + Global.ClsPingOut po; + + if (this.IsLocalNetwork) + { + po = await Global.IsConnected(this.Host, 15); + } + else + { + po = await Global.IsConnected(this.Host, AppSettings.Settings.MaxWaitForAIServerMS); //5000ms - overkill? + + } + this.IsOnline = po.Success; + } + } + else + { + if (IsAWS) + { + this.IsOnline = true; //assume true for now + } + else + { + this.IsOnline = false; + } + } + + return this.IsOnline; + } + + public bool Update(bool Init) + { + bool ret = false; + this.UrlFixed = false; + bool WasFixed = false; + bool HadError = false; + + Uri uri = null; + // CodeProject_AI, + // CodeProject_AI_Faces, + // CodeProject_AI_Custom, + // CodeProject_AI_Scene, + // CodeProject_AI_Plate, + // CodeProject_AI_IPCAM_Animal, + // CodeProject_AI_IPCAM_Dark, + // CodeProject_AI_IPCAM_General, + // CodeProject_AI_IPCAM_Combined, + // CodeProject_AI_IPCAM_Plate, + + bool HasCP = this.url.Has(":32168/v1/vision/detection"); + bool HasCPPlate = this.url.Has(":32168/v1/image/alpr"); + bool HasCPCustom = this.url.Has(":32168/v1/vision/custom"); + bool HasCPAnimal = this.url.Has(":32168/v1/vision/custom/ipcam-animal"); + bool HasCPDark = this.url.Has(":32168/v1/vision/custom/ipcam-dark"); + bool HasCPGeneral = this.url.Has(":32168/v1/vision/custom/ipcam-general"); + bool HasCPCombined = this.url.Has(":32168/v1/vision/custom/ipcam-combined"); + bool HasCPIPCamPlate = this.url.Has(":32168/v1/vision/custom/ipcam-license-plate"); + bool HasCPScene = this.url.Has(":32168/v1/vision/scene"); + bool HasCPFace = this.url.Has(":32168/v1/vision/face/recognize"); + + + bool HasDoods = this.url.EndsWith("/detect", StringComparison.OrdinalIgnoreCase); + bool HasAWSObj = this.url.Equals("amazon", StringComparison.OrdinalIgnoreCase) || this.url.Equals("amazon_objects", StringComparison.OrdinalIgnoreCase); + bool HasAWSFac = this.url.Equals("amazon_faces", StringComparison.OrdinalIgnoreCase); + bool HasSHPer = this.url.IndexOf("/v1/detections", StringComparison.OrdinalIgnoreCase) >= 0; + bool HasSHVeh = this.url.IndexOf("/v1/recognition", StringComparison.OrdinalIgnoreCase) >= 0; + bool HasDSFacRec = this.url.IndexOf("/v1/vision/face/recognize", StringComparison.OrdinalIgnoreCase) >= 0; //Face Recognition + bool HasDSFacDet = this.url.IndexOf("/v1/vision/face", StringComparison.OrdinalIgnoreCase) >= 0; //Face Detections + bool HasDSCus = this.url.IndexOf("/v1/vision/custom", StringComparison.OrdinalIgnoreCase) >= 0; + bool HasDSScn = this.url.IndexOf("/v1/vision/scene", StringComparison.OrdinalIgnoreCase) >= 0; + bool HasDSDet = this.url.IndexOf("/v1/vision/detection", StringComparison.OrdinalIgnoreCase) >= 0; + + + + //bool ShouldInit = Init || !this.IsValid || string.IsNullOrWhiteSpace(this.url) || (!this.url.Contains("/") && !this.url.Contains("_")) || this.Type == URLTypeEnum.Unknown; + bool ShouldInit = Init || string.IsNullOrWhiteSpace(this.url) || (!this.url.Contains("/") && !this.url.Contains("_")) || this.Type == URLTypeEnum.Unknown; + + if (ShouldInit) + { + if (this.Type == URLTypeEnum.DOODS || HasDoods) + { + this.DefaultURL = "http://127.0.0.1:8080/detect"; + this.HelpURL = "https://github.com/snowzach/doods"; + this.Type = URLTypeEnum.DOODS; + } + else if (this.Type == URLTypeEnum.AWSRekognition_Objects || HasAWSObj) + { + this.DefaultURL = "Amazon_Objects"; + this.HelpURL = "https://docs.aws.amazon.com/rekognition/latest/dg/setting-up.html"; + this.Type = URLTypeEnum.AWSRekognition_Objects; + this.MaxImagesPerMonth = 5000; + } + else if (this.Type == URLTypeEnum.AWSRekognition_Faces || HasAWSFac) + { + this.DefaultURL = "Amazon_Faces"; + this.HelpURL = "https://docs.aws.amazon.com/rekognition/latest/dg/setting-up.html"; + this.Type = URLTypeEnum.AWSRekognition_Faces; + this.UseAsRefinementServer = true; + this.RefinementObjects = "Person, People, Face"; + this.MaxImagesPerMonth = 5000; + } + else if (this.Type == URLTypeEnum.SightHound_Vehicle || HasSHVeh) + { + this.DefaultURL = "https://dev.sighthoundapi.com/v1/recognition?objectType=vehicle,licenseplate"; + this.HelpURL = "https://docs.sighthound.com/cloud/recognition/"; + this.Type = URLTypeEnum.SightHound_Vehicle; + this.MaxImagesPerMonth = 5000; + this.UseAsRefinementServer = true; + this.RefinementObjects = "Ambulance, Car, Truck, Pickup Truck, Bus, SUV, Van, Motorcycle, Motorbike, License Plate, Plate"; + this.IsLocalHost = false; + this.IsLocalNetwork = false; + this.HttpClient = null; + } + else if (this.Type == URLTypeEnum.SightHound_Person || HasSHPer) + { + this.DefaultURL = "https://dev.sighthoundapi.com/v1/detections?type=face,person&faceOption=gender,age,emotion"; + this.HelpURL = "https://docs.sighthound.com/cloud/detection/"; + this.Type = URLTypeEnum.SightHound_Person; + this.UseAsRefinementServer = true; + this.RefinementObjects = "Person, People, Face"; + this.IsLocalHost = false; + this.IsLocalNetwork = false; + this.HttpClient = null; + this.MaxImagesPerMonth = 5000; + } + else if (this.Type == URLTypeEnum.CodeProject_AI || HasCP) + { + this.DefaultURL = "http://127.0.0.1:32168/v1/vision/detection"; + this.HelpURL = "https://www.codeproject.com/AI/docs/api/api_reference.html"; + this.Type = URLTypeEnum.CodeProject_AI; + this.AllowAIServerBasedQueue = true; + } + else if (this.Type == URLTypeEnum.CodeProject_AI_Faces || HasCPFace) + { + this.DefaultURL = "http://127.0.0.1:32168/v1/vision/face/recognize"; + this.HelpURL = "https://www.codeproject.com/AI/docs/api/api_reference.html"; + this.Type = URLTypeEnum.CodeProject_AI_Faces; + this.UseAsRefinementServer = true; + this.RefinementObjects = "Person, People, Face"; + this.AllowAIServerBasedQueue = true; + } + else if (this.Type == URLTypeEnum.CodeProject_AI_IPCAM_Animal || HasCPAnimal) + { + this.DefaultURL = "http://127.0.0.1:32168/v1/vision/custom/ipcam-animal"; + this.HelpURL = "https://www.codeproject.com/AI/docs/api/api_reference.html"; + this.Type = URLTypeEnum.CodeProject_AI_IPCAM_Animal; + this.AllowAIServerBasedQueue = true; + } + else if (this.Type == URLTypeEnum.CodeProject_AI_IPCAM_Combined || HasCPCombined) + { + this.DefaultURL = "http://127.0.0.1:32168/v1/vision/custom/ipcam-combined"; + this.HelpURL = "https://www.codeproject.com/AI/docs/api/api_reference.html"; + this.Type = URLTypeEnum.CodeProject_AI_IPCAM_Combined; + this.AllowAIServerBasedQueue = true; + } + else if (this.Type == URLTypeEnum.CodeProject_AI_IPCAM_Dark || HasCPDark) + { + this.DefaultURL = "http://127.0.0.1:32168/v1/vision/custom/ipcam-dark"; + this.HelpURL = "https://www.codeproject.com/AI/docs/api/api_reference.html"; + this.Type = URLTypeEnum.CodeProject_AI_IPCAM_Dark; + this.AllowAIServerBasedQueue = true; + } + else if (this.Type == URLTypeEnum.CodeProject_AI_IPCAM_General || HasCPGeneral) + { + this.DefaultURL = "http://127.0.0.1:32168/v1/vision/custom/ipcam-general"; + this.HelpURL = "https://www.codeproject.com/AI/docs/api/api_reference.html"; + this.Type = URLTypeEnum.CodeProject_AI_IPCAM_General; + this.AllowAIServerBasedQueue = true; + } + else if (this.Type == URLTypeEnum.CodeProject_AI_Plate || HasCPPlate) + { + this.DefaultURL = "http://127.0.0.1:32168/v1/image/alpr"; + this.HelpURL = "https://www.codeproject.com/AI/docs/api/api_reference.html"; + this.Type = URLTypeEnum.CodeProject_AI_Plate; + this.UseAsRefinementServer = true; + this.RefinementObjects = "Ambulance, Car, Truck, Pickup Truck, Bus, SUV, Van, Motorcycle, Motorbike, License Plate, Plate"; + this.AllowAIServerBasedQueue = true; + + } + else if (this.Type == URLTypeEnum.CodeProject_AI_Scene || HasCPScene) + { + this.DefaultURL = "http://127.0.0.1:32168/v1/vision/scene"; + this.HelpURL = "https://www.codeproject.com/AI/docs/api/api_reference.html"; + this.Type = URLTypeEnum.CodeProject_AI_Scene; + this.AllowAIServerBasedQueue = true; + } + else if (this.Type == URLTypeEnum.CodeProject_AI_Custom || HasCPCustom) + { + this.DefaultURL = "http://127.0.0.1:32168/v1/vision/custom"; + this.HelpURL = "https://www.codeproject.com/AI/docs/api/api_reference.html"; + this.Type = URLTypeEnum.CodeProject_AI_Custom; + this.AllowAIServerBasedQueue = true; + } + + + + else if (this.Type == URLTypeEnum.DeepStack_Faces || HasDSFacRec) + { + this.DefaultURL = "http://127.0.0.1:80/v1/vision/face/recognize"; + this.HelpURL = "https://docs.deepstack.cc/face-recognition/index.html"; + this.Type = URLTypeEnum.DeepStack_Faces; + this.UseAsRefinementServer = true; + this.RefinementObjects = "Person, People, Face"; + } + else if (this.Type == URLTypeEnum.DeepStack_Scene || HasDSScn) + { + this.DefaultURL = "http://127.0.0.1:80/v1/vision/scene"; + this.HelpURL = "https://docs.deepstack.cc/face-recognition/index.html"; + this.Type = URLTypeEnum.DeepStack_Scene; + this.UseAsRefinementServer = true; + this.RefinementObjects = "*"; + } + else if (this.Type == URLTypeEnum.DeepStack_Custom || HasDSCus) // assume deepstack //if (this.Type == URLTypeEnum.DeepStack || this.url.IndexOf("/v1/vision/detection", StringComparison.OrdinalIgnoreCase) >= 0) + { + this.DefaultURL = "http://127.0.0.1:80/v1/vision/custom/YOUR_CUSTOM_MODEL_NAME_HERE"; + this.HelpURL = "https://docs.deepstack.cc/custom-models/index.html"; + this.Type = URLTypeEnum.DeepStack_Custom; + if (this.url.Has("dark")) + this.UseOnlyAsLinkedServer = false; + else + this.UseOnlyAsLinkedServer = true; + + } + else // assume deepstack //if (this.Type == URLTypeEnum.DeepStack || this.url.IndexOf("/v1/vision/detection", StringComparison.OrdinalIgnoreCase) >= 0) + { + this.DefaultURL = "http://127.0.0.1:80/v1/vision/detection"; + this.HelpURL = "https://docs.deepstack.cc/object-detection/index.html"; + this.Type = URLTypeEnum.DeepStack; + } + + } + + if (string.IsNullOrWhiteSpace(this.url)) + this.url = this.DefaultURL; + + HasCP = this.url.Has(":32168/v1/vision/detection"); + HasCPPlate = this.url.Has(":32168/v1/image/alpr"); + HasCPAnimal = this.url.Has(":32168/v1/vision/custom/ipcam-animal"); + HasCPDark = this.url.Has(":32168/v1/vision/custom/ipcam-dark"); + HasCPGeneral = this.url.Has(":32168/v1/vision/custom/ipcam-general"); + HasCPCombined = this.url.Has(":32168/v1/vision/custom/ipcam-combined"); + HasCPIPCamPlate = this.url.Has(":32168/v1/vision/custom/ipcam-license-plate"); + HasCPCustom = this.url.Has(":32168/v1/vision/custom"); + HasCPScene = this.url.Has(":32168/v1/vision/scene"); + HasCPFace = this.url.Has(":32168/v1/vision/face/recognize"); + + HasDoods = this.url.EndsWith("/detect", StringComparison.OrdinalIgnoreCase); + HasAWSObj = this.url.Equals("amazon", StringComparison.OrdinalIgnoreCase) || this.url.Equals("amazon_objects", StringComparison.OrdinalIgnoreCase); + HasAWSFac = this.url.Equals("amazon_faces", StringComparison.OrdinalIgnoreCase); + HasSHPer = this.url.IndexOf("/v1/detections", StringComparison.OrdinalIgnoreCase) >= 0; + HasSHVeh = this.url.IndexOf("/v1/recognition", StringComparison.OrdinalIgnoreCase) >= 0; + HasDSFacRec = this.url.IndexOf("/v1/vision/face/recognize", StringComparison.OrdinalIgnoreCase) >= 0; //Face Recognition + HasDSFacDet = this.url.IndexOf("/v1/vision/face", StringComparison.OrdinalIgnoreCase) >= 0; //Face Detections - Not using this for now + HasDSCus = this.url.IndexOf("/v1/vision/custom", StringComparison.OrdinalIgnoreCase) >= 0; + HasDSDet = this.url.IndexOf("/v1/vision/detection", StringComparison.OrdinalIgnoreCase) >= 0; + HasDSScn = this.url.IndexOf("/v1/vision/scene", StringComparison.OrdinalIgnoreCase) >= 0; + + //================================================================================ + // Try to correct any servers without a full URL + //================================================================================ + if (this.url.Has(":32168")) + { + //do nothing for now - this is a codeproject server + } + else if (this.Type == URLTypeEnum.DOODS || HasDoods) + { + + + this.url = Global.UpdateURL(this.url, 0, "/detect", "", ref WasFixed, ref HadError); + this.Type = URLTypeEnum.DOODS; + } + else if (this.Type == URLTypeEnum.AWSRekognition_Objects || HasAWSObj) + { + + this.url = "Amazon_Objects"; + this.Type = URLTypeEnum.AWSRekognition_Objects; + this.UrlFixed = true; + + } + else if (this.Type == URLTypeEnum.AWSRekognition_Faces || HasAWSFac) + { + + this.Type = URLTypeEnum.AWSRekognition_Faces; + this.url = "Amazon_Faces"; + this.UrlFixed = true; + } + else if (this.Type == URLTypeEnum.SightHound_Vehicle || HasSHVeh) + { + + this.url = Global.UpdateURL(this.url, 443, "/v1/recognition", "", ref WasFixed, ref HadError); + this.Type = URLTypeEnum.SightHound_Vehicle; + + } + else if (this.Type == URLTypeEnum.SightHound_Person || HasSHPer) + { + this.url = Global.UpdateURL(this.url, 443, "/v1/detections", "", ref WasFixed, ref HadError); + this.Type = URLTypeEnum.SightHound_Person; + } + else // assume deepstack, default to detection + { + + if (!HasDSCus && !HasDSDet && !HasDSScn && !HasDSFacRec && !HasDSFacDet) //default to regular detection + { + this.url = Global.UpdateURL(this.url, 0, "/v1/vision/detection", "", ref WasFixed, ref HadError); + this.Type = URLTypeEnum.DeepStack; + } + else if (this.Type == URLTypeEnum.DeepStack_Custom && !HasDSCus) + { + this.url = Global.UpdateURL(this.url, 0, "/v1/vision/custom/", "", ref WasFixed, ref HadError); + } + else if (this.Type != URLTypeEnum.DeepStack_Custom && HasDSCus) + { + this.Type = URLTypeEnum.DeepStack_Custom; + } + else if (this.Type == URLTypeEnum.DeepStack_Faces && !HasDSFacRec && !HasDSFacDet) + { + this.url = Global.UpdateURL(this.url, 0, "/v1/vision/face/recognize", "", ref WasFixed, ref HadError); + } + else if (this.Type != URLTypeEnum.DeepStack_Faces && HasDSFacRec) + { + this.Type = URLTypeEnum.DeepStack_Faces; + } + else if (this.Type == URLTypeEnum.DeepStack && !HasDSDet) + { + this.url = Global.UpdateURL(this.url, 0, "/v1/vision/detection", "", ref WasFixed, ref HadError); + } + else if (this.Type != URLTypeEnum.DeepStack && HasDSDet) + { + this.Type = URLTypeEnum.DeepStack; + } + + else if (this.Type == URLTypeEnum.DeepStack_Scene && !HasDSScn) + { + this.url = Global.UpdateURL(this.url, 0, "/v1/vision/scene", "", ref WasFixed, ref HadError); + } + else if (this.Type != URLTypeEnum.DeepStack_Scene && HasDSScn) + { + this.Type = URLTypeEnum.DeepStack_Scene; + } + + + } + + + //================================================================================ + // Do final validation tests + //================================================================================ + + bool IsAWS = this.Type == URLTypeEnum.AWSRekognition_Objects || this.Type == URLTypeEnum.AWSRekognition_Faces; + + if (!IsAWS) + { + if (Global.IsValidURL(this.url) && !HadError) + { + uri = new Uri(this.url); + + + this.Port = uri.Port; + this.Host = uri.Host; + this.IsLocalHost = Global.IsLocalHost(uri.Host); + this.IsLocalNetwork = Global.IsLocalNetwork(uri.Host); + + if (this.IsLocalHost && !uri.Host.Contains("127.")) + { + //force it to always be 127.0.0.1 for localhost + AITOOL.Log($"Debug: Converting localhost from '{uri.Host}' to '127.0.0.1'. Localhost and 0.0.0.0 do not seem to be reliable."); + this.url = Global.UpdateURL(this.url, 0, "", "127.0.0.1", ref WasFixed, ref HadError); + } + + if (url.Has(":32168")) + { + this.CurSrv = this.Type.ToString() + ":" + this.Name + ":" + uri.Host + ":" + uri.Port; + } + else if (this.Type == URLTypeEnum.DeepStack) + { + this.CurSrv = "Deepstack_Objects:" + uri.Host + ":" + uri.Port; + } + else if (this.Type == URLTypeEnum.DeepStack_Custom) + { + this.CurSrv = "Deepstack_Custom:" + uri.Host + ":" + uri.Port; + } + else if (this.Type == URLTypeEnum.DeepStack_Faces) + { + this.CurSrv = "Deepstack_Faces:" + uri.Host + ":" + uri.Port; + } + else if (this.Type == URLTypeEnum.DeepStack_Scene) + { + this.CurSrv = "Deepstack_Scene:" + uri.Host + ":" + uri.Port; + } + else if (this.Type == URLTypeEnum.DOODS) + { + this.CurSrv = "DOODS:" + uri.Host + ":" + uri.Port; + } + else if (this.Type == URLTypeEnum.SightHound_Person) + { + this.CurSrv = "SightHound_Person:" + uri.Host + ":" + uri.Port; this.IsLocalHost = false; this.IsLocalNetwork = false; + } + else if (this.Type == URLTypeEnum.SightHound_Vehicle) + { + this.CurSrv = "SightHound_Vehicle:" + uri.Host + ":" + uri.Port; this.IsLocalHost = false; this.IsLocalNetwork = false; + } + else + { + this.CurSrv = "Unknown:" + uri.Host + ":" + uri.Port; this.IsLocalHost = false; this.IsLocalNetwork = false; + } + + ret = (this.Type != URLTypeEnum.Unknown && !string.IsNullOrEmpty(this.CurSrv) && !this.CurSrv.StartsWith("Unknown")); + } + else + { + ret = false; + } + } + else + { + if (IsAWS) + { + this.CurSrv = this.url + ":" + AppSettings.Settings.AmazonRegionEndpoint; + this.IsLocalHost = false; this.IsLocalNetwork = false; + if (!this.ExternalSettingsValid || ShouldInit) + { + string error = AITOOL.UpdateAmazonSettings(); + if (string.IsNullOrEmpty(error)) + { + this.IsValid = true; + this.ExternalSettingsValid = true; + ret = true; + } + else + { + AITOOL.Log($"Error: {error}"); + this.IsValid = false; + this.ExternalSettingsValid = false; + ret = false; + this.Enabled = false; + } + } + else + { + ret = true; + } + + } + + } + + this.IsValid = ret; + + this.CheckIfOnlineAsync(); + + //disable if needed, but never try reenable if the user disabled by themselves + if (!this.IsValid) + { + this.Enabled = false; + AITOOL.Log($"Error: '{this.Type.ToString()}' URL is not known/valid: '{this.url}'"); + } + + if (!IsAWS && this.IsValid && this.HttpClient == null) + { + this.HttpClient = new HttpClient(); + this.HttpClient.Timeout = this.GetTimeout(); + } + + //remove duplicates from this.LinkedResultsServerList, assuming they are separated by " ," + if (this.LinkedResultsServerList.IsNotEmpty()) + { + List servers = this.LinkedResultsServerList.SplitStr(","); + //use linq to remove duplicates from servers in a case insensitive way using distinct: + servers = servers.Distinct(StringComparer.OrdinalIgnoreCase).ToList(); + //loop through servers and add them back to the string as long as AITOOL.GetServer() returns a match that is enabled + foreach (string server in servers) + { + ClsURLItem srv = AITOOL.GetURL(server, false, true); + if (srv != null && srv.Enabled && srv.UseOnlyAsLinkedServer) + { + this.LinkedResultsServerList = server + " ,"; + } + } + + this.LinkedResultsServerList = LinkedResultsServerList.Trim(' ', ','); + } + + if (WasFixed) + this.UrlFixed = true; + + + return ret; + } + + public static bool operator ==(ClsURLItem left, ClsURLItem right) + { + return EqualityComparer.Default.Equals(left, right); + } + + public static bool operator !=(ClsURLItem left, ClsURLItem right) + { + return !(left == right); + + + } + + public override bool Equals(object obj) + { + return Equals(obj as ClsURLItem); + } + + public bool Equals(ClsURLItem other) + { + return other != null && this.url.EqualsIgnoreCase(other.url) && this.Name.EqualsIgnoreCase(other.Name) && this.Type == other.Type; + } + + } +} diff --git a/src/UI/DOODS.png b/src/UI/DOODS.png new file mode 100644 index 00000000..617eab6e Binary files /dev/null and b/src/UI/DOODS.png differ diff --git a/src/UI/DeepStack.cs b/src/UI/DeepStack.cs new file mode 100644 index 00000000..df5b2f99 --- /dev/null +++ b/src/UI/DeepStack.cs @@ -0,0 +1,1213 @@ +using Microsoft.Win32; + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +using static AITool.AITOOL; + + +namespace AITool +{ + public class DeepstackPlatformJson + { + public string PROFILE = ""; + public string CUDA_MODE = ""; + } + public enum DeepStackTypeEnum + { + CPU, + GPU, + Unknown + } + public class DeepStack + { + + public string DisplayName = "Unknown"; + public string DisplayVersion = "Unknown"; + public DeepStackTypeEnum Type = DeepStackTypeEnum.Unknown; + public bool IsNewVersion = false; + public bool Is2022OrLaterVersion = false; + public string AdminKey = ""; + public string APIKey = ""; + public string Port = "81"; + public string Mode = "Medium"; + public int Count = 1; + public string DeepStackFolder = @"C:\DeepStack"; + public string DeepStackEXE = @"C:\DeepStack\DeepStack.exe"; + public string ServerEXE = @"C:\DeepStack\server\DeepStack.exe"; + public string PythonEXE = @"C:\DeepStack\interpreter\python.exe"; + public string RedisEXE = @"C:\DeepStack\redis\redis-server.exe"; + public bool SceneAPIEnabled = false; + public bool FaceAPIEnabled = false; + public bool DetectionAPIEnabled = true; + public bool CustomModelEnabled = false; + public string CustomModelPath = ""; + public string CustomModelName = ""; + public string CustomModelPort = ""; + public string CustomModelMode = ""; + public bool IsStarted = false; + public bool HasError = false; + public bool IsInstalled = false; + public bool IsActivated = false; + public bool VisionDetectionRunning = false; + public bool StopBeforeStart = true; + public bool NeedsSaving = false; + public string CommandLine = ""; + public List DeepStackProc = new List(); + public List ServerProc = new List(); + public List PythonProc = new List(); + public List RedisProc = new List(); + public List ResponseTimeList = new List(); //From this you can get min/max/avg + public string URLS = ""; + + public ThreadSafe.Boolean Starting = new ThreadSafe.Boolean(false); + public ThreadSafe.Boolean Stopping = new ThreadSafe.Boolean(false); + public int ExpectedPythonCnt = 0; + + public DeepStack(string AdminKey, string APIKey, string Mode, bool SceneAPIEnabled, bool FaceAPIEnabled, bool DetectionAPIEnabled, string Port, string CustomModelPath, bool StopBeforeStart, string CustomModelName, string CustomModelPort, string CustomModelMode, bool CustomModelEnabled) + { + + this.Update(AdminKey, APIKey, Mode, SceneAPIEnabled, FaceAPIEnabled, DetectionAPIEnabled, Port, CustomModelPath, StopBeforeStart, CustomModelName, CustomModelPort, CustomModelMode, CustomModelEnabled); + + } + + public void Update(string AdminKey, string APIKey, string Mode, bool SceneAPIEnabled, bool FaceAPIEnabled, bool DetectionAPIEnabled, string Port, string CustomModelPath, bool StopBeforeStart, string CustomModelName, string CustomModelPort, string CustomModelMode, bool CustomModelEnabled) + { + this.AdminKey = AdminKey.Trim(); + this.APIKey = APIKey.Trim(); + this.SceneAPIEnabled = SceneAPIEnabled; + this.FaceAPIEnabled = FaceAPIEnabled; + this.DetectionAPIEnabled = DetectionAPIEnabled; + this.CustomModelPath = CustomModelPath.Trim(); + this.CustomModelName = CustomModelName.Trim(); + this.CustomModelEnabled = CustomModelEnabled; + this.CustomModelPort = CustomModelPort; + this.CustomModelMode = CustomModelMode; + this.Port = Port; + this.Mode = Mode; + this.Count = this.Port.SplitStr(",|").Count; + this.StopBeforeStart = StopBeforeStart; + + bool found = this.RefreshDeepstackInfo(); + + } + public bool GetDeepStackRun() + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + bool Ret = false; + + if (this.IsNewVersion) + { + this.Count = Global.GetProcessesByPath(this.ServerEXE).Count; + + + if (!Global.ProcessValid(this.ServerProc)) + this.ServerProc = Global.GetProcessesByPath(this.ServerEXE); + if (!Global.ProcessValid(this.PythonProc)) + this.PythonProc = Global.GetProcessesByPath(this.PythonEXE); + if (!Global.ProcessValid(this.RedisProc)) + this.RedisProc = Global.GetProcessesByPath(this.RedisEXE); + + List montys = Global.GetProcessesByPath(this.PythonEXE); + + bool srvvalid = Global.ProcessValid(this.ServerProc); + bool redvalid = Global.ProcessValid(this.RedisProc); + bool pytvalid = false; + + if (this.ExpectedPythonCnt == 0) + { + + if (this.Is2022OrLaterVersion) + { + if (this.FaceAPIEnabled) + ExpectedPythonCnt = ExpectedPythonCnt + (this.Count * 2); + if (this.CustomModelEnabled) + ExpectedPythonCnt = ExpectedPythonCnt + (this.Count * 1); + if (this.DetectionAPIEnabled) + ExpectedPythonCnt = ExpectedPythonCnt + (this.Count * 1); + if (this.SceneAPIEnabled) + ExpectedPythonCnt = ExpectedPythonCnt + (this.Count * 1); + + } + else + { + if (this.FaceAPIEnabled) + ExpectedPythonCnt = ExpectedPythonCnt + (this.Count * 3); + if (this.CustomModelEnabled) + ExpectedPythonCnt = ExpectedPythonCnt + (this.Count * 2); + if (this.DetectionAPIEnabled) + ExpectedPythonCnt = ExpectedPythonCnt + (this.Count * 2); + if (this.SceneAPIEnabled) + ExpectedPythonCnt = ExpectedPythonCnt + (this.Count * 2); + + } + + + } + + pytvalid = srvvalid && montys.Count >= ExpectedPythonCnt; + + + bool allvalid = srvvalid && redvalid && pytvalid; + + bool partvalid = (srvvalid || redvalid || pytvalid || Global.ProcessValid(this.PythonProc)); + + if (allvalid) + { + this.HasError = false; + this.IsStarted = true; + Log("Debug: DeepStack Desktop IS running from " + this.ServerEXE + $": {this.Count} copies + {montys.Count} copies of python.exe"); + } + else if (partvalid) + { + this.HasError = true; + this.IsStarted = true; + Log($"Deepstack partially running. Only {montys.Count} out of {ExpectedPythonCnt} copies of python.exe are running."); + + } + else + { + this.IsStarted = false; + this.HasError = false; + Log("Debug: DeepStack Desktop NOT running."); + } + } + else if (this.IsInstalled) + { + Log("Error: Deepstack v3.4 for Windows is not supported. Install latest version, or uninstall the windows version on this machine to stop seeing this message. https://docs.deepstack.cc/windows/index.html"); + } + + return Ret; + } + public bool RefreshDeepstackInfo() + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + bool Ret = false; + this.IsInstalled = false; + RegistryKey key = null; + List reglocs = new List(); + // {0E2C3125-3440-4622-A82A-3B1E07310EF2}_is1 + reglocs.Add(@"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{0E2C3125-3440-4622-A82A-3B1E07310EF2}_is1"); //new 2020 beta + reglocs.Add(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{0E2C3125-3440-4622-A82A-3B1E07310EF2}_is1"); //check for 64 bit install but I dont think it exists + reglocs.Add(@"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{B976B0A1-C83C-4735-AC7F-196922A2748B}_is1"); //old 32 bit version + reglocs.Add(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{B976B0A1-C83C-4735-AC7F-196922A2748B}_is1"); //check for 64 bit install but I dont think it exists + + + try + { + + foreach (string keystr in reglocs) + { + key = Registry.LocalMachine.OpenSubKey(keystr); + if (key != null) + break; + } + + + if (key != null) + { + this.DisplayName = (string)key.GetValue("DisplayName"); + this.DisplayVersion = (string)key.GetValue("DisplayVersion"); + this.IsNewVersion = this.DisplayName.Contains("202") || this.DisplayVersion.Contains("202"); + int year = this.DisplayVersion.GetWord("", ".").ToInt(); + this.Is2022OrLaterVersion = year >= 2022; + + string dspath = (string)key.GetValue("Inno Setup: App Path"); + if (!string.IsNullOrWhiteSpace(dspath)) + { + Log("Debug: Deepstack Desktop install path found in Uninstall registry: " + dspath); + + string exepth = Path.Combine(dspath, "server", "DeepStack.exe"); + if (File.Exists(exepth)) + { + this.IsInstalled = true; + this.DeepStackFolder = dspath; + this.DeepStackEXE = exepth; + + this.PythonEXE = Path.Combine(dspath, @"interpreter\python.exe"); + this.RedisEXE = Path.Combine(dspath, @"redis\redis-server.exe"); + + if (this.IsNewVersion) + { + this.ServerEXE = exepth; + } + else + { + this.ServerEXE = Path.Combine(dspath, @"server\server.exe"); + } + + + if (!string.Equals(dspath, this.DeepStackFolder, StringComparison.OrdinalIgnoreCase)) + { + Log("Debug: Deepstack running from non-default path: " + dspath); + this.NeedsSaving = true; + } + } + else + { + Log("debug: DeepStack File not found " + exepth); + } + } + else + { + Log("Error: DeepStack registry App Path not found? 'Inno Setup: App Path'"); + } + + } + + + if (!this.IsInstalled) + { + Log("Debug: DeepStack does not appear to be installed in add/remove programs."); + + if (File.Exists(this.DeepStackEXE)) + { + //this.DeepStackFolder = Path.GetDirectoryName(this.DeepStackEXE); + this.IsInstalled = true; + + } + else + { + this.IsInstalled = false; + Log("Debug: DeepStack NOT installed"); + } + } + + if (this.IsInstalled) + { + //get type and version + + //this file exists with 2020 version: + string platformfile = Path.Combine(this.DeepStackFolder, "server", "platform.json"); + //New 2021 platform.json: + //{ + // "PROFILE":"windows_native", + // "CUDA_MODE":true + //} + + //OLD 2020 beta platform.json (which did not change between cpu and gpu + //{ + // "PROFILE":"windows_native", + // "GPU":false + //} + + //For old 2020 beta we have to read SERVER.GO: + // os.Setenv("CUDA_MODE", "True") + + string servergofile = Path.Combine(this.DeepStackFolder, "server", "server.go"); + + if (File.Exists(platformfile)) + { + this.IsNewVersion = true; + this.IsActivated = true; + string platcontents = File.ReadAllText(platformfile); + + if (platcontents.IndexOf("CUDA_MODE", StringComparison.OrdinalIgnoreCase) >= 0) + { + //2021 version + DeepstackPlatformJson dp = Global.SetJSONString(platcontents); + if (string.Equals(dp.CUDA_MODE, "true", StringComparison.OrdinalIgnoreCase)) + this.Type = DeepStackTypeEnum.GPU; + else + this.Type = DeepStackTypeEnum.CPU; + } + else + { + //2020 version - server.go file + // os.Setenv("CUDA_MODE", "True") + string gocontents = File.ReadAllText(servergofile); + if (gocontents.IndexOf("\"CUDA_MODE\", \"True\"", StringComparison.OrdinalIgnoreCase) >= 0 || gocontents.IndexOf("\"CUDA_MODE\",\"True\"", StringComparison.OrdinalIgnoreCase) >= 0) + this.Type = DeepStackTypeEnum.GPU; + else + this.Type = DeepStackTypeEnum.CPU; + } + + + //get the version if not already found (Not always in sync with the registry installer key?): + if (string.IsNullOrWhiteSpace(this.DisplayVersion)) + { + List files = Global.GetFiles(this.DeepStackFolder, "*.iss", SearchOption.TopDirectoryOnly); + if (files.Count > 0) + { + string isscontents = File.ReadAllText(files[0].FullName); + //#define MyAppVersion "2020.12.beta" + this.DisplayVersion = isscontents.GetWord("MyAppVersion \"", "\""); + } + else + { + Log($"Error: Could not find .ISS file in Deepstack folder?"); + } + + } + + Log($"Debug: DeepStack v'{this.DisplayVersion}' ({this.Type}) is installed: " + this.DeepStackEXE); + //Try to get running processes in any case + bool success = this.GetDeepStackRun(); + + Ret = true; + + + + } + else + { + this.IsNewVersion = false; + this.Type = DeepStackTypeEnum.CPU; + this.DisplayVersion = "3.4"; + Log("Error: Deepstack v3.4 not supported. Install version 2020+"); + + + } + + } + + } + catch (Exception ex) + { + + Log("Error: While detecting DeepStack, got: " + ex.Msg()); + } + + if (key != null) + key.Dispose(); + + return Ret; + + } + + private string LastStdErr = ""; + public string GetStdErr() + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + string ret = ""; + try + { + string errfile = Path.Combine(Environment.GetEnvironmentVariable("LOCALAPPDATA"), "DeepStack", "logs", "stderr.txt"); + + if (File.Exists(errfile)) + { + string contents = Global.SafeLoadTextFile(errfile); + if (!string.IsNullOrWhiteSpace(contents)) + { + List lines = contents.SplitStr("\r\n", TrimStr: false); + for (int i = lines.Count - 1; i >= 0; i--) + { + if (!string.IsNullOrWhiteSpace(lines[i]) && lines[i].Substring(0) != " " && !lines[i].Contains("Traceback")) + { + //stderr.txt + // File "C://DeepStack\windows_packages\torch\cuda\__init__.py", line 480, in _lazy_new + // return super(_CudaBase, cls).__new__(cls, *args, **kwargs) + //RuntimeError: CUDA out of memory. Tried to allocate 20.00 MiB (GPU 0; 2.00 GiB total capacity; 35.77 MiB already allocated; 0 bytes free; 38.00 MiB reserved in total by PyTorch) + + //this error happens after sending an image to deepstack - I believe it is still running out of video memory: + // File "C:\DeepStack\intelligencelayer\shared\detection.py", line 138, in objectdetection + // os.remove(img_path) + //FileNotFoundError: [WinError 2] The system cannot find the file specified: 'C:\\Users\\Vorlon\\AppData\\Local\\Temp\\DeepStack\\83e9c5b0-d698-44f3-a8df-d19655d9f7da' + + if (lines[i].Trim() != LastStdErr) + { + ret = lines[i].Trim(); + LastStdErr = ret; + } + break; + } + } + } + } + } + catch (Exception ex) + { + Log($"Error: {ex.Msg()}"); + } + + return ret; + } + + public void PrintDeepStackError() + { + if (!this.IsInstalled) + return; + + string err = this.GetStdErr(); + + if (!string.IsNullOrEmpty(err)) + { + if (!string.IsNullOrEmpty(err)) + Log($"Error: Last Deepstack STDERR.TXT error is '{err}'"); + } + } + public async Task StartDeepstackAsync(bool ForceRestart = false) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + return await Task.Run(() => this.StartDeepstack(ForceRestart)); + } + public bool StartDeepstack(bool ForceRestart = false) + { + //This error happens when you run out of video memory: + //stderr.txt + // File "C://DeepStack\windows_packages\torch\cuda\__init__.py", line 480, in _lazy_new + // return super(_CudaBase, cls).__new__(cls, *args, **kwargs) + //RuntimeError: CUDA out of memory. Tried to allocate 20.00 MiB (GPU 0; 2.00 GiB total capacity; 35.77 MiB already allocated; 0 bytes free; 38.00 MiB reserved in total by PyTorch) + + //this error happens after sending an image to deepstack - I believe it is still running out of video memory: + // File "C:\DeepStack\intelligencelayer\shared\detection.py", line 138, in objectdetection + // os.remove(img_path) + //FileNotFoundError: [WinError 2] The system cannot find the file specified: 'C:\\Users\\Vorlon\\AppData\\Local\\Temp\\DeepStack\\83e9c5b0-d698-44f3-a8df-d19655d9f7da' + + + + if (this.Starting) + { + Log("Already starting?"); + return false; + } + + this.Starting= true; + + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + bool Ret = false; + + try + { + + if (!this.IsInstalled) + { + Log("Error: Cannot start because not installed."); + this.IsStarted = false; + return Ret; + } + else + { + if (this.IsStarted) + { + if (this.StopBeforeStart || ForceRestart) + { + Log("Debug: Stopping already running DeepStack instance..."); + Global.UpdateProgressBar("Stopping Deepstack...", 1, 1, 1); + this.StopDeepstack(); + Thread.Sleep(250); + } + else + { + Log("Debug: Deepstack is already running, not re-starting due to 'deepstack_stopbeforestart' setting = false in aitool.settings.json file."); + return Ret; + } + } + + Log("Starting DeepStack..."); + Global.UpdateProgressBar("Starting Deepstack...", 1, 1, 1); + + } + + Stopwatch SW = Stopwatch.StartNew(); + this.URLS = ""; + this.CommandLine = ""; + int ccnt = 0; + int dcnt = 0; + ExpectedPythonCnt = 0; + + if (this.IsNewVersion) + { + //start the custom model(s) + if (this.CustomModelEnabled) + { + List cports = this.CustomModelPort.SplitStr(",;|"); + List cpaths = this.CustomModelPath.SplitStr(",;|"); + List cnames = this.CustomModelName.SplitStr(",;|"); + List cmodes = this.CustomModelMode.SplitStr(",;|"); + + this.Count = cports.Count; + + if (cports.Count > 0 && cpaths.Count > 0 && cnames.Count > 0 && cports.Count == cpaths.Count && cports.Count == cnames.Count && cmodes.Count == cnames.Count) + { + for (int i = 0; i < cports.Count; i++) + { + + if (!Directory.Exists(cpaths[i])) + { + Log($"Error: Path '{cpaths[i]}' does not exist."); + continue; + } + + if (Global.IsLocalPortInUse(Convert.ToInt32(cports[i]))) + { + Log($"Error: Port {cports[i]} is already open, so cannot start deepstack.exe using that port."); + continue; + } + + + Global.ClsProcess prc = new Global.ClsProcess(); + prc.process.StartInfo.FileName = this.DeepStackEXE; + prc.process.StartInfo.WorkingDirectory = Path.GetDirectoryName(this.DeepStackEXE); + string mode = cmodes.GetStrAtIndex(i).ToLower().UpperFirst(); //mode appears to be case sensitive in the Sept 21 version of deepstack + if (mode.IsNotNull()) + mode = "--MODE " + mode + " "; + + prc.process.StartInfo.Arguments = $"--MODELSTORE-DETECTION \"{cpaths[i].Replace("\\", "/")}\" {mode}--PORT {cports[i]}"; + + if (!AppSettings.Settings.deepstack_debug) + { + prc.process.StartInfo.CreateNoWindow = true; + prc.process.StartInfo.UseShellExecute = false; + prc.process.StartInfo.RedirectStandardOutput = true; + prc.process.StartInfo.RedirectStandardError = true; + prc.process.EnableRaisingEvents = true; + prc.process.OutputDataReceived += this.DSHandleServerProcMSG; + prc.process.ErrorDataReceived += this.DSHandleServerProcERROR; + } + else + { + prc.process.StartInfo.UseShellExecute = false; + } + + + prc.process.Exited += (sender, e) => this.DSProcess_Exited(sender, e, "deepstack.exe"); //new EventHandler(myProcess_Exited); + prc.FileName = this.DeepStackEXE; + prc.CommandLine = prc.process.StartInfo.Arguments; + + ccnt++; + Log($"Starting Deepstack process Type='DeepStack_Custom' - {ccnt} of {this.Count}: {prc.process.StartInfo.FileName} {prc.process.StartInfo.Arguments}..."); + prc.process.Start(); + + Global.WaitForProcessToStart(prc.process, 4000, this.DeepStackEXE); + + if (AppSettings.Settings.deepstack_highpriority) + { + prc.process.PriorityClass = ProcessPriorityClass.High; + } + + if (!AppSettings.Settings.deepstack_debug) + { + prc.process.BeginOutputReadLine(); + prc.process.BeginErrorReadLine(); + } + + this.ServerProc.Add(prc); + + this.CommandLine += $"{prc.FileName} {prc.CommandLine}\r\n"; + + ClsURLItem url = new ClsURLItem($"http://127.0.0.1:{cports[i]}/v1/vision/custom/{cnames[i]}", AppSettings.Settings.AIURLList.Count + 1, URLTypeEnum.DeepStack_Custom); + this.URLS += $"{url.ToString()}\r\n"; + if (AppSettings.Settings.deepstack_autoadd && AppSettings.Settings.deepstack_autoadd && !AppSettings.Settings.AIURLList.Contains(url)) + { + Log($"Debug: Automatically adding local Windows Deepstack URL Type='{url.Type.ToString()}': " + url.ToString()); + AppSettings.Settings.AIURLList.Add(url); + } + + if (this.Is2022OrLaterVersion) + ExpectedPythonCnt += 1; //1 per port + else + ExpectedPythonCnt += 2; + + Thread.Sleep(100); + + } + } + else + { + this.CustomModelEnabled = false; + Log($"Error: Invalid custom model configuration. All settings must be configured and have same number of items."); + } + + } + + List ports = this.Port.SplitStr(",;|"); + this.Count = ports.Count; + + if (this.FaceAPIEnabled || this.SceneAPIEnabled || this.DetectionAPIEnabled && ports.Count > 0) + { + foreach (string CurPort in ports) + { + + if (Global.IsLocalPortInUse(Convert.ToInt32(CurPort))) + { + Log($"Error: Port {CurPort} is already open, so cannot start deepstack.exe using that port."); + continue; + } + + + Global.ClsProcess prc = new Global.ClsProcess(); + prc.process.StartInfo.FileName = this.DeepStackEXE; + prc.process.StartInfo.WorkingDirectory = Path.GetDirectoryName(this.DeepStackEXE); + string face = ""; + string scene = ""; + string detect = ""; + string admin = ""; + string api = ""; + string mode = ""; + + if (this.FaceAPIEnabled) + face = $"--VISION-FACE {this.FaceAPIEnabled} "; + if (this.SceneAPIEnabled) + scene = $"--VISION-SCENE {this.SceneAPIEnabled} "; + if (this.DetectionAPIEnabled) + detect = $"--VISION-DETECTION {this.DetectionAPIEnabled} "; + if (!string.IsNullOrEmpty(this.AdminKey)) + admin = $"--ADMIN-KEY {this.AdminKey} "; + if (!string.IsNullOrEmpty(this.APIKey)) + api = $"--API-KEY {this.APIKey} "; + + if (!string.IsNullOrEmpty(this.Mode) && !this.DisplayVersion.Contains("2020")) + mode = $"--MODE {this.Mode} "; + + prc.process.StartInfo.Arguments = $"{face}{scene}{detect}{admin}{api}{mode}--PORT {CurPort}"; + + if (!AppSettings.Settings.deepstack_debug) + { + prc.process.StartInfo.CreateNoWindow = true; + prc.process.StartInfo.UseShellExecute = false; + prc.process.StartInfo.RedirectStandardOutput = true; + prc.process.StartInfo.RedirectStandardError = true; + prc.process.EnableRaisingEvents = true; + prc.process.OutputDataReceived += this.DSHandleServerProcMSG; + prc.process.ErrorDataReceived += this.DSHandleServerProcERROR; + } + else + { + prc.process.StartInfo.UseShellExecute = false; + } + + if (this.DisplayVersion.Contains("2020") && !string.Equals(this.Mode, "medium", StringComparison.OrdinalIgnoreCase)) + prc.process.StartInfo.EnvironmentVariables["MODE"] = this.Mode; + + prc.process.Exited += (sender, e) => this.DSProcess_Exited(sender, e, "deepstack.exe"); //new EventHandler(myProcess_Exited); + prc.FileName = this.DeepStackEXE; + prc.CommandLine = prc.process.StartInfo.Arguments; + + dcnt++; + Log($"Starting {dcnt} of {ports.Count}: {prc.process.StartInfo.FileName} {prc.process.StartInfo.Arguments}..."); + prc.process.Start(); + + Global.WaitForProcessToStart(prc.process, 4000, this.DeepStackEXE); + + if (AppSettings.Settings.deepstack_highpriority) + { + prc.process.PriorityClass = ProcessPriorityClass.High; + } + + if (!AppSettings.Settings.deepstack_debug) + { + prc.process.BeginOutputReadLine(); + prc.process.BeginErrorReadLine(); + } + + this.ServerProc.Add(prc); + + this.CommandLine += $"{prc.FileName} {prc.CommandLine}\r\n"; + + if (this.DetectionAPIEnabled) + { + ClsURLItem url = new ClsURLItem($"http://127.0.0.1:{CurPort}/v1/vision/detection", AppSettings.Settings.AIURLList.Count + 1, URLTypeEnum.DeepStack); + this.URLS += $"{url.ToString()}\r\n"; + if (AppSettings.Settings.deepstack_autoadd && !AppSettings.Settings.AIURLList.Contains(url)) + { + Log($"Debug: Automatically adding local Windows Deepstack URL Type='{url.Type.ToString()}': " + url.ToString()); + AppSettings.Settings.AIURLList.Add(url); + } + if (this.Is2022OrLaterVersion) + ExpectedPythonCnt = ExpectedPythonCnt + 1; + else + ExpectedPythonCnt = ExpectedPythonCnt + 2; + + + } + + if (this.FaceAPIEnabled) + { + ClsURLItem url = new ClsURLItem($"http://127.0.0.1:{CurPort}/v1/vision/face/recognize", AppSettings.Settings.AIURLList.Count + 1, URLTypeEnum.DeepStack_Faces); + this.URLS += $"{url.ToString()}\r\n"; + if (AppSettings.Settings.deepstack_autoadd && !AppSettings.Settings.AIURLList.Contains(url)) + { + Log($"Debug: Automatically adding local Windows Deepstack URL Type='{url.Type.ToString()}': " + url.ToString()); + AppSettings.Settings.AIURLList.Add(url); + } + if (this.Is2022OrLaterVersion) + ExpectedPythonCnt = ExpectedPythonCnt + 2; + else + ExpectedPythonCnt = ExpectedPythonCnt + 3; + + } + + if (this.SceneAPIEnabled) + { + ClsURLItem url = new ClsURLItem($"http://127.0.0.1:{CurPort}/v1/vision/scene", AppSettings.Settings.AIURLList.Count + 1, URLTypeEnum.DeepStack_Scene); + this.URLS += $"{url.ToString()}\r\n"; + if (AppSettings.Settings.deepstack_autoadd && !AppSettings.Settings.AIURLList.Contains(url)) + { + Log($"Debug: Automatically adding local Windows Deepstack URL Type='{url.Type.ToString()}': " + url.ToString()); + AppSettings.Settings.AIURLList.Add(url); + } + if (this.Is2022OrLaterVersion) + ExpectedPythonCnt = ExpectedPythonCnt + 1; + else + ExpectedPythonCnt = ExpectedPythonCnt + 2; + } + + Thread.Sleep(100); + } + + this.Count = this.ServerProc.Count; + + this.IsStarted = true; + this.HasError = false; + Ret = true; + + //Lets wait for the rest of the python.exe processes to spawn and set their priority too (otherwise they are normal) + + int PythonCnt = 0; + int cc = 0; + + do + { + + List montys = Global.GetProcessesByPath(this.PythonEXE); + PythonCnt = montys.Count; + + + if (montys.Count >= ExpectedPythonCnt) + { + //when deepstack is running normaly there will be 2 python.exe processes running for each deepstack.exe + //Set priority for each this way since we didnt start them in the first place... + if (AppSettings.Settings.deepstack_highpriority) + { + foreach (Global.ClsProcess prc in montys) + { + cc++; + if (Global.ProcessValid(prc)) + { + try + { + prc.process.PriorityClass = ProcessPriorityClass.High; + prc.process.StartInfo.UseShellExecute = false; + prc.process.StartInfo.RedirectStandardOutput = true; + prc.process.StartInfo.RedirectStandardError = true; + prc.process.EnableRaisingEvents = true; + prc.process.OutputDataReceived += this.DSHandlePythonProcMSG; + prc.process.ErrorDataReceived += this.DSHandlePythonProcERROR; + prc.process.Exited += (sender, e) => this.DSProcess_Exited(sender, e, "python.exe"); //new EventHandler(myProcess_Exited); + } + catch { } + + } + } + } + + this.PythonProc = montys; + + break; + } + else + { + Log($"Debug: ...Waiting for {ExpectedPythonCnt} copies of {this.PythonEXE} to start (now={montys.Count})..."); + Thread.Sleep(250); + } + + } while (SW.ElapsedMilliseconds < 30000); //wait 30 seconds max + + this.RedisProc = Global.GetProcessesByPath(this.RedisEXE); + + if (Global.ProcessValid(this.RedisProc)) + { + if (AppSettings.Settings.deepstack_highpriority) + { + + this.RedisProc[0].process.PriorityClass = ProcessPriorityClass.High; + + } + } + else + { + this.HasError = true; + Log($"Error: 1 'redis-server.exe' processes did not start within " + SW.ElapsedMilliseconds + "ms"); + } + + if (PythonCnt >= ExpectedPythonCnt) + { + + Log("Debug: Started in " + SW.ElapsedMilliseconds + "ms"); + } + else + { + this.HasError = true; + Log($"Error: Only {PythonCnt} out of {ExpectedPythonCnt} 'python.exe' processes did not start within " + SW.ElapsedMilliseconds + "ms"); + } + + if (!this.HasError) + { + this.IsActivated = true; + this.IsStarted = true; + } + + } + else + { + //nothing to enable + Log("Debug: DeepStack config is invalid, or only a custom model is enabled."); + + } + } + else + { + Log("Error: DeepStack for Windows v2020 or higher is not installed. https://github.com/johnolafenwa/DeepStack/releases"); + } + } + catch (Exception ex) + { + this.IsStarted = false; + this.HasError = true; + Log("Error: Cannot start: " + ex.Msg()); + } + finally + { + this.Starting = false; + //this.PrintDeepStackError(); + } + + Global.SendMessage(MessageType.UpdateDeepstackStatus, "Manual start"); + + + return Ret; + + } + + public async Task StopDeepstackAsync() + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + return await Task.Run(() => this.StopDeepstack()); + } + + + public bool StopDeepstack() + { + + if (this.Stopping) + { + Log("Already stopping?"); + return false; + } + + this.Stopping= true; + + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + bool Ret = false; + bool err = false; + + Log("Stopping Deepstack..."); + Stopwatch sw = Stopwatch.StartNew(); + + //Try to get current running processes in any case + bool success = this.GetDeepStackRun(); + + //more than one python process we need to take care of... Sometimes MANY + bool perr = Global.KillProcesses(this.PythonProc); + bool rerr = Global.KillProcesses(this.RedisProc); + bool serr = Global.KillProcesses(this.ServerProc); + if (serr) + serr = Global.KillProcesses("C:\\DeepStack\\DeepStack.exe"); //force the old location to be killed also + + err = !perr || !rerr || !serr; + + if (!err) + { + this.DeepStackProc = new List(); + this.ServerProc = new List(); + this.PythonProc = new List(); + this.RedisProc = new List(); + //this.DeepStackProc = null; + Log("Debug: Stopped DeepStack in " + sw.ElapsedMilliseconds + "ms"); + Ret = true; + } + else + { + Log("Could not fully stop - This can happen for a few reasons: 1) This tool did not originally START deepstack. 2) If this tool is 32 bit it cannot stop 64 bit Deepstack process. Kill manually via task manager - Server.exe, python.exe, redis-server.exe."); + } + + this.IsStarted = false; + + this.Stopping = false; + + this.HasError = !Ret; + + Global.SendMessage(MessageType.UpdateDeepstackStatus, "Manual stop"); + + return Ret; + + } + public void ResetDeepstack() + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + try + { + if (this.IsStarted) + { + Log("Stopping already running DeepStack instance..."); + this.StopDeepstack(); + } + + Thread.Sleep(250); + + Log("Resetting DeepStack..."); + + String curdir = Path.Combine(Environment.GetEnvironmentVariable("USERPROFILE"), ".deepstack"); + if (Directory.Exists(curdir)) + { + Log($"Removing {curdir}..."); + Directory.Delete(curdir, true); + } + curdir = Path.Combine(Environment.GetEnvironmentVariable("TEMP"), "DeepStack"); + if (Directory.Exists(curdir)) + { + Log($"Removing {curdir}..."); + Directory.Delete(curdir, true); + } + curdir = Path.Combine(Environment.GetEnvironmentVariable("LOCALAPPDATA"), "DeepStack"); + if (Directory.Exists(curdir)) + { + Log($"Removing {curdir}..."); + Directory.Delete(curdir, true); + } + + List files = Global.GetFiles(this.DeepStackFolder, "*.pyc"); + if (files.Count > 0) + { + Log($"Removing {files.Count} compiled Python files {this.DeepStackFolder}\\*.PYC..."); + foreach (FileInfo fi in files) + { + fi.Delete(); + } + } + + } + catch (Exception ex) + { + + Log($"Error: {ex.Msg()}"); + } + } + private void DSProcess_Exited(object sender, System.EventArgs e, string Name) + { + + string output = "Debug: DeepStack>> Process exited: "; + if (sender != null) + { + Process prc = (Process)sender; + try + { + //if (!prc.HasExited) + //{ + output += $"Debug: Name='{Name}'"; + //} + string err = prc.ExitCode.ToString(); + if (prc.ExitCode == 0) + { + err += " (Normal exit)"; + } + else + { + err += " (Process killed or exited with error)"; + } + output += $", ExitCode='{err}', Runtime='{Math.Round((prc.ExitTime - prc.StartTime).TotalMilliseconds)}'ms"; + } + catch + { + + output += " (error accessing process properties)"; + } + } + if (!Name.Contains("Init:")) + { + this.IsStarted = false; + this.HasError = true; + } + Log(output, ""); + Global.SendMessage(MessageType.UpdateDeepstackStatus, "Process exited"); + + + } + + private void DSHandleRedisProcERROR(object sender, DataReceivedEventArgs line) + { + + try + { + if (string.IsNullOrWhiteSpace(line.Data) || Global.progress == null) + { + return; + } + + if (AppSettings.Settings.deepstack_debug) + Log($"Debug: DeepStack>> STDERR: {line.Data}", "", "", "REDIS-SERVER.EXE"); + + } + catch (Exception) + { + + throw; + } + } + private void DSHandleRedisProcMSG(object sender, DataReceivedEventArgs line) + { + try + { + if (string.IsNullOrWhiteSpace(line.Data) || Global.progress == null) + { + return; + } + + if (AppSettings.Settings.deepstack_debug) + Log($"Debug: DeepStack>> {line.Data}", "", "", "REDIS-SERVER.EXE"); + + } + catch (Exception) + { + + throw; + } + } + + private void DSHandleInitProcERROR(object sender, DataReceivedEventArgs line) + { + + try + { + if (string.IsNullOrWhiteSpace(line.Data) || Global.progress == null) + { + return; + } + + if (AppSettings.Settings.deepstack_debug) + Log($"Debug: DeepStack>> Init: STDERR: {line.Data}", "", "PYTHON.EXE"); + + } + catch (Exception) + { + + throw; + } + } + private void DSHandleInitProcMSG(object sender, DataReceivedEventArgs line) + { + try + { + if (string.IsNullOrWhiteSpace(line.Data) || Global.progress == null) + { + return; + } + + if (AppSettings.Settings.deepstack_debug) + Log($"Debug: DeepStack>> Init: {line.Data}", "", "", "PYTHON.EXE"); + + } + catch (Exception) + { + + throw; + } + } + + private void DSHandlePythonProcERROR(object sender, DataReceivedEventArgs line) + { + + try + { + if (string.IsNullOrWhiteSpace(line.Data) || Global.progress == null) + { + return; + } + + if (AppSettings.Settings.deepstack_debug) + Log($"Debug: DeepStack>> STDERR: {line.Data}", "", "", "PYTHON.EXE"); + + } + catch (Exception) + { + + throw; + } + } + private void DSHandlePythonProcMSG(object sender, DataReceivedEventArgs line) + { + try + { + if (string.IsNullOrWhiteSpace(line.Data) || Global.progress == null) + { + return; + } + + if (AppSettings.Settings.deepstack_debug) + Log($"Debug: DeepStack>> {line.Data}", "", "", "PYTHON.EXE"); + + } + catch (Exception) + { + + throw; + } + } + + private void DSHandleServerProcERROR(object sender, DataReceivedEventArgs line) + { + + try + { + if (string.IsNullOrWhiteSpace(line.Data) || Global.progress == null) + { + return; + } + + if (AppSettings.Settings.deepstack_debug) + Log($"Debug: DeepStack>> STDERR: {line.Data}", "", "", "SERVER.EXE"); + + } + catch (Exception) + { + + throw; + } + } + private void DSHandleServerProcMSG(object sender, DataReceivedEventArgs line) + { + try + { + if (string.IsNullOrWhiteSpace(line.Data) || Global.progress == null) + { + return; + } + + //--VISION-DETECTION True --PORT 84 + Process prc = (Process)sender; + string dsinfo = $"DeepStack:{prc.StartInfo.Arguments.GetWord("PORT ", " |")}>>"; + + //Console.WriteLine(Message); + if (line.Data.IndexOf("visit localhost to activate deepstack", StringComparison.OrdinalIgnoreCase) >= 0) + { + this.IsActivated = false; + } + else if (line.Data.IndexOf("deepstack is active", StringComparison.OrdinalIgnoreCase) >= 0) + { + this.IsActivated = true; + } + + if (line.Data.IndexOf("vision/detection", StringComparison.OrdinalIgnoreCase) >= 0) + { + this.VisionDetectionRunning = true; + } + + Log($"Debug: {dsinfo} {line.Data}", "", "", "DEEPSTACK.EXE"); + + } + catch { } + } + + + } +} diff --git a/src/UI/Deepstack.png b/src/UI/Deepstack.png new file mode 100644 index 00000000..2f81f59e Binary files /dev/null and b/src/UI/Deepstack.png differ diff --git a/src/UI/Extensions/DoubleExtensions.cs b/src/UI/Extensions/DoubleExtensions.cs new file mode 100644 index 00000000..fc89f022 --- /dev/null +++ b/src/UI/Extensions/DoubleExtensions.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AITool +{ + public static class DoubleExtensions + { + public static int ToInt(this double val, bool Abs = false) + { + try + { + if (!val.IsNull()) + { + if (!Abs) + return Convert.ToInt32(val); //I believe ToInt32 rounds up so 1.5 is 2. (int) just truncates the decimal + else + return Math.Abs(Convert.ToInt32(val)); //I believe ToInt32 rounds up so 1.5 is 2. (int) just truncates the decimal + } + } + catch { } + + return 0; + } + public static float ToFloat(this double val, bool Abs = false) + { + try + { + if (!val.IsNull()) + { + if (!Abs) + return Convert.ToSingle(val); + else + return Math.Abs(Convert.ToSingle(val)); + } + } + catch { } + + return 0; + } + public static decimal Round(this decimal val, int Places = 1) + { + try + { + if (!val.IsNull()) + return Math.Round(val, Places); + } + catch { } + + return 0; + } + public static double Round(this double val, int Places = 1) + { + try + { + if (!val.IsNull()) + return Math.Round(val, Places); + } + catch { } + + return 0; + } + public static string ToPercent(this double val, int Places = 1) + { + string chars = ""; + if (Places > 0) + chars = "." + new string('#', Places); + + return val.Round(Places).ToString("##0" + chars) + "%"; + + } + + } +} diff --git a/src/UI/Extensions/EnumerableExtensions.cs b/src/UI/Extensions/EnumerableExtensions.cs new file mode 100644 index 00000000..8d8e9dbd --- /dev/null +++ b/src/UI/Extensions/EnumerableExtensions.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AITool +{ + public static class EnumerableExtensions + { + /// + /// Checks whether the given sequence is the empty set. + /// + /// + /// + /// + public static bool IsEmpty(this IEnumerable sequence) + { + return sequence == null || !sequence.Any(); + } + public static bool IsNotEmpty(this IEnumerable sequence) + { + return sequence != null && sequence.Any(); + } + + public static string GetStrAtIndex(this IEnumerable sequence, int idx) + { + //This is to make sure we return at lest the first element if the requested index is too high + if (sequence.IsEmpty()) + return ""; + int maxidx = sequence.Count() - 1; + if (idx <= maxidx) + return sequence.ElementAt(idx).ToString(); + else + return sequence.ElementAt(0).ToString(); + + } + public static void Move(this List list, int oldIndex, int newIndex) + { + // exit if positions are equal or outside array + if ((oldIndex == newIndex) || (0 > oldIndex) || (oldIndex >= list.Count) || (0 > newIndex) || + (newIndex >= list.Count)) return; + // local variables + var i = 0; + T tmp = list[oldIndex]; + // move element down and shift other elements up + if (oldIndex < newIndex) + { + for (i = oldIndex; i < newIndex; i++) + { + list[i] = list[i + 1]; + } + } + // move element up and shift other elements down + else + { + for (i = oldIndex; i > newIndex; i--) + { + list[i] = list[i - 1]; + } + } + // put element from position 1 to destination + list[newIndex] = tmp; + } + } + +} diff --git a/src/UI/Extensions/ExceptionExtensions.cs b/src/UI/Extensions/ExceptionExtensions.cs new file mode 100644 index 00000000..6ba42f4a --- /dev/null +++ b/src/UI/Extensions/ExceptionExtensions.cs @@ -0,0 +1,283 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; + +namespace AITool +{ + public static class ExceptionExtensions + { + public static string Msg(this Exception MyEx2, [CallerMemberName] string MemberName = null) + { + //Gets the nested/inner exception if found, and also line and column of error - assuming PDB is in same folder + string msg = ""; + try + { + string typ = ""; + Exception BaseEx = MyEx2.GetBaseException(); + Exception InnerEx = MyEx2.InnerException; + if (InnerEx != null) + { + typ = InnerEx.GetType().Name; + string Extra = GetExtraExceptionInfo(MyEx2); + msg = $"{InnerEx.Message} [{typ}] (In {Extra})"; + } + else if (BaseEx != null) + { + typ = BaseEx.GetType().Name; + string Extra = GetExtraExceptionInfo(MyEx2); + msg = $"{BaseEx.Message} [{typ}] (In {Extra})"; + } + else + { + typ = MyEx2.GetType().Name; + string Extra = GetExtraExceptionInfo(MyEx2); + msg = $"{MyEx2.Message} [{typ}] (In {Extra})"; + } + + //ExtraInfo = $"Mod: {LastMod} Line:{Frames(Frames.Count - 1).GetFileLineNumber}:{Frames(Frames.Count - 1).GetFileColumnNumber}" + + if (msg.IndexOf(MemberName, StringComparison.OrdinalIgnoreCase) == -1) + { + msg = $"{msg} ({MemberName})"; + } + + } + catch (Exception) { } + + msg = msg.Replace("\r\n", "|"); + return msg; + + } + + public static string GetExtraExceptionInfo(Exception ThisEX) + { + string ExtraInfo = ""; + try + { + //Dim st As StackTrace = New StackTrace(ThisEX, True) + if (ThisEX.StackTrace != null) + { + // at System.String.Substring(Int32 startIndex, Int32 length) + // at AITool.Shell.test2() in C:\Documents\Projects\_GIT\VorlonCD\bi-aidetection\src\UI\Shell.cs:line 91 + // at AITool.Shell.test1() in C:\Documents\Projects\_GIT\VorlonCD\bi-aidetection\src\UI\Shell.cs:line 86 + // at AITool.Shell.d__15.MoveNext() in C:\Documents\Projects\_GIT\VorlonCD\bi-aidetection\src\UI\Shell.cs:line 101 + + string ST = ThisEX.StackTrace; + + List lines = ST.SplitStr("\r\n"); + string lastmod = ""; + for (int i = lines.Count - 1; i >= 0; i--) + { + if (!lines[i].Contains(" System.") && !lines[i].IsEmpty()) + { + string fnc = lines[i].GetWord("at ", " in"); + fnc = fnc.GetWord(".", ""); + //Shell.d__15.MoveNext() + string tmp = fnc.GetWord("<", ">"); + if (tmp.IsEmpty()) + { + //Shell.test1() + fnc = fnc.Replace("()", ""); + } + else + { + fnc = fnc.GetWord("", ".") + "." + tmp; + } + string line = lines[i].GetWord("line ", ""); + if (!line.IsEmpty()) + line = ":" + line; + + if (!lastmod.IsEmpty()) + fnc = fnc.Replace(lastmod, ""); + + if (!fnc.IsEmpty()) + ExtraInfo += $"{fnc}{line} > "; + + lastmod = lines[i].GetWord("at ", ".") + "."; + } + } + + //string[] SpltStr = new string[1] { " at " }; + //string[] Splt = ST.Split(SpltStr, StringSplitOptions.None); + //string Lst = Splt[Splt.Length - 1].Replace(".MoveNext()", "").Trim(); + //Lst = GetWordBetween(Lst, "", " in "); + //string[] Splt2 = Lst.Split('.'); + //string LastMod = Splt2[Splt2.Length - 1].Trim(); + //StackFrame[] Frames = (new StackTrace(ThisEX, true)).GetFrames(); + + + //ExtraInfo = $"Mod: {LastMod} Line:{Frames[Frames.Length - 1].GetFileLineNumber()}"; + + ExtraInfo = ExtraInfo.Trim(" >".ToCharArray()); + } + } + catch { } + return ExtraInfo; + } + + public static string ToDetailedString(this Exception exception) => + ToDetailedString(exception, ExceptionOptions.Default); + + public static string ToDetailedString(this Exception exception, ExceptionOptions options) + { + if (exception == null) + { + throw new ArgumentNullException(nameof(exception)); + } + + var stringBuilder = new StringBuilder(); + + AppendValue(stringBuilder, "Type", exception.GetType().FullName, options); + + foreach (PropertyInfo property in exception + .GetType() + .GetProperties() + .OrderByDescending(x => string.Equals(x.Name, nameof(exception.Message), StringComparison.Ordinal)) + .ThenByDescending(x => string.Equals(x.Name, nameof(exception.Source), StringComparison.Ordinal)) + .ThenBy(x => string.Equals(x.Name, nameof(exception.InnerException), StringComparison.Ordinal)) + .ThenBy(x => string.Equals(x.Name, nameof(AggregateException.InnerExceptions), StringComparison.Ordinal))) + { + var value = property.GetValue(exception, null); + if (value == null && options.OmitNullProperties) + { + if (options.OmitNullProperties) + { + continue; + } + else + { + value = string.Empty; + } + } + + AppendValue(stringBuilder, property.Name, value, options); + } + + return stringBuilder.ToString().TrimEnd('\r', '\n'); + } + + private static void AppendCollection( + StringBuilder stringBuilder, + string propertyName, + IEnumerable collection, + ExceptionOptions options) + { + stringBuilder.AppendLine($"{options.Indent}{propertyName} ="); + + var innerOptions = new ExceptionOptions(options, options.CurrentIndentLevel + 1); + + var i = 0; + foreach (var item in collection) + { + var innerPropertyName = $"[{i}]"; + + if (item is Exception) + { + var innerException = (Exception)item; + AppendException( + stringBuilder, + innerPropertyName, + innerException, + innerOptions); + } + else + { + AppendValue( + stringBuilder, + innerPropertyName, + item, + innerOptions); + } + + ++i; + } + } + + private static void AppendException( + StringBuilder stringBuilder, + string propertyName, + Exception exception, + ExceptionOptions options) + { + var innerExceptionString = ToDetailedString( + exception, + new ExceptionOptions(options, options.CurrentIndentLevel + 1)); + + stringBuilder.AppendLine($"{options.Indent}{propertyName} ="); + stringBuilder.AppendLine(innerExceptionString); + } + + private static string IndentString(string value, ExceptionOptions options) + { + return value.Replace(Environment.NewLine, Environment.NewLine + options.Indent); + } + + private static void AppendValue( + StringBuilder stringBuilder, + string propertyName, + object value, + ExceptionOptions options) + { + if (value is DictionaryEntry) + { + DictionaryEntry dictionaryEntry = (DictionaryEntry)value; + stringBuilder.AppendLine($"{options.Indent}{propertyName} = {dictionaryEntry.Key} : {dictionaryEntry.Value}"); + } + else if (value is Exception) + { + var innerException = (Exception)value; + AppendException( + stringBuilder, + propertyName, + innerException, + options); + } + else if (value is IEnumerable && !(value is string)) + { + var collection = (IEnumerable)value; + if (collection.GetEnumerator().MoveNext()) + { + AppendCollection( + stringBuilder, + propertyName, + collection, + options); + } + } + else + { + stringBuilder.AppendLine($"{options.Indent}{propertyName} = {value}"); + } + } + } + + public struct ExceptionOptions + { + public static readonly ExceptionOptions Default = new ExceptionOptions() + { + CurrentIndentLevel = 0, + IndentSpaces = 4, + OmitNullProperties = true + }; + + internal ExceptionOptions(ExceptionOptions options, int currentIndent) + { + this.CurrentIndentLevel = currentIndent; + this.IndentSpaces = options.IndentSpaces; + this.OmitNullProperties = options.OmitNullProperties; + } + + internal string Indent { get { return new string(' ', this.IndentSpaces * this.CurrentIndentLevel); } } + + internal int CurrentIndentLevel { get; set; } + + public int IndentSpaces { get; set; } + + public bool OmitNullProperties { get; set; } + } +} diff --git a/src/UI/Extensions/RectangleExtensions.cs b/src/UI/Extensions/RectangleExtensions.cs new file mode 100644 index 00000000..75b33c8f --- /dev/null +++ b/src/UI/Extensions/RectangleExtensions.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AITool +{ + public static class RectangleExtensions + { + + public static double Area(this Rectangle rect) + { + return rect.Width * rect.Height; + } + public static double Perimeter(this Rectangle rect) + { + return 2 * (rect.Width + rect.Height); + } + public static double PercentOfSize(this Rectangle rect, Rectangle compareRect) + { + + double Diff = compareRect.Area() - rect.Area(); + double DiffInArea = Math.Abs(Diff); //we dont care if change is positive or negative + double PercentDifferent = 100 - (DiffInArea / rect.Area() * 100); + + return PercentDifferent; + + } + + public static double IntersectPercent(this Rectangle rect, Rectangle compareRect) + { + + Rectangle objIntersect = Rectangle.Intersect(rect, compareRect); + + double percentage = ((objIntersect.Width * objIntersect.Height * 2) * 100) / + ((compareRect.Width * compareRect.Height) + (compareRect.Width * rect.Height)); + + return percentage; + } + //I think there is another way of doing this extension so we dont have to use new Rectangle().FromVertices... + public static Rectangle FromVertices(this Rectangle rect, List vertices) + { + var minX = vertices.Min(p => p.X); + var minY = vertices.Min(p => p.Y); + var maxX = vertices.Max(p => p.X); + var maxY = vertices.Max(p => p.Y); + return new Rectangle(new Point(minX, minY), new Size(maxX - minX, maxY - minY)); + + } + + public static int MidX(this Rectangle rect) + { + return rect.Left + rect.Width / 2; + } + public static int MidY(this Rectangle rect) + { + return rect.Top + rect.Height / 2; + } + public static Point Center(this Rectangle rect) + { + return new Point(rect.MidX(), rect.MidY()); + } + + public static float MidX(this RectangleF rect) + { + return rect.Left + rect.Width / 2; + } + public static float MidY(this RectangleF rect) + { + return rect.Top + rect.Height / 2; + } + public static PointF Center(this RectangleF rect) + { + return new PointF(rect.MidX(), rect.MidY()); + } + } +} diff --git a/src/UI/Extensions/StringExtensions.cs b/src/UI/Extensions/StringExtensions.cs new file mode 100644 index 00000000..12cc6009 --- /dev/null +++ b/src/UI/Extensions/StringExtensions.cs @@ -0,0 +1,621 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Security; +using System.Text; + +using Microsoft.Win32.SafeHandles; + +namespace AITool +{ + public static class StringExtensions + { + public static bool Within(this string value, string findStr, int maxlen = 0, string trimStartChars = " .>-*") + { + if (value.IsEmpty() || findStr.IsEmpty()) + return false; + + if (maxlen > 0 && value.Length > maxlen) + value = value.Substring(0, maxlen); + + //trim the start of the string to get rid of any leading spaces or periods + value = value.TrimStart(trimStartChars.ToCharArray()); + + return value.Contains(findStr, StringComparison.OrdinalIgnoreCase); + } + public static bool IsNumeric(this string theValue) + { + double retNum; + return double.TryParse(theValue.Trim(), System.Globalization.NumberStyles.Any, System.Globalization.NumberFormatInfo.InvariantInfo, out retNum); + } + + [DebuggerStepThrough] + public static string JoinStr(this List values, string separator) + { + //this custom join should not be used for CSV type output because it does not include empty items + if (values == null) + throw new ArgumentNullException("values"); + + if (values.Count == 0) + return String.Empty; + + if (separator == null) + separator = String.Empty; + + StringBuilder sb = new StringBuilder(""); + + for (int i = 0; i < values.Count; i++) + { + if (!values[i].IsEmpty()) + sb.Append(values[i].Trim() + separator); + } + + return sb.ToString().Trim(separator.ToCharArray()); + + } + + [DebuggerStepThrough] + public static List SplitStr(this string InList, string Separators, bool RemoveEmpty = true, bool TrimStr = true, bool ToLower = false, string TrimChars = " ") + { + List Ret = new List(); + if (!string.IsNullOrWhiteSpace(InList)) + { + StringSplitOptions SSO = StringSplitOptions.None; + + if (RemoveEmpty) + SSO = StringSplitOptions.RemoveEmptyEntries; + + string[] splt = InList.Split(Separators.ToCharArray(), SSO); + for (int i = 0; i < splt.Length; i++) + { + if (ToLower) + splt[i] = splt[i].ToLower(); + + if (RemoveEmpty && !string.IsNullOrWhiteSpace(splt[i])) + { + if (TrimStr) + Ret.Add(splt[i].Trim(TrimChars)); + else + Ret.Add(splt[i]); + } + else if (!RemoveEmpty) + { + if (TrimStr) + Ret.Add(splt[i].Trim()); + else + Ret.Add(splt[i]); + } + + } + } + return Ret; + } + + //create a string extension that prepends a string to a string if it doesnt already have it. If it does already have it then move it to the front. The extension should have a parameter for the separator character. The extension should be case insensitive + //example: "1,2,3,4,5" prepend "3" with separator "," result: "3,1,2,4,5" + + + + //public static string Prepend(this string input, string valueToPrepend, int maxItems = 8, char separator = ',') + //{ + // string val = valueToPrepend.Trim(separator, ' '); + + // if (string.IsNullOrWhiteSpace(val)) + // return input; + + // var resultBuilder = new StringBuilder(); + // var uniqueValues = new HashSet(StringComparer.OrdinalIgnoreCase); + + // uniqueValues.Add(val); + // resultBuilder.Append(val); + + // int itemCount = 1; // Start from 1 as we have already added the valueToPrepend + + // foreach (var item in input.Split(separator).Select(s => s.Trim())) + // { + // if (!string.IsNullOrEmpty(item) && !uniqueValues.Contains(item) && itemCount < maxItems) + // { + // resultBuilder.Append(separator); + // resultBuilder.Append(item); + // uniqueValues.Add(item); + // itemCount++; + // } + // } + + // return resultBuilder.ToString(); + //} + + public static string Prepend(this string input, string valueToPrepend, int maxItems = 6, char separator = ',') + { + string val = valueToPrepend.Trim(separator, ' '); + var inputValues = input.Split(separator, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()); + + // Using StringBuilder for efficient string concatenation + var resultBuilder = new StringBuilder(val); + + int itemCount = 1; // Start from 1 as we have already added the valueToPrepend + foreach (var item in inputValues) + { + if (!StringComparer.OrdinalIgnoreCase.Equals(item, val) && itemCount < maxItems) + { + resultBuilder.Append(separator); + resultBuilder.Append(item); + itemCount++; + } + } + + return resultBuilder.ToString(); + } + //[DebuggerStepThrough] + public static string Append(this string value, string newvalue, string Separators, string ListSeparators = ",;") + { + //appends only if the string doesnt already have it and trims the separator characters + + if (newvalue.IsEmpty()) + return value; + + if (value.IsEmpty()) + return newvalue.Trim((Separators + ListSeparators).ToCharArray()); + + List newlist = newvalue.SplitStr(ListSeparators); + List existinglist = value.SplitStr((Separators + ListSeparators).Replace(" ", "")); + string newstr = ""; + foreach (var item in newlist) + { + if (!Global.IsInList(item, existinglist, TrueIfEmpty: true)) + { + newstr += item + ", "; + } + } + + return (value.Trim((Separators + ListSeparators).ToCharArray()) + Separators + newstr).Trim((Separators + ListSeparators).ToCharArray()); + + } + [DebuggerStepThrough] + public static string Truncate(this string value, int maxLength = 512, bool ellipsis = true) + { + if (string.IsNullOrEmpty(value)) return value; + + if (value.Length <= maxLength) return value; + + if (ellipsis) return value.Substring(0, maxLength) + "..."; + + return value.Substring(0, maxLength); + + } + [DebuggerStepThrough] + public static string Trim(this string value, string trimstrlist) + { + if (string.IsNullOrEmpty(value)) return value; + + return value.Trim(trimstrlist.ToCharArray()); + + } + [DebuggerStepThrough] + public static string ReplaceChars(this string value, char replacechar) + { + if (string.IsNullOrEmpty(value)) return value; + + return new string(replacechar, value.Length); + + } + + [DebuggerStepThrough] + public static double ToDouble(this string value) + { + double outdbl = 0; + + //Take into account that some countries may use 123,45 vs 123.45 + if (!value.IsNull() && double.TryParse(value.Trim(), System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out outdbl)) + return outdbl; + else + return 0; + } + public static float ToFloat(this string value) + { + float outdbl = 0; + + //Take into account that some countries may use 123,45 vs 123.45 + if (!value.IsNull() && float.TryParse(value.Trim(), System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out outdbl)) + return outdbl; + else + return 0; + } + + [DebuggerStepThrough] + public static int ToInt(this string value) + { + if (!value.IsNull()) + return Convert.ToInt32(value.Trim()); + else + return 0; + } + + [DebuggerStepThrough] + public static bool IsEmpty(this string value) + { + return string.IsNullOrWhiteSpace(value); + } + public static bool IsNotEmpty(this string value) + { + return !string.IsNullOrWhiteSpace(value); + } + + [DebuggerStepThrough] + public static bool IsNull(this object obj) + { + if (obj == null) + return true; + + if (obj is string && string.IsNullOrWhiteSpace((string)obj)) + return true; + + if (obj is IntPtr && (IntPtr)obj == IntPtr.Zero) + return true; + + if (obj is UIntPtr && (UIntPtr)obj == UIntPtr.Zero) + return true; + + if (obj is SafeFileHandle && ((SafeFileHandle)obj).IsInvalid) + return true; + + return false; + } + + [DebuggerStepThrough] + public static bool IsNotNull(this object obj) + { + return !obj.IsNull(); + } + [DebuggerStepThrough] + public static string CleanString(this string inp, string ReplaceStr = " ") + { + if (inp == null || string.IsNullOrWhiteSpace(inp)) + { + return ""; + } + else + { + return inp.Replace("\0", ReplaceStr).Replace("\r", ReplaceStr).Replace("\n", ReplaceStr); + } + } + [DebuggerStepThrough] + public static string UpperFirst(this string s) + { + if (string.IsNullOrEmpty(s)) + { + return string.Empty; + } + //char[] a = s.ToCharArray(); + //a[0] = char.ToUpper(a[0]); + //return new string(a); + return s.FormatPascalAndAcronym(); + } + private static string FormatPascalAndAcronym(this string input) + { + //Input: "QWERTYSomeThing OmitTRYSomeThing MayBeWorkingFYI" + //Output: "QWERTY Some Thing Omit TRY Some Thing May Be Working FYI" + + var builder = new StringBuilder(Char.ToUpper(input[0]).ToString()); + if (builder.Length > 0) + { + for (var index = 1; index < input.Length; index++) + { + char prevChar = input[index - 1]; + char nextChar = index + 1 < input.Length ? input[index + 1] : '\0'; + + bool isNextLower = Char.IsLower(nextChar); + bool isNextUpper = Char.IsUpper(nextChar); + bool isPresentUpper = Char.IsUpper(input[index]); + bool isPrevLower = Char.IsLower(prevChar); + bool isPrevUpper = Char.IsUpper(prevChar); + bool PrevSpace = Char.IsWhiteSpace(prevChar); + + if (!string.IsNullOrWhiteSpace(prevChar.ToString()) && + ((isPrevUpper && isPresentUpper && isNextLower) || + (isPrevLower && isPresentUpper && isNextLower) || + (isPrevLower && isPresentUpper && isNextUpper))) + { + builder.Append(' '); + builder.Append(Char.ToUpper(input[index])); + } + else if (PrevSpace) + builder.Append(Char.ToUpper(input[index])); + + else + { + builder.Append(input[index]); + } + } + } + return builder.ToString(); + } + public static bool IsStringBefore(this string teststring, string first, string second) + { + + //test something like this - make sure we arnt picking up the semicolon that could be part of a URL: + //person, car ; http://URL/; + bool ret = false; + int firstidx = teststring.IndexOf(first, StringComparison.OrdinalIgnoreCase); + + if (firstidx > -1) + { + int secondidx = teststring.IndexOf(second, StringComparison.OrdinalIgnoreCase); + if (secondidx > -1) + { + if (firstidx < secondidx) + { + ret = true; + } + } + else + { + ret = true; + } + } + + return ret; + + } + [DebuggerStepThrough] + public static bool Has(this string value, string FindStr) + { + return !string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(FindStr) + && value.Contains(FindStr, StringComparison.OrdinalIgnoreCase); + + } + [DebuggerStepThrough] + public static bool EqualsIgnoreCase(this string value, string FindStr) + { + if (string.Equals(value, FindStr, StringComparison.OrdinalIgnoreCase)) + return true; + else + return false; + } + [DebuggerStepThrough] + public static string GetWord(this string InpStr, string JustBefore, string JustAfter, Int32 LastPos = 0, Int32 FirstPos = 0, bool NoTrim = false, bool MustFindJustAfter = false) + { + string Ret = ""; + + try + { + string[] JB = JustBefore.Split('|'); + string[] JA = JustAfter.Split('|'); + int JBPos = 0; + int JAPos = 0; + string BefStr = ""; + string AftStr = ""; + int WordLen = 0; + string RetWord = ""; + + if (JustBefore.Length > 0) + { + foreach (string BefStrTmp in JB) + { + BefStr = BefStrTmp; + if (BefStr.Length > 0) + { + JBPos = InpStr.IndexOf(BefStr, FirstPos, StringComparison.OrdinalIgnoreCase); + if (JBPos >= 0) + break; + } + } + } + else + JBPos = FirstPos; + if (JBPos == -1) + return Ret; + int FirstFnd = InpStr.Length; + foreach (string AftStrTmp in JA) + { + AftStr = AftStrTmp; + if (AftStr.Length > 0) + { + Int32 count = InpStr.Length - (JBPos + BefStr.Length); + Int32 StartIndex = JBPos + BefStr.Length; + JAPos = InpStr.IndexOf(AftStr, StartIndex, count, StringComparison.OrdinalIgnoreCase); + if (JAPos >= 0) + // If JAPos <= FirstFnd Then FirstFnd = JAPos + FirstFnd = Math.Min(JAPos, FirstFnd); + } + } + + // If FirstFnd <= JAPos Then + JAPos = FirstFnd; + // End If + + if (JAPos == -1 || JAPos == 0 || JustAfter.Length == 0) + { + if (!MustFindJustAfter) + JAPos = InpStr.Length; + } + + if (JAPos <= JBPos) + return Ret; + + WordLen = JAPos - (JBPos + BefStr.Length); + if (WordLen > 0) + { + RetWord = InpStr.Substring(JBPos + BefStr.Length, WordLen); + LastPos = JAPos; // JBPos + BefStr.Length + RetWord.Length + if (NoTrim) + Ret = RetWord; + else + Ret = RetWord.Trim(); + } + } + // Return "" + + catch (Exception) + { + } + finally + { + + } + + return Ret; + } + + static byte[] entropy = System.Text.Encoding.Unicode.GetBytes("sdsgtj;lrjwteojtkslkdjsl;dvlbmv.bmvlfu7r0tret-rereigjejgkgljg42"); + + //this is not truly secure, but better than storing plain text in the JSON file + public static string Encrypt(this string input) + { + byte[] encryptedData = System.Security.Cryptography.ProtectedData.Protect( + System.Text.Encoding.Unicode.GetBytes(input), + entropy, + System.Security.Cryptography.DataProtectionScope.CurrentUser); + return Convert.ToBase64String(encryptedData); + } + + public static string Decrypt(this string encryptedData) + { + if (String.IsNullOrEmpty(encryptedData)) + return ""; + + try + { + byte[] decryptedData = System.Security.Cryptography.ProtectedData.Unprotect( + Convert.FromBase64String(encryptedData), + entropy, + System.Security.Cryptography.DataProtectionScope.CurrentUser); + return System.Text.Encoding.Unicode.GetString(decryptedData); + } + catch + { + return ""; + } + } + + public static SecureString ToSecureString(this string input) + { + SecureString secure = new SecureString(); + foreach (char c in input) + { + secure.AppendChar(c); + } + secure.MakeReadOnly(); + return secure; + } + + public static string ToInsecureString(this SecureString input) + { + string returnValue = string.Empty; + IntPtr ptr = System.Runtime.InteropServices.Marshal.SecureStringToBSTR(input); + try + { + returnValue = System.Runtime.InteropServices.Marshal.PtrToStringBSTR(ptr); + } + finally + { + System.Runtime.InteropServices.Marshal.ZeroFreeBSTR(ptr); + } + return returnValue; + } + + /// + /// Implement's VB's Like operator logic. + /// + public static bool IsLike(this string s, string pattern) + { + // Characters matched so far + int matched = 0; + + // Loop through pattern string + for (int i = 0; i < pattern.Length;) + { + // Check for end of string + if (matched > s.Length) + return false; + + // Get next pattern character + char c = pattern[i++]; + if (c == '[') // Character list + { + // Test for exclude character + bool exclude = (i < pattern.Length && pattern[i] == '!'); + if (exclude) + i++; + // Build character list + int j = pattern.IndexOf(']', i); + if (j < 0) + j = s.Length; + HashSet charList = CharListToSet(pattern.Substring(i, j - i)); + i = j + 1; + + if (charList.Contains(s[matched]) == exclude) + return false; + matched++; + } + else if (c == '?') // Any single character + { + matched++; + } + else if (c == '#') // Any single digit + { + if (!Char.IsDigit(s[matched])) + return false; + matched++; + } + else if (c == '*') // Zero or more characters + { + if (i < pattern.Length) + { + // Matches all characters until + // next character in pattern + char next = pattern[i]; + int j = s.IndexOf(next, matched); + if (j < 0) + return false; + matched = j; + } + else + { + // Matches all remaining characters + matched = s.Length; + break; + } + } + else // Exact character + { + if (matched >= s.Length || c != s[matched]) + return false; + matched++; + } + } + // Return true if all characters matched + return (matched == s.Length); + } + + /// + /// Converts a string of characters to a HashSet of characters. If the string + /// contains character ranges, such as A-Z, all characters in the range are + /// also added to the returned set of characters. + /// + /// Character list string + private static HashSet CharListToSet(string charList) + { + HashSet set = new HashSet(); + + for (int i = 0; i < charList.Length; i++) + { + if ((i + 1) < charList.Length && charList[i + 1] == '-') + { + // Character range + char startChar = charList[i++]; + i++; // Hyphen + char endChar = (char)0; + if (i < charList.Length) + endChar = charList[i++]; + for (int j = startChar; j <= endChar; j++) + set.Add((char)j); + } + else set.Add(charList[i]); + } + return set; + } + + } +} diff --git a/src/UI/Extensions/TaskCancellationExtension.cs b/src/UI/Extensions/TaskCancellationExtension.cs new file mode 100644 index 00000000..ebd0a543 --- /dev/null +++ b/src/UI/Extensions/TaskCancellationExtension.cs @@ -0,0 +1,179 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace AITool +{ + public static class TaskCancellationExtension + { + /// + /// add cancellation functionality to Task T + /// + /// + /// + /// + /// + /// + public static async Task CancelAfter( + this Task task, CancellationToken cancellationToken) + { + var tcs = new TaskCompletionSource(); + using (cancellationToken.Register( + s => ((TaskCompletionSource)s).TrySetResult(true), tcs)) + if (task != await Task.WhenAny(task, tcs.Task)) + throw new OperationCanceledException(cancellationToken); + return await task; + } + + + /// + /// add cancellation functionality to Task T with exception message + /// + /// + /// + /// + /// + /// + /// + public static async Task CancelAfter( + this Task task, CancellationToken cancellationToken, string message) + { + var tcs = new TaskCompletionSource(); + using (cancellationToken.Register( + s => ((TaskCompletionSource)s).TrySetResult(true), tcs)) + if (task != await Task.WhenAny(task, tcs.Task)) + throw new OperationCanceledException(message, cancellationToken); + return await task; + } + + + /// + /// add cancellation functionality to Tasks + /// + /// + /// + /// + /// + /// + public static async Task CancelAfter( + this Task task, CancellationToken cancellationToken) + { + var tcs = new TaskCompletionSource(); + using (cancellationToken.Register( + s => ((TaskCompletionSource)s).TrySetResult(true), tcs)) + if (task != await Task.WhenAny(task, tcs.Task)) + throw new OperationCanceledException(cancellationToken); + await task; + } + + + /// + /// add cancellation functionality to Tasks with exception message + /// + /// + /// + /// + /// + /// + /// + public static async Task CancelAfter( + this Task task, CancellationToken cancellationToken, string message) + { + var tcs = new TaskCompletionSource(); + using (cancellationToken.Register( + s => ((TaskCompletionSource)s).TrySetResult(true), tcs)) + if (task != await Task.WhenAny(task, tcs.Task)) + throw new OperationCanceledException(message, cancellationToken); + await task; + } + + + /// + /// add cancellation functionality to Task T + /// + /// + /// + /// + /// + /// + public static async Task CancelAfter( + this Task task, int milliseconds) + { + var cts = new CancellationTokenSource(); + cts.CancelAfter(milliseconds); + var tcs = new TaskCompletionSource(); + using (cts.Token.Register( + s => ((TaskCompletionSource)s).TrySetResult(true), tcs)) + if (task != await Task.WhenAny(task, tcs.Task)) + throw new OperationCanceledException(cts.Token); + return await task; + } + + + /// + /// add cancellation functionality to Task T with exception message + /// + /// + /// + /// + /// + /// + /// + public static async Task CancelAfter( + this Task task, int milliseconds, string message) + { + var cts = new CancellationTokenSource(); + cts.CancelAfter(milliseconds); + var tcs = new TaskCompletionSource(); + using (cts.Token.Register( + s => ((TaskCompletionSource)s).TrySetResult(true), tcs)) + if (task != await Task.WhenAny(task, tcs.Task)) + throw new OperationCanceledException(message, cts.Token); + return await task; + } + + /// + /// add cancellation functionality to Task + /// + /// + /// + /// + /// + /// + public static async Task CancelAfter( + this Task task, int milliseconds) + { + var cts = new CancellationTokenSource(); + cts.CancelAfter(milliseconds); + var tcs = new TaskCompletionSource(); + using (cts.Token.Register( + s => ((TaskCompletionSource)s).TrySetResult(true), tcs)) + if (task != await Task.WhenAny(task, tcs.Task)) + throw new OperationCanceledException(cts.Token); + await task; + } + + + /// + /// add cancellation functionality to Task with exception message + /// + /// + /// + /// + /// + /// + /// + public static async Task CancelAfter( + this Task task, int milliseconds, string message) + { + var cts = new CancellationTokenSource(); + cts.CancelAfter(milliseconds); + var tcs = new TaskCompletionSource(); + using (cts.Token.Register( + s => ((TaskCompletionSource)s).TrySetResult(true), tcs)) + if (task != await Task.WhenAny(task, tcs.Task)) + throw new OperationCanceledException(message, cts.Token); + await task; + } + } +} \ No newline at end of file diff --git a/src/UI/Extensions/TimeSpanExtensions.cs b/src/UI/Extensions/TimeSpanExtensions.cs new file mode 100644 index 00000000..7262d20b --- /dev/null +++ b/src/UI/Extensions/TimeSpanExtensions.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AITool +{ + public static class TimeSpanExtensions + { + + public static string FormatTS(this TimeSpan span, bool shortformat) + { + string formatted = ""; + + if (!shortformat) + formatted = string.Format("{0}{1}{2}{3}{4}", span.Duration().Days > 0 ? string.Format("{0:0} day{1}, ", span.TotalDays, span.TotalDays == 1 ? String.Empty : "s") : string.Empty, + span.Duration().Hours > 0 ? string.Format("{0:0} hr{1}, ", span.Hours, span.Hours == 1 ? String.Empty : "s") : string.Empty, + span.Duration().Minutes > 0 ? string.Format("{0:0} min{1}, ", span.Minutes, span.Minutes == 1 ? String.Empty : "s") : string.Empty, + span.Duration().Seconds > 0 ? string.Format("{0:0} sec{1}, ", span.Seconds, span.Seconds == 1 ? String.Empty : "s") : string.Empty, + span.Duration().Milliseconds > 20 ? string.Format("{0:0} ms", span.Milliseconds) : string.Empty); + else + formatted = string.Format("{0}{1}{2}{3}{4}", span.Duration().Days > 0 ? string.Format("{0:0}d", span.Days) : string.Empty, + span.Duration().Hours > 0 ? string.Format("{0:0}h", span.Hours) : string.Empty, + span.Duration().Minutes > 0 ? string.Format("{0:0}m", span.Minutes) : string.Empty, + span.Duration().Seconds > 0 ? string.Format("{0:0}s", span.Seconds) : string.Empty, + span.Duration().Milliseconds > 20 ? string.Format("{0:0}ms", span.Milliseconds) : string.Empty); + + formatted = formatted.Trim(", ".ToCharArray()); + + if (string.IsNullOrEmpty(formatted)) + formatted = "0 sec"; + + return formatted; + } + } +} diff --git a/src/UI/FrmSplash.Designer.cs b/src/UI/FrmSplash.Designer.cs new file mode 100644 index 00000000..4394fda8 --- /dev/null +++ b/src/UI/FrmSplash.Designer.cs @@ -0,0 +1,116 @@ +namespace AITool +{ + partial class FrmSplash + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.progressBar = new System.Windows.Forms.ProgressBar(); + this.pictureBox1 = new System.Windows.Forms.PictureBox(); + this.lbl_version = new System.Windows.Forms.Label(); + this.lbl_status = new System.Windows.Forms.Label(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit(); + this.SuspendLayout(); + // + // progressBar + // + this.progressBar.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.progressBar.Location = new System.Drawing.Point(8, 263); + this.progressBar.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.progressBar.Name = "progressBar"; + this.progressBar.Size = new System.Drawing.Size(517, 21); + this.progressBar.TabIndex = 1; + // + // pictureBox1 + // + this.pictureBox1.Anchor = System.Windows.Forms.AnchorStyles.None; + this.pictureBox1.Image = global::AITool.Properties.Resources.Logo; + this.pictureBox1.Location = new System.Drawing.Point(159, 4); + this.pictureBox1.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.pictureBox1.Name = "pictureBox1"; + this.pictureBox1.Size = new System.Drawing.Size(216, 211); + this.pictureBox1.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.pictureBox1.TabIndex = 0; + this.pictureBox1.TabStop = false; + // + // lbl_version + // + this.lbl_version.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.lbl_version.Location = new System.Drawing.Point(8, 216); + this.lbl_version.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + this.lbl_version.Name = "lbl_version"; + this.lbl_version.Size = new System.Drawing.Size(517, 13); + this.lbl_version.TabIndex = 2; + this.lbl_version.Text = "AITool"; + this.lbl_version.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // lbl_status + // + this.lbl_status.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.lbl_status.Font = new System.Drawing.Font("Segoe UI", 9F); + this.lbl_status.Location = new System.Drawing.Point(5, 243); + this.lbl_status.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + this.lbl_status.Name = "lbl_status"; + this.lbl_status.Size = new System.Drawing.Size(520, 18); + this.lbl_status.TabIndex = 3; + this.lbl_status.Text = "Loading"; + this.lbl_status.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // FrmSplash + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.BackColor = System.Drawing.Color.GhostWhite; + this.ClientSize = new System.Drawing.Size(533, 292); + this.ControlBox = false; + this.Controls.Add(this.lbl_status); + this.Controls.Add(this.lbl_version); + this.Controls.Add(this.progressBar); + this.Controls.Add(this.pictureBox1); + this.Font = new System.Drawing.Font("Segoe UI", 8.25F); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None; + this.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "FrmSplash"; + this.ShowIcon = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; + this.Text = "FrmSplash"; + this.Load += new System.EventHandler(this.FrmSplash_Load); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.PictureBox pictureBox1; + public System.Windows.Forms.ProgressBar progressBar; + private System.Windows.Forms.Label lbl_version; + public System.Windows.Forms.Label lbl_status; + } +} \ No newline at end of file diff --git a/src/UI/FrmSplash.cs b/src/UI/FrmSplash.cs new file mode 100644 index 00000000..14963961 --- /dev/null +++ b/src/UI/FrmSplash.cs @@ -0,0 +1,30 @@ +using System; +using System.Reflection; +using System.Windows.Forms; + +namespace AITool +{ + public partial class FrmSplash : Form + { + protected override CreateParams CreateParams + { + get + { + const int CS_DROPSHADOW = 0x20000; + CreateParams cp = base.CreateParams; + cp.ClassStyle |= CS_DROPSHADOW; + return cp; + } + } + public FrmSplash() + { + this.InitializeComponent(); + } + + private void FrmSplash_Load(object sender, EventArgs e) + { + string AssemVer = Assembly.GetExecutingAssembly().GetName().Version.ToString(); + this.lbl_version.Text = $"AITOOL version {AssemVer} built on {Global.RetrieveLinkerTimestamp()}"; + } + } +} diff --git a/src/UI/FrmSplash.resx b/src/UI/FrmSplash.resx new file mode 100644 index 00000000..1af7de15 --- /dev/null +++ b/src/UI/FrmSplash.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/UI/Frm_AIServerDeepstackEdit.Designer.cs b/src/UI/Frm_AIServerDeepstackEdit.Designer.cs new file mode 100644 index 00000000..cf581d33 --- /dev/null +++ b/src/UI/Frm_AIServerDeepstackEdit.Designer.cs @@ -0,0 +1,757 @@ + +namespace AITool +{ + partial class Frm_AIServerDeepstackEdit + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + components = new System.ComponentModel.Container(); + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Frm_AIServerDeepstackEdit)); + label1 = new System.Windows.Forms.Label(); + label2 = new System.Windows.Forms.Label(); + lbl_type = new System.Windows.Forms.Label(); + tb_URL = new System.Windows.Forms.TextBox(); + bt_Save = new System.Windows.Forms.Button(); + label4 = new System.Windows.Forms.Label(); + tb_ActiveTimeRange = new System.Windows.Forms.TextBox(); + label5 = new System.Windows.Forms.Label(); + tb_ApplyToCams = new System.Windows.Forms.TextBox(); + chk_Enabled = new System.Windows.Forms.CheckBox(); + groupBox1 = new System.Windows.Forms.GroupBox(); + gb_AIServerQueue = new System.Windows.Forms.GroupBox(); + lbl_ImgQueueStats = new System.Windows.Forms.Label(); + lbl_QueueTimeStats = new System.Windows.Forms.Label(); + lbl_QueueLengthStats = new System.Windows.Forms.Label(); + label19 = new System.Windows.Forms.Label(); + label18 = new System.Windows.Forms.Label(); + cb_AllowAIServerBasedQueue = new System.Windows.Forms.CheckBox(); + tb_SkipIfImgQueueLengthLarger = new System.Windows.Forms.TextBox(); + tb_SkipIfAIQueueTimeOverSecs = new System.Windows.Forms.TextBox(); + tb_MaxQueueLength = new System.Windows.Forms.TextBox(); + label17 = new System.Windows.Forms.Label(); + label16 = new System.Windows.Forms.Label(); + cb_IgnoreOffline = new System.Windows.Forms.CheckBox(); + tb_Name = new System.Windows.Forms.TextBox(); + label14 = new System.Windows.Forms.Label(); + cb_OnlyLinked = new System.Windows.Forms.CheckBox(); + cb_TimeoutError = new System.Windows.Forms.CheckBox(); + label12 = new System.Windows.Forms.Label(); + label10 = new System.Windows.Forms.Label(); + groupBoxLinked = new System.Windows.Forms.GroupBox(); + cb_LinkedServers = new System.Windows.Forms.CheckBox(); + label13 = new System.Windows.Forms.Label(); + checkedComboBoxLinked = new CheckComboBoxTest.CheckedComboBox(); + groupBoxRefine = new System.Windows.Forms.GroupBox(); + cb_RefinementServer = new System.Windows.Forms.CheckBox(); + label11 = new System.Windows.Forms.Label(); + tb_RefinementObjects = new System.Windows.Forms.TextBox(); + tb_Upper = new System.Windows.Forms.TextBox(); + tb_LinkedRefineTimeout = new System.Windows.Forms.TextBox(); + tb_Lower = new System.Windows.Forms.TextBox(); + label3 = new System.Windows.Forms.Label(); + tb_timeout = new System.Windows.Forms.TextBox(); + tb_ImagesPerMonth = new System.Windows.Forms.TextBox(); + label15 = new System.Windows.Forms.Label(); + label9 = new System.Windows.Forms.Label(); + label8 = new System.Windows.Forms.Label(); + labelTimeout = new System.Windows.Forms.Label(); + label7 = new System.Windows.Forms.Label(); + toolTip1 = new System.Windows.Forms.ToolTip(components); + linkHelpURL = new System.Windows.Forms.LinkLabel(); + btTest = new System.Windows.Forms.Button(); + bt_clear = new System.Windows.Forms.Button(); + timer1 = new System.Windows.Forms.Timer(components); + groupBox1.SuspendLayout(); + gb_AIServerQueue.SuspendLayout(); + groupBoxLinked.SuspendLayout(); + groupBoxRefine.SuspendLayout(); + SuspendLayout(); + // + // label1 + // + label1.AutoSize = true; + label1.Location = new System.Drawing.Point(84, 16); + label1.Name = "label1"; + label1.Size = new System.Drawing.Size(33, 13); + label1.TabIndex = 0; + label1.Text = "Type:"; + // + // label2 + // + label2.AutoSize = true; + label2.Location = new System.Drawing.Point(87, 64); + label2.Name = "label2"; + label2.Size = new System.Drawing.Size(30, 13); + label2.TabIndex = 1; + label2.Text = "URL:"; + // + // lbl_type + // + lbl_type.AutoSize = true; + lbl_type.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, 0); + lbl_type.ForeColor = System.Drawing.Color.DodgerBlue; + lbl_type.Location = new System.Drawing.Point(121, 16); + lbl_type.Name = "lbl_type"; + lbl_type.Size = new System.Drawing.Size(10, 15); + lbl_type.TabIndex = 0; + lbl_type.Text = "."; + // + // tb_URL + // + tb_URL.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + tb_URL.Font = new System.Drawing.Font("Consolas", 8.25F); + tb_URL.Location = new System.Drawing.Point(121, 60); + tb_URL.Name = "tb_URL"; + tb_URL.Size = new System.Drawing.Size(508, 20); + tb_URL.TabIndex = 1; + tb_URL.TextChanged += tb_URL_TextChanged; + // + // bt_Save + // + bt_Save.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; + bt_Save.DialogResult = System.Windows.Forms.DialogResult.OK; + bt_Save.Location = new System.Drawing.Point(576, 533); + bt_Save.Name = "bt_Save"; + bt_Save.Size = new System.Drawing.Size(70, 30); + bt_Save.TabIndex = 17; + bt_Save.Text = "Save"; + bt_Save.UseVisualStyleBackColor = true; + bt_Save.Click += bt_Save_Click; + // + // label4 + // + label4.AutoSize = true; + label4.Location = new System.Drawing.Point(14, 116); + label4.Name = "label4"; + label4.Size = new System.Drawing.Size(103, 13); + label4.TabIndex = 1; + label4.Text = "Active Time Range:"; + // + // tb_ActiveTimeRange + // + tb_ActiveTimeRange.Font = new System.Drawing.Font("Consolas", 8.25F); + tb_ActiveTimeRange.Location = new System.Drawing.Point(121, 112); + tb_ActiveTimeRange.Name = "tb_ActiveTimeRange"; + tb_ActiveTimeRange.Size = new System.Drawing.Size(152, 20); + tb_ActiveTimeRange.TabIndex = 3; + toolTip1.SetToolTip(tb_ActiveTimeRange, "Active time range in form of 00:00:00-23:59:59"); + // + // label5 + // + label5.AutoSize = true; + label5.Location = new System.Drawing.Point(18, 90); + label5.Name = "label5"; + label5.Size = new System.Drawing.Size(99, 13); + label5.TabIndex = 1; + label5.Text = "Apply to Cameras:"; + // + // tb_ApplyToCams + // + tb_ApplyToCams.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + tb_ApplyToCams.Font = new System.Drawing.Font("Consolas", 8.25F); + tb_ApplyToCams.Location = new System.Drawing.Point(121, 86); + tb_ApplyToCams.Name = "tb_ApplyToCams"; + tb_ApplyToCams.Size = new System.Drawing.Size(508, 20); + tb_ApplyToCams.TabIndex = 2; + toolTip1.SetToolTip(tb_ApplyToCams, "A comma separated list of cameras that this AI server will work with.\r\n\r\nLeave empty for ALL."); + // + // chk_Enabled + // + chk_Enabled.AutoSize = true; + chk_Enabled.ForeColor = System.Drawing.Color.DodgerBlue; + chk_Enabled.Location = new System.Drawing.Point(11, 0); + chk_Enabled.Name = "chk_Enabled"; + chk_Enabled.Size = new System.Drawing.Size(68, 17); + chk_Enabled.TabIndex = 0; + chk_Enabled.Text = "Enabled"; + chk_Enabled.UseVisualStyleBackColor = true; + chk_Enabled.CheckedChanged += chk_Enabled_CheckedChanged; + // + // groupBox1 + // + groupBox1.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + groupBox1.Controls.Add(gb_AIServerQueue); + groupBox1.Controls.Add(cb_IgnoreOffline); + groupBox1.Controls.Add(tb_Name); + groupBox1.Controls.Add(label14); + groupBox1.Controls.Add(cb_OnlyLinked); + groupBox1.Controls.Add(cb_TimeoutError); + groupBox1.Controls.Add(label12); + groupBox1.Controls.Add(label10); + groupBox1.Controls.Add(groupBoxLinked); + groupBox1.Controls.Add(groupBoxRefine); + groupBox1.Controls.Add(tb_Upper); + groupBox1.Controls.Add(tb_LinkedRefineTimeout); + groupBox1.Controls.Add(tb_Lower); + groupBox1.Controls.Add(label3); + groupBox1.Controls.Add(tb_timeout); + groupBox1.Controls.Add(tb_ImagesPerMonth); + groupBox1.Controls.Add(chk_Enabled); + groupBox1.Controls.Add(tb_ApplyToCams); + groupBox1.Controls.Add(label15); + groupBox1.Controls.Add(label1); + groupBox1.Controls.Add(tb_ActiveTimeRange); + groupBox1.Controls.Add(lbl_type); + groupBox1.Controls.Add(label9); + groupBox1.Controls.Add(label8); + groupBox1.Controls.Add(label2); + groupBox1.Controls.Add(tb_URL); + groupBox1.Controls.Add(labelTimeout); + groupBox1.Controls.Add(label7); + groupBox1.Controls.Add(label4); + groupBox1.Controls.Add(label5); + groupBox1.Location = new System.Drawing.Point(5, 6); + groupBox1.Name = "groupBox1"; + groupBox1.Size = new System.Drawing.Size(636, 522); + groupBox1.TabIndex = 7; + groupBox1.TabStop = false; + // + // gb_AIServerQueue + // + gb_AIServerQueue.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + gb_AIServerQueue.Controls.Add(lbl_ImgQueueStats); + gb_AIServerQueue.Controls.Add(lbl_QueueTimeStats); + gb_AIServerQueue.Controls.Add(lbl_QueueLengthStats); + gb_AIServerQueue.Controls.Add(label19); + gb_AIServerQueue.Controls.Add(label18); + gb_AIServerQueue.Controls.Add(cb_AllowAIServerBasedQueue); + gb_AIServerQueue.Controls.Add(tb_SkipIfImgQueueLengthLarger); + gb_AIServerQueue.Controls.Add(tb_SkipIfAIQueueTimeOverSecs); + gb_AIServerQueue.Controls.Add(tb_MaxQueueLength); + gb_AIServerQueue.Controls.Add(label17); + gb_AIServerQueue.Controls.Add(label16); + gb_AIServerQueue.Location = new System.Drawing.Point(7, 389); + gb_AIServerQueue.Name = "gb_AIServerQueue"; + gb_AIServerQueue.Size = new System.Drawing.Size(619, 127); + gb_AIServerQueue.TabIndex = 26; + gb_AIServerQueue.TabStop = false; + // + // lbl_ImgQueueStats + // + lbl_ImgQueueStats.AutoSize = true; + lbl_ImgQueueStats.Font = new System.Drawing.Font("Segoe UI", 8.142858F); + lbl_ImgQueueStats.Location = new System.Drawing.Point(280, 102); + lbl_ImgQueueStats.Name = "lbl_ImgQueueStats"; + lbl_ImgQueueStats.Size = new System.Drawing.Size(10, 13); + lbl_ImgQueueStats.TabIndex = 28; + lbl_ImgQueueStats.Text = "."; + // + // lbl_QueueTimeStats + // + lbl_QueueTimeStats.AutoSize = true; + lbl_QueueTimeStats.Font = new System.Drawing.Font("Segoe UI", 8.142858F); + lbl_QueueTimeStats.Location = new System.Drawing.Point(281, 74); + lbl_QueueTimeStats.Name = "lbl_QueueTimeStats"; + lbl_QueueTimeStats.Size = new System.Drawing.Size(10, 13); + lbl_QueueTimeStats.TabIndex = 28; + lbl_QueueTimeStats.Text = "."; + // + // lbl_QueueLengthStats + // + lbl_QueueLengthStats.AutoSize = true; + lbl_QueueLengthStats.Font = new System.Drawing.Font("Segoe UI", 8.142858F); + lbl_QueueLengthStats.Location = new System.Drawing.Point(281, 47); + lbl_QueueLengthStats.Name = "lbl_QueueLengthStats"; + lbl_QueueLengthStats.Size = new System.Drawing.Size(10, 13); + lbl_QueueLengthStats.TabIndex = 28; + lbl_QueueLengthStats.Text = "."; + // + // label19 + // + label19.AutoSize = true; + label19.Location = new System.Drawing.Point(11, 74); + label19.Name = "label19"; + label19.Size = new System.Drawing.Size(201, 13); + label19.TabIndex = 27; + label19.Text = "AI Server Queue Seconds Larger Than:"; + // + // label18 + // + label18.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + label18.AutoSize = true; + label18.ForeColor = System.Drawing.Color.Gray; + label18.Location = new System.Drawing.Point(11, 23); + label18.Name = "label18"; + label18.Size = new System.Drawing.Size(531, 13); + label18.TabIndex = 26; + label18.Text = "Skip to the next AITOOL url (if more than one available) when any of the following conditions are met:"; + // + // cb_AllowAIServerBasedQueue + // + cb_AllowAIServerBasedQueue.AutoSize = true; + cb_AllowAIServerBasedQueue.ForeColor = System.Drawing.Color.DodgerBlue; + cb_AllowAIServerBasedQueue.Location = new System.Drawing.Point(7, 0); + cb_AllowAIServerBasedQueue.Name = "cb_AllowAIServerBasedQueue"; + cb_AllowAIServerBasedQueue.Size = new System.Drawing.Size(212, 17); + cb_AllowAIServerBasedQueue.TabIndex = 23; + cb_AllowAIServerBasedQueue.Text = "Allow AI Server Based Queue / MESH"; + toolTip1.SetToolTip(cb_AllowAIServerBasedQueue, resources.GetString("cb_AllowAIServerBasedQueue.ToolTip")); + cb_AllowAIServerBasedQueue.UseVisualStyleBackColor = true; + cb_AllowAIServerBasedQueue.CheckedChanged += cb_AllowAIServerBasedQueue_CheckedChanged; + // + // tb_SkipIfImgQueueLengthLarger + // + tb_SkipIfImgQueueLengthLarger.Location = new System.Drawing.Point(219, 97); + tb_SkipIfImgQueueLengthLarger.Name = "tb_SkipIfImgQueueLengthLarger"; + tb_SkipIfImgQueueLengthLarger.Size = new System.Drawing.Size(56, 22); + tb_SkipIfImgQueueLengthLarger.TabIndex = 25; + toolTip1.SetToolTip(tb_SkipIfImgQueueLengthLarger, "If the AITOOL image queue is larger than this value, and the server\r\nhas at least 1 item in its queue, skip to the next server URL to give it a\r\nchance to help lower the queue."); + // + // tb_SkipIfAIQueueTimeOverSecs + // + tb_SkipIfAIQueueTimeOverSecs.Location = new System.Drawing.Point(219, 69); + tb_SkipIfAIQueueTimeOverSecs.Name = "tb_SkipIfAIQueueTimeOverSecs"; + tb_SkipIfAIQueueTimeOverSecs.Size = new System.Drawing.Size(56, 22); + tb_SkipIfAIQueueTimeOverSecs.TabIndex = 25; + toolTip1.SetToolTip(tb_SkipIfAIQueueTimeOverSecs, "If the AI Server queue has been busy for over this number of seconds,\r\njump to the next AITOOL server url."); + // + // tb_MaxQueueLength + // + tb_MaxQueueLength.Location = new System.Drawing.Point(219, 42); + tb_MaxQueueLength.Name = "tb_MaxQueueLength"; + tb_MaxQueueLength.Size = new System.Drawing.Size(56, 22); + tb_MaxQueueLength.TabIndex = 25; + toolTip1.SetToolTip(tb_MaxQueueLength, "CodeProjectAI internal default maximum is 1024. Lower this number to force the server\r\nto be 'busy' so that the next AITOOL server url in the list is used. "); + // + // label17 + // + label17.AutoSize = true; + label17.ForeColor = System.Drawing.SystemColors.ControlText; + label17.Location = new System.Drawing.Point(41, 102); + label17.Name = "label17"; + label17.Size = new System.Drawing.Size(171, 13); + label17.TabIndex = 24; + label17.Text = "AITOOL Img Queue Larger Than:"; + // + // label16 + // + label16.AutoSize = true; + label16.ForeColor = System.Drawing.SystemColors.ControlText; + label16.Location = new System.Drawing.Point(18, 47); + label16.Name = "label16"; + label16.Size = new System.Drawing.Size(194, 13); + label16.TabIndex = 24; + label16.Text = "AI Server Queue Length Larger Than:"; + // + // cb_IgnoreOffline + // + cb_IgnoreOffline.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right; + cb_IgnoreOffline.AutoSize = true; + cb_IgnoreOffline.ForeColor = System.Drawing.Color.DodgerBlue; + cb_IgnoreOffline.Location = new System.Drawing.Point(179, 142); + cb_IgnoreOffline.Name = "cb_IgnoreOffline"; + cb_IgnoreOffline.Size = new System.Drawing.Size(109, 17); + cb_IgnoreOffline.TabIndex = 22; + cb_IgnoreOffline.Text = "Ignore if Offline"; + toolTip1.SetToolTip(cb_IgnoreOffline, "If we cant even ping the server (ie a machine is asleep part of the day) we ignore any errors and skip the URL."); + cb_IgnoreOffline.UseVisualStyleBackColor = true; + // + // tb_Name + // + tb_Name.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + tb_Name.Location = new System.Drawing.Point(121, 33); + tb_Name.Name = "tb_Name"; + tb_Name.Size = new System.Drawing.Size(508, 22); + tb_Name.TabIndex = 21; + // + // label14 + // + label14.AutoSize = true; + label14.ForeColor = System.Drawing.SystemColors.ControlDark; + label14.Location = new System.Drawing.Point(170, 266); + label14.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + label14.Name = "label14"; + label14.Size = new System.Drawing.Size(393, 13); + label14.TabIndex = 20; + label14.Text = "(This should be checked if ANOTHER server uses this one as a linked server)"; + // + // cb_OnlyLinked + // + cb_OnlyLinked.AutoSize = true; + cb_OnlyLinked.ForeColor = System.Drawing.Color.DodgerBlue; + cb_OnlyLinked.Location = new System.Drawing.Point(16, 265); + cb_OnlyLinked.Name = "cb_OnlyLinked"; + cb_OnlyLinked.Size = new System.Drawing.Size(156, 17); + cb_OnlyLinked.TabIndex = 10; + cb_OnlyLinked.Text = "Use ONLY as linked server"; + cb_OnlyLinked.UseVisualStyleBackColor = true; + cb_OnlyLinked.CheckedChanged += cb_OnlyLinked_CheckedChanged; + // + // cb_TimeoutError + // + cb_TimeoutError.AutoSize = true; + cb_TimeoutError.ForeColor = System.Drawing.Color.Firebrick; + cb_TimeoutError.Location = new System.Drawing.Point(537, 355); + cb_TimeoutError.Name = "cb_TimeoutError"; + cb_TimeoutError.Size = new System.Drawing.Size(51, 17); + cb_TimeoutError.TabIndex = 14; + cb_TimeoutError.Text = "Error"; + toolTip1.SetToolTip(cb_TimeoutError, "An error will show in the log if a timeout happens"); + cb_TimeoutError.UseVisualStyleBackColor = true; + // + // label12 + // + label12.AutoSize = true; + label12.Location = new System.Drawing.Point(501, 357); + label12.Name = "label12"; + label12.Size = new System.Drawing.Size(21, 13); + label12.TabIndex = 18; + label12.Text = "ms"; + // + // label10 + // + label10.AutoSize = true; + label10.ForeColor = System.Drawing.Color.Firebrick; + label10.Location = new System.Drawing.Point(8, 357); + label10.Name = "label10"; + label10.Size = new System.Drawing.Size(433, 13); + label10.TabIndex = 18; + label10.Text = "Maximum time to wait for a LINKED or REFINEMENT server URL to become available:"; + // + // groupBoxLinked + // + groupBoxLinked.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + groupBoxLinked.Controls.Add(cb_LinkedServers); + groupBoxLinked.Controls.Add(label13); + groupBoxLinked.Controls.Add(checkedComboBoxLinked); + groupBoxLinked.Location = new System.Drawing.Point(8, 288); + groupBoxLinked.Name = "groupBoxLinked"; + groupBoxLinked.Size = new System.Drawing.Size(619, 63); + groupBoxLinked.TabIndex = 17; + groupBoxLinked.TabStop = false; + // + // cb_LinkedServers + // + cb_LinkedServers.AutoSize = true; + cb_LinkedServers.ForeColor = System.Drawing.Color.DodgerBlue; + cb_LinkedServers.Location = new System.Drawing.Point(8, 0); + cb_LinkedServers.Name = "cb_LinkedServers"; + cb_LinkedServers.Size = new System.Drawing.Size(238, 17); + cb_LinkedServers.TabIndex = 11; + cb_LinkedServers.Text = "Link / Combine Results with other servers"; + cb_LinkedServers.UseVisualStyleBackColor = true; + cb_LinkedServers.CheckedChanged += cb_LinkedServers_CheckedChanged; + // + // label13 + // + label13.AutoSize = true; + label13.Location = new System.Drawing.Point(6, 20); + label13.Name = "label13"; + label13.Size = new System.Drawing.Size(609, 13); + label13.TabIndex = 13; + label13.Text = "Wait for results from all linked servers - Useful to combine output from normal Deepstack and custom trained models"; + // + // checkedComboBoxLinked + // + checkedComboBoxLinked.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + checkedComboBoxLinked.CheckOnClick = true; + checkedComboBoxLinked.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawVariable; + checkedComboBoxLinked.DropDownHeight = 1; + checkedComboBoxLinked.Font = new System.Drawing.Font("Consolas", 8.25F); + checkedComboBoxLinked.FormattingEnabled = true; + checkedComboBoxLinked.IntegralHeight = false; + checkedComboBoxLinked.Location = new System.Drawing.Point(6, 36); + checkedComboBoxLinked.Name = "checkedComboBoxLinked"; + checkedComboBoxLinked.Size = new System.Drawing.Size(607, 21); + checkedComboBoxLinked.TabIndex = 12; + checkedComboBoxLinked.Text = "Click dropdown to select"; + checkedComboBoxLinked.ValueSeparator = ", "; + // + // groupBoxRefine + // + groupBoxRefine.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + groupBoxRefine.Controls.Add(cb_RefinementServer); + groupBoxRefine.Controls.Add(label11); + groupBoxRefine.Controls.Add(tb_RefinementObjects); + groupBoxRefine.Location = new System.Drawing.Point(8, 196); + groupBoxRefine.Name = "groupBoxRefine"; + groupBoxRefine.Size = new System.Drawing.Size(619, 63); + groupBoxRefine.TabIndex = 16; + groupBoxRefine.TabStop = false; + // + // cb_RefinementServer + // + cb_RefinementServer.AutoSize = true; + cb_RefinementServer.ForeColor = System.Drawing.Color.DodgerBlue; + cb_RefinementServer.Location = new System.Drawing.Point(8, 0); + cb_RefinementServer.Name = "cb_RefinementServer"; + cb_RefinementServer.Size = new System.Drawing.Size(155, 17); + cb_RefinementServer.TabIndex = 8; + cb_RefinementServer.Text = "Use as Refinement Server"; + cb_RefinementServer.UseVisualStyleBackColor = true; + cb_RefinementServer.CheckedChanged += cb_RefinementServer_CheckedChanged; + // + // label11 + // + label11.AutoSize = true; + label11.Location = new System.Drawing.Point(3, 20); + label11.Name = "label11"; + label11.Size = new System.Drawing.Size(376, 13); + label11.TabIndex = 13; + label11.Text = "Use this server ONLY if another server detects the following objects first:"; + // + // tb_RefinementObjects + // + tb_RefinementObjects.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + tb_RefinementObjects.Font = new System.Drawing.Font("Consolas", 8.25F); + tb_RefinementObjects.Location = new System.Drawing.Point(8, 36); + tb_RefinementObjects.Name = "tb_RefinementObjects"; + tb_RefinementObjects.Size = new System.Drawing.Size(605, 20); + tb_RefinementObjects.TabIndex = 9; + // + // tb_Upper + // + tb_Upper.Font = new System.Drawing.Font("Consolas", 8.25F); + tb_Upper.Location = new System.Drawing.Point(214, 165); + tb_Upper.Name = "tb_Upper"; + tb_Upper.Size = new System.Drawing.Size(49, 20); + tb_Upper.TabIndex = 7; + tb_Upper.Text = "100"; + // + // tb_LinkedRefineTimeout + // + tb_LinkedRefineTimeout.Font = new System.Drawing.Font("Consolas", 8.25F); + tb_LinkedRefineTimeout.Location = new System.Drawing.Point(446, 353); + tb_LinkedRefineTimeout.Name = "tb_LinkedRefineTimeout"; + tb_LinkedRefineTimeout.Size = new System.Drawing.Size(49, 20); + tb_LinkedRefineTimeout.TabIndex = 13; + tb_LinkedRefineTimeout.Tag = ""; + tb_LinkedRefineTimeout.Text = "5000"; + // + // tb_Lower + // + tb_Lower.Font = new System.Drawing.Font("Consolas", 8.25F); + tb_Lower.Location = new System.Drawing.Point(121, 165); + tb_Lower.Name = "tb_Lower"; + tb_Lower.Size = new System.Drawing.Size(49, 20); + tb_Lower.TabIndex = 6; + tb_Lower.Tag = ""; + tb_Lower.Text = "0"; + // + // label3 + // + label3.AutoSize = true; + label3.Location = new System.Drawing.Point(18, 168); + label3.Name = "label3"; + label3.Size = new System.Drawing.Size(99, 13); + label3.TabIndex = 10; + label3.Text = "Confidence limits:"; + // + // tb_timeout + // + tb_timeout.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + tb_timeout.Font = new System.Drawing.Font("Consolas", 8.25F); + tb_timeout.Location = new System.Drawing.Point(121, 140); + tb_timeout.Name = "tb_timeout"; + tb_timeout.Size = new System.Drawing.Size(52, 20); + tb_timeout.TabIndex = 5; + tb_timeout.Text = "0"; + toolTip1.SetToolTip(tb_timeout, "If you set this to any value other than 0 it will override the default timeout"); + // + // tb_ImagesPerMonth + // + tb_ImagesPerMonth.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + tb_ImagesPerMonth.Font = new System.Drawing.Font("Consolas", 8.25F); + tb_ImagesPerMonth.Location = new System.Drawing.Point(584, 112); + tb_ImagesPerMonth.Name = "tb_ImagesPerMonth"; + tb_ImagesPerMonth.Size = new System.Drawing.Size(45, 20); + tb_ImagesPerMonth.TabIndex = 4; + tb_ImagesPerMonth.Text = "0"; + toolTip1.SetToolTip(tb_ImagesPerMonth, "Max images per month - 0 for unlimited. Amazon has 5000 free images a month"); + // + // label15 + // + label15.AllowDrop = true; + label15.AutoSize = true; + label15.Location = new System.Drawing.Point(78, 36); + label15.Name = "label15"; + label15.Size = new System.Drawing.Size(39, 13); + label15.TabIndex = 0; + label15.Text = "Name:"; + // + // label9 + // + label9.AutoSize = true; + label9.Location = new System.Drawing.Point(267, 168); + label9.Name = "label9"; + label9.Size = new System.Drawing.Size(329, 13); + label9.TabIndex = 1; + label9.Text = "Upper (These will override the CAMERA setting if configured)"; + // + // label8 + // + label8.AutoSize = true; + label8.Location = new System.Drawing.Point(172, 168); + label8.Name = "label8"; + label8.Size = new System.Drawing.Size(38, 13); + label8.TabIndex = 1; + label8.Text = "Lower"; + // + // labelTimeout + // + labelTimeout.AutoSize = true; + labelTimeout.Location = new System.Drawing.Point(65, 144); + labelTimeout.Name = "labelTimeout"; + labelTimeout.Size = new System.Drawing.Size(52, 13); + labelTimeout.TabIndex = 1; + labelTimeout.Text = "Timeout:"; + // + // label7 + // + label7.AutoSize = true; + label7.Location = new System.Drawing.Point(451, 116); + label7.Name = "label7"; + label7.Size = new System.Drawing.Size(127, 13); + label7.TabIndex = 1; + label7.Text = "Max Images Per Month:"; + // + // linkHelpURL + // + linkHelpURL.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; + linkHelpURL.AutoSize = true; + linkHelpURL.Location = new System.Drawing.Point(5, 530); + linkHelpURL.Name = "linkHelpURL"; + linkHelpURL.Size = new System.Drawing.Size(10, 13); + linkHelpURL.TabIndex = 8; + linkHelpURL.TabStop = true; + linkHelpURL.Text = "."; + linkHelpURL.LinkClicked += linkHelpURL_LinkClicked; + // + // btTest + // + btTest.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; + btTest.Location = new System.Drawing.Point(500, 533); + btTest.Name = "btTest"; + btTest.Size = new System.Drawing.Size(70, 30); + btTest.TabIndex = 16; + btTest.Text = "Test"; + btTest.UseVisualStyleBackColor = true; + btTest.Click += btTest_Click; + // + // bt_clear + // + bt_clear.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; + bt_clear.Location = new System.Drawing.Point(424, 533); + bt_clear.Name = "bt_clear"; + bt_clear.Size = new System.Drawing.Size(70, 30); + bt_clear.TabIndex = 15; + bt_clear.Text = "Clear Stats"; + bt_clear.UseVisualStyleBackColor = true; + bt_clear.Click += bt_clear_Click; + // + // timer1 + // + timer1.Enabled = true; + timer1.Interval = 500; + timer1.Tick += timer1_Tick; + // + // Frm_AIServerDeepstackEdit + // + AutoScaleMode = System.Windows.Forms.AutoScaleMode.Inherit; + AutoScroll = true; + ClientSize = new System.Drawing.Size(649, 570); + Controls.Add(bt_Save); + Controls.Add(bt_clear); + Controls.Add(btTest); + Controls.Add(linkHelpURL); + Controls.Add(groupBox1); + Font = new System.Drawing.Font("Segoe UI", 8.25F); + Name = "Frm_AIServerDeepstackEdit"; + StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + Text = "Edit AI Server"; + FormClosing += Frm_AIServerDeepstackEdit_FormClosing; + Load += Frm_AIServerDeepstackEdit_Load; + groupBox1.ResumeLayout(false); + groupBox1.PerformLayout(); + gb_AIServerQueue.ResumeLayout(false); + gb_AIServerQueue.PerformLayout(); + groupBoxLinked.ResumeLayout(false); + groupBoxLinked.PerformLayout(); + groupBoxRefine.ResumeLayout(false); + groupBoxRefine.PerformLayout(); + ResumeLayout(false); + PerformLayout(); + } + + #endregion + + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Label lbl_type; + private System.Windows.Forms.Button bt_Save; + public System.Windows.Forms.TextBox tb_URL; + private System.Windows.Forms.Label label4; + public System.Windows.Forms.TextBox tb_ActiveTimeRange; + private System.Windows.Forms.Label label5; + public System.Windows.Forms.TextBox tb_ApplyToCams; + private System.Windows.Forms.ToolTip toolTip1; + private System.Windows.Forms.CheckBox chk_Enabled; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.TextBox tb_ImagesPerMonth; + private System.Windows.Forms.Label label7; + private System.Windows.Forms.LinkLabel linkHelpURL; + private System.Windows.Forms.Button btTest; + private System.Windows.Forms.Button bt_clear; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.TextBox tb_Upper; + private System.Windows.Forms.TextBox tb_Lower; + private System.Windows.Forms.Label label9; + private System.Windows.Forms.Label label8; + private System.Windows.Forms.TextBox tb_RefinementObjects; + private System.Windows.Forms.Label label11; + private System.Windows.Forms.CheckBox cb_RefinementServer; + private System.Windows.Forms.TextBox tb_timeout; + private System.Windows.Forms.Label labelTimeout; + private System.Windows.Forms.Label label13; + private System.Windows.Forms.CheckBox cb_LinkedServers; + private CheckComboBoxTest.CheckedComboBox checkedComboBoxLinked; + private System.Windows.Forms.GroupBox groupBoxLinked; + private System.Windows.Forms.GroupBox groupBoxRefine; + private System.Windows.Forms.CheckBox cb_TimeoutError; + private System.Windows.Forms.Label label12; + private System.Windows.Forms.Label label10; + private System.Windows.Forms.TextBox tb_LinkedRefineTimeout; + private System.Windows.Forms.Label label14; + private System.Windows.Forms.CheckBox cb_OnlyLinked; + private System.Windows.Forms.TextBox tb_Name; + private System.Windows.Forms.Label label15; + private System.Windows.Forms.CheckBox cb_IgnoreOffline; + private System.Windows.Forms.CheckBox cb_AllowAIServerBasedQueue; + private System.Windows.Forms.TextBox tb_MaxQueueLength; + private System.Windows.Forms.Label label16; + private System.Windows.Forms.GroupBox gb_AIServerQueue; + private System.Windows.Forms.TextBox tb_SkipIfImgQueueLengthLarger; + private System.Windows.Forms.Label label17; + private System.Windows.Forms.Label label18; + private System.Windows.Forms.Label label19; + private System.Windows.Forms.TextBox tb_SkipIfAIQueueTimeOverSecs; + private System.Windows.Forms.Label lbl_QueueLengthStats; + private System.Windows.Forms.Label lbl_ImgQueueStats; + private System.Windows.Forms.Label lbl_QueueTimeStats; + private System.Windows.Forms.Timer timer1; + } +} \ No newline at end of file diff --git a/src/UI/Frm_AIServerDeepstackEdit.cs b/src/UI/Frm_AIServerDeepstackEdit.cs new file mode 100644 index 00000000..ecd19811 --- /dev/null +++ b/src/UI/Frm_AIServerDeepstackEdit.cs @@ -0,0 +1,364 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Diagnostics; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace AITool +{ + public partial class Frm_AIServerDeepstackEdit:Form + { + public ClsURLItem CurURL; + + public Frm_AIServerDeepstackEdit() + { + InitializeComponent(); + } + + private void Frm_AIServerDeepstackEdit_Load(object sender, EventArgs e) + { + //Global_GUI.RestoreWindowState(this); + + this.tb_URL.Text = this.CurURL.url; + this.lbl_type.Text = this.CurURL.Type.ToString(); + this.tb_Name.Text = this.CurURL.Name; + + if (this.CurURL.Type == URLTypeEnum.AWSRekognition_Objects || this.CurURL.Type == URLTypeEnum.AWSRekognition_Faces) + this.tb_URL.Enabled = false; + else + this.tb_URL.Enabled = true; + + this.tb_ActiveTimeRange.Text = this.CurURL.ActiveTimeRange; + this.chk_Enabled.Checked = this.CurURL.Enabled; + this.tb_ApplyToCams.Text = this.CurURL.Cameras; + this.tb_ImagesPerMonth.Text = this.CurURL.MaxImagesPerMonth.ToString(); + //this.cb_ImageAdjustProfile.Text = this.CurURL.ImageAdjustProfile; + this.linkHelpURL.Text = this.CurURL.HelpURL; + this.tb_Lower.Text = this.CurURL.Threshold_Lower.ToString(); + this.tb_Upper.Text = this.CurURL.Threshold_Upper.ToString(); + this.tb_timeout.Text = this.CurURL.HttpClientTimeoutSeconds.ToString(); + //this.labelTimeout.Text = $"Timeout Seconds Override (Default={this.CurURL.GetTimeout().TotalSeconds}):"; + + this.cb_RefinementServer.Checked = this.CurURL.UseAsRefinementServer; + this.tb_RefinementObjects.Text = this.CurURL.RefinementObjects; + + this.cb_LinkedServers.Checked = this.CurURL.LinkServerResults; + + this.cb_TimeoutError.Checked = AppSettings.Settings.MaxWaitForAIServerTimeoutError; + this.tb_LinkedRefineTimeout.Text = AppSettings.Settings.MaxWaitForAIServerMS.ToString(); + + this.cb_OnlyLinked.Checked = this.CurURL.UseOnlyAsLinkedServer; + + this.cb_IgnoreOffline.Checked = this.CurURL.IgnoreOfflineError; + + this.cb_AllowAIServerBasedQueue.Checked = this.CurURL.AllowAIServerBasedQueue; + + this.tb_MaxQueueLength.Text = this.CurURL.AIMaxQueueLength.ToString(); + + this.tb_SkipIfImgQueueLengthLarger.Text = this.CurURL.SkipIfImgQueueLengthLarger.ToString(); + + this.tb_SkipIfAIQueueTimeOverSecs.Text = this.CurURL.SkipIfAIQueueTimeOverSecs.ToString(); + + List linked = this.CurURL.LinkedResultsServerList.SplitStr(",;|"); + + + + //Add all servers except current one and refinement server + int idx = 0; + //make a case insensitive hashset: + HashSet dupes = new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (ClsURLItem url in AppSettings.Settings.AIURLList) + { + + if (url.Enabled && !dupes.Contains(url.ToString()) && !url.UseAsRefinementServer && !string.Equals(this.CurURL.ToString(), url.ToString(), StringComparison.OrdinalIgnoreCase)) + { + dupes.Add(url.ToString()); + this.checkedComboBoxLinked.Items.Add(url); + if (Global.IsInList(url.ToString(), this.CurURL.LinkedResultsServerList, TrueIfEmpty: false)) + this.checkedComboBoxLinked.SetItemChecked(idx, true); + idx++; + } + + } + + //foreach (ClsImageAdjust ia in AppSettings.Settings.ImageAdjustProfiles) + // this.cb_ImageAdjustProfile.Items.Add(ia.Name); + + //this.cb_ImageAdjustProfile.SelectedIndex = this.cb_ImageAdjustProfile.Items.IndexOf(this.CurURL.DefaultURL); + + Global_GUI.GroupboxEnableDisable(groupBox1, chk_Enabled); + Global_GUI.GroupboxEnableDisable(groupBoxRefine, cb_RefinementServer); + Global_GUI.GroupboxEnableDisable(groupBoxLinked, cb_LinkedServers); + Global_GUI.GroupboxEnableDisable(gb_AIServerQueue, cb_AllowAIServerBasedQueue); + + + } + + private void UpdateStats() + { + this.lbl_QueueLengthStats.Text = $"{this.CurURL.AIQueueLengthCalcs.ToStringShort()}"; + this.lbl_QueueTimeStats.Text = $"{this.CurURL.AIQueueTimeCalcs.ToStringShort()}"; + this.lbl_ImgQueueStats.Text = $"{AITOOL.scalc.ToStringShort()}"; + } + + private void Frm_AIServerDeepstackEdit_FormClosing(object sender, FormClosingEventArgs e) + { + Global_GUI.SaveWindowState(this); + } + + + private void tb_URL_TextChanged(object sender, EventArgs e) + { + //ValidateForm(); + } + + private void ValidateForm() + { + this.CurURL.url = tb_URL.Text.Trim(); + if (!this.CurURL.Update(false)) + { + tb_URL.ForeColor = Color.White; + tb_URL.BackColor = Color.Red; + bt_Save.Enabled = false; + btTest.Enabled = false; + + } + else + { + if (this.CurURL.UrlFixed) + tb_URL.Text = this.CurURL.url; //not sure if this is the right thing to do here... May interfere with peoples typing + + tb_URL.ResetBackColor(); + tb_URL.ResetForeColor(); + bt_Save.Enabled = true; + btTest.Enabled = true; + } + } + + private void bt_Save_Click(object sender, EventArgs e) + { + this.UpdateURL(); + AITOOL.UpdateAIURLs(); + AppSettings.SaveAsync(true); + this.DialogResult = DialogResult.OK; + this.Close(); + } + + private void UpdateURL() + { + this.CurURL.url = this.tb_URL.Text.Trim(); + this.CurURL.Name = this.tb_Name.Text.Trim(); + this.CurURL.ActiveTimeRange = this.tb_ActiveTimeRange.Text.Trim(); + this.CurURL.Enabled = this.chk_Enabled.Checked; + this.CurURL.Cameras = this.tb_ApplyToCams.Text.Trim(); + //this.CurURL.ImageAdjustProfile = this.cb_ImageAdjustProfile.Text; + this.CurURL.MaxImagesPerMonth = this.tb_ImagesPerMonth.Text.ToInt(); + + this.CurURL.Threshold_Lower = this.tb_Lower.Text.ToInt(); + this.CurURL.Threshold_Upper = this.tb_Upper.Text.ToInt(); + + this.CurURL.RefinementObjects = this.tb_RefinementObjects.Text.Trim(); + this.CurURL.UseAsRefinementServer = this.cb_RefinementServer.Checked; + + this.CurURL.LinkServerResults = this.cb_LinkedServers.Checked; + this.CurURL.LinkedResultsServerList = ""; + foreach (ClsURLItem url in checkedComboBoxLinked.CheckedItems) + { + this.CurURL.LinkedResultsServerList += url.ToString() + ", "; + url.UseOnlyAsLinkedServer = true; + } + this.CurURL.LinkedResultsServerList = this.CurURL.LinkedResultsServerList.Trim(" ,".ToCharArray()); + + this.CurURL.UseOnlyAsLinkedServer = this.cb_OnlyLinked.Checked; + + this.CurURL.HttpClientTimeoutSeconds = this.tb_timeout.Text.ToInt(); + + this.CurURL.IgnoreOfflineError = this.cb_IgnoreOffline.Checked; + + this.CurURL.AllowAIServerBasedQueue = this.cb_AllowAIServerBasedQueue.Checked; + + this.CurURL.AIMaxQueueLength = this.tb_MaxQueueLength.Text.ToInt(); + + this.CurURL.SkipIfImgQueueLengthLarger = this.tb_SkipIfImgQueueLengthLarger.Text.ToInt(); + + this.CurURL.SkipIfAIQueueTimeOverSecs = this.tb_SkipIfAIQueueTimeOverSecs.Text.ToInt(); + + if (!string.IsNullOrWhiteSpace(this.tb_LinkedRefineTimeout.Text) && this.tb_LinkedRefineTimeout.Text.ToInt() >= 20) + AppSettings.Settings.MaxWaitForAIServerMS = this.tb_LinkedRefineTimeout.Text.ToInt(); + + AppSettings.Settings.MaxWaitForAIServerTimeoutError = this.cb_TimeoutError.Checked; + + + + } + private void linkHelpURL_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) + { + Process.Start(linkHelpURL.Text); + } + + private void btn_ImageAdjustEdit_Click(object sender, EventArgs e) + { + MessageBox.Show("Not implemented yet."); + } + + private async void btTest_Click(object sender, EventArgs e) + { + + this.UpdateURL(); + + AITOOL.UpdateAIURLs(); + + string pth = Global.GetRegSetting("TestImage", Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "TestImage.jpg")); + + OpenFileDialog ofd = new OpenFileDialog + { + InitialDirectory = Path.GetDirectoryName(pth), + FileName = Path.GetFileName(pth), + Title = "Select test image", + + CheckFileExists = true, + CheckPathExists = true, + + DefaultExt = "jpg", + Filter = "jpg files (*.jpg)|*.jpg", + FilterIndex = 2, + RestoreDirectory = true, + ShowReadOnly = true + }; + + if (ofd.ShowDialog() == DialogResult.OK) + { + pth = ofd.FileName; + Global.SaveRegSetting("TestImage", pth); + + if (File.Exists(pth)) + { + //must create a temp unique file every time because database key is the filename + Camera cam = AITOOL.GetCamera("default", true); + if (cam == null) + cam = new Camera("TEST_CAM"); + + string ext = Path.GetExtension(pth); + string tpth = Path.Combine(Global.GetTempFolder(), $"{cam.Name}.URLTEST.{DateTime.Now:yyyy-MM-dd_HH-mm-ss-fff}{ext}"); + File.Copy(pth, tpth, true); + + btTest.Enabled = false; + bt_Save.Enabled = false; + btTest.Text = "Working..."; + this.UpdateURL(); + ClsImageQueueItem CurImg = new ClsImageQueueItem(tpth, 0); + + List linked = new List { this.CurURL }; + + if (this.CurURL.LinkServerResults && !string.IsNullOrEmpty(this.CurURL.LinkedResultsServerList)) + { + linked.AddRange(await AITOOL.WaitForNextURL(cam, false, null, this.CurURL.LinkedResultsServerList)); + } + if (linked.Count > 1) + { + AITOOL.Log($"Debug: ---- Found '{linked.Count}' linked AI URL's."); + } + + AITOOL.DetectObjectsResult result = await AITOOL.DetectObjects(CurImg, linked, cam); + + //make sure not stuck in use for the test: + foreach (var url in result.OutURLs) + { + url.DecrementQueue(); + } + + btTest.Enabled = true; + bt_Save.Enabled = true; + btTest.Text = "Test"; + if (result.Success) + { + + Frm_ObjectDetail frm = new Frm_ObjectDetail(); + frm.PredictionObjectDetailsList = result.OutPredictions; + frm.ImageFileName = tpth; + frm.Show(); + + MessageBox.Show($"Success! Time={result.TimeMS}ms: {this.CurURL.LastResultMessage}", "Success"); + } + else + MessageBox.Show($"Error! Time={result.TimeMS}ms: {this.CurURL.LastResultMessage}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + + this.CurURL.ErrCount = 0; + this.CurURL.CurErrCount = 0; + + } + else + { + MessageBox.Show($"Test file does not exist:\r\n{pth}"); + } + + } + + } + + private void bt_clear_Click(object sender, EventArgs e) + { + this.CurURL.ErrCount = 0; + this.CurURL.CurErrCount = 0; + this.CurURL.ErrsInRowCount = 0; + this.CurURL.ErrDisabled = false; + this.CurURL.AITimeCalcs = new MovingCalcs(500, "AITime", true); + this.CurURL.AIQueueLengthCalcs = new MovingCalcs(500, "AIQueueLength", false); + this.CurURL.AIQueueTimeCalcs = new MovingCalcs(500, "AIQueueTime", true); + this.CurURL.LastResultMessage = ""; + this.CurURL.NotReadyReason = ""; + this.CurURL.LastSkippedReason = ""; + this.CurURL.LastTimeMS = 0; + this.CurURL.LastTestedTime = new ThreadSafe.DateTime(DateTime.MinValue, AppSettings.Settings.DateFormat); + this.CurURL.LastUsedTime = new ThreadSafe.DateTime(DateTime.MinValue, AppSettings.Settings.DateFormat); + this.CurURL.LastResultSuccess = false; + this.CurURL.InUse = false; + this.CurURL.AIQueueLength = 0; + this.CurURL.AIQueueSkippedCount = 0; + + MessageBox.Show("Cleared error counts and stats."); + + } + + private void cb_RefinementServer_CheckedChanged(object sender, EventArgs e) + { + Global_GUI.GroupboxEnableDisable(groupBoxRefine, cb_RefinementServer); + } + + private void chk_Enabled_CheckedChanged(object sender, EventArgs e) + { + Global_GUI.GroupboxEnableDisable(groupBox1, chk_Enabled); + } + + private void cb_LinkedServers_CheckedChanged(object sender, EventArgs e) + { + Global_GUI.GroupboxEnableDisable(groupBoxLinked, cb_LinkedServers); + if (!cb_LinkedServers.Checked) + this.CurURL.LinkedResultsServerList = ""; + + } + + private void cb_OnlyLinked_CheckedChanged(object sender, EventArgs e) + { + if (cb_OnlyLinked.Checked && cb_LinkedServers.Checked) + cb_LinkedServers.Checked = false; + } + + private void cb_AllowAIServerBasedQueue_CheckedChanged(object sender, EventArgs e) + { + Global_GUI.GroupboxEnableDisable(gb_AIServerQueue, cb_AllowAIServerBasedQueue); + } + + private void timer1_Tick(object sender, EventArgs e) + { + UpdateStats(); + } + } +} diff --git a/src/UI/Frm_AIServerDeepstackEdit.resx b/src/UI/Frm_AIServerDeepstackEdit.resx new file mode 100644 index 00000000..2386018d --- /dev/null +++ b/src/UI/Frm_AIServerDeepstackEdit.resx @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + This will allow the queue functionality built into CodeProjectAI to work. With this enabled, we will send as +many image requests as it can handle. We will ignore that the server is 'BUSY' unless the conditions below +are met. + +This will also allow CPAI 'Mesh' feature to work correctly. + + + 152, 17 + + \ No newline at end of file diff --git a/src/UI/Frm_AIServers.Designer.cs b/src/UI/Frm_AIServers.Designer.cs new file mode 100644 index 00000000..899ed118 --- /dev/null +++ b/src/UI/Frm_AIServers.Designer.cs @@ -0,0 +1,383 @@ + +namespace AITool +{ + partial class Frm_AIServers + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + components = new System.ComponentModel.Container(); + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Frm_AIServers)); + FOLV_AIServers = new BrightIdeasSoftware.FastObjectListView(); + toolStrip1 = new System.Windows.Forms.ToolStrip(); + toolStripSplitButtonAdd = new System.Windows.Forms.ToolStripSplitButton(); + codeProjectAIObjectsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + codeProjectAILicensePlateToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + codeProjectAIFacesToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + codeProjectAISceneToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + codeProjectAICustomToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + codeProjectAIIPCAMAnimalToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + codeProjectAIIPCAMCombinedToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + codeProjectAIIPCAMDarkToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + codeProjectAIIPCAMGeneralToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + deepstackObjectsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + deepstackCustomToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + deepstackSceneToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + deepstackFacesToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + addDoodsServerToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + addAmazonObjectsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + addAmazonFaceToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + sightHoundVehicleAIServerToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + sightHoundPersonAIServerToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator(); + toolStripButtonEdit = new System.Windows.Forms.ToolStripButton(); + toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator(); + toolStripButtonDelete = new System.Windows.Forms.ToolStripButton(); + toolStripSeparator3 = new System.Windows.Forms.ToolStripSeparator(); + toolStripButtonUp = new System.Windows.Forms.ToolStripButton(); + toolStripButtonDown = new System.Windows.Forms.ToolStripButton(); + imageList1 = new System.Windows.Forms.ImageList(components); + timer1 = new System.Windows.Forms.Timer(components); + ((System.ComponentModel.ISupportInitialize)FOLV_AIServers).BeginInit(); + toolStrip1.SuspendLayout(); + SuspendLayout(); + // + // FOLV_AIServers + // + FOLV_AIServers.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + FOLV_AIServers.CheckBoxes = true; + FOLV_AIServers.Location = new System.Drawing.Point(0, 34); + FOLV_AIServers.Name = "FOLV_AIServers"; + FOLV_AIServers.ShowGroups = false; + FOLV_AIServers.ShowImagesOnSubItems = true; + FOLV_AIServers.Size = new System.Drawing.Size(591, 122); + FOLV_AIServers.TabIndex = 0; + FOLV_AIServers.UseCompatibleStateImageBehavior = false; + FOLV_AIServers.View = System.Windows.Forms.View.Details; + FOLV_AIServers.VirtualMode = true; + FOLV_AIServers.FormatCell += FOLV_AIServers_FormatCell; + FOLV_AIServers.FormatRow += FOLV_AIServers_FormatRow; + FOLV_AIServers.SelectionChanged += FOLV_AIServers_SelectionChanged; + FOLV_AIServers.SelectedIndexChanged += FOLV_AIServers_SelectedIndexChanged; + FOLV_AIServers.DoubleClick += FOLV_AIServers_DoubleClick; + // + // toolStrip1 + // + toolStrip1.ImageScalingSize = new System.Drawing.Size(24, 24); + toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { toolStripSplitButtonAdd, toolStripSeparator1, toolStripButtonEdit, toolStripSeparator2, toolStripButtonDelete, toolStripSeparator3, toolStripButtonUp, toolStripButtonDown }); + toolStrip1.Location = new System.Drawing.Point(0, 0); + toolStrip1.Name = "toolStrip1"; + toolStrip1.Size = new System.Drawing.Size(591, 31); + toolStrip1.TabIndex = 2; + toolStrip1.Text = "toolStrip1"; + // + // toolStripSplitButtonAdd + // + toolStripSplitButtonAdd.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { codeProjectAIObjectsToolStripMenuItem, codeProjectAILicensePlateToolStripMenuItem, codeProjectAIFacesToolStripMenuItem, codeProjectAISceneToolStripMenuItem, codeProjectAICustomToolStripMenuItem, codeProjectAIIPCAMAnimalToolStripMenuItem, codeProjectAIIPCAMCombinedToolStripMenuItem, codeProjectAIIPCAMDarkToolStripMenuItem, codeProjectAIIPCAMGeneralToolStripMenuItem, deepstackObjectsToolStripMenuItem, deepstackCustomToolStripMenuItem, deepstackSceneToolStripMenuItem, deepstackFacesToolStripMenuItem, addDoodsServerToolStripMenuItem, addAmazonObjectsToolStripMenuItem, addAmazonFaceToolStripMenuItem, sightHoundVehicleAIServerToolStripMenuItem, sightHoundPersonAIServerToolStripMenuItem }); + toolStripSplitButtonAdd.Image = (System.Drawing.Image)resources.GetObject("toolStripSplitButtonAdd.Image"); + toolStripSplitButtonAdd.ImageTransparentColor = System.Drawing.Color.Magenta; + toolStripSplitButtonAdd.Name = "toolStripSplitButtonAdd"; + toolStripSplitButtonAdd.Size = new System.Drawing.Size(69, 28); + toolStripSplitButtonAdd.Text = "Add"; + toolStripSplitButtonAdd.ButtonClick += toolStripSplitButtonAdd_ButtonClick; + // + // codeProjectAIObjectsToolStripMenuItem + // + codeProjectAIObjectsToolStripMenuItem.Image = Properties.Resources.Codeproject_2023_06_03_15_45_28; + codeProjectAIObjectsToolStripMenuItem.Name = "codeProjectAIObjectsToolStripMenuItem"; + codeProjectAIObjectsToolStripMenuItem.Size = new System.Drawing.Size(273, 30); + codeProjectAIObjectsToolStripMenuItem.Text = "CodeProject AI (Objects)"; + codeProjectAIObjectsToolStripMenuItem.Click += codeProjectAIObjectsToolStripMenuItem_Click; + // + // codeProjectAILicensePlateToolStripMenuItem + // + codeProjectAILicensePlateToolStripMenuItem.Image = Properties.Resources.Codeproject_2023_06_03_15_45_28; + codeProjectAILicensePlateToolStripMenuItem.Name = "codeProjectAILicensePlateToolStripMenuItem"; + codeProjectAILicensePlateToolStripMenuItem.Size = new System.Drawing.Size(273, 30); + codeProjectAILicensePlateToolStripMenuItem.Text = "CodeProject AI (License Plate)"; + codeProjectAILicensePlateToolStripMenuItem.Click += codeProjectAILicensePlateToolStripMenuItem_Click; + // + // codeProjectAIFacesToolStripMenuItem + // + codeProjectAIFacesToolStripMenuItem.Image = Properties.Resources.Codeproject_2023_06_03_15_45_28; + codeProjectAIFacesToolStripMenuItem.Name = "codeProjectAIFacesToolStripMenuItem"; + codeProjectAIFacesToolStripMenuItem.Size = new System.Drawing.Size(273, 30); + codeProjectAIFacesToolStripMenuItem.Text = "CodeProject AI (Faces)"; + codeProjectAIFacesToolStripMenuItem.Click += codeProjectAIFacesToolStripMenuItem_Click; + // + // codeProjectAISceneToolStripMenuItem + // + codeProjectAISceneToolStripMenuItem.Image = Properties.Resources.Codeproject_2023_06_03_15_45_28; + codeProjectAISceneToolStripMenuItem.Name = "codeProjectAISceneToolStripMenuItem"; + codeProjectAISceneToolStripMenuItem.Size = new System.Drawing.Size(273, 30); + codeProjectAISceneToolStripMenuItem.Text = "CodeProject AI (Scene)"; + codeProjectAISceneToolStripMenuItem.Click += codeProjectAISceneToolStripMenuItem_Click; + // + // codeProjectAICustomToolStripMenuItem + // + codeProjectAICustomToolStripMenuItem.Image = Properties.Resources.Codeproject_2023_06_03_15_45_28; + codeProjectAICustomToolStripMenuItem.Name = "codeProjectAICustomToolStripMenuItem"; + codeProjectAICustomToolStripMenuItem.Size = new System.Drawing.Size(273, 30); + codeProjectAICustomToolStripMenuItem.Text = "CodeProject AI (Custom)"; + codeProjectAICustomToolStripMenuItem.Click += codeProjectAICustomToolStripMenuItem_Click; + // + // codeProjectAIIPCAMAnimalToolStripMenuItem + // + codeProjectAIIPCAMAnimalToolStripMenuItem.Image = Properties.Resources.Codeproject_2023_06_03_15_45_28; + codeProjectAIIPCAMAnimalToolStripMenuItem.Name = "codeProjectAIIPCAMAnimalToolStripMenuItem"; + codeProjectAIIPCAMAnimalToolStripMenuItem.Size = new System.Drawing.Size(273, 30); + codeProjectAIIPCAMAnimalToolStripMenuItem.Text = "CodeProject AI (IPCAM Animal)"; + codeProjectAIIPCAMAnimalToolStripMenuItem.Click += codeProjectAIIPCAMAnimalToolStripMenuItem_Click; + // + // codeProjectAIIPCAMCombinedToolStripMenuItem + // + codeProjectAIIPCAMCombinedToolStripMenuItem.Image = Properties.Resources.Codeproject_2023_06_03_15_45_28; + codeProjectAIIPCAMCombinedToolStripMenuItem.Name = "codeProjectAIIPCAMCombinedToolStripMenuItem"; + codeProjectAIIPCAMCombinedToolStripMenuItem.Size = new System.Drawing.Size(273, 30); + codeProjectAIIPCAMCombinedToolStripMenuItem.Text = "CodeProject AI (IPCAM Combined)"; + codeProjectAIIPCAMCombinedToolStripMenuItem.Click += codeProjectAIIPCAMCombinedToolStripMenuItem_Click; + // + // codeProjectAIIPCAMDarkToolStripMenuItem + // + codeProjectAIIPCAMDarkToolStripMenuItem.Image = Properties.Resources.Codeproject_2023_06_03_15_45_28; + codeProjectAIIPCAMDarkToolStripMenuItem.Name = "codeProjectAIIPCAMDarkToolStripMenuItem"; + codeProjectAIIPCAMDarkToolStripMenuItem.Size = new System.Drawing.Size(273, 30); + codeProjectAIIPCAMDarkToolStripMenuItem.Text = "CodeProject AI (IPCAM Dark)"; + codeProjectAIIPCAMDarkToolStripMenuItem.Click += codeProjectAIIPCAMDarkToolStripMenuItem_Click; + // + // codeProjectAIIPCAMGeneralToolStripMenuItem + // + codeProjectAIIPCAMGeneralToolStripMenuItem.Image = Properties.Resources.Codeproject_2023_06_03_15_45_28; + codeProjectAIIPCAMGeneralToolStripMenuItem.Name = "codeProjectAIIPCAMGeneralToolStripMenuItem"; + codeProjectAIIPCAMGeneralToolStripMenuItem.Size = new System.Drawing.Size(273, 30); + codeProjectAIIPCAMGeneralToolStripMenuItem.Text = "CodeProject AI (IPCAM General)"; + codeProjectAIIPCAMGeneralToolStripMenuItem.Click += codeProjectAIIPCAMGeneralToolStripMenuItem_Click; + // + // deepstackObjectsToolStripMenuItem + // + deepstackObjectsToolStripMenuItem.Image = (System.Drawing.Image)resources.GetObject("deepstackObjectsToolStripMenuItem.Image"); + deepstackObjectsToolStripMenuItem.Name = "deepstackObjectsToolStripMenuItem"; + deepstackObjectsToolStripMenuItem.Size = new System.Drawing.Size(273, 30); + deepstackObjectsToolStripMenuItem.Text = "Deepstack (Objects) AI Server"; + deepstackObjectsToolStripMenuItem.ToolTipText = "Detect regular objects such as person, car, truck, etc."; + deepstackObjectsToolStripMenuItem.Click += deepstackToolStripMenuItem_Click; + // + // deepstackCustomToolStripMenuItem + // + deepstackCustomToolStripMenuItem.Image = (System.Drawing.Image)resources.GetObject("deepstackCustomToolStripMenuItem.Image"); + deepstackCustomToolStripMenuItem.Name = "deepstackCustomToolStripMenuItem"; + deepstackCustomToolStripMenuItem.Size = new System.Drawing.Size(273, 30); + deepstackCustomToolStripMenuItem.Text = "Deepstack (Custom) AI Server"; + deepstackCustomToolStripMenuItem.ToolTipText = "Use a custom trained model for detection"; + deepstackCustomToolStripMenuItem.Click += deepstackCustomToolStripMenuItem_Click; + // + // deepstackSceneToolStripMenuItem + // + deepstackSceneToolStripMenuItem.Image = (System.Drawing.Image)resources.GetObject("deepstackSceneToolStripMenuItem.Image"); + deepstackSceneToolStripMenuItem.Name = "deepstackSceneToolStripMenuItem"; + deepstackSceneToolStripMenuItem.Size = new System.Drawing.Size(273, 30); + deepstackSceneToolStripMenuItem.Text = "Deepstack (Scene) AI Server"; + deepstackSceneToolStripMenuItem.ToolTipText = "Detect the scene such as \"conference_room\"."; + deepstackSceneToolStripMenuItem.Click += deepstackSceneToolStripMenuItem_Click; + // + // deepstackFacesToolStripMenuItem + // + deepstackFacesToolStripMenuItem.Image = (System.Drawing.Image)resources.GetObject("deepstackFacesToolStripMenuItem.Image"); + deepstackFacesToolStripMenuItem.Name = "deepstackFacesToolStripMenuItem"; + deepstackFacesToolStripMenuItem.Size = new System.Drawing.Size(273, 30); + deepstackFacesToolStripMenuItem.Text = "Deepstack (Faces) AI Server"; + deepstackFacesToolStripMenuItem.ToolTipText = "Face detection"; + deepstackFacesToolStripMenuItem.Click += deepstackFacesToolStripMenuItem_Click; + // + // addDoodsServerToolStripMenuItem + // + addDoodsServerToolStripMenuItem.Image = Properties.Resources.network_server; + addDoodsServerToolStripMenuItem.Name = "addDoodsServerToolStripMenuItem"; + addDoodsServerToolStripMenuItem.Size = new System.Drawing.Size(273, 30); + addDoodsServerToolStripMenuItem.Text = "DOODS AI Server"; + addDoodsServerToolStripMenuItem.Click += addDoodsServerToolStripMenuItem_Click; + // + // addAmazonObjectsToolStripMenuItem + // + addAmazonObjectsToolStripMenuItem.Image = (System.Drawing.Image)resources.GetObject("addAmazonObjectsToolStripMenuItem.Image"); + addAmazonObjectsToolStripMenuItem.Name = "addAmazonObjectsToolStripMenuItem"; + addAmazonObjectsToolStripMenuItem.Size = new System.Drawing.Size(273, 30); + addAmazonObjectsToolStripMenuItem.Text = "AWS Rekognition (Objects) AI Server"; + addAmazonObjectsToolStripMenuItem.ToolTipText = "Detect regular objects such as person, car, truck, etc."; + addAmazonObjectsToolStripMenuItem.Click += addAmazonReToolStripMenuItem_Click; + // + // addAmazonFaceToolStripMenuItem + // + addAmazonFaceToolStripMenuItem.Image = (System.Drawing.Image)resources.GetObject("addAmazonFaceToolStripMenuItem.Image"); + addAmazonFaceToolStripMenuItem.Name = "addAmazonFaceToolStripMenuItem"; + addAmazonFaceToolStripMenuItem.Size = new System.Drawing.Size(273, 30); + addAmazonFaceToolStripMenuItem.Text = "AWS Rekognition (Faces) AI Server"; + addAmazonFaceToolStripMenuItem.ToolTipText = "Person age, emotion and gender"; + addAmazonFaceToolStripMenuItem.Click += addAmazonFaceToolStripMenuItem_Click; + // + // sightHoundVehicleAIServerToolStripMenuItem + // + sightHoundVehicleAIServerToolStripMenuItem.Image = (System.Drawing.Image)resources.GetObject("sightHoundVehicleAIServerToolStripMenuItem.Image"); + sightHoundVehicleAIServerToolStripMenuItem.Name = "sightHoundVehicleAIServerToolStripMenuItem"; + sightHoundVehicleAIServerToolStripMenuItem.Size = new System.Drawing.Size(273, 30); + sightHoundVehicleAIServerToolStripMenuItem.Text = "SightHound (Vehicle) AI Server"; + sightHoundVehicleAIServerToolStripMenuItem.ToolTipText = "Car make model and license plate"; + sightHoundVehicleAIServerToolStripMenuItem.Click += sightHoundAIServerToolStripMenuItem_Click; + // + // sightHoundPersonAIServerToolStripMenuItem + // + sightHoundPersonAIServerToolStripMenuItem.Image = (System.Drawing.Image)resources.GetObject("sightHoundPersonAIServerToolStripMenuItem.Image"); + sightHoundPersonAIServerToolStripMenuItem.Name = "sightHoundPersonAIServerToolStripMenuItem"; + sightHoundPersonAIServerToolStripMenuItem.Size = new System.Drawing.Size(273, 30); + sightHoundPersonAIServerToolStripMenuItem.Text = "SightHound (Person) AI Server"; + sightHoundPersonAIServerToolStripMenuItem.ToolTipText = "Person age, emotion, gender"; + sightHoundPersonAIServerToolStripMenuItem.Click += sightHoundPersonAIServerToolStripMenuItem_Click; + // + // toolStripSeparator1 + // + toolStripSeparator1.Name = "toolStripSeparator1"; + toolStripSeparator1.Size = new System.Drawing.Size(6, 31); + // + // toolStripButtonEdit + // + toolStripButtonEdit.Enabled = false; + toolStripButtonEdit.Image = (System.Drawing.Image)resources.GetObject("toolStripButtonEdit.Image"); + toolStripButtonEdit.ImageTransparentColor = System.Drawing.Color.Magenta; + toolStripButtonEdit.Name = "toolStripButtonEdit"; + toolStripButtonEdit.Size = new System.Drawing.Size(55, 28); + toolStripButtonEdit.Text = "Edit"; + toolStripButtonEdit.Click += toolStripButtonEdit_Click; + // + // toolStripSeparator2 + // + toolStripSeparator2.Name = "toolStripSeparator2"; + toolStripSeparator2.Size = new System.Drawing.Size(6, 31); + // + // toolStripButtonDelete + // + toolStripButtonDelete.Enabled = false; + toolStripButtonDelete.Image = (System.Drawing.Image)resources.GetObject("toolStripButtonDelete.Image"); + toolStripButtonDelete.ImageTransparentColor = System.Drawing.Color.Magenta; + toolStripButtonDelete.Name = "toolStripButtonDelete"; + toolStripButtonDelete.Size = new System.Drawing.Size(68, 28); + toolStripButtonDelete.Text = "Delete"; + toolStripButtonDelete.Click += toolStripButtonDelete_Click; + // + // toolStripSeparator3 + // + toolStripSeparator3.Name = "toolStripSeparator3"; + toolStripSeparator3.Size = new System.Drawing.Size(6, 31); + // + // toolStripButtonUp + // + toolStripButtonUp.Enabled = false; + toolStripButtonUp.Image = (System.Drawing.Image)resources.GetObject("toolStripButtonUp.Image"); + toolStripButtonUp.ImageTransparentColor = System.Drawing.Color.Magenta; + toolStripButtonUp.Name = "toolStripButtonUp"; + toolStripButtonUp.Size = new System.Drawing.Size(50, 28); + toolStripButtonUp.Text = "Up"; + toolStripButtonUp.Click += toolStripButtonUp_Click; + // + // toolStripButtonDown + // + toolStripButtonDown.Enabled = false; + toolStripButtonDown.Image = (System.Drawing.Image)resources.GetObject("toolStripButtonDown.Image"); + toolStripButtonDown.ImageTransparentColor = System.Drawing.Color.Magenta; + toolStripButtonDown.Name = "toolStripButtonDown"; + toolStripButtonDown.Size = new System.Drawing.Size(66, 28); + toolStripButtonDown.Text = "Down"; + toolStripButtonDown.Click += toolStripButtonDown_Click; + // + // imageList1 + // + imageList1.ColorDepth = System.Windows.Forms.ColorDepth.Depth8Bit; + imageList1.ImageStream = (System.Windows.Forms.ImageListStreamer)resources.GetObject("imageList1.ImageStream"); + imageList1.TransparentColor = System.Drawing.Color.Transparent; + imageList1.Images.SetKeyName(0, "AWSRekognition.png"); + imageList1.Images.SetKeyName(1, "Deepstack.png"); + imageList1.Images.SetKeyName(2, "DOODS.png"); + imageList1.Images.SetKeyName(3, "SightHound.png"); + imageList1.Images.SetKeyName(4, "Codeproject.png"); + // + // timer1 + // + timer1.Enabled = true; + timer1.Interval = 1000; + timer1.Tick += timer1_Tick; + // + // Frm_AIServers + // + AutoScaleMode = System.Windows.Forms.AutoScaleMode.Inherit; + ClientSize = new System.Drawing.Size(591, 159); + Controls.Add(toolStrip1); + Controls.Add(FOLV_AIServers); + Font = new System.Drawing.Font("Segoe UI", 8.25F); + Name = "Frm_AIServers"; + StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + Tag = "SAVE"; + Text = "AI Servers"; + FormClosing += Frm_AddAIServers_FormClosing; + Load += Frm_AddAIServers_Load; + ((System.ComponentModel.ISupportInitialize)FOLV_AIServers).EndInit(); + toolStrip1.ResumeLayout(false); + toolStrip1.PerformLayout(); + ResumeLayout(false); + PerformLayout(); + } + + #endregion + public BrightIdeasSoftware.FastObjectListView FOLV_AIServers; + private System.Windows.Forms.ToolStrip toolStrip1; + private System.Windows.Forms.ToolStripButton toolStripButtonEdit; + private System.Windows.Forms.ToolStripSplitButton toolStripSplitButtonAdd; + private System.Windows.Forms.ToolStripMenuItem deepstackObjectsToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem addAmazonObjectsToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem addDoodsServerToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator1; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator2; + private System.Windows.Forms.ToolStripButton toolStripButtonDelete; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator3; + private System.Windows.Forms.ToolStripButton toolStripButtonUp; + private System.Windows.Forms.ToolStripButton toolStripButtonDown; + private System.Windows.Forms.ToolStripMenuItem sightHoundVehicleAIServerToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem sightHoundPersonAIServerToolStripMenuItem; + private System.Windows.Forms.ImageList imageList1; + private System.Windows.Forms.ToolStripMenuItem addAmazonFaceToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem deepstackCustomToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem deepstackFacesToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem deepstackSceneToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem codeProjectAIObjectsToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem codeProjectAIFacesToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem codeProjectAICustomToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem codeProjectAISceneToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem codeProjectAILicensePlateToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem codeProjectAIIPCAMAnimalToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem codeProjectAIIPCAMDarkToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem codeProjectAIIPCAMGeneralToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem codeProjectAIIPCAMCombinedToolStripMenuItem; + private System.Windows.Forms.Timer timer1; + } +} \ No newline at end of file diff --git a/src/UI/Frm_AIServers.cs b/src/UI/Frm_AIServers.cs new file mode 100644 index 00000000..5b419c09 --- /dev/null +++ b/src/UI/Frm_AIServers.cs @@ -0,0 +1,596 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Diagnostics; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace AITool +{ + public partial class Frm_AIServers:Form + { + + ClsURLItem CurURL = null; + + public Frm_AIServers() + { + InitializeComponent(); + } + + private void Frm_AddAIServers_Load(object sender, EventArgs e) + { + using Working w = new Working(); + Global_GUI.RestoreWindowState(this); + Global_GUI.ConfigureFOLV(FOLV_AIServers, typeof(ClsURLItem), null, this.imageList1, editmode: BrightIdeasSoftware.ObjectListView.CellEditActivateMode.F2Only); + + this.FOLV_AIServers.BooleanCheckStateGetter = delegate (Object rowObject) + { + return !rowObject.IsNull() && ((ClsURLItem)rowObject).Enabled; + }; + + this.FOLV_AIServers.BooleanCheckStatePutter = delegate (Object rowObject, bool newValue) + { + if (rowObject.IsNull()) + return false; + ((ClsURLItem)rowObject).Enabled = newValue; + return newValue; + }; + + Global_GUI.UpdateFOLV(FOLV_AIServers, AppSettings.Settings.AIURLList); + } + + private void Frm_AddAIServers_FormClosing(object sender, FormClosingEventArgs e) + { + using Working w = new Working(); + Global_GUI.SaveWindowState(this); + } + + + private void FOLV_AIServers_SelectedIndexChanged(object sender, EventArgs e) + { + + } + + private void FOLV_AIServers_SelectionChanged(object sender, EventArgs e) + { + using Working w = new Working(); + try + { + if (this.FOLV_AIServers.SelectedObjects != null && this.FOLV_AIServers.SelectedObjects.Count > 0) + { + this.CurURL = (ClsURLItem)this.FOLV_AIServers.SelectedObjects[0]; + } + else + { + this.CurURL = null; + } + + } + catch (Exception ex) + { + AITOOL.Log($"Error: {ex.Msg()}"); + + } + + UpdateButtons(); + + } + + private void UpdateButtons() + { + if (this.CurURL != null) + { + + toolStripButtonDelete.Enabled = true; + toolStripButtonDown.Enabled = true; + toolStripButtonEdit.Enabled = true; + toolStripButtonUp.Enabled = true; + } + else + { + toolStripButtonDelete.Enabled = false; + toolStripButtonDown.Enabled = false; + toolStripButtonEdit.Enabled = false; + toolStripButtonUp.Enabled = false; + } + + } + + private void deepstackToolStripMenuItem_Click(object sender, EventArgs e) + { + using Working w = new Working(); + ClsURLItem url = new ClsURLItem("", AppSettings.Settings.AIURLList.Count + 1, URLTypeEnum.DeepStack); + this.CurURL = url; + AppSettings.Settings.AIURLList.Add(url); + Global_GUI.UpdateFOLV(FOLV_AIServers, AppSettings.Settings.AIURLList, UseSelected: true, SelectObject: this.CurURL, FullRefresh: true); + UpdateButtons(); + + } + + private void addAmazonReToolStripMenuItem_Click(object sender, EventArgs e) + { + using Working w = new Working(); + ClsURLItem url = new ClsURLItem("", AppSettings.Settings.AIURLList.Count + 1, URLTypeEnum.AWSRekognition_Objects); + if (!AppSettings.Settings.AIURLList.Contains(url)) + { + this.CurURL = url; + AppSettings.Settings.AIURLList.Add(url); + Global_GUI.UpdateFOLV(FOLV_AIServers, AppSettings.Settings.AIURLList, UseSelected: true, SelectObject: this.CurURL, FullRefresh: true); + AITOOL.UpdateAIURLs(); + + } + else + { + MessageBox.Show("Already exists"); + } + UpdateButtons(); + + } + + private void addDoodsServerToolStripMenuItem_Click(object sender, EventArgs e) + { + using Working w = new Working(); + ClsURLItem url = new ClsURLItem("", AppSettings.Settings.AIURLList.Count + 1, URLTypeEnum.DOODS); + if (!AppSettings.Settings.AIURLList.Contains(url)) + { + this.CurURL = url; + AppSettings.Settings.AIURLList.Add(url); + Global_GUI.UpdateFOLV(FOLV_AIServers, AppSettings.Settings.AIURLList, UseSelected: true, SelectObject: this.CurURL, FullRefresh: true); + } + else + { + MessageBox.Show("Already exists"); + } + UpdateButtons(); + + } + + private void toolStripButtonDelete_Click(object sender, EventArgs e) + { + using Working w = new Working(); + if (AppSettings.Settings.AIURLList.Contains(this.CurURL)) + { + AppSettings.Settings.AIURLList.Remove(this.CurURL); + for (int i = 0; i < AppSettings.Settings.AIURLList.Count; i++) + { + AppSettings.Settings.AIURLList[i].Order = i + 1; + } + Global_GUI.UpdateFOLV(FOLV_AIServers, AppSettings.Settings.AIURLList, UseSelected: true, FullRefresh: true); + } + UpdateButtons(); + + } + + private void toolStripButtonEdit_Click(object sender, EventArgs e) + { + Edit(); + + + } + + private void Edit() + { + using (Frm_AIServerDeepstackEdit frm = new Frm_AIServerDeepstackEdit()) + { + frm.CurURL = this.CurURL; + if (frm.ShowDialog() == DialogResult.OK) + { + this.CurURL = frm.CurURL; //this should update the list item by ref - prob not needed but makes me feel warm and fuzzy + Global_GUI.UpdateFOLV(FOLV_AIServers, AppSettings.Settings.AIURLList, UseSelected: true, SelectObject: this.CurURL, FullRefresh: true); + } + } + UpdateButtons(); + } + + private void toolStripButtonUp_Click(object sender, EventArgs e) + { + using Working w = new Working(); + if (this.CurURL == null) + return; + + int idx = AppSettings.Settings.AIURLList.IndexOf(this.CurURL); + + if (idx > -1) + { + AppSettings.Settings.AIURLList.Move(idx, idx - 1); + + for (int i = 0; i < AppSettings.Settings.AIURLList.Count; i++) + { + AppSettings.Settings.AIURLList[i].Order = i + 1; + } + + Global_GUI.UpdateFOLV(FOLV_AIServers, AppSettings.Settings.AIURLList, UseSelected: true, SelectObject: this.CurURL, FullRefresh: true, ForcedSelection: true); + + } + } + + private void toolStripButtonDown_Click(object sender, EventArgs e) + { + using Working w = new Working(); + + if (this.CurURL == null) + return; + + int idx = AppSettings.Settings.AIURLList.IndexOf(this.CurURL); + + if (idx > -1) + { + AppSettings.Settings.AIURLList.Move(idx, idx + 1); + + for (int i = 0; i < AppSettings.Settings.AIURLList.Count; i++) + { + AppSettings.Settings.AIURLList[i].Order = i + 1; + } + + Global_GUI.UpdateFOLV(FOLV_AIServers, AppSettings.Settings.AIURLList, UseSelected: true, SelectObject: this.CurURL, FullRefresh: true); + + } + } + + private void FOLV_AIServers_MouseDoubleClick(object sender, MouseEventArgs e) + { + Edit(); + } + + private void FOLV_AIServers_FormatRow(object sender, BrightIdeasSoftware.FormatRowEventArgs e) + { + FormatURLRow(sender, e); + } + + private void FormatCell(object sender, BrightIdeasSoftware.FormatCellEventArgs e) + { + try + { + if (e.CellValue is DateTime) + { + //26-Feb-2015 08:51:18.820 + DateTime dt = ((DateTime)e.CellValue); + string NewText = ""; + if (dt > DateTime.MinValue) + { + NewText = dt.ToString(AppSettings.Settings.DateFormat); //"yyyy-MM-dd hh:mm:ss tt" + } + else + { + NewText = ""; + } + //"yyyy-MM-dd HH:mm:ss.fff" + e.SubItem.Text = NewText; + } + else if (e.CellValue is ThreadSafe.DateTime) + { + //26-Feb-2015 08:51:18.820 + DateTime dt = ((ThreadSafe.DateTime)e.CellValue); + string NewText = ""; + if (dt > DateTime.MinValue) + { + NewText = dt.ToString(AppSettings.Settings.DateFormat); //"yyyy-MM-dd hh:mm:ss tt" + } + else + { + NewText = ""; + } + //"yyyy-MM-dd HH:mm:ss.fff" + e.SubItem.Text = NewText; + } + + //else if (e.CellValue is long) + //{ + // long cv = (long)e.CellValue; + // if (cv == 0) + // { + // e.SubItem.Text = ""; + // } + // else + // { + // if (e.Column.Name == "DwgSize" || e.Column.Name.StartsWith("Mem")) + // { + // e.SubItem.Text = Shared.ToPrettySize(cv); + // } + // } + //} + else if (e.CellValue is int) + { + int cv = (int)e.CellValue; + if (cv == 0) + { + e.SubItem.Text = ""; + } + } + else if (e.CellValue is ThreadSafe.Integer) + { + int cv = (ThreadSafe.Integer)e.CellValue; + if (cv == 0) + { + e.SubItem.Text = ""; + } + } + else if (e.CellValue is TimeSpan) + { + TimeSpan ts = (TimeSpan)e.CellValue; + if ((ts.TotalMilliseconds == 0) || (ts == TimeSpan.MinValue) || (ts == TimeSpan.MaxValue)) + { + e.SubItem.Text = ""; + } + else + { + e.SubItem.Text = string.Format("{0}d {1}h {2}m {3}s", ts.Duration().Days, ts.Duration().Hours, ts.Duration().Minutes, ts.Duration().Seconds); + } + } + } + catch //(Exception ex) + { + //LogMsg("Error: " + ex.Message, , MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + private void FormatURLRow(object Sender, BrightIdeasSoftware.FormatRowEventArgs e) + { + using Working w = new Working(); + try + { + ClsURLItem url = (ClsURLItem)e.Model; + + // If SPI IsNot Nothing Then + if (url.Enabled && url.CurErrCount == 0 && !url.UseAsRefinementServer && !url.UseOnlyAsLinkedServer) + e.Item.ForeColor = Color.Green; + + else if (url.Enabled && url.CurErrCount == 0 && url.UseAsRefinementServer) + e.Item.ForeColor = Color.DarkOrange; + + else if (url.Enabled && url.CurErrCount == 0 && url.UseOnlyAsLinkedServer) + e.Item.ForeColor = Color.DarkCyan; + + else if (url.Enabled && url.CurErrCount > 0) + { + e.Item.ForeColor = Color.Black; + e.Item.BackColor = Color.Red; + } + else if (!url.Enabled) + e.Item.ForeColor = Color.Gray; + else + e.Item.ForeColor = Color.Black; + } + + + catch (Exception) + { + } + // Log("Error: " & ex.Msg()) + finally + { + } + } + + private void toolStripSplitButtonAdd_ButtonClick(object sender, EventArgs e) + { + toolStripSplitButtonAdd.ShowDropDown(); + } + + private void sightHoundAIServerToolStripMenuItem_Click(object sender, EventArgs e) + { + using Working w = new Working(); + ClsURLItem url = new ClsURLItem("", AppSettings.Settings.AIURLList.Count + 1, URLTypeEnum.SightHound_Vehicle); + if (!AppSettings.Settings.AIURLList.Contains(url)) + { + this.CurURL = url; + AppSettings.Settings.AIURLList.Add(url); + Global_GUI.UpdateFOLV(FOLV_AIServers, AppSettings.Settings.AIURLList, UseSelected: true, SelectObject: this.CurURL, FullRefresh: true); + AITOOL.UpdateAIURLs(); + } + else + { + MessageBox.Show("Already exists"); + } + UpdateButtons(); + } + + private void sightHoundPersonAIServerToolStripMenuItem_Click(object sender, EventArgs e) + { + using Working w = new Working(); + ClsURLItem url = new ClsURLItem("", AppSettings.Settings.AIURLList.Count + 1, URLTypeEnum.SightHound_Person); + if (!AppSettings.Settings.AIURLList.Contains(url)) + { + this.CurURL = url; + AppSettings.Settings.AIURLList.Add(url); + Global_GUI.UpdateFOLV(FOLV_AIServers, AppSettings.Settings.AIURLList, UseSelected: true, SelectObject: this.CurURL, FullRefresh: true); + AITOOL.UpdateAIURLs(); + } + else + { + MessageBox.Show("Already exists"); + } + UpdateButtons(); + } + + private void addAmazonFaceToolStripMenuItem_Click(object sender, EventArgs e) + { + using Working w = new Working(); + ClsURLItem url = new ClsURLItem("", AppSettings.Settings.AIURLList.Count + 1, URLTypeEnum.AWSRekognition_Faces); + if (!AppSettings.Settings.AIURLList.Contains(url)) + { + this.CurURL = url; + AppSettings.Settings.AIURLList.Add(url); + Global_GUI.UpdateFOLV(FOLV_AIServers, AppSettings.Settings.AIURLList, UseSelected: true, SelectObject: this.CurURL, FullRefresh: true); + AITOOL.UpdateAIURLs(); + + } + else + { + MessageBox.Show("Already exists"); + } + UpdateButtons(); + } + + private void deepstackCustomToolStripMenuItem_Click(object sender, EventArgs e) + { + using Working w = new Working(); + ClsURLItem url = new ClsURLItem("", AppSettings.Settings.AIURLList.Count + 1, URLTypeEnum.DeepStack_Custom); + this.CurURL = url; + AppSettings.Settings.AIURLList.Add(url); + Global_GUI.UpdateFOLV(FOLV_AIServers, AppSettings.Settings.AIURLList, UseSelected: true, SelectObject: this.CurURL, FullRefresh: true); + UpdateButtons(); + } + + private void deepstackFacesToolStripMenuItem_Click(object sender, EventArgs e) + { + using Working w = new Working(); + ClsURLItem url = new ClsURLItem("", AppSettings.Settings.AIURLList.Count + 1, URLTypeEnum.DeepStack_Faces); + this.CurURL = url; + AppSettings.Settings.AIURLList.Add(url); + Global_GUI.UpdateFOLV(FOLV_AIServers, AppSettings.Settings.AIURLList, UseSelected: true, SelectObject: this.CurURL, FullRefresh: true); + UpdateButtons(); + + } + + private void deepstackSceneToolStripMenuItem_Click(object sender, EventArgs e) + { + using Working w = new Working(); + ClsURLItem url = new ClsURLItem("", AppSettings.Settings.AIURLList.Count + 1, URLTypeEnum.DeepStack_Scene); + this.CurURL = url; + AppSettings.Settings.AIURLList.Add(url); + Global_GUI.UpdateFOLV(FOLV_AIServers, AppSettings.Settings.AIURLList, UseSelected: true, SelectObject: this.CurURL, FullRefresh: true); + UpdateButtons(); + + } + + private void codeProjectAIObjectsToolStripMenuItem_Click(object sender, EventArgs e) + { + using Working w = new Working(); + ClsURLItem url = new ClsURLItem("", AppSettings.Settings.AIURLList.Count + 1, URLTypeEnum.CodeProject_AI); + this.CurURL = url; + AppSettings.Settings.AIURLList.Add(url); + Global_GUI.UpdateFOLV(FOLV_AIServers, AppSettings.Settings.AIURLList, UseSelected: true, SelectObject: this.CurURL, FullRefresh: true); + UpdateButtons(); + } + + private void codeProjectAILicensePlateToolStripMenuItem_Click(object sender, EventArgs e) + { + using Working w = new Working(); + ClsURLItem url = new ClsURLItem("", AppSettings.Settings.AIURLList.Count + 1, URLTypeEnum.CodeProject_AI_Plate); + this.CurURL = url; + AppSettings.Settings.AIURLList.Add(url); + Global_GUI.UpdateFOLV(FOLV_AIServers, AppSettings.Settings.AIURLList, UseSelected: true, SelectObject: this.CurURL, FullRefresh: true); + UpdateButtons(); + + } + + private void codeProjectAICustomToolStripMenuItem_Click(object sender, EventArgs e) + { + using Working w = new Working(); + ClsURLItem url = new ClsURLItem("", AppSettings.Settings.AIURLList.Count + 1, URLTypeEnum.CodeProject_AI_Custom); + this.CurURL = url; + AppSettings.Settings.AIURLList.Add(url); + Global_GUI.UpdateFOLV(FOLV_AIServers, AppSettings.Settings.AIURLList, UseSelected: true, SelectObject: this.CurURL, FullRefresh: true); + UpdateButtons(); + + } + + + private void codeProjectAIIPCAMCombinedToolStripMenuItem_Click(object sender, EventArgs e) + { + using Working w = new Working(); + ClsURLItem url = new ClsURLItem("", AppSettings.Settings.AIURLList.Count + 1, URLTypeEnum.CodeProject_AI_IPCAM_Combined); + this.CurURL = url; + AppSettings.Settings.AIURLList.Add(url); + Global_GUI.UpdateFOLV(FOLV_AIServers, AppSettings.Settings.AIURLList, UseSelected: true, SelectObject: this.CurURL, FullRefresh: true); + UpdateButtons(); + + } + + private void codeProjectAIIPCAMGeneralToolStripMenuItem_Click(object sender, EventArgs e) + { + using Working w = new Working(); + ClsURLItem url = new ClsURLItem("", AppSettings.Settings.AIURLList.Count + 1, URLTypeEnum.CodeProject_AI_IPCAM_General); + this.CurURL = url; + AppSettings.Settings.AIURLList.Add(url); + Global_GUI.UpdateFOLV(FOLV_AIServers, AppSettings.Settings.AIURLList, UseSelected: true, SelectObject: this.CurURL, FullRefresh: true); + UpdateButtons(); + + } + + private void codeProjectAIIPCAMDarkToolStripMenuItem_Click(object sender, EventArgs e) + { + using Working w = new Working(); + ClsURLItem url = new ClsURLItem("", AppSettings.Settings.AIURLList.Count + 1, URLTypeEnum.CodeProject_AI_IPCAM_Dark); + this.CurURL = url; + AppSettings.Settings.AIURLList.Add(url); + Global_GUI.UpdateFOLV(FOLV_AIServers, AppSettings.Settings.AIURLList, UseSelected: true, SelectObject: this.CurURL, FullRefresh: true); + UpdateButtons(); + + } + + private void codeProjectAIIPCAMAnimalToolStripMenuItem_Click(object sender, EventArgs e) + { + using Working w = new Working(); + ClsURLItem url = new ClsURLItem("", AppSettings.Settings.AIURLList.Count + 1, URLTypeEnum.CodeProject_AI_IPCAM_Animal); + this.CurURL = url; + AppSettings.Settings.AIURLList.Add(url); + Global_GUI.UpdateFOLV(FOLV_AIServers, AppSettings.Settings.AIURLList, UseSelected: true, SelectObject: this.CurURL, FullRefresh: true); + UpdateButtons(); + + } + + private void codeProjectAIFacesToolStripMenuItem_Click(object sender, EventArgs e) + { + using Working w = new Working(); + ClsURLItem url = new ClsURLItem("", AppSettings.Settings.AIURLList.Count + 1, URLTypeEnum.CodeProject_AI_Faces); + this.CurURL = url; + AppSettings.Settings.AIURLList.Add(url); + Global_GUI.UpdateFOLV(FOLV_AIServers, AppSettings.Settings.AIURLList, UseSelected: true, SelectObject: this.CurURL, FullRefresh: true); + UpdateButtons(); + + } + + private void codeProjectAISceneToolStripMenuItem_Click(object sender, EventArgs e) + { + using Working w = new Working(); + + ClsURLItem url = new ClsURLItem("", AppSettings.Settings.AIURLList.Count + 1, URLTypeEnum.CodeProject_AI_Scene); + this.CurURL = url; + AppSettings.Settings.AIURLList.Add(url); + Global_GUI.UpdateFOLV(FOLV_AIServers, AppSettings.Settings.AIURLList, UseSelected: true, SelectObject: this.CurURL, FullRefresh: true); + UpdateButtons(); + + } + + private void timer1_Tick(object sender, EventArgs e) + { + if (_Working) + return; + + //this.FOLV_AIServers.RefreshObjects(AppSettings.Settings.AIURLList); + this.FOLV_AIServers.Refresh(); + //this.FOLV_AIServers.Update(); + //this.FOLV_AIServers.Invalidate(); + + } + + private void FOLV_AIServers_DoubleClick(object sender, EventArgs e) + { + Edit(); + } + + public static bool _Working = true; + //create a class that is disposable that simply sets a working bool to false when disposed + public class Working:IDisposable + { + public Working() + { + _Working = true; + } + public void Dispose() + { + _Working = false; + } + } + + private void FOLV_AIServers_FormatCell(object sender, BrightIdeasSoftware.FormatCellEventArgs e) + { + FormatCell(sender, e); + } + } +} diff --git a/src/UI/Frm_AIServers.resx b/src/UI/Frm_AIServers.resx new file mode 100644 index 00000000..e1d42d92 --- /dev/null +++ b/src/UI/Frm_AIServers.resx @@ -0,0 +1,2133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + + + 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 + AAAASUVORK5CYII= + + + + + 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 + AAAASUVORK5CYII= + + + + + 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 + AAAASUVORK5CYII= + + + + + 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 + AAAASUVORK5CYII= + + + + + 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 + Ev8D0fvWBwVLmtMAAAAASUVORK5CYII= + + + + + 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 + Ev8D0fvWBwVLmtMAAAAASUVORK5CYII= + + + + + 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 + AAAASUVORK5CYII= + + + + + iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAABGdBTUEAALGPC/xhBQAADVpJREFUeF7t + nImXFEUSxvf/W3QVj3VX5ZCbRbmVQ0HkEgVFRBFRRERRBOVGQBRE8EBEERVkZnpmeu77vofY/CWVY01P + dU91dVVXdb+K977HzFCVWRlfZmRkZGT+419TSyRGdBATEjHEhEQMMSERQ0xIxBATEjHEhEQMMSERQ0xI + xBATEjHEhEQMMSERQ0xIxBATEjHEhEQMMSERg2dC7p1SIhMm39Y/PzijVB6eVTrmmRjZwxMhkDF5fkJ2 + fdQop75ul2s3euT0+faYFB+QNSGQcf+0Ejn8RavY5Zffe2Ti9FK5Z7LzezHcIStCIOO+J0rkrb0N0ts3 + bFFxVxKV/fKOGjFPPlsp91nPOpURIzNcE0LP/+//ymTfoWbp6RlNhl2aWgbl0+MtMnVhIh4tHuCKEHr7 + qper5fofPTKcnotR8vutXlnyQlLuiUdKVnA9Qs5f7rBU7V6qagdk2YaqeKRkgXEJmTDptsx6ukKS1f2W + mrMT5pannquUex3KLjZgDeh8wOscmpYQCmUCX6zMztXr3ZZ6vYkhpRhHiiHgIeXyz3qmQhauTsrcFRV6 + voWUbNs8hhAKAXhLx79sk9b2IUutucnJr9q1u5xaX6GBTko7+Pfh2aUyd3mFvPl+g/z8W7c0Ng9KZ9ew + tLQNyV9lfdq5QY9Gp07lpWIUIbw0eUFCe1INTYOWKv2RelXeTGX6CnmUoJ9JTyXkk6Mt8u2PnfLHX73S + Nk6HRY9v72uUB2eWuiJlhBAenq2GHJW4kfaOIblZ0qt7Rml5n/T137H+x1nwziDaXnkhgpGxaE1S9/7L + P3VpnDjXJnsONOmRsuvDRvnsZKtcu9GtRwsyNHRHDqm/PeSClFGEYPc++rxZ23wKscuw+pUh+f3PXZrx + BcpWPjK3TB6YUSqPqvf4mPFI6Vbrl9d3N4zUZ+ouNJh5437LfNEW4no4QIDJnTnl6XVVcuH7ThlSyqND + 7v64ybE8O8aYLMCwfOXtevnyYof8cK1Lx6m27qqXOcsqNAFUzgcZpfLz1IXlUlbRZ6k+vUDKB2qk/EeR + aYKTxYoJSi8Qc+BYi9xRfZW5BScpk9keM6kDFG1eIj7Fv5oEiwAn4NYu31gl1XUDlurTC70Fz+35LTX6 + g3WvguCUMosB6PLfs8t0x0ZOKecGs+f0LHAkxCuo/Ow37bpiN9LTOyw//tKtzFi99kb4cMoYIShDB8gW + upMBVS7QJsYa6X7XlQras3F7re6INarDTltcnrY+Xwmhgdvfa7DU7V4YzrjXN272yNHTbdo8EnbBDBLS + p0dpJVo2OlWZduj/s57jd76L/ZrHlRk264Rn1lfJyherZYXC0rVVMm9lpY69UZfuEKoMPwmiI+AwEefr + H7ij68acOT3rKyEogAZ3dOa2doGgru5hbf6In537tkMOKFdz5weN8vKOOm3qMI9L1ya1x7Po+aQmkDDN + qs3VsumNWnljT4N2UJj/8ATLk/1aIXg+RKr7lQOCE8IoxXWtrh2QX1VdR8+0qffrdC82bUptZ7aAXJYT + hJKQl96s0x3G6VnfTRaT9Z+33bnOXoRhPzColNl3RzsInYo40KV+RtEDqgfyTC6ChwlB7PkQYTBtS22v + W/AuBNc15pkQQOVHlNkpFsHVx11lVe51tPAeLjDbFozMlZswWXkiBOYxGanrmEIW1hGYzRlLyz2Rwjvv + f9qky6qtz+OkDpjAiA77HXqJgpSU98n612pl4jT3o4Xnpi8pl4SawxCIzRTT850QwOKRFX0xCk7AF1+3 + j8wtKDxdb+f/HplTNrIU4F2cjkxkBkIIFWJ3i1nw2I6dbdOeHY4MpBh3m/mBUQBpF77rHHEyIIbO6qQz + g8AIwSXFdS12wdMjIMtWBUkerMMINEIEpBkhHI8pzxTtAIEQQm9hqLKvXkwyqNxtL52MqPhCtVbKZKoM + AiEEUPn+wy3WJxWHQMbOfY3y8ZEWua16PKMjkzBCjpxu1ZO6GzJAYIRgSzdsKy73Fzl4vEUr99F5ZToE + 89beRm2uLl3pkp+ud2tnhmxOtiMIyRD2STfpOyE4QtTERuoQsZtiEqLUTMwoGWJM3ItJ/IHppaOj4y5H + hR2BmiziTYQ4ikmYF4lKO7XZDwRKCAkAza3hLxAJKKJI3E6SLYgqE/PyIuQwsxXr1GY/EBghgHA2DQhL + auoH5OCJVh0Jxuvjm4wH+No79VLfmH1nuXSlM+NKO1cESgijhDB4rtHXbIWAIJkh7EEYW2+fWM3fmONa + WrPbKiCcb2+j3wiUEBqON8I+Qz4ENxSFmUzJ8RZhfB97JtnI/sPNmkyn8vxAoIQAPn715upAV+241mxC + rd5ck13gTxFCrhjbqm6EjTNMXbq9DD8QOCGAdBl24oIQdgIxi8STvPZcFm9uhA2wFRszBwdzRV4IMSF5 + rwnbTlLXMKDNDXsLKMg+R2QD3n12U7X09o4/gtlSYER5rcsN8kIIoOE71Oo13QSPOSA8nUl4hn1pJmxc + ahSTq3J4nwTBW6Xjx93YmsZDKwpCaAQTPGuAdIKLzAZOg/KSSEBg75yJml22y1e6dNYjcSHK8tNsUB6p + nuPJV5c6MuZU+YG8EQJQIlkjKNpJSAUiRYatUvadyS5hLxqzREjChCmcys4FlEtd7HdnEkyknx3BCXkl + BGUy+V7/M/0oIVEZBRmggCBIsIPyydsqSWROhcWlLqoRAlDytt31jnMJCd1bcSvVM07vBglIYY7LFJ0m + N9ftvoZX5J0QGv7EonKpdPC4SFgjpTRos+AEvotQD7m3mYQEdKK6TmX4gbwTYsB+dKpwzoSJP2gTlQ7U + +9i8hFz8odP6orFC9JpEt6A6TSiEYJLWbq0ZM7mTIW72E8ICa6YpCxLy3dX0WTOsp4IayaEQQk+crjwn + 3Fm7kF7j9Hy+ASnc5cIknm5OIVQzJYDLEUIzWZy5Sz3dS0J1GPOHE+g0nF15d3+TtKdJHuecIcT5+c2h + EUKDT54bPY+QyxWGh5UOZi5b80qNPubnJJhZnd7jEymhEcL5iL0HR4e+CRIGGUn1CpQ9Z3mFTmQgfJMq + ZKCwt8KzhkSvCI8QpXgO5tgb+Kr6PYqEAEhhUct+iFP6D2aNGBsufS6khDhCRmelsCgkrBJVQgCKZqX+ + 4vbakcM3qYJp2/h6rWdSQiOEHsfOHufdEVL+aWiUCTEw337l125HE8YpY69zSqiT+lQ1vElEQHAv6VmF + QAhA4Sxiz1wYvbJnxLNz6dU5CZUQbPKt0rsBPQgh07FQCAGQwkFSNsuMkD46e9n4SdXpEBohgLWISRMq + JJNlB1mMV9Ui0QhZ7uYmIKfnx0OohOjGWItDor+ZDkNGFUzyHD0wgmucS95WqISwEjZ7I0yOW3YWHiGM + BHuEmJO7XkcHCHUOmTQ/IRVVf6+A3axDeA9gv1lcMnnyjob6mb9rWM9let9g5H1bGea51PdTwbP2K3M5 + rkA5Ts+6QWiE0BBSPE2+FibLzCFGYUbZ/IxpIKf2sScTMmNJucxfVamTuYkac1HOjr0NsueTJn0mhf3x + Y2fadB4vAUI8IXBagaMCHB/4/FSrXsgRLeB2IzbGNigvj61jNqEIh3AJD3skmCAINqQZwmgH/5qtBBwT + DoUWLCHvKQUaISF68ZqkTnQjYDdf+fkvvFqjFX3oRKucV3aaDEgWXqTjsDImEWJQKcFpLeBF6BS4rXwL + 3hKj97ebPXp/BALp/eu21sqCVUl9MwNXdvC93JmFsILXN7Gqtjm12Q3yTgg97Z+qB+GJ2G986OgaknMX + O/Shl2RNv76eI9N2KgIRHDMztzqwyCTbnqwVXFEyEqtqBvTuJMo14Hfq4OqOWvUcBPMe71OOIdpJqJNb + I7jpgRA8I4+tXYScYlxe2sfodmPyUhE4Icb8AHoUfvs6NazZ+0incFxgFEMDyUykl9ILMT+cYGJkcbgS + r4xILKaL0cXqmSAgyWxkqrDRxGjD9JDEYMDv/J3/J/ZElgt3gbHpxL0pnKxds6VG33lC6hH1cYMc9bNx + xdEGCIUIOgPfi9A58BrJTiGLhbIxd2ZectJPKgIhBBJMD8Hmc+vOvs+adY+iJ9IAzAOjgpgQnhY5T9h0 + FI2p4lIZFPu4et/YccqmYWZu0bAaawf1ZgtGrr0Mpzqof6L6Dq7ZgFSy67lNaL1a0HIxDmaNjsNaJKlG + JhcNcKMc/wfRHPQx9dj1ZYdvhOhsc1URjcN7WqcmWyZP9snJt6K3Y6LOftOhexyN4JpAdt1QOJO2VoSl + BFMWcKovbNhJHPlm9TsdhwjETDXqGLncBWZGF97Ytnfr9UiGVKdycybEfBgfgYdC5YwEMhTJQuTs9nMv + 3U1+M0fBTO8zSk8ts9BhOpIhi39ZBNP5MK2rlTlbochiGzt1EZkTIRSGrSYoyD4Bx4VZS+CFMGnzjFa+ + QjEqPlsYkgAnAphTUxPvPBNCdggTIRMgQ5DIJ38v5p6fD3gmBGbxwQ3rMQH+wLdJPYY/iAmJGGJCIoaY + kIghJiRiiAmJGGJCIoaYkIghJiRiiAmJGGJCIoaYkIghJiRiiAmJGGJCIoaYkEihRP4PUqbgpOE2RfEA + AAAASUVORK5CYII= + + + + + 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 + QgAAAABJRU5ErkJggg== + + + + + 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 + + + + 122, 17 + + + + AAEAAAD/////AQAAAAAAAAAMAgAAAEZTeXN0ZW0uV2luZG93cy5Gb3JtcywgQ3VsdHVyZT1uZXV0cmFs + LCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5BQEAAAAmU3lzdGVtLldpbmRvd3MuRm9ybXMu + SW1hZ2VMaXN0U3RyZWFtZXIBAAAABERhdGEHAgIAAAAJAwAAAA8DAAAAPgsAAAJNU0Z0AUkBTAIBAQUB + AAHYAQAB2AEAARABAAEQAQAE/wEJAQAI/wFCAU0BNgEEBgABNgEEAgABKAMAAUADAAEgAwABAQEAAQgG + AAEIGAABgAIAAYADAAKAAQABgAMAAYABAAGAAQACgAIAA8ABAAHAAdwBwAEAAfABygGmAQABMwUAATMB + AAEzAQABMwEAAjMCAAMWAQADHAEAAyIBAAMpAQADVQEAA00BAANCAQADOQEAAYABfAH/AQACUAH/AQAB + kwEAAdYBAAH/AewBzAEAAcYB1gHvAQAB1gLnAQABkAGpAa0CAAH/ATMDAAFmAwABmQMAAcwCAAEzAwAC + MwIAATMBZgIAATMBmQIAATMBzAIAATMB/wIAAWYDAAFmATMCAAJmAgABZgGZAgABZgHMAgABZgH/AgAB + mQMAAZkBMwIAAZkBZgIAApkCAAGZAcwCAAGZAf8CAAHMAwABzAEzAgABzAFmAgABzAGZAgACzAIAAcwB + /wIAAf8BZgIAAf8BmQIAAf8BzAEAATMB/wIAAf8BAAEzAQABMwEAAWYBAAEzAQABmQEAATMBAAHMAQAB + MwEAAf8BAAH/ATMCAAMzAQACMwFmAQACMwGZAQACMwHMAQACMwH/AQABMwFmAgABMwFmATMBAAEzAmYB + AAEzAWYBmQEAATMBZgHMAQABMwFmAf8BAAEzAZkCAAEzAZkBMwEAATMBmQFmAQABMwKZAQABMwGZAcwB + AAEzAZkB/wEAATMBzAIAATMBzAEzAQABMwHMAWYBAAEzAcwBmQEAATMCzAEAATMBzAH/AQABMwH/ATMB + AAEzAf8BZgEAATMB/wGZAQABMwH/AcwBAAEzAv8BAAFmAwABZgEAATMBAAFmAQABZgEAAWYBAAGZAQAB + ZgEAAcwBAAFmAQAB/wEAAWYBMwIAAWYCMwEAAWYBMwFmAQABZgEzAZkBAAFmATMBzAEAAWYBMwH/AQAC + ZgIAAmYBMwEAA2YBAAJmAZkBAAJmAcwBAAFmAZkCAAFmAZkBMwEAAWYBmQFmAQABZgKZAQABZgGZAcwB + AAFmAZkB/wEAAWYBzAIAAWYBzAEzAQABZgHMAZkBAAFmAswBAAFmAcwB/wEAAWYB/wIAAWYB/wEzAQAB + ZgH/AZkBAAFmAf8BzAEAAcwBAAH/AQAB/wEAAcwBAAKZAgABmQEzAZkBAAGZAQABmQEAAZkBAAHMAQAB + mQMAAZkCMwEAAZkBAAFmAQABmQEzAcwBAAGZAQAB/wEAAZkBZgIAAZkBZgEzAQABmQEzAWYBAAGZAWYB + mQEAAZkBZgHMAQABmQEzAf8BAAKZATMBAAKZAWYBAAOZAQACmQHMAQACmQH/AQABmQHMAgABmQHMATMB + AAFmAcwBZgEAAZkBzAGZAQABmQLMAQABmQHMAf8BAAGZAf8CAAGZAf8BMwEAAZkBzAFmAQABmQH/AZkB + AAGZAf8BzAEAAZkC/wEAAcwDAAGZAQABMwEAAcwBAAFmAQABzAEAAZkBAAHMAQABzAEAAZkBMwIAAcwC + MwEAAcwBMwFmAQABzAEzAZkBAAHMATMBzAEAAcwBMwH/AQABzAFmAgABzAFmATMBAAGZAmYBAAHMAWYB + mQEAAcwBZgHMAQABmQFmAf8BAAHMAZkCAAHMAZkBMwEAAcwBmQFmAQABzAKZAQABzAGZAcwBAAHMAZkB + /wEAAswCAALMATMBAALMAWYBAALMAZkBAAPMAQACzAH/AQABzAH/AgABzAH/ATMBAAGZAf8BZgEAAcwB + /wGZAQABzAH/AcwBAAHMAv8BAAHMAQABMwEAAf8BAAFmAQAB/wEAAZkBAAHMATMCAAH/AjMBAAH/ATMB + ZgEAAf8BMwGZAQAB/wEzAcwBAAH/ATMB/wEAAf8BZgIAAf8BZgEzAQABzAJmAQAB/wFmAZkBAAH/AWYB + zAEAAcwBZgH/AQAB/wGZAgAB/wGZATMBAAH/AZkBZgEAAf8CmQEAAf8BmQHMAQAB/wGZAf8BAAH/AcwC + AAH/AcwBMwEAAf8BzAFmAQAB/wHMAZkBAAH/AswBAAH/AcwB/wEAAv8BMwEAAcwB/wFmAQAC/wGZAQAC + /wHMAQACZgH/AQABZgH/AWYBAAFmAv8BAAH/AmYBAAH/AWYB/wEAAv8BZgEAASEBAAGlAQADXwEAA3cB + AAOGAQADlgEAA8sBAAOyAQAD1wEAA90BAAPjAQAD6gEAA/EBAAP4AQAB8AH7Af8BAAGkAqABAAOAAwAB + /wIAAf8DAAL/AQAB/wMAAf8BAAH/AQAC/wIAA/8BAAF6Dv8BBzAAAXoBmgK9ARoBAAFXAXIBAAFyAVcB + DgERAr0BBzAAAXoHMgExBjIBBzAAAXoBMgErATEDMgEiAQABIgUyAQcwAAF6ATIBDwEAAjIBAANXASIC + MgEAASMBBzAAAXoEMgEPAQADVwEOAQADMgEHMAABegUyBAABKgQyAQcwAAF6BDICAARXATEDMgEHMAAB + egMyAgABVwEAAQ8BVwEAAVcDMgEHMAABegMyAgABlwH/AfcBAAEUAQ4BIwIyAQcwAAF6AzICAAFXAv8B + AAH/AQABMQIyAQcwAAF6BDICAAFWAXIDVwMyAQcwAAF6BTIBAAFJA1cEMgEHMAABegb/AQACVwHzBP8B + BzAAAXoH/wFDBv8BBzAAD3oxAAdrA2wGcQYAAfECBAGuCgAB9AbyAfMB/wMAEK0GawNsB3EEAAH/BgQB + BwgAAeoH/wH0AwAQrQVrA2wDcQGQArsBcQGQAgAB/wGLAwQChQQEAfQGAAHqAfAF7wH/BAADrQHWC60B + zwRrA2wEcQG7AXEBuwKQAQABtQMEAYUFoQMEAYYB/wQAAeoB8AUHAf8EAAWtAQkBzwEZBP8B1gEZAq0D + awJsAXEDuwGQAbsBcQG7A5ABrgIEAYUJoQMEBAAB6gbwAf8EAAWtAbQHrQH/Aq0CawJsAbsBcQGQAZ0B + cQGdAXEBuwSQAf8BpgShBfwDoQGFAbwEAAHqBu8B/wQABa0B3QatAf8BzwKtAWsCbAGRAXEBuwJxAZAB + uwGdBZAB8wOhA/wCQgP8BKEEAAHqA+8CvAEHAf8B7wMABa0B/wWtAf8B3QOtA2wCuwFxAbsBcQHzApAB + uwSQAf8CoQL8AUIFzQP8AaEBpwH/AvMB8gHqBvEB/wLyAvMErQG0A/8B3QKtAv8DrQJsAXEBuwH0BHEB + kAK7BJAB/wP8CM0BQgL8AT0EAAHqAe8FvAH/Ae8DAAStCf8CzwGtAWwCcQG7AfEBcQGQAXEBuwKQAbsE + kAH/AfwMzQL8BAAB6gbvAf8EAAStCP8B9AGtAs8DcQGYAXEBuwGRAZABkQG7AZABkQSQAf8OzQHUBAAB + 6gbvAf8EAAStAv8BzwOtAc8ErQHPBHEB9AGQAfIB/wG7AZABuwSQAZYBGQ/NBAAB6gHvBbwB/wQAAa0B + tAP/CK0C1gGtBXEEuwGdBJAClgEAAf8MzQHcBQAB6gbzAf8EAAKtAc8B1gytBHEJkAOWAwAB8wjNAdQB + /wYABvQB/wFtBAAQrQNxCZADlgGdBQAB3AXNAf8JAAEHBeoFABCtAnEJkAOWAp0GAAH/AdwB1QEZFgAQ + rQFCAU0BPgcAAT4DAAEoAwABQAMAASADAAEBAQABAQYAAQEWAAP/egABAQgAAfwBPwHwAQcEAAHwAQ8B + 8AEHBAABwAEDAfABDwQAAYABAAHwAQ8GAAHwAQ8GAAHwAQ8GAAHwAQcOAAHwAQcGAAHwAQ8GAAHwAQ8G + AAHwAQ8EAAGAAQEB8AEPBAAB4AEDAfABDwQAAfgBDwH4AR8EAAH8AT8C/wIACw== + + + + 291, 17 + + \ No newline at end of file diff --git a/src/UI/Frm_AnnoAdjust.Designer.cs b/src/UI/Frm_AnnoAdjust.Designer.cs new file mode 100644 index 00000000..394977a9 --- /dev/null +++ b/src/UI/Frm_AnnoAdjust.Designer.cs @@ -0,0 +1,639 @@ + +namespace AITool +{ + partial class Frm_AnnoAdjust + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.label1 = new System.Windows.Forms.Label(); + this.tb_BorderWidthPixels = new System.Windows.Forms.TextBox(); + this.label2 = new System.Windows.Forms.Label(); + this.tb_FontName = new System.Windows.Forms.TextBox(); + this.label3 = new System.Windows.Forms.Label(); + this.tb_FontSize = new System.Windows.Forms.TextBox(); + this.label4 = new System.Windows.Forms.Label(); + this.label5 = new System.Windows.Forms.Label(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.button1 = new System.Windows.Forms.Button(); + this.label7 = new System.Windows.Forms.Label(); + this.tb_RelevantAlpha = new System.Windows.Forms.TextBox(); + this.label6 = new System.Windows.Forms.Label(); + this.tb_RelevantColor = new System.Windows.Forms.TextBox(); + this.label8 = new System.Windows.Forms.Label(); + this.groupBox2 = new System.Windows.Forms.GroupBox(); + this.button2 = new System.Windows.Forms.Button(); + this.label9 = new System.Windows.Forms.Label(); + this.tb_IrrelevantAlpha = new System.Windows.Forms.TextBox(); + this.label10 = new System.Windows.Forms.Label(); + this.tb_IrrelevantColor = new System.Windows.Forms.TextBox(); + this.label11 = new System.Windows.Forms.Label(); + this.groupBox3 = new System.Windows.Forms.GroupBox(); + this.button3 = new System.Windows.Forms.Button(); + this.label12 = new System.Windows.Forms.Label(); + this.tb_MaskedAlpha = new System.Windows.Forms.TextBox(); + this.label13 = new System.Windows.Forms.Label(); + this.tb_MaskedColor = new System.Windows.Forms.TextBox(); + this.label14 = new System.Windows.Forms.Label(); + this.groupBox4 = new System.Windows.Forms.GroupBox(); + this.button5 = new System.Windows.Forms.Button(); + this.button4 = new System.Windows.Forms.Button(); + this.Lbl_Example = new System.Windows.Forms.Label(); + this.cb_UseAssignedBackColor = new System.Windows.Forms.CheckBox(); + this.label16 = new System.Windows.Forms.Label(); + this.label15 = new System.Windows.Forms.Label(); + this.tb_FontBackColor = new System.Windows.Forms.TextBox(); + this.tb_FontForeColor = new System.Windows.Forms.TextBox(); + this.linkLabel1 = new System.Windows.Forms.LinkLabel(); + this.btnCancel = new System.Windows.Forms.Button(); + this.btnSave = new System.Windows.Forms.Button(); + this.colorDialog1 = new System.Windows.Forms.ColorDialog(); + this.groupBox1.SuspendLayout(); + this.groupBox2.SuspendLayout(); + this.groupBox3.SuspendLayout(); + this.groupBox4.SuspendLayout(); + this.SuspendLayout(); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(8, 13); + this.label1.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(79, 13); + this.label1.TabIndex = 0; + this.label1.Text = "Border Width:"; + // + // tb_BorderWidthPixels + // + this.tb_BorderWidthPixels.Location = new System.Drawing.Point(87, 8); + this.tb_BorderWidthPixels.Margin = new System.Windows.Forms.Padding(2); + this.tb_BorderWidthPixels.Name = "tb_BorderWidthPixels"; + this.tb_BorderWidthPixels.Size = new System.Drawing.Size(34, 22); + this.tb_BorderWidthPixels.TabIndex = 2; + this.tb_BorderWidthPixels.Text = "3"; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(31, 21); + this.label2.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(34, 13); + this.label2.TabIndex = 0; + this.label2.Text = "Font:"; + // + // tb_FontName + // + this.tb_FontName.Location = new System.Drawing.Point(66, 19); + this.tb_FontName.Margin = new System.Windows.Forms.Padding(2); + this.tb_FontName.Name = "tb_FontName"; + this.tb_FontName.Size = new System.Drawing.Size(121, 22); + this.tb_FontName.TabIndex = 2; + this.tb_FontName.Text = "Segoe UI"; + this.tb_FontName.Enter += new System.EventHandler(this.tb_FontName_Enter); + this.tb_FontName.Leave += new System.EventHandler(this.tb_FontName_Leave); + // + // label3 + // + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(224, 20); + this.label3.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(30, 13); + this.label3.TabIndex = 0; + this.label3.Text = "Size:"; + // + // tb_FontSize + // + this.tb_FontSize.Location = new System.Drawing.Point(258, 18); + this.tb_FontSize.Margin = new System.Windows.Forms.Padding(2); + this.tb_FontSize.Name = "tb_FontSize"; + this.tb_FontSize.Size = new System.Drawing.Size(34, 22); + this.tb_FontSize.TabIndex = 2; + this.tb_FontSize.Text = "14"; + this.tb_FontSize.Enter += new System.EventHandler(this.tb_FontSize_Enter); + this.tb_FontSize.Leave += new System.EventHandler(this.tb_FontSize_Leave); + // + // label4 + // + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(125, 13); + this.label4.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(41, 13); + this.label4.TabIndex = 0; + this.label4.Text = "(Pixels)"; + // + // label5 + // + this.label5.AutoSize = true; + this.label5.Location = new System.Drawing.Point(294, 20); + this.label5.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + this.label5.Name = "label5"; + this.label5.Size = new System.Drawing.Size(45, 13); + this.label5.TabIndex = 0; + this.label5.Text = "(Points)"; + // + // groupBox1 + // + this.groupBox1.Controls.Add(this.button1); + this.groupBox1.Controls.Add(this.label7); + this.groupBox1.Controls.Add(this.tb_RelevantAlpha); + this.groupBox1.Controls.Add(this.label6); + this.groupBox1.Controls.Add(this.tb_RelevantColor); + this.groupBox1.Controls.Add(this.label8); + this.groupBox1.Location = new System.Drawing.Point(11, 38); + this.groupBox1.Margin = new System.Windows.Forms.Padding(2); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.Padding = new System.Windows.Forms.Padding(2); + this.groupBox1.Size = new System.Drawing.Size(387, 44); + this.groupBox1.TabIndex = 3; + this.groupBox1.TabStop = false; + this.groupBox1.Text = "Relevant Object"; + // + // button1 + // + this.button1.FlatAppearance.BorderSize = 0; + this.button1.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.button1.Image = global::AITool.Properties.Resources.color_wheel; + this.button1.Location = new System.Drawing.Point(173, 15); + this.button1.Name = "button1"; + this.button1.Size = new System.Drawing.Size(22, 20); + this.button1.TabIndex = 5; + this.button1.UseVisualStyleBackColor = true; + this.button1.Click += new System.EventHandler(this.button1_Click); + // + // label7 + // + this.label7.AutoSize = true; + this.label7.Location = new System.Drawing.Point(218, 19); + this.label7.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + this.label7.Name = "label7"; + this.label7.Size = new System.Drawing.Size(40, 13); + this.label7.TabIndex = 0; + this.label7.Text = "Alpha:"; + // + // tb_RelevantAlpha + // + this.tb_RelevantAlpha.Location = new System.Drawing.Point(258, 17); + this.tb_RelevantAlpha.Margin = new System.Windows.Forms.Padding(2); + this.tb_RelevantAlpha.Name = "tb_RelevantAlpha"; + this.tb_RelevantAlpha.Size = new System.Drawing.Size(34, 22); + this.tb_RelevantAlpha.TabIndex = 2; + this.tb_RelevantAlpha.Text = "255"; + this.tb_RelevantAlpha.Enter += new System.EventHandler(this.tb_RelevantAlpha_Enter); + this.tb_RelevantAlpha.Leave += new System.EventHandler(this.tb_RelevantAlpha_Leave); + // + // label6 + // + this.label6.AutoSize = true; + this.label6.Location = new System.Drawing.Point(10, 18); + this.label6.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + this.label6.Name = "label6"; + this.label6.Size = new System.Drawing.Size(38, 13); + this.label6.TabIndex = 0; + this.label6.Text = "Color:"; + // + // tb_RelevantColor + // + this.tb_RelevantColor.Location = new System.Drawing.Point(47, 16); + this.tb_RelevantColor.Margin = new System.Windows.Forms.Padding(2); + this.tb_RelevantColor.Name = "tb_RelevantColor"; + this.tb_RelevantColor.Size = new System.Drawing.Size(121, 22); + this.tb_RelevantColor.TabIndex = 2; + this.tb_RelevantColor.Text = "DarkSlateGray"; + this.tb_RelevantColor.Enter += new System.EventHandler(this.tb_RelevantColor_Enter); + this.tb_RelevantColor.Leave += new System.EventHandler(this.tb_RelevantColor_Leave); + // + // label8 + // + this.label8.AutoSize = true; + this.label8.Location = new System.Drawing.Point(294, 19); + this.label8.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + this.label8.Name = "label8"; + this.label8.Size = new System.Drawing.Size(70, 13); + this.label8.TabIndex = 0; + this.label8.Text = "(255 = solid)"; + // + // groupBox2 + // + this.groupBox2.Controls.Add(this.button2); + this.groupBox2.Controls.Add(this.label9); + this.groupBox2.Controls.Add(this.tb_IrrelevantAlpha); + this.groupBox2.Controls.Add(this.label10); + this.groupBox2.Controls.Add(this.tb_IrrelevantColor); + this.groupBox2.Controls.Add(this.label11); + this.groupBox2.Location = new System.Drawing.Point(11, 86); + this.groupBox2.Margin = new System.Windows.Forms.Padding(2); + this.groupBox2.Name = "groupBox2"; + this.groupBox2.Padding = new System.Windows.Forms.Padding(2); + this.groupBox2.Size = new System.Drawing.Size(387, 44); + this.groupBox2.TabIndex = 3; + this.groupBox2.TabStop = false; + this.groupBox2.Text = "Irrelevant Object"; + // + // button2 + // + this.button2.FlatAppearance.BorderSize = 0; + this.button2.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.button2.Image = global::AITool.Properties.Resources.color_wheel; + this.button2.Location = new System.Drawing.Point(173, 15); + this.button2.Name = "button2"; + this.button2.Size = new System.Drawing.Size(22, 20); + this.button2.TabIndex = 5; + this.button2.UseVisualStyleBackColor = true; + this.button2.Click += new System.EventHandler(this.button2_Click); + // + // label9 + // + this.label9.AutoSize = true; + this.label9.Location = new System.Drawing.Point(218, 19); + this.label9.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + this.label9.Name = "label9"; + this.label9.Size = new System.Drawing.Size(40, 13); + this.label9.TabIndex = 0; + this.label9.Text = "Alpha:"; + // + // tb_IrrelevantAlpha + // + this.tb_IrrelevantAlpha.Location = new System.Drawing.Point(258, 17); + this.tb_IrrelevantAlpha.Margin = new System.Windows.Forms.Padding(2); + this.tb_IrrelevantAlpha.Name = "tb_IrrelevantAlpha"; + this.tb_IrrelevantAlpha.Size = new System.Drawing.Size(34, 22); + this.tb_IrrelevantAlpha.TabIndex = 2; + this.tb_IrrelevantAlpha.Text = "255"; + this.tb_IrrelevantAlpha.Leave += new System.EventHandler(this.tb_IrrelevantAlpha_Leave); + // + // label10 + // + this.label10.AutoSize = true; + this.label10.Location = new System.Drawing.Point(10, 18); + this.label10.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + this.label10.Name = "label10"; + this.label10.Size = new System.Drawing.Size(38, 13); + this.label10.TabIndex = 0; + this.label10.Text = "Color:"; + // + // tb_IrrelevantColor + // + this.tb_IrrelevantColor.Location = new System.Drawing.Point(47, 16); + this.tb_IrrelevantColor.Margin = new System.Windows.Forms.Padding(2); + this.tb_IrrelevantColor.Name = "tb_IrrelevantColor"; + this.tb_IrrelevantColor.Size = new System.Drawing.Size(121, 22); + this.tb_IrrelevantColor.TabIndex = 2; + this.tb_IrrelevantColor.Text = "DarkSlateGray"; + this.tb_IrrelevantColor.Enter += new System.EventHandler(this.tb_IrrelevantColor_Enter); + this.tb_IrrelevantColor.Leave += new System.EventHandler(this.tb_IrrelevantColor_Leave); + // + // label11 + // + this.label11.AutoSize = true; + this.label11.Location = new System.Drawing.Point(294, 19); + this.label11.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + this.label11.Name = "label11"; + this.label11.Size = new System.Drawing.Size(70, 13); + this.label11.TabIndex = 0; + this.label11.Text = "(255 = solid)"; + // + // groupBox3 + // + this.groupBox3.Controls.Add(this.button3); + this.groupBox3.Controls.Add(this.label12); + this.groupBox3.Controls.Add(this.tb_MaskedAlpha); + this.groupBox3.Controls.Add(this.label13); + this.groupBox3.Controls.Add(this.tb_MaskedColor); + this.groupBox3.Controls.Add(this.label14); + this.groupBox3.Location = new System.Drawing.Point(11, 134); + this.groupBox3.Margin = new System.Windows.Forms.Padding(2); + this.groupBox3.Name = "groupBox3"; + this.groupBox3.Padding = new System.Windows.Forms.Padding(2); + this.groupBox3.Size = new System.Drawing.Size(387, 44); + this.groupBox3.TabIndex = 3; + this.groupBox3.TabStop = false; + this.groupBox3.Text = "Masked Object"; + // + // button3 + // + this.button3.FlatAppearance.BorderSize = 0; + this.button3.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.button3.Image = global::AITool.Properties.Resources.color_wheel; + this.button3.Location = new System.Drawing.Point(173, 15); + this.button3.Name = "button3"; + this.button3.Size = new System.Drawing.Size(22, 20); + this.button3.TabIndex = 5; + this.button3.UseVisualStyleBackColor = true; + this.button3.Click += new System.EventHandler(this.button3_Click); + // + // label12 + // + this.label12.AutoSize = true; + this.label12.Location = new System.Drawing.Point(218, 18); + this.label12.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + this.label12.Name = "label12"; + this.label12.Size = new System.Drawing.Size(40, 13); + this.label12.TabIndex = 0; + this.label12.Text = "Alpha:"; + // + // tb_MaskedAlpha + // + this.tb_MaskedAlpha.Location = new System.Drawing.Point(258, 16); + this.tb_MaskedAlpha.Margin = new System.Windows.Forms.Padding(2); + this.tb_MaskedAlpha.Name = "tb_MaskedAlpha"; + this.tb_MaskedAlpha.Size = new System.Drawing.Size(34, 22); + this.tb_MaskedAlpha.TabIndex = 2; + this.tb_MaskedAlpha.Text = "255"; + this.tb_MaskedAlpha.TextChanged += new System.EventHandler(this.tb_MaskedAlpha_TextChanged); + this.tb_MaskedAlpha.Leave += new System.EventHandler(this.tb_MaskedAlpha_Leave); + // + // label13 + // + this.label13.AutoSize = true; + this.label13.Location = new System.Drawing.Point(10, 18); + this.label13.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + this.label13.Name = "label13"; + this.label13.Size = new System.Drawing.Size(38, 13); + this.label13.TabIndex = 0; + this.label13.Text = "Color:"; + // + // tb_MaskedColor + // + this.tb_MaskedColor.Location = new System.Drawing.Point(47, 16); + this.tb_MaskedColor.Margin = new System.Windows.Forms.Padding(2); + this.tb_MaskedColor.Name = "tb_MaskedColor"; + this.tb_MaskedColor.Size = new System.Drawing.Size(121, 22); + this.tb_MaskedColor.TabIndex = 2; + this.tb_MaskedColor.Text = "DarkSlateGray"; + this.tb_MaskedColor.Enter += new System.EventHandler(this.tb_MaskedColor_Enter); + this.tb_MaskedColor.Leave += new System.EventHandler(this.tb_MaskedColor_Leave); + // + // label14 + // + this.label14.AutoSize = true; + this.label14.Location = new System.Drawing.Point(294, 18); + this.label14.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + this.label14.Name = "label14"; + this.label14.Size = new System.Drawing.Size(70, 13); + this.label14.TabIndex = 0; + this.label14.Text = "(255 = solid)"; + // + // groupBox4 + // + this.groupBox4.Controls.Add(this.button5); + this.groupBox4.Controls.Add(this.button4); + this.groupBox4.Controls.Add(this.Lbl_Example); + this.groupBox4.Controls.Add(this.cb_UseAssignedBackColor); + this.groupBox4.Controls.Add(this.label16); + this.groupBox4.Controls.Add(this.label15); + this.groupBox4.Controls.Add(this.label2); + this.groupBox4.Controls.Add(this.label5); + this.groupBox4.Controls.Add(this.label3); + this.groupBox4.Controls.Add(this.tb_FontBackColor); + this.groupBox4.Controls.Add(this.tb_FontForeColor); + this.groupBox4.Controls.Add(this.tb_FontName); + this.groupBox4.Controls.Add(this.tb_FontSize); + this.groupBox4.Location = new System.Drawing.Point(11, 201); + this.groupBox4.Margin = new System.Windows.Forms.Padding(2); + this.groupBox4.Name = "groupBox4"; + this.groupBox4.Padding = new System.Windows.Forms.Padding(2); + this.groupBox4.Size = new System.Drawing.Size(389, 144); + this.groupBox4.TabIndex = 4; + this.groupBox4.TabStop = false; + this.groupBox4.Text = "Text Label"; + this.groupBox4.Enter += new System.EventHandler(this.groupBox4_Enter); + // + // button5 + // + this.button5.FlatAppearance.BorderSize = 0; + this.button5.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.button5.Image = global::AITool.Properties.Resources.color_wheel; + this.button5.Location = new System.Drawing.Point(197, 43); + this.button5.Name = "button5"; + this.button5.Size = new System.Drawing.Size(22, 20); + this.button5.TabIndex = 5; + this.button5.UseVisualStyleBackColor = true; + this.button5.Click += new System.EventHandler(this.button5_Click); + // + // button4 + // + this.button4.FlatAppearance.BorderSize = 0; + this.button4.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.button4.Image = global::AITool.Properties.Resources.color_wheel; + this.button4.Location = new System.Drawing.Point(197, 67); + this.button4.Name = "button4"; + this.button4.Size = new System.Drawing.Size(22, 20); + this.button4.TabIndex = 5; + this.button4.UseVisualStyleBackColor = true; + this.button4.Click += new System.EventHandler(this.button4_Click); + // + // Lbl_Example + // + this.Lbl_Example.AutoSize = true; + this.Lbl_Example.Location = new System.Drawing.Point(7, 99); + this.Lbl_Example.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + this.Lbl_Example.Name = "Lbl_Example"; + this.Lbl_Example.Size = new System.Drawing.Size(106, 13); + this.Lbl_Example.TabIndex = 4; + this.Lbl_Example.Text = "This is example text"; + this.Lbl_Example.Click += new System.EventHandler(this.Lbl_Example_Click); + // + // cb_UseAssignedBackColor + // + this.cb_UseAssignedBackColor.AutoSize = true; + this.cb_UseAssignedBackColor.Location = new System.Drawing.Point(230, 69); + this.cb_UseAssignedBackColor.Margin = new System.Windows.Forms.Padding(2); + this.cb_UseAssignedBackColor.Name = "cb_UseAssignedBackColor"; + this.cb_UseAssignedBackColor.Size = new System.Drawing.Size(163, 17); + this.cb_UseAssignedBackColor.TabIndex = 3; + this.cb_UseAssignedBackColor.Text = "Use Assigned Object Color"; + this.cb_UseAssignedBackColor.UseVisualStyleBackColor = true; + this.cb_UseAssignedBackColor.CheckedChanged += new System.EventHandler(this.cb_UseAssignedBackColor_CheckedChanged); + this.cb_UseAssignedBackColor.Enter += new System.EventHandler(this.cb_UseAssignedBackColor_Enter); + this.cb_UseAssignedBackColor.Leave += new System.EventHandler(this.cb_UseAssignedBackColor_Leave); + // + // label16 + // + this.label16.AutoSize = true; + this.label16.Location = new System.Drawing.Point(2, 71); + this.label16.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + this.label16.Name = "label16"; + this.label16.Size = new System.Drawing.Size(64, 13); + this.label16.TabIndex = 0; + this.label16.Text = "Back Color:"; + // + // label15 + // + this.label15.AutoSize = true; + this.label15.Location = new System.Drawing.Point(4, 47); + this.label15.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + this.label15.Name = "label15"; + this.label15.Size = new System.Drawing.Size(64, 13); + this.label15.TabIndex = 0; + this.label15.Text = "Fore Color:"; + // + // tb_FontBackColor + // + this.tb_FontBackColor.Location = new System.Drawing.Point(66, 67); + this.tb_FontBackColor.Margin = new System.Windows.Forms.Padding(2); + this.tb_FontBackColor.Name = "tb_FontBackColor"; + this.tb_FontBackColor.Size = new System.Drawing.Size(121, 22); + this.tb_FontBackColor.TabIndex = 2; + this.tb_FontBackColor.Text = "DarkSlateGray"; + this.tb_FontBackColor.Enter += new System.EventHandler(this.tb_FontBackColor_Enter); + this.tb_FontBackColor.Leave += new System.EventHandler(this.tb_FontBackColor_Leave); + // + // tb_FontForeColor + // + this.tb_FontForeColor.Location = new System.Drawing.Point(66, 43); + this.tb_FontForeColor.Margin = new System.Windows.Forms.Padding(2); + this.tb_FontForeColor.Name = "tb_FontForeColor"; + this.tb_FontForeColor.Size = new System.Drawing.Size(121, 22); + this.tb_FontForeColor.TabIndex = 2; + this.tb_FontForeColor.Text = "DarkSlateGray"; + this.tb_FontForeColor.Enter += new System.EventHandler(this.tb_FontForeColor_Enter); + this.tb_FontForeColor.Leave += new System.EventHandler(this.tb_FontForeColor_Leave); + // + // linkLabel1 + // + this.linkLabel1.AutoSize = true; + this.linkLabel1.Location = new System.Drawing.Point(8, 183); + this.linkLabel1.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + this.linkLabel1.Name = "linkLabel1"; + this.linkLabel1.Size = new System.Drawing.Size(208, 13); + this.linkLabel1.TabIndex = 5; + this.linkLabel1.TabStop = true; + this.linkLabel1.Text = "Click here for a list of valid color names"; + this.linkLabel1.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.linkLabel1_LinkClicked); + // + // btnCancel + // + this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.btnCancel.Location = new System.Drawing.Point(330, 352); + this.btnCancel.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.btnCancel.Name = "btnCancel"; + this.btnCancel.Size = new System.Drawing.Size(70, 30); + this.btnCancel.TabIndex = 39; + this.btnCancel.Text = "Cancel"; + this.btnCancel.UseVisualStyleBackColor = true; + this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click); + // + // btnSave + // + this.btnSave.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.btnSave.Location = new System.Drawing.Point(252, 352); + this.btnSave.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.btnSave.Name = "btnSave"; + this.btnSave.Size = new System.Drawing.Size(70, 30); + this.btnSave.TabIndex = 38; + this.btnSave.Text = "Save"; + this.btnSave.UseVisualStyleBackColor = true; + this.btnSave.Click += new System.EventHandler(this.btnSave_Click); + // + // colorDialog1 + // + this.colorDialog1.AnyColor = true; + // + // Frm_AnnoAdjust + // + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Inherit; + this.ClientSize = new System.Drawing.Size(406, 392); + this.Controls.Add(this.btnCancel); + this.Controls.Add(this.btnSave); + this.Controls.Add(this.linkLabel1); + this.Controls.Add(this.groupBox4); + this.Controls.Add(this.groupBox3); + this.Controls.Add(this.groupBox2); + this.Controls.Add(this.groupBox1); + this.Controls.Add(this.tb_BorderWidthPixels); + this.Controls.Add(this.label4); + this.Controls.Add(this.label1); + this.Font = new System.Drawing.Font("Segoe UI", 8.25F); + this.Margin = new System.Windows.Forms.Padding(2); + this.Name = "Frm_AnnoAdjust"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Adjust Annotation Appearance "; + this.Load += new System.EventHandler(this.Frm_AnnoAdjust_Load); + this.Click += new System.EventHandler(this.Frm_AnnoAdjust_Click); + this.groupBox1.ResumeLayout(false); + this.groupBox1.PerformLayout(); + this.groupBox2.ResumeLayout(false); + this.groupBox2.PerformLayout(); + this.groupBox3.ResumeLayout(false); + this.groupBox3.PerformLayout(); + this.groupBox4.ResumeLayout(false); + this.groupBox4.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.Label label5; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.Label label6; + private System.Windows.Forms.Label label7; + private System.Windows.Forms.Label label8; + private System.Windows.Forms.GroupBox groupBox2; + private System.Windows.Forms.Label label9; + private System.Windows.Forms.Label label10; + private System.Windows.Forms.Label label11; + private System.Windows.Forms.GroupBox groupBox3; + private System.Windows.Forms.Label label12; + private System.Windows.Forms.Label label13; + private System.Windows.Forms.Label label14; + private System.Windows.Forms.GroupBox groupBox4; + private System.Windows.Forms.Label label16; + private System.Windows.Forms.Label label15; + private System.Windows.Forms.LinkLabel linkLabel1; + public System.Windows.Forms.TextBox tb_BorderWidthPixels; + public System.Windows.Forms.TextBox tb_FontName; + public System.Windows.Forms.TextBox tb_FontSize; + public System.Windows.Forms.TextBox tb_RelevantColor; + public System.Windows.Forms.TextBox tb_RelevantAlpha; + public System.Windows.Forms.TextBox tb_IrrelevantAlpha; + public System.Windows.Forms.TextBox tb_IrrelevantColor; + public System.Windows.Forms.TextBox tb_MaskedAlpha; + public System.Windows.Forms.TextBox tb_MaskedColor; + public System.Windows.Forms.CheckBox cb_UseAssignedBackColor; + public System.Windows.Forms.TextBox tb_FontBackColor; + public System.Windows.Forms.TextBox tb_FontForeColor; + public System.Windows.Forms.Label Lbl_Example; + private System.Windows.Forms.Button btnCancel; + private System.Windows.Forms.Button btnSave; + private System.Windows.Forms.ColorDialog colorDialog1; + private System.Windows.Forms.Button button1; + private System.Windows.Forms.Button button2; + private System.Windows.Forms.Button button3; + private System.Windows.Forms.Button button5; + private System.Windows.Forms.Button button4; + } +} \ No newline at end of file diff --git a/src/UI/Frm_AnnoAdjust.cs b/src/UI/Frm_AnnoAdjust.cs new file mode 100644 index 00000000..0a366d28 --- /dev/null +++ b/src/UI/Frm_AnnoAdjust.cs @@ -0,0 +1,390 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Diagnostics; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace AITool +{ + public partial class Frm_AnnoAdjust : Form + { + public Frm_AnnoAdjust() + { + InitializeComponent(); + } + + private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) + { + Process.Start("https://docs.microsoft.com/en-us/dotnet/api/system.windows.media.colors?view=net-5.0"); + } + + private void btnSave_Click(object sender, EventArgs e) + { + this.Save(); + this.DialogResult = DialogResult.OK; + this.Close(); + + } + + private void Frm_AnnoAdjust_Load(object sender, EventArgs e) + { + LoadAnnoSettings(); + } + + private void LoadAnnoSettings() + { + try + { + if (AppSettings.Settings.RectDetectionTextForeColor.Name == "Gainsboro") //not using gainsboro for foreground color any longer + AppSettings.Settings.RectDetectionTextForeColor = Color.Black; + + + tb_BorderWidthPixels.Text = AppSettings.Settings.RectBorderWidth.ToString(); + + tb_RelevantColor.Text = AppSettings.Settings.RectRelevantColor.Name; + tb_RelevantAlpha.Text = AppSettings.Settings.RectRelevantColorAlpha.ToString(); + + tb_IrrelevantColor.Text = AppSettings.Settings.RectIrrelevantColor.Name; + tb_IrrelevantAlpha.Text = AppSettings.Settings.RectIrrelevantColorAlpha.ToString(); + + tb_MaskedColor.Text = AppSettings.Settings.RectMaskedColor.Name; + tb_MaskedAlpha.Text = AppSettings.Settings.RectMaskedColorAlpha.ToString(); + + tb_FontForeColor.Text = AppSettings.Settings.RectDetectionTextForeColor.Name; + tb_FontName.Text = AppSettings.Settings.RectDetectionTextFont; + tb_FontSize.Text = AppSettings.Settings.RectDetectionTextSize.ToString(); + + cb_UseAssignedBackColor.Checked = AppSettings.Settings.RectDetectionTextBackColor.Name == "Gainsboro"; + + if (cb_UseAssignedBackColor.Checked) + { + tb_FontBackColor.Text = ""; + tb_FontBackColor.Enabled = false; + } + else + { + tb_FontBackColor.Text = AppSettings.Settings.RectDetectionTextBackColor.Name; + tb_FontBackColor.Enabled = true; + } + + UpdateExample(); + } + catch (Exception ex) + { + + MessageBox.Show("Error: " + ex.Msg()); + } + } + + private void UpdateCheckbox() + { + + } + + private void UpdateExample() + { + try + { + + Lbl_Example.Text = "This is example text"; + Lbl_Example.Font = new Font(tb_FontName.Text.Trim(), tb_FontSize.Text.ToFloat()); + Lbl_Example.ForeColor = Global.ConvertStringToColor(tb_FontForeColor.Text); + + if (cb_UseAssignedBackColor.Checked) + { + Color backclr; + if (!Lbl_Example.Tag.IsNull() && Lbl_Example.Tag is string) + { + List splt = Lbl_Example.Tag.ToString().SplitStr(","); + backclr = Color.FromArgb(splt[0].ToInt(), Global.ConvertStringToColor(splt[1])); + } + else + { + backclr = Color.FromArgb(tb_RelevantAlpha.Text.ToInt(), Global.ConvertStringToColor(tb_RelevantColor.Text)); + } + Lbl_Example.BackColor = backclr; + } + else + { + Lbl_Example.BackColor = Global.ConvertStringToColor(tb_FontBackColor.Text); + } + + } + catch (Exception ex) + { + Lbl_Example.Text = ex.Message; + //MessageBox.Show("Error: " + ex.Msg()); + } + } + + private void Save() + { + try + { + + AppSettings.Settings.RectBorderWidth = tb_BorderWidthPixels.Text.ToInt(); + + AppSettings.Settings.RectRelevantColor = Global.ConvertStringToColor(tb_RelevantColor.Text); + AppSettings.Settings.RectRelevantColorAlpha = tb_RelevantAlpha.Text.ToInt(); + + AppSettings.Settings.RectIrrelevantColor = Global.ConvertStringToColor(tb_IrrelevantColor.Text); + AppSettings.Settings.RectIrrelevantColorAlpha = tb_IrrelevantAlpha.Text.ToInt(); + + AppSettings.Settings.RectMaskedColor = Global.ConvertStringToColor(tb_MaskedColor.Text); + AppSettings.Settings.RectMaskedColorAlpha = tb_MaskedAlpha.Text.ToInt(); + + AppSettings.Settings.RectDetectionTextForeColor = Global.ConvertStringToColor(tb_FontForeColor.Text); + if (AppSettings.Settings.RectDetectionTextForeColor.Name == "Gainsboro") + AppSettings.Settings.RectDetectionTextForeColor = Global.ConvertStringToColor("Black"); + + AppSettings.Settings.RectDetectionTextFont = tb_FontName.Text; + AppSettings.Settings.RectDetectionTextSize = tb_FontSize.Text.ToInt(); + + if (cb_UseAssignedBackColor.Checked) + { + AppSettings.Settings.RectDetectionTextBackColor = Global.ConvertStringToColor("Gainsboro"); + } + else + { + AppSettings.Settings.RectDetectionTextBackColor = Global.ConvertStringToColor(tb_FontBackColor.Text); + } + + } + catch (Exception ex) + { + Lbl_Example.Text = ex.Message; + MessageBox.Show("Error: " + ex.Msg()); + } + } + + private void btnCancel_Click(object sender, EventArgs e) + { + this.DialogResult = DialogResult.Cancel; + this.Close(); + } + + private void tb_RelevantColor_Enter(object sender, EventArgs e) + { + Lbl_Example.Tag = $"{tb_RelevantAlpha.Text},{tb_RelevantColor.Text}"; + this.UpdateExample(); + } + + private void tb_IrrelevantColor_Enter(object sender, EventArgs e) + { + Lbl_Example.Tag = $"{tb_IrrelevantAlpha.Text},{tb_IrrelevantColor.Text}"; + this.UpdateExample(); + + } + + private void tb_MaskedColor_Enter(object sender, EventArgs e) + { + Lbl_Example.Tag = $"{tb_MaskedAlpha.Text},{tb_MaskedColor.Text}"; + this.UpdateExample(); + } + + private void tb_MaskedColor_Leave(object sender, EventArgs e) + { + Lbl_Example.Tag = $"{tb_MaskedAlpha.Text},{tb_MaskedColor.Text}"; + this.UpdateExample(); + + } + + private void tb_MaskedAlpha_TextChanged(object sender, EventArgs e) + { + + } + + private void tb_MaskedAlpha_Leave(object sender, EventArgs e) + { + Lbl_Example.Tag = $"{tb_MaskedAlpha.Text},{tb_MaskedColor.Text}"; + this.UpdateExample(); + + } + + private void tb_IrrelevantColor_Leave(object sender, EventArgs e) + { + Lbl_Example.Tag = $"{tb_IrrelevantAlpha.Text},{tb_IrrelevantColor.Text}"; + this.UpdateExample(); + } + + private void tb_IrrelevantAlpha_Leave(object sender, EventArgs e) + { + Lbl_Example.Tag = $"{tb_IrrelevantAlpha.Text},{tb_IrrelevantColor.Text}"; + this.UpdateExample(); + } + + private void tb_RelevantColor_Leave(object sender, EventArgs e) + { + Lbl_Example.Tag = $"{tb_RelevantAlpha.Text},{tb_RelevantColor.Text}"; + this.UpdateExample(); + } + + private void tb_RelevantAlpha_Leave(object sender, EventArgs e) + { + Lbl_Example.Tag = $"{tb_RelevantAlpha.Text},{tb_RelevantColor.Text}"; + this.UpdateExample(); + } + + private void tb_RelevantAlpha_Enter(object sender, EventArgs e) + { + Lbl_Example.Tag = $"{tb_RelevantAlpha.Text},{tb_RelevantColor.Text}"; + this.UpdateExample(); + } + + private void tb_FontName_Leave(object sender, EventArgs e) + { + this.UpdateExample(); + + } + + private void tb_FontName_Enter(object sender, EventArgs e) + { + this.UpdateExample(); + + } + + private void tb_FontSize_Leave(object sender, EventArgs e) + { + this.UpdateExample(); + + } + + private void tb_FontSize_Enter(object sender, EventArgs e) + { + this.UpdateExample(); + + } + + private void tb_FontForeColor_Leave(object sender, EventArgs e) + { + this.UpdateExample(); + + } + + private void tb_FontForeColor_Enter(object sender, EventArgs e) + { + this.UpdateExample(); + + } + + private void tb_FontBackColor_Leave(object sender, EventArgs e) + { + this.UpdateExample(); + + } + + private void tb_FontBackColor_Enter(object sender, EventArgs e) + { + this.UpdateExample(); + + } + + private void cb_UseAssignedBackColor_Leave(object sender, EventArgs e) + { + this.UpdateExample(); + + } + + private void cb_UseAssignedBackColor_Enter(object sender, EventArgs e) + { + this.UpdateExample(); + + } + + private void Lbl_Example_Click(object sender, EventArgs e) + { + this.UpdateExample(); + + } + + private void groupBox4_Enter(object sender, EventArgs e) + { + this.UpdateExample(); + + } + + private void Frm_AnnoAdjust_Click(object sender, EventArgs e) + { + this.UpdateExample(); + + } + + private void button1_Click(object sender, EventArgs e) + { + + colorDialog1.Color = Global.ConvertStringToColor(tb_RelevantColor.Text, tb_RelevantAlpha.Text); + if (colorDialog1.ShowDialog() == DialogResult.OK) + { + tb_RelevantColor.Text = colorDialog1.Color.Name; + tb_RelevantAlpha.Text = colorDialog1.Color.A.ToString(); + } + } + + private void cb_UseAssignedBackColor_CheckedChanged(object sender, EventArgs e) + { + if (cb_UseAssignedBackColor.Checked) + { + tb_FontBackColor.Text = ""; + tb_FontBackColor.Enabled = false; + } + else + { + if (AppSettings.Settings.RectDetectionTextBackColor.Name == "Gainsboro") + { + tb_FontBackColor.Text = tb_RelevantColor.Text; + } + else + { + tb_FontBackColor.Text = AppSettings.Settings.RectDetectionTextBackColor.Name; + + } + tb_FontBackColor.Enabled = true; + } + } + + private void button2_Click(object sender, EventArgs e) + { + colorDialog1.Color = Global.ConvertStringToColor(tb_IrrelevantColor.Text, tb_IrrelevantAlpha.Text); + if (colorDialog1.ShowDialog() == DialogResult.OK) + { + tb_IrrelevantColor.Text = colorDialog1.Color.Name; + tb_IrrelevantAlpha.Text = colorDialog1.Color.A.ToString(); + } + } + + private void button3_Click(object sender, EventArgs e) + { + colorDialog1.Color = Global.ConvertStringToColor(tb_MaskedColor.Text, tb_MaskedAlpha.Text); + if (colorDialog1.ShowDialog() == DialogResult.OK) + { + tb_MaskedColor.Text = colorDialog1.Color.Name; + tb_MaskedAlpha.Text = colorDialog1.Color.A.ToString(); + } + } + + private void button5_Click(object sender, EventArgs e) + { + colorDialog1.Color = Global.ConvertStringToColor(tb_FontForeColor.Text, ""); + if (colorDialog1.ShowDialog() == DialogResult.OK) + { + tb_FontForeColor.Text = colorDialog1.Color.Name; + } + } + + private void button4_Click(object sender, EventArgs e) + { + colorDialog1.Color = Global.ConvertStringToColor(tb_FontBackColor.Text, ""); + if (colorDialog1.ShowDialog() == DialogResult.OK) + { + tb_FontBackColor.Text = colorDialog1.Color.Name; + } + + } + } + +} diff --git a/src/UI/Frm_AnnoAdjust.resx b/src/UI/Frm_AnnoAdjust.resx new file mode 100644 index 00000000..aa0ca0f6 --- /dev/null +++ b/src/UI/Frm_AnnoAdjust.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + \ No newline at end of file diff --git a/src/UI/Frm_ApplyCameraTo.Designer.cs b/src/UI/Frm_ApplyCameraTo.Designer.cs new file mode 100644 index 00000000..a2ac850c --- /dev/null +++ b/src/UI/Frm_ApplyCameraTo.Designer.cs @@ -0,0 +1,169 @@ +namespace AITool +{ + partial class Frm_ApplyCameraTo + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.btnApply = new System.Windows.Forms.Button(); + this.checkedListBoxCameras = new System.Windows.Forms.CheckedListBox(); + this.label1 = new System.Windows.Forms.Label(); + this.cb_apply_objects = new System.Windows.Forms.CheckBox(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.cb_apply_mask_settings = new System.Windows.Forms.CheckBox(); + this.cb_apply_actions = new System.Windows.Forms.CheckBox(); + this.cb_apply_confidence_limits = new System.Windows.Forms.CheckBox(); + this.groupBox1.SuspendLayout(); + this.SuspendLayout(); + // + // btnApply + // + this.btnApply.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.btnApply.Location = new System.Drawing.Point(172, 326); + this.btnApply.Name = "btnApply"; + this.btnApply.Size = new System.Drawing.Size(70, 30); + this.btnApply.TabIndex = 5; + this.btnApply.Text = "Apply"; + this.btnApply.UseVisualStyleBackColor = true; + this.btnApply.Click += new System.EventHandler(this.btnApply_Click); + // + // checkedListBoxCameras + // + this.checkedListBoxCameras.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.checkedListBoxCameras.CheckOnClick = true; + this.checkedListBoxCameras.FormattingEnabled = true; + this.checkedListBoxCameras.Location = new System.Drawing.Point(3, 40); + this.checkedListBoxCameras.Name = "checkedListBoxCameras"; + this.checkedListBoxCameras.Size = new System.Drawing.Size(239, 199); + this.checkedListBoxCameras.TabIndex = 0; + // + // label1 + // + this.label1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.label1.ForeColor = System.Drawing.Color.DodgerBlue; + this.label1.Location = new System.Drawing.Point(0, 9); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(242, 28); + this.label1.TabIndex = 28; + this.label1.Text = "Select the cameras you want to apply the current cameras settings to."; + // + // cb_apply_objects + // + this.cb_apply_objects.AutoSize = true; + this.cb_apply_objects.Checked = true; + this.cb_apply_objects.CheckState = System.Windows.Forms.CheckState.Checked; + this.cb_apply_objects.Location = new System.Drawing.Point(8, 19); + this.cb_apply_objects.Name = "cb_apply_objects"; + this.cb_apply_objects.Size = new System.Drawing.Size(112, 17); + this.cb_apply_objects.TabIndex = 1; + this.cb_apply_objects.Text = "Relevant Objects"; + this.cb_apply_objects.UseVisualStyleBackColor = true; + // + // groupBox1 + // + this.groupBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.groupBox1.Controls.Add(this.cb_apply_mask_settings); + this.groupBox1.Controls.Add(this.cb_apply_actions); + this.groupBox1.Controls.Add(this.cb_apply_confidence_limits); + this.groupBox1.Controls.Add(this.cb_apply_objects); + this.groupBox1.Location = new System.Drawing.Point(4, 248); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.Size = new System.Drawing.Size(238, 72); + this.groupBox1.TabIndex = 30; + this.groupBox1.TabStop = false; + this.groupBox1.Text = "Settings to apply"; + // + // cb_apply_mask_settings + // + this.cb_apply_mask_settings.AutoSize = true; + this.cb_apply_mask_settings.Checked = true; + this.cb_apply_mask_settings.CheckState = System.Windows.Forms.CheckState.Checked; + this.cb_apply_mask_settings.Location = new System.Drawing.Point(123, 42); + this.cb_apply_mask_settings.Name = "cb_apply_mask_settings"; + this.cb_apply_mask_settings.Size = new System.Drawing.Size(98, 17); + this.cb_apply_mask_settings.TabIndex = 4; + this.cb_apply_mask_settings.Text = "Mask Settings"; + this.cb_apply_mask_settings.UseVisualStyleBackColor = true; + // + // cb_apply_actions + // + this.cb_apply_actions.AutoSize = true; + this.cb_apply_actions.Checked = true; + this.cb_apply_actions.CheckState = System.Windows.Forms.CheckState.Checked; + this.cb_apply_actions.Location = new System.Drawing.Point(8, 42); + this.cb_apply_actions.Name = "cb_apply_actions"; + this.cb_apply_actions.Size = new System.Drawing.Size(64, 17); + this.cb_apply_actions.TabIndex = 3; + this.cb_apply_actions.Text = "Actions"; + this.cb_apply_actions.UseVisualStyleBackColor = true; + // + // cb_apply_confidence_limits + // + this.cb_apply_confidence_limits.AutoSize = true; + this.cb_apply_confidence_limits.Checked = true; + this.cb_apply_confidence_limits.CheckState = System.Windows.Forms.CheckState.Checked; + this.cb_apply_confidence_limits.Location = new System.Drawing.Point(123, 19); + this.cb_apply_confidence_limits.Name = "cb_apply_confidence_limits"; + this.cb_apply_confidence_limits.Size = new System.Drawing.Size(117, 17); + this.cb_apply_confidence_limits.TabIndex = 2; + this.cb_apply_confidence_limits.Text = "Confidence Limits"; + this.cb_apply_confidence_limits.UseVisualStyleBackColor = true; + // + // Frm_ApplyCameraTo + // + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Inherit; + this.ClientSize = new System.Drawing.Size(245, 368); + this.Controls.Add(this.groupBox1); + this.Controls.Add(this.label1); + this.Controls.Add(this.checkedListBoxCameras); + this.Controls.Add(this.btnApply); + this.Font = new System.Drawing.Font("Segoe UI", 8.25F); + this.Name = "Frm_ApplyCameraTo"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Apply to..."; + this.TopMost = true; + this.groupBox1.ResumeLayout(false); + this.groupBox1.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Button btnApply; + public System.Windows.Forms.CheckedListBox checkedListBoxCameras; + private System.Windows.Forms.Label label1; + public System.Windows.Forms.CheckBox cb_apply_objects; + private System.Windows.Forms.GroupBox groupBox1; + public System.Windows.Forms.CheckBox cb_apply_mask_settings; + public System.Windows.Forms.CheckBox cb_apply_actions; + public System.Windows.Forms.CheckBox cb_apply_confidence_limits; + } +} \ No newline at end of file diff --git a/src/UI/Frm_ApplyCameraTo.cs b/src/UI/Frm_ApplyCameraTo.cs new file mode 100644 index 00000000..8edee994 --- /dev/null +++ b/src/UI/Frm_ApplyCameraTo.cs @@ -0,0 +1,19 @@ +using System; +using System.Windows.Forms; + +namespace AITool +{ + public partial class Frm_ApplyCameraTo : Form + { + public Frm_ApplyCameraTo() + { + this.InitializeComponent(); + } + + private void btnApply_Click(object sender, EventArgs e) + { + this.DialogResult = DialogResult.OK; + this.Close(); + } + } +} diff --git a/src/UI/Frm_ApplyCameraTo.resx b/src/UI/Frm_ApplyCameraTo.resx new file mode 100644 index 00000000..1af7de15 --- /dev/null +++ b/src/UI/Frm_ApplyCameraTo.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/UI/Frm_CameraAdd.Designer.cs b/src/UI/Frm_CameraAdd.Designer.cs new file mode 100644 index 00000000..b6022e35 --- /dev/null +++ b/src/UI/Frm_CameraAdd.Designer.cs @@ -0,0 +1,124 @@ +namespace AITool +{ + partial class Frm_CameraAdd + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.btnApply = new System.Windows.Forms.Button(); + this.checkedListBoxCameras = new System.Windows.Forms.CheckedListBox(); + this.label1 = new System.Windows.Forms.Label(); + this.label2 = new System.Windows.Forms.Label(); + this.tb_Cameras = new System.Windows.Forms.TextBox(); + this.SuspendLayout(); + // + // btnApply + // + this.btnApply.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.btnApply.Location = new System.Drawing.Point(192, 326); + this.btnApply.Name = "btnApply"; + this.btnApply.Size = new System.Drawing.Size(70, 30); + this.btnApply.TabIndex = 26; + this.btnApply.Text = "Add"; + this.btnApply.UseVisualStyleBackColor = true; + this.btnApply.Click += new System.EventHandler(this.btnApply_Click); + // + // checkedListBoxCameras + // + this.checkedListBoxCameras.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.checkedListBoxCameras.CheckOnClick = true; + this.checkedListBoxCameras.FormattingEnabled = true; + this.checkedListBoxCameras.Location = new System.Drawing.Point(3, 31); + this.checkedListBoxCameras.Name = "checkedListBoxCameras"; + this.checkedListBoxCameras.Size = new System.Drawing.Size(259, 191); + this.checkedListBoxCameras.TabIndex = 27; + this.checkedListBoxCameras.ItemCheck += new System.Windows.Forms.ItemCheckEventHandler(this.checkedListBoxCameras_ItemCheck); + this.checkedListBoxCameras.SelectedIndexChanged += new System.EventHandler(this.checkedListBoxCameras_SelectedIndexChanged); + this.checkedListBoxCameras.Leave += new System.EventHandler(this.checkedListBoxCameras_Leave); + this.checkedListBoxCameras.Validated += new System.EventHandler(this.checkedListBoxCameras_Validated); + // + // label1 + // + this.label1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.label1.ForeColor = System.Drawing.Color.DodgerBlue; + this.label1.Location = new System.Drawing.Point(0, 9); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(262, 19); + this.label1.TabIndex = 28; + this.label1.Text = "Select the Blue Iris cameras you want to add"; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(0, 233); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(261, 13); + this.label2.TabIndex = 29; + this.label2.Text = "Or type new camera names seperated by commas:"; + // + // tb_Cameras + // + this.tb_Cameras.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tb_Cameras.Location = new System.Drawing.Point(1, 249); + this.tb_Cameras.Multiline = true; + this.tb_Cameras.Name = "tb_Cameras"; + this.tb_Cameras.Size = new System.Drawing.Size(261, 67); + this.tb_Cameras.TabIndex = 30; + this.tb_Cameras.Leave += new System.EventHandler(this.tb_Cameras_Leave); + // + // Frm_CameraAdd + // + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Inherit; + this.ClientSize = new System.Drawing.Size(265, 368); + this.Controls.Add(this.tb_Cameras); + this.Controls.Add(this.label2); + this.Controls.Add(this.label1); + this.Controls.Add(this.checkedListBoxCameras); + this.Controls.Add(this.btnApply); + this.Font = new System.Drawing.Font("Segoe UI", 8.25F); + this.Name = "Frm_CameraAdd"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Add Cameras"; + this.TopMost = true; + this.Load += new System.EventHandler(this.Frm_CameraAdd_Load); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Button btnApply; + public System.Windows.Forms.CheckedListBox checkedListBoxCameras; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Label label2; + public System.Windows.Forms.TextBox tb_Cameras; + } +} \ No newline at end of file diff --git a/src/UI/Frm_CameraAdd.cs b/src/UI/Frm_CameraAdd.cs new file mode 100644 index 00000000..ad15c49c --- /dev/null +++ b/src/UI/Frm_CameraAdd.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows.Forms; + +namespace AITool +{ + public partial class Frm_CameraAdd : Form + { + public Frm_CameraAdd() + { + this.InitializeComponent(); + } + + private void btnApply_Click(object sender, EventArgs e) + { + UpdateCamList(true); + this.DialogResult = DialogResult.OK; + this.Close(); + } + + private void checkedListBoxCameras_SelectedIndexChanged(object sender, EventArgs e) + { + UpdateCamList(false); + } + + private void Frm_CameraAdd_Load(object sender, EventArgs e) + { + UpdateCamList(false); + } + + private void UpdateCamList(bool keeptextcontents) + { + List cams = new List(); + + if (keeptextcontents) + cams = tb_Cameras.Text.SplitStr("\r\n|;,"); + + foreach (object itm in this.checkedListBoxCameras.CheckedItems) + { + if (!cams.Contains(itm.ToString(), StringComparer.OrdinalIgnoreCase)) + cams.Add(itm.ToString()); + + } + + tb_Cameras.Text = string.Join(",", cams); + + } + + private void checkedListBoxCameras_ItemCheck(object sender, ItemCheckEventArgs e) + { + UpdateCamList(false); + } + + private void checkedListBoxCameras_Leave(object sender, EventArgs e) + { + UpdateCamList(true); + + } + + private void checkedListBoxCameras_Validated(object sender, EventArgs e) + { + UpdateCamList(false); + } + + private void tb_Cameras_Leave(object sender, EventArgs e) + { + UpdateCamList(true); + } + } +} diff --git a/src/UI/Frm_CameraAdd.resx b/src/UI/Frm_CameraAdd.resx new file mode 100644 index 00000000..1af7de15 --- /dev/null +++ b/src/UI/Frm_CameraAdd.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/UI/Frm_CustomMasking.Designer.cs b/src/UI/Frm_CustomMasking.Designer.cs new file mode 100644 index 00000000..33632023 --- /dev/null +++ b/src/UI/Frm_CustomMasking.Designer.cs @@ -0,0 +1,293 @@ +namespace AITool +{ + partial class Frm_CustomMasking + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.panel1 = new System.Windows.Forms.Panel(); + this.pbMaskImage = new System.Windows.Forms.PictureBox(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.flowLayoutPanel1 = new System.Windows.Forms.FlowLayoutPanel(); + this.label1 = new System.Windows.Forms.Label(); + this.numBrushSize = new System.Windows.Forms.NumericUpDown(); + this.rbBrush = new System.Windows.Forms.RadioButton(); + this.rbRectangle = new System.Windows.Forms.RadioButton(); + this.btnClear = new System.Windows.Forms.Button(); + this.btnSave = new System.Windows.Forms.Button(); + this.btnCancel = new System.Windows.Forms.Button(); + this.label2 = new System.Windows.Forms.Label(); + this.cbResolution = new System.Windows.Forms.ComboBox(); + this.linkLabelScan = new System.Windows.Forms.LinkLabel(); + this.toolTip1 = new System.Windows.Forms.ToolTip(this.components); + this.panel1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.pbMaskImage)).BeginInit(); + this.groupBox1.SuspendLayout(); + this.flowLayoutPanel1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.numBrushSize)).BeginInit(); + this.SuspendLayout(); + // + // panel1 + // + this.panel1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.panel1.Controls.Add(this.pbMaskImage); + this.panel1.Location = new System.Drawing.Point(2, 4); + this.panel1.Margin = new System.Windows.Forms.Padding(6); + this.panel1.Name = "panel1"; + this.panel1.Size = new System.Drawing.Size(906, 407); + this.panel1.TabIndex = 1; + // + // pbMaskImage + // + this.pbMaskImage.BackColor = System.Drawing.SystemColors.Control; + this.pbMaskImage.BackgroundImageLayout = System.Windows.Forms.ImageLayout.None; + this.pbMaskImage.Cursor = System.Windows.Forms.Cursors.Cross; + this.pbMaskImage.Dock = System.Windows.Forms.DockStyle.Fill; + this.pbMaskImage.Location = new System.Drawing.Point(0, 0); + this.pbMaskImage.Margin = new System.Windows.Forms.Padding(4); + this.pbMaskImage.Name = "pbMaskImage"; + this.pbMaskImage.Size = new System.Drawing.Size(906, 407); + this.pbMaskImage.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.pbMaskImage.TabIndex = 1; + this.pbMaskImage.TabStop = false; + this.pbMaskImage.Click += new System.EventHandler(this.pbMaskImage_Click); + this.pbMaskImage.Paint += new System.Windows.Forms.PaintEventHandler(this.pbMaskImage_Paint); + this.pbMaskImage.MouseDown += new System.Windows.Forms.MouseEventHandler(this.pbMaskImage_MouseDown); + this.pbMaskImage.MouseMove += new System.Windows.Forms.MouseEventHandler(this.pbMaskImage_MouseMove); + this.pbMaskImage.MouseUp += new System.Windows.Forms.MouseEventHandler(this.pbMaskImage_MouseUp); + // + // groupBox1 + // + this.groupBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.groupBox1.Controls.Add(this.flowLayoutPanel1); + this.groupBox1.Location = new System.Drawing.Point(2, 414); + this.groupBox1.Margin = new System.Windows.Forms.Padding(6); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.Padding = new System.Windows.Forms.Padding(6); + this.groupBox1.Size = new System.Drawing.Size(421, 58); + this.groupBox1.TabIndex = 3; + this.groupBox1.TabStop = false; + this.groupBox1.Text = "Tools"; + // + // flowLayoutPanel1 + // + this.flowLayoutPanel1.Controls.Add(this.label1); + this.flowLayoutPanel1.Controls.Add(this.numBrushSize); + this.flowLayoutPanel1.Controls.Add(this.rbBrush); + this.flowLayoutPanel1.Controls.Add(this.rbRectangle); + this.flowLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill; + this.flowLayoutPanel1.Location = new System.Drawing.Point(6, 21); + this.flowLayoutPanel1.Margin = new System.Windows.Forms.Padding(6, 15, 6, 6); + this.flowLayoutPanel1.Name = "flowLayoutPanel1"; + this.flowLayoutPanel1.Size = new System.Drawing.Size(409, 31); + this.flowLayoutPanel1.TabIndex = 1; + // + // label1 + // + this.label1.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(22, 10); + this.label1.Margin = new System.Windows.Forms.Padding(22, 0, 6, 0); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(59, 13); + this.label1.TabIndex = 3; + this.label1.Text = "Brush Size"; + // + // numBrushSize + // + this.numBrushSize.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.numBrushSize.Location = new System.Drawing.Point(93, 6); + this.numBrushSize.Margin = new System.Windows.Forms.Padding(6); + this.numBrushSize.Maximum = new decimal(new int[] { + 200, + 0, + 0, + 0}); + this.numBrushSize.Minimum = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.numBrushSize.Name = "numBrushSize"; + this.numBrushSize.Size = new System.Drawing.Size(48, 22); + this.numBrushSize.TabIndex = 0; + this.numBrushSize.Value = new decimal(new int[] { + 20, + 0, + 0, + 0}); + this.numBrushSize.ValueChanged += new System.EventHandler(this.numBrushSize_ValueChanged); + this.numBrushSize.KeyUp += new System.Windows.Forms.KeyEventHandler(this.numBrushSize_KeyUp); + this.numBrushSize.Leave += new System.EventHandler(this.numBrushSize_Leave); + // + // rbBrush + // + this.rbBrush.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.rbBrush.AutoSize = true; + this.rbBrush.Checked = true; + this.rbBrush.Location = new System.Drawing.Point(202, 8); + this.rbBrush.Margin = new System.Windows.Forms.Padding(55, 6, 6, 6); + this.rbBrush.Name = "rbBrush"; + this.rbBrush.Size = new System.Drawing.Size(54, 17); + this.rbBrush.TabIndex = 1; + this.rbBrush.TabStop = true; + this.rbBrush.Text = "Brush"; + this.rbBrush.UseVisualStyleBackColor = true; + // + // rbRectangle + // + this.rbRectangle.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.rbRectangle.AutoSize = true; + this.rbRectangle.Location = new System.Drawing.Point(268, 8); + this.rbRectangle.Margin = new System.Windows.Forms.Padding(6, 6, 55, 6); + this.rbRectangle.Name = "rbRectangle"; + this.rbRectangle.Size = new System.Drawing.Size(76, 17); + this.rbRectangle.TabIndex = 2; + this.rbRectangle.Text = "Rectangle"; + this.rbRectangle.UseVisualStyleBackColor = true; + // + // btnClear + // + this.btnClear.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.btnClear.Location = new System.Drawing.Point(665, 433); + this.btnClear.Margin = new System.Windows.Forms.Padding(6, 15, 6, 6); + this.btnClear.Name = "btnClear"; + this.btnClear.Size = new System.Drawing.Size(70, 30); + this.btnClear.TabIndex = 5; + this.btnClear.Text = "Clear"; + this.btnClear.UseVisualStyleBackColor = true; + this.btnClear.Click += new System.EventHandler(this.btnClear_Click); + // + // btnSave + // + this.btnSave.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.btnSave.Location = new System.Drawing.Point(747, 433); + this.btnSave.Margin = new System.Windows.Forms.Padding(6); + this.btnSave.Name = "btnSave"; + this.btnSave.Size = new System.Drawing.Size(70, 30); + this.btnSave.TabIndex = 6; + this.btnSave.Text = "OK"; + this.btnSave.UseVisualStyleBackColor = true; + this.btnSave.Click += new System.EventHandler(this.btnSave_Click); + // + // btnCancel + // + this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.btnCancel.Location = new System.Drawing.Point(829, 433); + this.btnCancel.Margin = new System.Windows.Forms.Padding(6, 6, 46, 6); + this.btnCancel.Name = "btnCancel"; + this.btnCancel.Size = new System.Drawing.Size(70, 30); + this.btnCancel.TabIndex = 7; + this.btnCancel.Text = "Cancel"; + this.btnCancel.UseVisualStyleBackColor = true; + // + // label2 + // + this.label2.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(426, 442); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(66, 13); + this.label2.TabIndex = 8; + this.label2.Text = "Resolution:"; + // + // cbResolution + // + this.cbResolution.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.cbResolution.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cbResolution.FormattingEnabled = true; + this.cbResolution.Location = new System.Drawing.Point(492, 438); + this.cbResolution.Name = "cbResolution"; + this.cbResolution.Size = new System.Drawing.Size(100, 21); + this.cbResolution.TabIndex = 3; + this.cbResolution.SelectedIndexChanged += new System.EventHandler(this.cbResolution_SelectedIndexChanged); + this.cbResolution.SelectionChangeCommitted += new System.EventHandler(this.cbResolution_SelectionChangeCommitted); + // + // linkLabelScan + // + this.linkLabelScan.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.linkLabelScan.AutoSize = true; + this.linkLabelScan.Location = new System.Drawing.Point(598, 442); + this.linkLabelScan.Name = "linkLabelScan"; + this.linkLabelScan.Size = new System.Drawing.Size(31, 13); + this.linkLabelScan.TabIndex = 4; + this.linkLabelScan.TabStop = true; + this.linkLabelScan.Text = "Scan"; + this.toolTip1.SetToolTip(this.linkLabelScan, "Scan BI Images folder for this camera to find all image resolutions"); + this.linkLabelScan.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.linkLabelScan_LinkClicked); + // + // Frm_CustomMasking + // + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Inherit; + this.ClientSize = new System.Drawing.Size(909, 478); + this.Controls.Add(this.linkLabelScan); + this.Controls.Add(this.cbResolution); + this.Controls.Add(this.label2); + this.Controls.Add(this.btnSave); + this.Controls.Add(this.btnClear); + this.Controls.Add(this.groupBox1); + this.Controls.Add(this.btnCancel); + this.Controls.Add(this.panel1); + this.DoubleBuffered = true; + this.Font = new System.Drawing.Font("Segoe UI", 8.25F); + this.Margin = new System.Windows.Forms.Padding(6); + this.Name = "Frm_CustomMasking"; + this.Text = "Custom Masking"; + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.Frm_CustomMasking_FormClosing); + this.Load += new System.EventHandler(this.Frm_CustomMasking_Load); + this.panel1.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.pbMaskImage)).EndInit(); + this.groupBox1.ResumeLayout(false); + this.flowLayoutPanel1.ResumeLayout(false); + this.flowLayoutPanel1.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.numBrushSize)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + private System.Windows.Forms.Panel panel1; + private System.Windows.Forms.PictureBox pbMaskImage; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel1; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.NumericUpDown numBrushSize; + private System.Windows.Forms.RadioButton rbBrush; + private System.Windows.Forms.RadioButton rbRectangle; + private System.Windows.Forms.Button btnClear; + private System.Windows.Forms.Button btnSave; + private System.Windows.Forms.Button btnCancel; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.ComboBox cbResolution; + private System.Windows.Forms.LinkLabel linkLabelScan; + private System.Windows.Forms.ToolTip toolTip1; + } +} \ No newline at end of file diff --git a/src/UI/Frm_CustomMasking.cs b/src/UI/Frm_CustomMasking.cs new file mode 100644 index 00000000..28491f36 --- /dev/null +++ b/src/UI/Frm_CustomMasking.cs @@ -0,0 +1,541 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.IO; +using System.Windows.Forms; +using static AITool.AITOOL; + +namespace AITool +{ + public partial class Frm_CustomMasking : Form + { + public Camera Cam { get; set; } + private Bitmap _transparentLayer, _cameraLayer, _inProgessLayer; + private string _maskfilename { get; set; } = ""; + private const float DEFAULT_OPACITY = .5f; + public int BrushSize { get; set; } + private bool _drawing = false; + + private ImageResItem _imageRes = null; + //brush drawing + private PointHistory _currentPoints = new PointHistory(); //Contains all points since the mouse down event fired. Draw all points at once in Paint method. Prevents tearing and performance issues + private List _allPointLists = new List(); //History of all points. Reserved for future undo feature + + //rectangle drawing + private Point _startRectanglePoint = Point.Empty; // mouse-down position + private Point _currentRectanglePoint = Point.Empty; // current mouse position + + + public Frm_CustomMasking() + { + this.InitializeComponent(); + } + + /********************* Start EVENT section*************************************/ + + private void Frm_CustomMasking_Load(object sender, EventArgs e) + { + Global_GUI.RestoreWindowState(this); + + UpdateRes(); + + UpdateMaskFile(); + + } + + private void UpdateMaskFile() + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + try + { + + + this._maskfilename = this.Cam.GetMaskFile(false, null, this._imageRes); + this.Text = "Custom Masking - " + this._maskfilename; + + if (!File.Exists(this._maskfilename)) + this.Text += " (Missing)"; + + this.BrushSize = this.Cam.mask_brush_size; + this.numBrushSize.Value = this.Cam.mask_brush_size; + this.ShowImage(); + + } + catch (Exception ex) + { + AITOOL.Log($"Error: {ex.Msg()}"); + } + + } + + private void UpdateRes() + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + cbResolution.Items.Clear(); + foreach (ImageResItem res in this.Cam.ImageResolutions) + { + cbResolution.Items.Add(res); + } + + if (cbResolution.Items.Count > 0) + { + cbResolution.SelectedIndex = 0; + if (cbResolution.SelectedValue != null) + this._imageRes = (ImageResItem)cbResolution.SelectedValue; + + } + + } + + private async void linkLabelScan_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + linkLabelScan.Text = "Scanning 60 secs..."; + linkLabelScan.Enabled = false; + linkLabelScan.Update(); + await this.Cam.ScanImagesAsync(); + linkLabelScan.Text = "Scan"; + linkLabelScan.Update(); + linkLabelScan.Enabled = true; + + this.UpdateRes(); + this.UpdateMaskFile(); + + + } + + private void cbResolution_SelectedIndexChanged(object sender, EventArgs e) + { + + } + + private void ShowImage() + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + try + { + string lastimage = ""; + + if (this._imageRes == null && this.Cam.ImageResolutions.Count > 0) + this._imageRes = this.Cam.ImageResolutions[0]; + + if (this._imageRes != null) + lastimage = this._imageRes.LastFileName; + + //first check for saved image in cameras folder. If doesn't exist load the last camera image. + if (this.pbMaskImage.Tag == null || !string.Equals(this.pbMaskImage.Tag.ToString(), lastimage, StringComparison.OrdinalIgnoreCase)) + { + this.pbMaskImage.Tag = lastimage; + + if ((!string.IsNullOrWhiteSpace(lastimage)) && (File.Exists(lastimage))) + { + this._cameraLayer = new Bitmap(lastimage); + + //merge layer if masks exist + if (File.Exists(this._maskfilename)) + { + using (Bitmap maskLayer = new Bitmap(this._maskfilename)) + { + this.pbMaskImage.Image = this.MergeBitmaps(this._cameraLayer, maskLayer); + this._transparentLayer = new Bitmap(this.AdjustImageOpacity(maskLayer, 2f)); // create new bitmap here to prevent file locks and increase to 100% opacity + } + } + else //if there are no masks + { + this.pbMaskImage.Image = new Bitmap(this._cameraLayer); + this._transparentLayer = new Bitmap(this.pbMaskImage.Image.Width, this.pbMaskImage.Image.Height, PixelFormat.Format32bppPArgb); + } + } + else + { + //lbl_imagefile.ForeColor = Color.Gray; + //this.Text += " (Missing)"; + } + } + + this.pbMaskImage.Refresh(); + + } + catch (Exception ex) + { + Log("Error: " + ex.Msg()); + } + } + + private Bitmap MergeBitmaps(Bitmap cameraImage, Bitmap layer) + { + Bitmap newImage = new Bitmap(cameraImage.Width, cameraImage.Height, PixelFormat.Format32bppPArgb); + + using (Graphics g = Graphics.FromImage(newImage)) + { + g.DrawImage(cameraImage, Point.Empty); + g.DrawImage(layer, Point.Empty); + } + return newImage; + } + + private Point AdjustZoomMousePosition(Point point) + { + //return point; + if (point == null || point.IsEmpty || this.pbMaskImage.Image == null) + { + return point; + } + //default to current values + double xScaled = point.X; + double yScaled = point.Y; + + //get dimensions of picturebox and scaled image + double imgWidth = this.pbMaskImage.Image.Width; + double imgHeight = this.pbMaskImage.Image.Height; + double picboxWidth = this.pbMaskImage.Width; + double picboxHeight = this.pbMaskImage.Height; + + double picAspect = picboxWidth / picboxHeight; + double imgAspect = imgWidth / imgHeight; + + if (picAspect > imgAspect) + { + // pictureBox is wider/shorter than the image. + yScaled = (imgHeight * point.Y / picboxHeight); + + // image fills the height of the PictureBox. + // Get width. + double scaledWidth = imgWidth * picboxHeight / imgHeight; + double dx = (picboxWidth - scaledWidth) / 2; + xScaled = ((point.X - dx) * imgHeight / picboxHeight); + } + else + { + // pictureBox is taller/thinner than the image. + xScaled = (imgWidth * point.X / picboxWidth); + + // image fills the height of the PictureBox. + // Get height. + double scaledHeight = imgHeight * picboxWidth / imgWidth; + double dy = (picboxHeight - scaledHeight) / 2; + yScaled = ((point.Y - dy) * imgWidth / picboxWidth); + } + + return new Point(xScaled.ToInt(), yScaled.ToInt()); + } + + private Bitmap AdjustImageOpacity(Image image, float alphaLevel) + { + // Initialize the color matrix. + // Note the value {level} in row 4, column 4. this is for the alpha channel + float[][] matrixItems ={ + new float[] {1, 0, 0, 0, 0}, + new float[] {0, 1, 0, 0, 0}, + new float[] {0, 0, 1, 0, 0}, + new float[] {0, 0, 0, alphaLevel, 0}, + new float[] {0, 0, 0, 0, 1}}; + ColorMatrix colorMatrix = new ColorMatrix(matrixItems); + + // Create an ImageAttributes object and set its color matrix. + ImageAttributes imageAtt = new ImageAttributes(); + imageAtt.SetColorMatrix(colorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap); + + // Now draw the semitransparent bitmap image. + int iWidth = image.Width; + int iHeight = image.Height; + + Bitmap newBmp = new Bitmap(image.Width, image.Height); + + using (Graphics g = Graphics.FromImage(newBmp)) + { + g.DrawImage( + image, + new Rectangle(0, 0, iWidth, iHeight), // destination rectangle + 0.0f, // source rectangle x + 0.0f, // source rectangle y + iWidth, // source rectangle width + iHeight, // source rectangle height + GraphicsUnit.Pixel, + imageAtt); + } + return newBmp; + } + + private Rectangle getRectangle() + { + if (this._startRectanglePoint != Point.Empty && this._currentRectanglePoint != Point.Empty) + { + return new Rectangle( + Math.Min(this._startRectanglePoint.X, this._currentRectanglePoint.X), + Math.Min(this._startRectanglePoint.Y, this._currentRectanglePoint.Y), + Math.Abs(this._startRectanglePoint.X - this._currentRectanglePoint.X), + Math.Abs(this._startRectanglePoint.Y - this._currentRectanglePoint.Y)); + } + + else return new Rectangle(); + } + + private Rectangle getScaledRectangle() + { + if (this._startRectanglePoint != Point.Empty && this._currentRectanglePoint != Point.Empty) + { + Point scaledStart = this.AdjustZoomMousePosition(this._startRectanglePoint); + Point scaleCurrent = this.AdjustZoomMousePosition(this._currentRectanglePoint); + + return new Rectangle( + Math.Min(scaledStart.X, scaleCurrent.X), + Math.Min(scaledStart.Y, scaleCurrent.Y), + Math.Abs(scaledStart.X - scaleCurrent.X), + Math.Abs(scaledStart.Y - scaleCurrent.Y)); + } + + else return new Rectangle(); + } + + private void drawRectangle(Graphics e) + { + Color color = Color.FromArgb(255, 255, 255, 70); + SolidBrush fillBrush = new SolidBrush(color); + + if (this._drawing) + { + e.DrawRectangle(Pens.Yellow, this.getRectangle()); + } + else if (this._startRectanglePoint.IsEmpty == false) + { + using (Graphics g = Graphics.FromImage(this._inProgessLayer)) + { + g.FillRectangle(fillBrush, this.getScaledRectangle()); + } + } + } + + private void drawBrush(Graphics e) + { + Color color = Color.FromArgb(255, 255, 255, 70); + //first draw the image for the picturebox. Used as a readonly background layer + using (Pen pen = new Pen(color, this.BrushSize)) + { + pen.MiterLimit = pen.Width / 2; + pen.LineJoin = LineJoin.Round; + pen.StartCap = LineCap.Round; + pen.EndCap = LineCap.Round; + + if (this._currentPoints.GetPoints().Count > 1) + { + using (Graphics g = Graphics.FromImage(this.pbMaskImage.Image)) + { + //first draw the mask on the picturebox. Used as a readonly background layer + g.SmoothingMode = SmoothingMode.AntiAlias; + g.DrawLines(pen, this._currentPoints.GetPoints().ToArray()); + } + + using (Graphics g = Graphics.FromImage(this._inProgessLayer)) + { + //second draw the mask on a transparent layer. Used as a mask overlay on background defined above. + g.SmoothingMode = SmoothingMode.AntiAlias; + g.DrawLines(pen, this._currentPoints.GetPoints().ToArray()); + } + } + } + } + + + private void pbMaskImage_Paint(object sender, PaintEventArgs e) + { + if (this.pbMaskImage.Image != null) + { + if (this._inProgessLayer == null) + { + this._inProgessLayer = new Bitmap(this._transparentLayer.Width, this._transparentLayer.Height, PixelFormat.Format32bppPArgb); + } + + if (this.rbRectangle.Checked) + { + this.drawRectangle(e.Graphics); + } + else if (this.rbBrush.Checked) + { + this.drawBrush(e.Graphics); + } + } + } + + private void pbMaskImage_MouseDown(object sender, MouseEventArgs e) + { + if (this.rbBrush.Checked) + { + this._currentPoints = new PointHistory(this.AdjustZoomMousePosition(e.Location), this.BrushSize); + } + else if (this.rbRectangle.Checked) + { + this._currentRectanglePoint = this._startRectanglePoint = e.Location; + this._drawing = true; + } + } + + private void pbMaskImage_MouseMove(object sender, MouseEventArgs e) + { + if (e.Button == MouseButtons.Left) + { + this._currentPoints.AddPoint(this.AdjustZoomMousePosition(e.Location)); + this._currentRectanglePoint = e.Location; + this.pbMaskImage.Invalidate(); + } + } + + private void pbMaskImage_MouseUp(object sender, MouseEventArgs e) + { + if (this.rbBrush.Checked) + { + if (this._inProgessLayer != null) + { + this._transparentLayer = this.MergeBitmaps(this._transparentLayer, this._inProgessLayer); + this.pbMaskImage.Image = this.MergeBitmaps(this._cameraLayer, this.AdjustImageOpacity(this._transparentLayer, DEFAULT_OPACITY)); + this._inProgessLayer = null; + } + + if (this._currentPoints.GetPoints().Count > 1) + { + this._allPointLists.Add(this._currentPoints); + this._currentPoints = new PointHistory(); + } + } + else if (this.rbRectangle.Checked && e.Button == MouseButtons.Left) + { + this._drawing = false; + + if (this._inProgessLayer != null) + { + this.pbMaskImage.Refresh(); // call paint to draw inprogress rectangle to image + this._transparentLayer = this.MergeBitmaps(this._transparentLayer, this._inProgessLayer); + this.pbMaskImage.Image = this.MergeBitmaps(this._cameraLayer, this.AdjustImageOpacity(this._transparentLayer, DEFAULT_OPACITY)); + + //reset rectangle positions and in progress layer + this._startRectanglePoint = Point.Empty; + this._currentRectanglePoint = Point.Empty; + this._inProgessLayer = null; + } + } + } + + private void numBrushSize_Leave(object sender, EventArgs e) + { + if (this.numBrushSize.Text == "") + { + this.numBrushSize.Text = this.numBrushSize.Value.ToString(); + } + } + + private void btnClear_Click(object sender, EventArgs e) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + this._allPointLists.Clear(); + + this.pbMaskImage.Tag = null; + + //if mask exists, delete it + if (File.Exists(this._maskfilename)) + { + File.Delete(this._maskfilename); + } + + UpdateMaskFile(); + this.ShowImage(); + } + + private void numBrushSize_ValueChanged(object sender, EventArgs e) + { + this.BrushSize = (int)this.numBrushSize.Value; + } + + private void numBrushSize_KeyUp(object sender, KeyEventArgs e) + { + this.BrushSize = (int)this.numBrushSize.Value; + } + + private void pbMaskImage_Click(object sender, EventArgs e) + { + + } + + private void flowLayoutPanel2_Paint(object sender, PaintEventArgs e) + { + + } + + private void Frm_CustomMasking_FormClosing(object sender, FormClosingEventArgs e) + { + Global_GUI.SaveWindowState(this); + } + + private void cbResolution_SelectionChangeCommitted(object sender, EventArgs e) + { + UpdateMaskFile(); + } + + private void btnSave_Click(object sender, EventArgs e) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + try + { + if (this._transparentLayer != null) + { + //save masks at 50% opacity + Bitmap img = this.AdjustImageOpacity(this._transparentLayer, DEFAULT_OPACITY); + img.Save(this._maskfilename); + //save mask file with resolution attached + //string resfile = Path.Combine(Path.GetDirectoryName(this._maskfilename), $"{Path.GetFileNameWithoutExtension(this._maskfilename)}_{this._transparentLayer.Width}x{this._transparentLayer.Height}.bmp"); + Log($"Mask file saved. Resolution={img.Width}x{img.Height}. Transparency Resolution={this._transparentLayer.Width}x{this._transparentLayer.Height} : " + this._maskfilename); + } + + this.DialogResult = DialogResult.OK; + } + catch (Exception ex) + { + Log("Error: An error occurred saving custom mask with file name: " + this._maskfilename + ex.ToString()); + } + finally + { + this.Close(); + } + } + + + public class PointHistory + { + private List _points = new List(); + public int BrushSize { get; set; } + + public PointHistory() + { + this._points = new List(); + this.BrushSize = 40; + } + + public PointHistory(Point point, int brushSize) + { + this._points.Add(point); + this.BrushSize = brushSize; + } + + public void AddPoint(Point point) + { + this._points.Add(point); + } + + public List GetPoints() + { + return this._points; + } + + public void ClearPoints() + { + this._points.Clear(); + } + } + } +} diff --git a/src/UI/Frm_CustomMasking.resx b/src/UI/Frm_CustomMasking.resx new file mode 100644 index 00000000..df8339b6 --- /dev/null +++ b/src/UI/Frm_CustomMasking.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + \ No newline at end of file diff --git a/src/UI/Frm_DynamicMaskDetails.Designer.cs b/src/UI/Frm_DynamicMaskDetails.Designer.cs new file mode 100644 index 00000000..3d7a72ca --- /dev/null +++ b/src/UI/Frm_DynamicMaskDetails.Designer.cs @@ -0,0 +1,387 @@ +namespace AITool +{ + partial class Frm_DynamicMaskDetails + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.splitContainer1 = new System.Windows.Forms.SplitContainer(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.lblClearHistory = new System.Windows.Forms.Label(); + this.FOLV_MaskHistory = new BrightIdeasSoftware.FastObjectListView(); + this.staticMaskMenu = new System.Windows.Forms.ContextMenuStrip(this.components); + this.createStaticMaskToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.createDynamicMaskTemporaryToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.removeHistoryMaskToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.label3 = new System.Windows.Forms.Label(); + this.groupBox2 = new System.Windows.Forms.GroupBox(); + this.lblClearMasks = new System.Windows.Forms.Label(); + this.FOLV_Masks = new BrightIdeasSoftware.FastObjectListView(); + this.removeMaskMenu = new System.Windows.Forms.ContextMenuStrip(this.components); + this.createStaticMaskToolStripMenuItem1 = new System.Windows.Forms.ToolStripMenuItem(); + this.removeMaskToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.splitContainer2 = new System.Windows.Forms.SplitContainer(); + this.lbl_lastfile = new System.Windows.Forms.Label(); + this.pictureBox1 = new System.Windows.Forms.PictureBox(); + this.comboBox_filter_camera = new System.Windows.Forms.ComboBox(); + this.BtnDynamicMaskingSettings = new System.Windows.Forms.Button(); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); + this.splitContainer1.Panel1.SuspendLayout(); + this.splitContainer1.Panel2.SuspendLayout(); + this.splitContainer1.SuspendLayout(); + this.groupBox1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.FOLV_MaskHistory)).BeginInit(); + this.staticMaskMenu.SuspendLayout(); + this.groupBox2.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.FOLV_Masks)).BeginInit(); + this.removeMaskMenu.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer2)).BeginInit(); + this.splitContainer2.Panel1.SuspendLayout(); + this.splitContainer2.Panel2.SuspendLayout(); + this.splitContainer2.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit(); + this.SuspendLayout(); + // + // splitContainer1 + // + this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill; + this.splitContainer1.Location = new System.Drawing.Point(0, 0); + this.splitContainer1.Margin = new System.Windows.Forms.Padding(4); + this.splitContainer1.Name = "splitContainer1"; + this.splitContainer1.Orientation = System.Windows.Forms.Orientation.Horizontal; + // + // splitContainer1.Panel1 + // + this.splitContainer1.Panel1.Controls.Add(this.groupBox1); + // + // splitContainer1.Panel2 + // + this.splitContainer1.Panel2.Controls.Add(this.groupBox2); + this.splitContainer1.Size = new System.Drawing.Size(567, 459); + this.splitContainer1.SplitterDistance = 228; + this.splitContainer1.TabIndex = 0; + // + // groupBox1 + // + this.groupBox1.Controls.Add(this.lblClearHistory); + this.groupBox1.Controls.Add(this.FOLV_MaskHistory); + this.groupBox1.Controls.Add(this.label3); + this.groupBox1.Dock = System.Windows.Forms.DockStyle.Fill; + this.groupBox1.Location = new System.Drawing.Point(0, 0); + this.groupBox1.Margin = new System.Windows.Forms.Padding(4); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.Padding = new System.Windows.Forms.Padding(4); + this.groupBox1.Size = new System.Drawing.Size(567, 228); + this.groupBox1.TabIndex = 3; + this.groupBox1.TabStop = false; + this.groupBox1.Text = "History"; + // + // lblClearHistory + // + this.lblClearHistory.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left))); + this.lblClearHistory.AutoSize = true; + this.lblClearHistory.BackColor = System.Drawing.SystemColors.Control; + this.lblClearHistory.ForeColor = System.Drawing.SystemColors.HotTrack; + this.lblClearHistory.Location = new System.Drawing.Point(79, 0); + this.lblClearHistory.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.lblClearHistory.Name = "lblClearHistory"; + this.lblClearHistory.Size = new System.Drawing.Size(72, 13); + this.lblClearHistory.TabIndex = 3; + this.lblClearHistory.Text = "Clear History"; + this.lblClearHistory.Click += new System.EventHandler(this.lblClearHistory_Click); + // + // FOLV_MaskHistory + // + this.FOLV_MaskHistory.ContextMenuStrip = this.staticMaskMenu; + this.FOLV_MaskHistory.Dock = System.Windows.Forms.DockStyle.Fill; + this.FOLV_MaskHistory.EmptyListMsg = "None"; + this.FOLV_MaskHistory.HideSelection = false; + this.FOLV_MaskHistory.Location = new System.Drawing.Point(4, 19); + this.FOLV_MaskHistory.Margin = new System.Windows.Forms.Padding(4); + this.FOLV_MaskHistory.Name = "FOLV_MaskHistory"; + this.FOLV_MaskHistory.ShowGroups = false; + this.FOLV_MaskHistory.Size = new System.Drawing.Size(559, 205); + this.FOLV_MaskHistory.TabIndex = 0; + this.FOLV_MaskHistory.UseCompatibleStateImageBehavior = false; + this.FOLV_MaskHistory.View = System.Windows.Forms.View.Details; + this.FOLV_MaskHistory.VirtualMode = true; + this.FOLV_MaskHistory.CellRightClick += new System.EventHandler(this.FOLV_MaskHistory_CellRightClick); + this.FOLV_MaskHistory.FormatRow += new System.EventHandler(this.FOLV_MaskHistory_FormatRow); + this.FOLV_MaskHistory.SelectionChanged += new System.EventHandler(this.FOLV_MaskHistory_SelectionChanged); + this.FOLV_MaskHistory.SelectedIndexChanged += new System.EventHandler(this.FOLV_MaskHistory_SelectedIndexChanged); + // + // staticMaskMenu + // + this.staticMaskMenu.ImageScalingSize = new System.Drawing.Size(28, 28); + this.staticMaskMenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.createStaticMaskToolStripMenuItem, + this.createDynamicMaskTemporaryToolStripMenuItem, + this.removeHistoryMaskToolStripMenuItem}); + this.staticMaskMenu.Name = "staticMaskMenu"; + this.staticMaskMenu.RenderMode = System.Windows.Forms.ToolStripRenderMode.System; + this.staticMaskMenu.ShowImageMargin = false; + this.staticMaskMenu.Size = new System.Drawing.Size(245, 70); + this.staticMaskMenu.Opening += new System.ComponentModel.CancelEventHandler(this.staticMaskMenu_Opening); + // + // createStaticMaskToolStripMenuItem + // + this.createStaticMaskToolStripMenuItem.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text; + this.createStaticMaskToolStripMenuItem.Name = "createStaticMaskToolStripMenuItem"; + this.createStaticMaskToolStripMenuItem.Size = new System.Drawing.Size(244, 22); + this.createStaticMaskToolStripMenuItem.Text = "Create Static Mask(s)"; + this.createStaticMaskToolStripMenuItem.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + this.createStaticMaskToolStripMenuItem.Click += new System.EventHandler(this.createStaticMaskToolStripMenuItem_Click); + // + // createDynamicMaskTemporaryToolStripMenuItem + // + this.createDynamicMaskTemporaryToolStripMenuItem.Name = "createDynamicMaskTemporaryToolStripMenuItem"; + this.createDynamicMaskTemporaryToolStripMenuItem.Size = new System.Drawing.Size(244, 22); + this.createDynamicMaskTemporaryToolStripMenuItem.Text = "Create Dynamic Mask(s) (Temporary)"; + this.createDynamicMaskTemporaryToolStripMenuItem.ToolTipText = "Forced creation of dynamic mask before conditions in settings are met."; + this.createDynamicMaskTemporaryToolStripMenuItem.Click += new System.EventHandler(this.createDynamicMaskTemporaryToolStripMenuItem_Click); + // + // removeHistoryMaskToolStripMenuItem + // + this.removeHistoryMaskToolStripMenuItem.Name = "removeHistoryMaskToolStripMenuItem"; + this.removeHistoryMaskToolStripMenuItem.Size = new System.Drawing.Size(244, 22); + this.removeHistoryMaskToolStripMenuItem.Text = "Remove Mask(s)"; + this.removeHistoryMaskToolStripMenuItem.Click += new System.EventHandler(this.removeHistoryMaskToolStripMenuItem_Click); + // + // label3 + // + this.label3.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.label3.AutoSize = true; + this.label3.ForeColor = System.Drawing.SystemColors.HotTrack; + this.label3.Location = new System.Drawing.Point(483, 0); + this.label3.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(46, 13); + this.label3.TabIndex = 2; + this.label3.Text = "Refresh"; + this.label3.Click += new System.EventHandler(this.label3_Click); + // + // groupBox2 + // + this.groupBox2.Controls.Add(this.lblClearMasks); + this.groupBox2.Controls.Add(this.FOLV_Masks); + this.groupBox2.Dock = System.Windows.Forms.DockStyle.Fill; + this.groupBox2.Location = new System.Drawing.Point(0, 0); + this.groupBox2.Margin = new System.Windows.Forms.Padding(4); + this.groupBox2.Name = "groupBox2"; + this.groupBox2.Padding = new System.Windows.Forms.Padding(4); + this.groupBox2.Size = new System.Drawing.Size(567, 227); + this.groupBox2.TabIndex = 1; + this.groupBox2.TabStop = false; + this.groupBox2.Text = "Active Masks"; + // + // lblClearMasks + // + this.lblClearMasks.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left))); + this.lblClearMasks.AutoSize = true; + this.lblClearMasks.ForeColor = System.Drawing.SystemColors.HotTrack; + this.lblClearMasks.Location = new System.Drawing.Point(135, 0); + this.lblClearMasks.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.lblClearMasks.Name = "lblClearMasks"; + this.lblClearMasks.Size = new System.Drawing.Size(68, 13); + this.lblClearMasks.TabIndex = 4; + this.lblClearMasks.Text = "Clear Masks"; + this.lblClearMasks.Click += new System.EventHandler(this.lblClearMasks_Click); + // + // FOLV_Masks + // + this.FOLV_Masks.ContextMenuStrip = this.removeMaskMenu; + this.FOLV_Masks.Dock = System.Windows.Forms.DockStyle.Fill; + this.FOLV_Masks.EmptyListMsg = "Empty"; + this.FOLV_Masks.HideSelection = false; + this.FOLV_Masks.Location = new System.Drawing.Point(4, 19); + this.FOLV_Masks.Margin = new System.Windows.Forms.Padding(4); + this.FOLV_Masks.Name = "FOLV_Masks"; + this.FOLV_Masks.ShowGroups = false; + this.FOLV_Masks.Size = new System.Drawing.Size(559, 204); + this.FOLV_Masks.TabIndex = 0; + this.FOLV_Masks.UseCompatibleStateImageBehavior = false; + this.FOLV_Masks.View = System.Windows.Forms.View.Details; + this.FOLV_Masks.VirtualMode = true; + this.FOLV_Masks.CellRightClick += new System.EventHandler(this.FOLV_Masks_CellRightClick); + this.FOLV_Masks.FormatRow += new System.EventHandler(this.FOLV_Masks_FormatRow); + this.FOLV_Masks.SelectionChanged += new System.EventHandler(this.FOLV_Masks_SelectionChanged); + // + // removeMaskMenu + // + this.removeMaskMenu.ImageScalingSize = new System.Drawing.Size(28, 28); + this.removeMaskMenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.createStaticMaskToolStripMenuItem1, + this.removeMaskToolStripMenuItem}); + this.removeMaskMenu.Name = "removeMaskMenu"; + this.removeMaskMenu.ShowImageMargin = false; + this.removeMaskMenu.Size = new System.Drawing.Size(160, 48); + // + // createStaticMaskToolStripMenuItem1 + // + this.createStaticMaskToolStripMenuItem1.Name = "createStaticMaskToolStripMenuItem1"; + this.createStaticMaskToolStripMenuItem1.Size = new System.Drawing.Size(159, 22); + this.createStaticMaskToolStripMenuItem1.Text = "Create Static Mask(s)"; + this.createStaticMaskToolStripMenuItem1.Click += new System.EventHandler(this.createStaticMaskToolStripMenuItem1_Click); + // + // removeMaskToolStripMenuItem + // + this.removeMaskToolStripMenuItem.Name = "removeMaskToolStripMenuItem"; + this.removeMaskToolStripMenuItem.Size = new System.Drawing.Size(159, 22); + this.removeMaskToolStripMenuItem.Text = "Remove Mask(s)"; + this.removeMaskToolStripMenuItem.Click += new System.EventHandler(this.removeMaskToolStripMenuItem_Click); + // + // splitContainer2 + // + this.splitContainer2.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.splitContainer2.Location = new System.Drawing.Point(4, 41); + this.splitContainer2.Margin = new System.Windows.Forms.Padding(4); + this.splitContainer2.Name = "splitContainer2"; + // + // splitContainer2.Panel1 + // + this.splitContainer2.Panel1.Controls.Add(this.splitContainer1); + // + // splitContainer2.Panel2 + // + this.splitContainer2.Panel2.Controls.Add(this.lbl_lastfile); + this.splitContainer2.Panel2.Controls.Add(this.pictureBox1); + this.splitContainer2.Size = new System.Drawing.Size(1106, 459); + this.splitContainer2.SplitterDistance = 567; + this.splitContainer2.TabIndex = 1; + // + // lbl_lastfile + // + this.lbl_lastfile.AutoSize = true; + this.lbl_lastfile.Location = new System.Drawing.Point(-2, 2); + this.lbl_lastfile.Name = "lbl_lastfile"; + this.lbl_lastfile.Size = new System.Drawing.Size(0, 13); + this.lbl_lastfile.TabIndex = 1; + // + // pictureBox1 + // + this.pictureBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.pictureBox1.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Zoom; + this.pictureBox1.Location = new System.Drawing.Point(0, 31); + this.pictureBox1.Margin = new System.Windows.Forms.Padding(4); + this.pictureBox1.Name = "pictureBox1"; + this.pictureBox1.Size = new System.Drawing.Size(535, 428); + this.pictureBox1.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.pictureBox1.TabIndex = 0; + this.pictureBox1.TabStop = false; + this.pictureBox1.Click += new System.EventHandler(this.pictureBox1_Click); + this.pictureBox1.Paint += new System.Windows.Forms.PaintEventHandler(this.pictureBox1_Paint); + // + // comboBox_filter_camera + // + this.comboBox_filter_camera.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.comboBox_filter_camera.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.comboBox_filter_camera.FormattingEnabled = true; + this.comboBox_filter_camera.Location = new System.Drawing.Point(4, 6); + this.comboBox_filter_camera.Margin = new System.Windows.Forms.Padding(5); + this.comboBox_filter_camera.Name = "comboBox_filter_camera"; + this.comboBox_filter_camera.Size = new System.Drawing.Size(196, 25); + this.comboBox_filter_camera.TabIndex = 3; + this.comboBox_filter_camera.SelectedIndexChanged += new System.EventHandler(this.comboBox_filter_camera_SelectedIndexChanged); + this.comboBox_filter_camera.SelectionChangeCommitted += new System.EventHandler(this.comboBox_filter_camera_SelectionChangeCommitted); + // + // BtnDynamicMaskingSettings + // + this.BtnDynamicMaskingSettings.Location = new System.Drawing.Point(214, 3); + this.BtnDynamicMaskingSettings.Margin = new System.Windows.Forms.Padding(9, 2, 9, 2); + this.BtnDynamicMaskingSettings.Name = "BtnDynamicMaskingSettings"; + this.BtnDynamicMaskingSettings.Size = new System.Drawing.Size(70, 30); + this.BtnDynamicMaskingSettings.TabIndex = 23; + this.BtnDynamicMaskingSettings.Text = "Settings"; + this.BtnDynamicMaskingSettings.UseVisualStyleBackColor = true; + this.BtnDynamicMaskingSettings.Click += new System.EventHandler(this.BtnDynamicMaskingSettings_Click); + // + // Frm_DynamicMaskDetails + // + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Inherit; + this.ClientSize = new System.Drawing.Size(1114, 504); + this.Controls.Add(this.BtnDynamicMaskingSettings); + this.Controls.Add(this.comboBox_filter_camera); + this.Controls.Add(this.splitContainer2); + this.Font = new System.Drawing.Font("Segoe UI", 8.25F); + this.Margin = new System.Windows.Forms.Padding(4); + this.Name = "Frm_DynamicMaskDetails"; + this.Tag = "SAVE"; + this.Text = "Dynamic Mask Detail"; + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.Frm_DynamicMaskDetails_FormClosing); + this.Load += new System.EventHandler(this.Frm_DynamicMaskDetails_Load); + this.splitContainer1.Panel1.ResumeLayout(false); + this.splitContainer1.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit(); + this.splitContainer1.ResumeLayout(false); + this.groupBox1.ResumeLayout(false); + this.groupBox1.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.FOLV_MaskHistory)).EndInit(); + this.staticMaskMenu.ResumeLayout(false); + this.groupBox2.ResumeLayout(false); + this.groupBox2.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.FOLV_Masks)).EndInit(); + this.removeMaskMenu.ResumeLayout(false); + this.splitContainer2.Panel1.ResumeLayout(false); + this.splitContainer2.Panel2.ResumeLayout(false); + this.splitContainer2.Panel2.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer2)).EndInit(); + this.splitContainer2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.SplitContainer splitContainer1; + private BrightIdeasSoftware.FastObjectListView FOLV_MaskHistory; + private BrightIdeasSoftware.FastObjectListView FOLV_Masks; + private System.Windows.Forms.SplitContainer splitContainer2; + private System.Windows.Forms.PictureBox pictureBox1; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.GroupBox groupBox2; + private System.Windows.Forms.Label lblClearHistory; + private System.Windows.Forms.Label lblClearMasks; + private System.Windows.Forms.ContextMenuStrip staticMaskMenu; + private System.Windows.Forms.ToolStripMenuItem createStaticMaskToolStripMenuItem; + private System.Windows.Forms.ContextMenuStrip removeMaskMenu; + private System.Windows.Forms.ToolStripMenuItem removeMaskToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem createStaticMaskToolStripMenuItem1; + private System.Windows.Forms.Label lbl_lastfile; + private System.Windows.Forms.ComboBox comboBox_filter_camera; + private System.Windows.Forms.Button BtnDynamicMaskingSettings; + private System.Windows.Forms.ToolStripMenuItem createDynamicMaskTemporaryToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem removeHistoryMaskToolStripMenuItem; + } +} \ No newline at end of file diff --git a/src/UI/Frm_DynamicMaskDetails.cs b/src/UI/Frm_DynamicMaskDetails.cs new file mode 100644 index 00000000..e78e5b42 --- /dev/null +++ b/src/UI/Frm_DynamicMaskDetails.cs @@ -0,0 +1,693 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Windows.Forms; + +using static AITool.AITOOL; + +namespace AITool +{ + public partial class Frm_DynamicMaskDetails : Form + { + + public Camera cam; + private List CurObjPosLst = new List(); + private ObjectPosition contextMenuPosObj; + private bool loading = false; + + public string GetBestImage() + { + //try to prioritize the images from actual detections first + //goal is to always try to have an image to display + // IS THIS OVERKILL OR CONFUSING TO USER??? + + try + { + string lastfolder = ""; + + if (this.contextMenuPosObj != null && !string.IsNullOrEmpty(this.contextMenuPosObj.ImagePath)) + { + lastfolder = Path.GetDirectoryName(this.contextMenuPosObj.ImagePath); + if (File.Exists(this.contextMenuPosObj.ImagePath)) + { + Log($" (Found image from ACTUAL detected object: {this.contextMenuPosObj.ImagePath})"); + return this.contextMenuPosObj.ImagePath; + } + else + { + Log("debug: Mask image file not found at location: " + this.contextMenuPosObj.ImagePath + ". Defaulting to last processed image"); + } + } + else + { + Log("debug: Mask image file path was blank or NULL. Defaulting to last processed image"); + + } + + //See if we have an image stored that had ANY detections and use it + if (this.cam != null) + { + if (!string.IsNullOrEmpty(this.cam.last_image_file_with_detections)) + { + lastfolder = Path.GetDirectoryName(this.cam.last_image_file_with_detections); + if (File.Exists(this.cam.last_image_file_with_detections)) + { + Log($" (Found image from -last- detected object: {this.cam.last_image_file_with_detections})"); + return this.cam.last_image_file_with_detections; + } + else + { + //extra debugging + Log(" >CAM.last_image_file_with_detections file no longer exists."); + } + + } + else + { + //extra debugging + Log($" >No CAM.last_image_file_with_detections for '{this.cam.Name}'"); + } + + + //Just take the last image processed by the camera even if no detections + if (!string.IsNullOrEmpty(this.cam.last_image_file)) + { + lastfolder = Path.GetDirectoryName(this.cam.last_image_file); + if (File.Exists(this.cam.last_image_file)) + { + Log($" (Found image from last processed image (no detections): {this.cam.last_image_file})"); + return this.cam.last_image_file; + } + else + { + //extra debugging + Log(" >CAM.last_image_file file no longer exists."); + } + } + else + { + //extra debugging + Log($" >No CAM.last_image_file for '{this.cam.Name}'"); + } + + //try to use the LastCamImages version + string fldr = Path.Combine(Path.GetDirectoryName(AppSettings.Settings.SettingsFileName), "LastCamImages"); + string file = Path.Combine(fldr, $"{cam.Name}-Last.jpg"); + + if (File.Exists(file)) + { + Log($" (Found image from LastCamImages folder: ({file})"); + return file; + } + + + if (string.IsNullOrEmpty(lastfolder)) + { + lastfolder = this.cam.input_path; + } + } + else + { + //extra debugging + Log(" >No CAM selected."); + } + + //FAIL, scan the camera folder for the most recent image file + if (!string.IsNullOrEmpty(lastfolder) && Directory.Exists(lastfolder)) + { + //I expect this may take a few seconds if folder is huge + DirectoryInfo dirinfo = new DirectoryInfo(lastfolder); + FileInfo myFile; + if (this.cam != null && !string.IsNullOrEmpty(this.cam.Prefix)) + { + myFile = dirinfo.GetFiles($"{this.cam.Prefix.Trim()}*.jpg").OrderByDescending(f => f.LastWriteTime).First(); + if (myFile != null) + { + Log($" (Found most recent image in camera folder for prefix '{this.cam.Prefix}' (no detections): {myFile.FullName})"); + return myFile.FullName; + } + else + { + //extra debugging + Log($" >No files found starting with '{this.cam.Prefix}' in {lastfolder}'"); + } + + } + else + { + myFile = dirinfo.GetFiles("*.jpg").OrderByDescending(f => f.LastWriteTime).First(); + if (myFile != null) + { + Log($" (Found most recent image in camera folder (no detections): {myFile.FullName})"); + return myFile.FullName; + } + else + { + //extra debugging + Log($" >No files found in {lastfolder}'"); + } + } + } + else + { + //extra debugging + Log($">Lastfolder not found or doesnt exist - '{lastfolder}'"); + } + + + } + catch (Exception ex) + { + + Log("Error: " + ex.Msg()); + } + + return ""; + + } + + + public Frm_DynamicMaskDetails() + { + this.InitializeComponent(); + } + + private void Frm_DynamicMaskDetails_Load(object sender, EventArgs e) + { + this.loading = true; + + Global_GUI.ConfigureFOLV(this.FOLV_MaskHistory, typeof(ObjectPosition), null, null, "createDate", SortOrder.Descending); + Global_GUI.ConfigureFOLV(this.FOLV_Masks, typeof(ObjectPosition), null, null, "createDate", SortOrder.Descending); + + Global_GUI.RestoreWindowState(this); + + this.comboBox_filter_camera.Items.Clear(); + this.comboBox_filter_camera.Items.Add("All Cameras"); + + int i = 1; + int curidx = 1; + foreach (Camera curcam in AppSettings.Settings.CameraList) + { + this.comboBox_filter_camera.Items.Add($" {curcam.Name}"); + if (this.cam.Name.Trim().ToLower() == curcam.Name.Trim().ToLower()) + { + curidx = i; + Log($"Cam '{curcam.Name}' is at index '{curidx}'"); + } + i++; + } + + this.comboBox_filter_camera.SelectedIndex = curidx; + + + this.Refresh(); + + this.loading = false; + + + } + + private void Refresh() + { + + //in case of disabled cameras: + if (!string.Equals(this.comboBox_filter_camera.Text, "All Cameras", StringComparison.OrdinalIgnoreCase) && this.comboBox_filter_camera.Text.ToLower().Trim() != this.cam.Name.ToLower().Trim()) + { + this.cam = AITOOL.GetCamera(this.comboBox_filter_camera.Text); + } + + List hist = new List(); + List masked = new List(); + + if (this.comboBox_filter_camera.Text == "All Cameras") + { + foreach (Camera curcam in AppSettings.Settings.CameraList) + { + hist.AddRange(curcam.maskManager.LastPositionsHistory); + masked.AddRange(curcam.maskManager.MaskedPositions); + } + } + else + { + hist = this.cam.maskManager.LastPositionsHistory; + masked = this.cam.maskManager.MaskedPositions; + } + + Global_GUI.UpdateFOLV(this.FOLV_MaskHistory, hist, FullRefresh: true); + Global_GUI.UpdateFOLV(this.FOLV_Masks, masked, FullRefresh: true); + this.CurObjPosLst.Clear(); + this.ShowMaskImage(); + this.ShowImageMask(null); + } + + + private void ShowMaskImage() + { + try + { + string imagePath = this.GetBestImage(); + + if (!string.IsNullOrEmpty(imagePath) && (this.pictureBox1.Tag == null || !string.Equals(this.pictureBox1.Tag.ToString(), imagePath, StringComparison.OrdinalIgnoreCase))) + { + using (var img = new Bitmap(imagePath)) + { + this.pictureBox1.BackgroundImage = new Bitmap(img); //load actual image as background, so that an overlay can be added as the image + } + + this.pictureBox1.Tag = imagePath; + if (this.contextMenuPosObj != null) + { + if (this.contextMenuPosObj.ImagePath.IsNotEmpty() && !string.Equals(this.contextMenuPosObj.ImagePath, imagePath, StringComparison.OrdinalIgnoreCase)) + { + this.lbl_lastfile.ForeColor = Color.DarkMagenta; + this.lbl_lastfile.Text = "Original Mask Image not found, using: " + imagePath; + } + else + { + this.lbl_lastfile.ForeColor = SystemColors.ControlText; + this.lbl_lastfile.Text = "Mask image: " + imagePath; + } + + } + else + { + this.lbl_lastfile.ForeColor = SystemColors.ControlText; + this.lbl_lastfile.Text = "Mask image: " + imagePath; + } + + } + else if (string.IsNullOrEmpty(imagePath)) + { + this.pictureBox1.BackgroundImage = null; + this.lbl_lastfile.Text = "No image to display"; + this.pictureBox1.Tag = ""; + + } + + this.pictureBox1.Refresh(); + + } + catch (Exception ex) + { + Log("Error: " + ex.Msg()); + } + } + + private void ShowLastImage() + { + try + { + if (this.pictureBox1.Tag == null || !string.Equals(this.pictureBox1.Tag.ToString(), this.cam.last_image_file, StringComparison.OrdinalIgnoreCase)) + { + if ((!string.IsNullOrWhiteSpace(this.cam.last_image_file))) + { + if (File.Exists(this.cam.last_image_file)) + { + using (var img = new Bitmap(this.cam.last_image_file)) + { + this.pictureBox1.BackgroundImage = new Bitmap(img); //load actual image as background, so that an overlay can be added as the image + } + + this.lbl_lastfile.Text = "Last image: " + this.cam.last_image_file; + this.pictureBox1.Tag = this.cam.last_image_file; + } + else + { + this.lbl_lastfile.Text = "Last image doesn't exist: " + this.cam.last_image_file; + this.pictureBox1.BackgroundImage = null; + } + } + else + { + this.lbl_lastfile.Text = "No image to show for this camera yet (Must have processed an image within the current session)"; + this.pictureBox1.BackgroundImage = null; + } + } + + this.pictureBox1.Refresh(); + + } + catch (Exception ex) + { + Log("Error: " + ex.Msg()); + } + } + + private void ShowImageMask(PaintEventArgs e = null) + { + try + { + if (this.CurObjPosLst.Count > 0 && e != null && this.pictureBox1 != null && this.pictureBox1.BackgroundImage != null) + { + //1. get the padding between the image and the picturebox border + + //get dimensions of the image and the picturebox + double imgWidth = this.pictureBox1.BackgroundImage.Width; + double imgHeight = this.pictureBox1.BackgroundImage.Height; + double boxWidth = this.pictureBox1.Width; + double boxHeight = this.pictureBox1.Height; + + //these variables store the padding between image border and picturebox border + double absX = 0; + double absY = 0; + + //because the sizemode of the picturebox is set to 'zoom', the image is scaled down + double scale = 1; + + //Comparing the aspect ratio of both the control and the image itself. + if (imgWidth / imgHeight > boxWidth / boxHeight) //if the image is p.e. 16:9 and the picturebox is 4:3 + { + scale = boxWidth / imgWidth; //get scale factor + absY = (boxHeight - scale * imgHeight) / 2; //padding on top and below the image + } + else //if the image is p.e. 4:3 and the picturebox is widescreen 16:9 + { + scale = boxHeight / imgHeight; //get scale factor + absX = (boxWidth - scale * imgWidth) / 2; //padding left and right of the image + } + + bool showkey = this.CurObjPosLst.Count > 1; + + foreach (ObjectPosition op in this.CurObjPosLst) + { + if (op != null) + { + //Log("Painting object"); + //2. inputted position values are for the original image size. As the image is probably smaller in the picturebox, the positions must be adapted. + + double xmin = (scale * op.Xmin + this.cam.XOffset) + absX; + double xmax = (scale * op.Xmax) + absX; + double ymin = (scale * op.Ymin + this.cam.YOffset) + absY; + double ymax = (scale * op.Ymax) + absY; + + Color color; + if (op.IsStatic) + { + color = System.Drawing.Color.FromArgb(AppSettings.Settings.RectRelevantColorAlpha, AppSettings.Settings.RectRelevantColor); + } + else + { + color = System.Drawing.Color.FromArgb(AppSettings.Settings.RectIrrelevantColorAlpha, AppSettings.Settings.RectIrrelevantColor); + } + + //3. paint rectangle + System.Drawing.Rectangle rect = new System.Drawing.Rectangle(xmin.ToInt(), ymin.ToInt(), (xmax - xmin).ToInt(), (ymax - ymin).ToInt()); + using (Pen pen = new Pen(color, AppSettings.Settings.RectBorderWidth)) + { + e.Graphics.DrawRectangle(pen, rect); //draw rectangle + } + + //object name text below rectangle + rect = new System.Drawing.Rectangle((xmin - 1).ToInt(), ymax.ToInt(), boxWidth.ToInt(), boxHeight.ToInt()); //sets bounding box for drawn text + + Brush brush = new SolidBrush(color); //sets background rectangle color + //if (AppSettings.Settings.RectDetectionTextBackColor != System.Drawing.Color.Gainsboro) + // brush = new SolidBrush(AppSettings.Settings.RectDetectionTextBackColor); + + Brush forecolor = Brushes.Black; + //if (AppSettings.Settings.RectDetectionTextForeColor != System.Drawing.Color.Gainsboro) + // forecolor = new SolidBrush(AppSettings.Settings.RectDetectionTextForeColor); + + string display = $"{op.Label}"; + + if (showkey) + display += $" ({op.Key})"; + + System.Drawing.SizeF size = e.Graphics.MeasureString(display, new Font(AppSettings.Settings.RectDetectionTextFont, AppSettings.Settings.RectDetectionTextSize)); //finds size of text to draw the background rectangle + e.Graphics.FillRectangle(brush, (xmin - 1).ToInt(), ymax.ToInt(), size.Width, size.Height); //draw grey background rectangle for detection text + e.Graphics.DrawString(display, new Font(AppSettings.Settings.RectDetectionTextFont, AppSettings.Settings.RectDetectionTextSize), forecolor, rect); //draw detection text + } + else + { + //Log("op is empty"); + } + } + } + else + { + //this.pictureBox1 = null; + //this.pictureBox1.BackgroundImage = null; + } + } + catch (Exception ex) + { + Log("Error: " + ex.Msg()); + } + } + + private void FOLV_MaskHistory_SelectionChanged(object sender, EventArgs e) + { + this.CurObjPosLst.Clear(); + + if (this.FOLV_MaskHistory.SelectedObjects != null && this.FOLV_MaskHistory.SelectedObjects.Count > 0) + { + this.contextMenuPosObj = (ObjectPosition)this.FOLV_MaskHistory.SelectedObjects[0]; + this.cam = AITOOL.GetCamera(this.contextMenuPosObj.CameraName); + + this.ShowMaskImage(); + + foreach (object obj in this.FOLV_MaskHistory.SelectedObjects) + { + this.CurObjPosLst.Add((ObjectPosition)obj); + } + } + this.pictureBox1.Refresh(); + } + + private void FOLV_Masks_SelectionChanged(object sender, EventArgs e) + { + this.CurObjPosLst.Clear(); + + if (this.FOLV_Masks.SelectedObjects != null && this.FOLV_Masks.SelectedObjects.Count > 0) + { + this.contextMenuPosObj = (ObjectPosition)this.FOLV_Masks.SelectedObjects[0]; + this.cam = AITOOL.GetCamera(this.contextMenuPosObj.CameraName); + + this.ShowMaskImage(); + + foreach (object obj in this.FOLV_Masks.SelectedObjects) + { + this.CurObjPosLst.Add((ObjectPosition)obj); + } + } + + this.pictureBox1.Refresh(); + } + + private void label3_Click(object sender, EventArgs e) + { + this.Refresh(); + } + + private void pictureBox1_Click(object sender, EventArgs e) + { + + } + + private void pictureBox1_Paint(object sender, PaintEventArgs e) + { + this.ShowImageMask(e); + } + + private void lblClearMasks_Click(object sender, EventArgs e) + { + this.cam.maskManager.MaskedPositions.Clear(); + this.Refresh(); + AppSettings.SaveAsync(true); + } + + private void lblClearHistory_Click(object sender, EventArgs e) + { + this.cam.maskManager.LastPositionsHistory.Clear(); + this.Refresh(); + AppSettings.SaveAsync(true); + } + + private void FOLV_MaskHistory_CellRightClick(object sender, BrightIdeasSoftware.CellRightClickEventArgs e) + { + if (e.Model != null) + { + this.contextMenuPosObj = (ObjectPosition)e.Model; + this.cam = AITOOL.GetCamera(this.contextMenuPosObj.CameraName); + } + } + + private void createStaticMaskToolStripMenuItem_Click(object sender, EventArgs e) + { + + foreach (ObjectPosition op in this.CurObjPosLst) + { + op.IsStatic = true; + op.Counter = 0; + this.cam.maskManager.CreateDynamicMask(op, true); + } + this.Refresh(); + AppSettings.SaveAsync(true); + } + + private void FOLV_Masks_CellRightClick(object sender, BrightIdeasSoftware.CellRightClickEventArgs e) + { + if (e.Model != null) + { + this.contextMenuPosObj = (ObjectPosition)e.Model; + } + } + + private void removeMaskToolStripMenuItem_Click(object sender, EventArgs e) + { + foreach (ObjectPosition op in this.CurObjPosLst) + { + this.cam.maskManager.RemoveActiveMask(op); + } + this.Refresh(); + AppSettings.SaveAsync(true); + } + + private void Frm_DynamicMaskDetails_FormClosing(object sender, FormClosingEventArgs e) + { + Global_GUI.SaveWindowState(this); + } + + private void FOLV_MaskHistory_FormatRow(object sender, BrightIdeasSoftware.FormatRowEventArgs e) + { + this.FormatRow(sender, e); + } + + private void FormatRow(object Sender, BrightIdeasSoftware.FormatRowEventArgs e) + { + try + { + ObjectPosition OP = (ObjectPosition)e.Model; + + // If SPI IsNot Nothing Then + if (OP.IsStatic && e.Item.ForeColor != Color.Blue) + e.Item.ForeColor = Color.Blue; + else if (!OP.IsStatic && e.Item.ForeColor != Color.Black) + e.Item.ForeColor = Color.Black; + } + + + + catch (Exception) + { + } + // Log("Error: " & ex.Msg()) + finally + { + } + } + + private void FOLV_Masks_FormatRow(object sender, BrightIdeasSoftware.FormatRowEventArgs e) + { + this.FormatRow(sender, e); + } + + private void createStaticMaskToolStripMenuItem1_Click(object sender, EventArgs e) + { + foreach (ObjectPosition op in this.CurObjPosLst) + { + op.IsStatic = true; + op.Counter = 0; + } + this.Refresh(); + AppSettings.SaveAsync(true); + } + + private void FOLV_MaskHistory_SelectedIndexChanged(object sender, EventArgs e) + { + + } + + private void comboBox_filter_camera_SelectedIndexChanged(object sender, EventArgs e) + { + + + } + + private void comboBox_filter_camera_SelectionChangeCommitted(object sender, EventArgs e) + { + //I think this event only triggers when user picks something NOT when items are initially added to combobox + if (!(this.comboBox_filter_camera.Text == "All Cameras")) + { + this.BtnDynamicMaskingSettings.Enabled = true; + this.cam = AITOOL.GetCamera(this.comboBox_filter_camera.Text); + } + else + { + this.BtnDynamicMaskingSettings.Enabled = false; + } + + this.Refresh(); + } + + private void BtnDynamicMaskingSettings_Click(object sender, EventArgs e) + { + using (Frm_DynamicMasking frm = new Frm_DynamicMasking()) + { + frm.cam = this.cam; + frm.Text = "Dynamic Masking Settings - " + this.cam.Name; + + //Camera cam = AITOOL.GetCamera(list2.SelectedItems[0].Text); + + //Merge ClassObject's code + frm.num_history_mins.Value = this.cam.maskManager.HistorySaveMins;//load minutes to retain history objects that have yet to become masks + frm.num_mask_create.Value = this.cam.maskManager.HistoryThresholdCount; // load mask create counter + frm.num_mask_remove.Value = this.cam.maskManager.MaskRemoveMins; //load mask remove counter + frm.numMaskThreshold.Value = this.cam.maskManager.MaskRemoveThreshold; + frm.num_max_unused.Value = this.cam.maskManager.MaxMaskUnusedDays; + + //frm.num_percent_var.Value = (decimal)cam.maskManager.thresholdPercent * 100; + frm.num_percent_var.Value = (decimal)this.cam.maskManager.PercentMatch; + + frm.cb_enabled.Checked = this.cam.maskManager.MaskingEnabled; + + //frm.tb_objects.Text = this.cam.maskManager.Objects; + + if (frm.ShowDialog() == DialogResult.OK) + { + ////get masking values from textboxes + this.cam.maskManager.HistorySaveMins = frm.num_history_mins.Text.ToInt(); + this.cam.maskManager.HistoryThresholdCount = frm.num_mask_create.Text.ToInt(); + this.cam.maskManager.MaskRemoveMins = frm.num_mask_remove.Text.ToInt(); + this.cam.maskManager.MaskRemoveThreshold = frm.numMaskThreshold.Text.ToInt(); + this.cam.maskManager.PercentMatch = frm.num_percent_var.Text.ToDouble(); + this.cam.maskManager.MaxMaskUnusedDays = frm.num_max_unused.Text.ToInt(); + this.cam.maskManager.MaskingEnabled = frm.cb_enabled.Checked; + + AppSettings.SaveAsync(true); + + } + } + } + + private void staticMaskMenu_Opening(object sender, CancelEventArgs e) + { + + } + + private void createDynamicMaskTemporaryToolStripMenuItem_Click(object sender, EventArgs e) + { + foreach (ObjectPosition op in this.CurObjPosLst) + { + this.cam.maskManager.CreateDynamicMask(op, false, true); + } + this.Refresh(); + AppSettings.SaveAsync(true); + } + + private void removeHistoryMaskToolStripMenuItem_Click(object sender, EventArgs e) + { + foreach (ObjectPosition op in this.CurObjPosLst) + { + this.cam.maskManager.RemoveHistoryMask(op); + + } + this.Refresh(); + AppSettings.SaveAsync(true); + } + } +} diff --git a/src/UI/Frm_DynamicMaskDetails.resx b/src/UI/Frm_DynamicMaskDetails.resx new file mode 100644 index 00000000..36f2a70b --- /dev/null +++ b/src/UI/Frm_DynamicMaskDetails.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + 157, 17 + + \ No newline at end of file diff --git a/src/UI/Frm_DynamicMasking.Designer.cs b/src/UI/Frm_DynamicMasking.Designer.cs new file mode 100644 index 00000000..d7871009 --- /dev/null +++ b/src/UI/Frm_DynamicMasking.Designer.cs @@ -0,0 +1,522 @@ +namespace AITool +{ + partial class Frm_DynamicMasking + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.btnSave = new System.Windows.Forms.Button(); + this.btnCancel = new System.Windows.Forms.Button(); + this.tableLayoutAdvancedMasking = new System.Windows.Forms.TableLayoutPanel(); + this.label4 = new System.Windows.Forms.Label(); + this.label16 = new System.Windows.Forms.Label(); + this.label17 = new System.Windows.Forms.Label(); + this.label18 = new System.Windows.Forms.Label(); + this.num_history_mins = new System.Windows.Forms.NumericUpDown(); + this.num_mask_create = new System.Windows.Forms.NumericUpDown(); + this.label20 = new System.Windows.Forms.Label(); + this.num_mask_remove = new System.Windows.Forms.NumericUpDown(); + this.label22 = new System.Windows.Forms.Label(); + this.num_percent_var = new System.Windows.Forms.NumericUpDown(); + this.label19 = new System.Windows.Forms.Label(); + this.flowLayoutPanel2 = new System.Windows.Forms.FlowLayoutPanel(); + this.label21 = new System.Windows.Forms.Label(); + this.numMaskThreshold = new System.Windows.Forms.NumericUpDown(); + this.label2 = new System.Windows.Forms.Label(); + this.flowLayoutPanel1 = new System.Windows.Forms.FlowLayoutPanel(); + this.label23 = new System.Windows.Forms.Label(); + this.btnAdvanced = new System.Windows.Forms.Button(); + this.label3 = new System.Windows.Forms.Label(); + this.num_max_unused = new System.Windows.Forms.NumericUpDown(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.cb_enabled = new System.Windows.Forms.CheckBox(); + this.toolTip1 = new System.Windows.Forms.ToolTip(this.components); + this.linkLabel1 = new System.Windows.Forms.LinkLabel(); + this.lbl_objects = new System.Windows.Forms.Label(); + this.tableLayoutAdvancedMasking.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.num_history_mins)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.num_mask_create)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.num_mask_remove)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.num_percent_var)).BeginInit(); + this.flowLayoutPanel2.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.numMaskThreshold)).BeginInit(); + this.flowLayoutPanel1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.num_max_unused)).BeginInit(); + this.groupBox1.SuspendLayout(); + this.SuspendLayout(); + // + // btnSave + // + this.btnSave.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.btnSave.Location = new System.Drawing.Point(649, 289); + this.btnSave.Margin = new System.Windows.Forms.Padding(4); + this.btnSave.Name = "btnSave"; + this.btnSave.Size = new System.Drawing.Size(70, 30); + this.btnSave.TabIndex = 9; + this.btnSave.Text = "OK"; + this.btnSave.UseVisualStyleBackColor = true; + this.btnSave.Click += new System.EventHandler(this.btnSave_Click); + // + // btnCancel + // + this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.btnCancel.Location = new System.Drawing.Point(727, 289); + this.btnCancel.Margin = new System.Windows.Forms.Padding(4); + this.btnCancel.Name = "btnCancel"; + this.btnCancel.Size = new System.Drawing.Size(70, 30); + this.btnCancel.TabIndex = 10; + this.btnCancel.Text = "Cancel"; + this.btnCancel.UseVisualStyleBackColor = true; + // + // tableLayoutAdvancedMasking + // + this.tableLayoutAdvancedMasking.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tableLayoutAdvancedMasking.ColumnCount = 3; + this.tableLayoutAdvancedMasking.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 14.81959F)); + this.tableLayoutAdvancedMasking.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 7.603093F)); + this.tableLayoutAdvancedMasking.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 77.57732F)); + this.tableLayoutAdvancedMasking.Controls.Add(this.label4, 2, 4); + this.tableLayoutAdvancedMasking.Controls.Add(this.label16, 0, 2); + this.tableLayoutAdvancedMasking.Controls.Add(this.label17, 0, 0); + this.tableLayoutAdvancedMasking.Controls.Add(this.label18, 0, 1); + this.tableLayoutAdvancedMasking.Controls.Add(this.num_history_mins, 1, 0); + this.tableLayoutAdvancedMasking.Controls.Add(this.num_mask_create, 1, 1); + this.tableLayoutAdvancedMasking.Controls.Add(this.label20, 2, 0); + this.tableLayoutAdvancedMasking.Controls.Add(this.num_mask_remove, 1, 2); + this.tableLayoutAdvancedMasking.Controls.Add(this.label22, 0, 3); + this.tableLayoutAdvancedMasking.Controls.Add(this.num_percent_var, 1, 3); + this.tableLayoutAdvancedMasking.Controls.Add(this.label19, 2, 1); + this.tableLayoutAdvancedMasking.Controls.Add(this.flowLayoutPanel2, 2, 2); + this.tableLayoutAdvancedMasking.Controls.Add(this.flowLayoutPanel1, 2, 3); + this.tableLayoutAdvancedMasking.Controls.Add(this.label3, 0, 4); + this.tableLayoutAdvancedMasking.Controls.Add(this.num_max_unused, 1, 4); + this.tableLayoutAdvancedMasking.Location = new System.Drawing.Point(4, 24); + this.tableLayoutAdvancedMasking.Margin = new System.Windows.Forms.Padding(0); + this.tableLayoutAdvancedMasking.Name = "tableLayoutAdvancedMasking"; + this.tableLayoutAdvancedMasking.RowCount = 5; + this.tableLayoutAdvancedMasking.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 20F)); + this.tableLayoutAdvancedMasking.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 20F)); + this.tableLayoutAdvancedMasking.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 20F)); + this.tableLayoutAdvancedMasking.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 20F)); + this.tableLayoutAdvancedMasking.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 20F)); + this.tableLayoutAdvancedMasking.Size = new System.Drawing.Size(776, 212); + this.tableLayoutAdvancedMasking.TabIndex = 20; + // + // label4 + // + this.label4.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(175, 185); + this.label4.Margin = new System.Windows.Forms.Padding(1, 4, 0, 0); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(198, 13); + this.label4.TabIndex = 12; + this.label4.Text = "days if static mask has not been used"; + // + // label16 + // + this.label16.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.label16.AutoSize = true; + this.label16.Location = new System.Drawing.Point(5, 98); + this.label16.Margin = new System.Windows.Forms.Padding(5, 0, 0, 0); + this.label16.Name = "label16"; + this.label16.Size = new System.Drawing.Size(106, 13); + this.label16.TabIndex = 33; + this.label16.Text = "Remove mask after "; + // + // label17 + // + this.label17.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.label17.AutoSize = true; + this.label17.Location = new System.Drawing.Point(5, 14); + this.label17.Margin = new System.Windows.Forms.Padding(5, 0, 0, 0); + this.label17.Name = "label17"; + this.label17.Size = new System.Drawing.Size(84, 13); + this.label17.TabIndex = 34; + this.label17.Text = "Clear history in"; + // + // label18 + // + this.label18.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.label18.AutoSize = true; + this.label18.Location = new System.Drawing.Point(5, 55); + this.label18.Margin = new System.Windows.Forms.Padding(5, 0, 0, 0); + this.label18.Name = "label18"; + this.label18.Size = new System.Drawing.Size(99, 15); + this.label18.TabIndex = 22; + this.label18.Text = "Create mask after"; + // + // num_history_mins + // + this.num_history_mins.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.num_history_mins.Location = new System.Drawing.Point(119, 10); + this.num_history_mins.Margin = new System.Windows.Forms.Padding(4); + this.num_history_mins.Maximum = new decimal(new int[] { + 300, + 0, + 0, + 0}); + this.num_history_mins.Minimum = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.num_history_mins.Name = "num_history_mins"; + this.num_history_mins.Size = new System.Drawing.Size(49, 22); + this.num_history_mins.TabIndex = 0; + this.num_history_mins.Value = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.num_history_mins.ValueChanged += new System.EventHandler(this.num_history_mins_ValueChanged); + // + // num_mask_create + // + this.num_mask_create.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.num_mask_create.Location = new System.Drawing.Point(119, 52); + this.num_mask_create.Margin = new System.Windows.Forms.Padding(4); + this.num_mask_create.Maximum = new decimal(new int[] { + 20, + 0, + 0, + 0}); + this.num_mask_create.Minimum = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.num_mask_create.Name = "num_mask_create"; + this.num_mask_create.Size = new System.Drawing.Size(49, 22); + this.num_mask_create.TabIndex = 1; + this.num_mask_create.Value = new decimal(new int[] { + 1, + 0, + 0, + 0}); + // + // label20 + // + this.label20.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.label20.AutoSize = true; + this.label20.Location = new System.Drawing.Point(175, 14); + this.label20.Margin = new System.Windows.Forms.Padding(1, 0, 2, 0); + this.label20.Name = "label20"; + this.label20.Size = new System.Drawing.Size(307, 13); + this.label20.TabIndex = 9; + this.label20.Text = "minute(s). Clears list of objects detected in same location "; + // + // num_mask_remove + // + this.num_mask_remove.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.num_mask_remove.Location = new System.Drawing.Point(119, 94); + this.num_mask_remove.Margin = new System.Windows.Forms.Padding(4); + this.num_mask_remove.Maximum = new decimal(new int[] { + 300, + 0, + 0, + 0}); + this.num_mask_remove.Minimum = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.num_mask_remove.Name = "num_mask_remove"; + this.num_mask_remove.Size = new System.Drawing.Size(49, 22); + this.num_mask_remove.TabIndex = 2; + this.num_mask_remove.Value = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.num_mask_remove.ValueChanged += new System.EventHandler(this.num_mask_remove_ValueChanged); + // + // label22 + // + this.label22.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.label22.AutoSize = true; + this.label22.Location = new System.Drawing.Point(5, 140); + this.label22.Margin = new System.Windows.Forms.Padding(5, 0, 0, 0); + this.label22.Name = "label22"; + this.label22.Size = new System.Drawing.Size(79, 13); + this.label22.TabIndex = 12; + this.label22.Text = "Percent match"; + // + // num_percent_var + // + this.num_percent_var.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.num_percent_var.Location = new System.Drawing.Point(119, 136); + this.num_percent_var.Margin = new System.Windows.Forms.Padding(4); + this.num_percent_var.Minimum = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.num_percent_var.Name = "num_percent_var"; + this.num_percent_var.Size = new System.Drawing.Size(49, 22); + this.num_percent_var.TabIndex = 4; + this.num_percent_var.Value = new decimal(new int[] { + 1, + 0, + 0, + 0}); + // + // label19 + // + this.label19.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.label19.AutoSize = true; + this.label19.Location = new System.Drawing.Point(175, 56); + this.label19.Margin = new System.Windows.Forms.Padding(1, 0, 2, 0); + this.label19.Name = "label19"; + this.label19.Size = new System.Drawing.Size(354, 13); + this.label19.TabIndex = 8; + this.label19.Text = "detection(s). Number of history detections needed to create a mask"; + this.label19.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // flowLayoutPanel2 + // + this.flowLayoutPanel2.Controls.Add(this.label21); + this.flowLayoutPanel2.Controls.Add(this.numMaskThreshold); + this.flowLayoutPanel2.Controls.Add(this.label2); + this.flowLayoutPanel2.Location = new System.Drawing.Point(174, 84); + this.flowLayoutPanel2.Margin = new System.Windows.Forms.Padding(0); + this.flowLayoutPanel2.Name = "flowLayoutPanel2"; + this.flowLayoutPanel2.Size = new System.Drawing.Size(602, 42); + this.flowLayoutPanel2.TabIndex = 15; + // + // label21 + // + this.label21.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.label21.AutoSize = true; + this.label21.Location = new System.Drawing.Point(1, 13); + this.label21.Margin = new System.Windows.Forms.Padding(1, 4, 0, 0); + this.label21.Name = "label21"; + this.label21.Size = new System.Drawing.Size(212, 13); + this.label21.TabIndex = 12; + this.label21.Text = "minute(s) if object not visible in the last "; + // + // numMaskThreshold + // + this.numMaskThreshold.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.numMaskThreshold.Location = new System.Drawing.Point(214, 10); + this.numMaskThreshold.Margin = new System.Windows.Forms.Padding(1, 10, 0, 4); + this.numMaskThreshold.Maximum = new decimal(new int[] { + 300, + 0, + 0, + 0}); + this.numMaskThreshold.Name = "numMaskThreshold"; + this.numMaskThreshold.Size = new System.Drawing.Size(49, 22); + this.numMaskThreshold.TabIndex = 3; + this.numMaskThreshold.Value = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.numMaskThreshold.Leave += new System.EventHandler(this.numMaskThreshold_Leave); + // + // label2 + // + this.label2.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(267, 13); + this.label2.Margin = new System.Windows.Forms.Padding(4, 4, 0, 0); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(61, 13); + this.label2.TabIndex = 14; + this.label2.Text = "detections"; + // + // flowLayoutPanel1 + // + this.flowLayoutPanel1.Controls.Add(this.label23); + this.flowLayoutPanel1.Controls.Add(this.btnAdvanced); + this.flowLayoutPanel1.Location = new System.Drawing.Point(175, 126); + this.flowLayoutPanel1.Margin = new System.Windows.Forms.Padding(1, 0, 5, 0); + this.flowLayoutPanel1.Name = "flowLayoutPanel1"; + this.flowLayoutPanel1.Size = new System.Drawing.Size(596, 42); + this.flowLayoutPanel1.TabIndex = 14; + // + // label23 + // + this.label23.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.label23.AutoSize = true; + this.label23.Location = new System.Drawing.Point(1, 13); + this.label23.Margin = new System.Windows.Forms.Padding(1, 0, 4, 0); + this.label23.Name = "label23"; + this.label23.Size = new System.Drawing.Size(141, 13); + this.label23.TabIndex = 15; + this.label23.Text = "% to be considered equal."; + // + // btnAdvanced + // + this.btnAdvanced.Location = new System.Drawing.Point(151, 5); + this.btnAdvanced.Margin = new System.Windows.Forms.Padding(5); + this.btnAdvanced.Name = "btnAdvanced"; + this.btnAdvanced.Size = new System.Drawing.Size(70, 29); + this.btnAdvanced.TabIndex = 5; + this.btnAdvanced.Text = "Advanced"; + this.btnAdvanced.UseVisualStyleBackColor = true; + this.btnAdvanced.Click += new System.EventHandler(this.btnAdvanced_Click); + // + // label3 + // + this.label3.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(5, 183); + this.label3.Margin = new System.Windows.Forms.Padding(5, 0, 0, 0); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(106, 13); + this.label3.TabIndex = 36; + this.label3.Text = "Remove mask after "; + // + // num_max_unused + // + this.num_max_unused.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.num_max_unused.Location = new System.Drawing.Point(118, 179); + this.num_max_unused.Name = "num_max_unused"; + this.num_max_unused.Size = new System.Drawing.Size(49, 22); + this.num_max_unused.TabIndex = 6; + this.num_max_unused.Value = new decimal(new int[] { + 60, + 0, + 0, + 0}); + this.num_max_unused.Leave += new System.EventHandler(this.num_max_unused_Leave); + // + // groupBox1 + // + this.groupBox1.Controls.Add(this.tableLayoutAdvancedMasking); + this.groupBox1.Location = new System.Drawing.Point(12, 2); + this.groupBox1.Margin = new System.Windows.Forms.Padding(4); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.Padding = new System.Windows.Forms.Padding(4); + this.groupBox1.Size = new System.Drawing.Size(784, 248); + this.groupBox1.TabIndex = 21; + this.groupBox1.TabStop = false; + // + // cb_enabled + // + this.cb_enabled.AutoSize = true; + this.cb_enabled.Location = new System.Drawing.Point(16, 289); + this.cb_enabled.Margin = new System.Windows.Forms.Padding(4); + this.cb_enabled.Name = "cb_enabled"; + this.cb_enabled.Size = new System.Drawing.Size(68, 19); + this.cb_enabled.TabIndex = 8; + this.cb_enabled.Text = "Enabled"; + this.cb_enabled.UseVisualStyleBackColor = true; + // + // linkLabel1 + // + this.linkLabel1.AutoSize = true; + this.linkLabel1.Location = new System.Drawing.Point(9, 254); + this.linkLabel1.Name = "linkLabel1"; + this.linkLabel1.Size = new System.Drawing.Size(102, 13); + this.linkLabel1.TabIndex = 7; + this.linkLabel1.TabStop = true; + this.linkLabel1.Text = "Triggering Objects"; + this.linkLabel1.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.linkLabel1_LinkClicked); + // + // lbl_objects + // + this.lbl_objects.AutoSize = true; + this.lbl_objects.Location = new System.Drawing.Point(118, 256); + this.lbl_objects.Name = "lbl_objects"; + this.lbl_objects.Size = new System.Drawing.Size(10, 13); + this.lbl_objects.TabIndex = 23; + this.lbl_objects.Text = "."; + // + // Frm_DynamicMasking + // + this.AcceptButton = this.btnSave; + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Inherit; + this.CancelButton = this.btnCancel; + this.ClientSize = new System.Drawing.Size(807, 332); + this.Controls.Add(this.lbl_objects); + this.Controls.Add(this.linkLabel1); + this.Controls.Add(this.cb_enabled); + this.Controls.Add(this.groupBox1); + this.Controls.Add(this.btnCancel); + this.Controls.Add(this.btnSave); + this.Font = new System.Drawing.Font("Segoe UI", 8.25F); + this.Margin = new System.Windows.Forms.Padding(4); + this.Name = "Frm_DynamicMasking"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Dynamic Masking"; + this.Load += new System.EventHandler(this.Frm_DynamicMasking_Load); + this.tableLayoutAdvancedMasking.ResumeLayout(false); + this.tableLayoutAdvancedMasking.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.num_history_mins)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.num_mask_create)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.num_mask_remove)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.num_percent_var)).EndInit(); + this.flowLayoutPanel2.ResumeLayout(false); + this.flowLayoutPanel2.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.numMaskThreshold)).EndInit(); + this.flowLayoutPanel1.ResumeLayout(false); + this.flowLayoutPanel1.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.num_max_unused)).EndInit(); + this.groupBox1.ResumeLayout(false); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Button btnSave; + private System.Windows.Forms.Button btnCancel; + private System.Windows.Forms.TableLayoutPanel tableLayoutAdvancedMasking; + private System.Windows.Forms.Label label16; + private System.Windows.Forms.Label label17; + private System.Windows.Forms.Label label18; + private System.Windows.Forms.Label label19; + private System.Windows.Forms.Label label20; + private System.Windows.Forms.Label label22; + public System.Windows.Forms.NumericUpDown num_history_mins; + public System.Windows.Forms.NumericUpDown num_mask_create; + public System.Windows.Forms.NumericUpDown num_mask_remove; + public System.Windows.Forms.NumericUpDown num_percent_var; + private System.Windows.Forms.GroupBox groupBox1; + public System.Windows.Forms.CheckBox cb_enabled; + private System.Windows.Forms.ToolTip toolTip1; + private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel1; + private System.Windows.Forms.Label label23; + private System.Windows.Forms.Button btnAdvanced; + private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel2; + private System.Windows.Forms.Label label21; + public System.Windows.Forms.NumericUpDown numMaskThreshold; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.Label label4; + public System.Windows.Forms.NumericUpDown num_max_unused; + private System.Windows.Forms.LinkLabel linkLabel1; + public System.Windows.Forms.Label lbl_objects; + } +} \ No newline at end of file diff --git a/src/UI/Frm_DynamicMasking.cs b/src/UI/Frm_DynamicMasking.cs new file mode 100644 index 00000000..ca16fe72 --- /dev/null +++ b/src/UI/Frm_DynamicMasking.cs @@ -0,0 +1,108 @@ +using System; +using System.Windows.Forms; + +namespace AITool +{ + public partial class Frm_DynamicMasking : Form + { + public Camera cam; + + public Frm_DynamicMasking() + { + this.InitializeComponent(); + } + + private void Frm_DynamicMasking_Load(object sender, EventArgs e) + { + //CenterToParent(); + } + + + private void num_history_mins_Leave(object sender, EventArgs e) + { + this.num_history_mins.Text = this.num_history_mins.Value.ToString(); + } + + private void num_mask_create_Leave(object sender, EventArgs e) + { + this.num_mask_create.Text = this.num_mask_create.Value.ToString(); + } + + private void num_mask_remove_Leave(object sender, EventArgs e) + { + this.num_mask_remove.Text = this.num_mask_remove.Value.ToString(); + } + + private void num_percent_var_Leave(object sender, EventArgs e) + { + this.num_percent_var.Text = this.num_percent_var.Value.ToString(); + } + private void numMaskThreshold_Leave(object sender, EventArgs e) + { + this.numMaskThreshold.Text = this.numMaskThreshold.Value.ToString(); + } + + private void btnSave_Click(object sender, EventArgs e) + { + this.DialogResult = DialogResult.OK; + this.Close(); + } + + private void btnAdvanced_Click(object sender, EventArgs e) + { + using (Frm_DynamicMaskingAdvanced frm = new Frm_DynamicMaskingAdvanced()) + { + frm.cbEnableScaling.Checked = this.cam.maskManager.ScaleConfig.IsScaledObject; + frm.numSmallObjMax.Value = this.cam.maskManager.ScaleConfig.SmallObjectMaxPercent; + frm.numSmallObjPercent.Value = this.cam.maskManager.ScaleConfig.SmallObjectMatchPercent; + frm.numMidObjMin.Value = this.cam.maskManager.ScaleConfig.MediumObjectMinPercent; + frm.numMidObjMax.Value = this.cam.maskManager.ScaleConfig.MediumObjectMaxPercent; + frm.numMidObjPercent.Value = this.cam.maskManager.ScaleConfig.MediumObjectMatchPercent; + + if (frm.ShowDialog() == DialogResult.OK) + { + Int32.TryParse(frm.numSmallObjMax.Text, out int smallObjMax); + Int32.TryParse(frm.numSmallObjPercent.Text, out int smallObjPercent); + Int32.TryParse(frm.numMidObjMin.Text, out int midObjMin); + Int32.TryParse(frm.numMidObjMax.Text, out int midObjMax); + Int32.TryParse(frm.numMidObjPercent.Text, out int midObjPercent); + + this.cam.maskManager.ScaleConfig.IsScaledObject = frm.cbEnableScaling.Checked; + + this.cam.maskManager.ScaleConfig.SmallObjectMaxPercent = smallObjMax; + this.cam.maskManager.ScaleConfig.SmallObjectMatchPercent = smallObjPercent; + this.cam.maskManager.ScaleConfig.MediumObjectMinPercent = midObjMin; + this.cam.maskManager.ScaleConfig.MediumObjectMaxPercent = midObjMax; + this.cam.maskManager.ScaleConfig.MediumObjectMatchPercent = midObjPercent; + + AppSettings.SaveAsync(true); + } + } + + } + + private void num_mask_remove_ValueChanged(object sender, EventArgs e) + { + + } + + private void num_max_unused_Leave(object sender, EventArgs e) + { + this.num_max_unused.Text = this.num_max_unused.Value.ToString(); + } + + private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) + { + using (Frm_RelevantObjects frm = new Frm_RelevantObjects()) + { + frm.ROMName = $"{cam.Name}\\{cam.maskManager.MaskTriggeringObjects.TypeName}"; + frm.ShowDialog(this); + } + } + + private void num_history_mins_ValueChanged(object sender, EventArgs e) + { + + } + } +} diff --git a/src/UI/Frm_DynamicMasking.resx b/src/UI/Frm_DynamicMasking.resx new file mode 100644 index 00000000..df8339b6 --- /dev/null +++ b/src/UI/Frm_DynamicMasking.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + \ No newline at end of file diff --git a/src/UI/Frm_DynamicMaskingAdvanced.Designer.cs b/src/UI/Frm_DynamicMaskingAdvanced.Designer.cs new file mode 100644 index 00000000..eace6117 --- /dev/null +++ b/src/UI/Frm_DynamicMaskingAdvanced.Designer.cs @@ -0,0 +1,467 @@ +namespace AITool +{ + partial class Frm_DynamicMaskingAdvanced + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.flowLayoutButtons = new System.Windows.Forms.FlowLayoutPanel(); + this.btnCancel = new System.Windows.Forms.Button(); + this.btnOk = new System.Windows.Forms.Button(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.flowLayoutSmallObjects = new System.Windows.Forms.FlowLayoutPanel(); + this.numericUpDown2 = new System.Windows.Forms.NumericUpDown(); + this.label3 = new System.Windows.Forms.Label(); + this.label1 = new System.Windows.Forms.Label(); + this.numSmallObjMax = new System.Windows.Forms.NumericUpDown(); + this.label2 = new System.Windows.Forms.Label(); + this.numSmallObjPercent = new System.Windows.Forms.NumericUpDown(); + this.label11 = new System.Windows.Forms.Label(); + this.flowLayoutMidObjects = new System.Windows.Forms.FlowLayoutPanel(); + this.numericUpDown1 = new System.Windows.Forms.NumericUpDown(); + this.label12 = new System.Windows.Forms.Label(); + this.label7 = new System.Windows.Forms.Label(); + this.numMidObjMin = new System.Windows.Forms.NumericUpDown(); + this.label10 = new System.Windows.Forms.Label(); + this.numMidObjMax = new System.Windows.Forms.NumericUpDown(); + this.label6 = new System.Windows.Forms.Label(); + this.numMidObjPercent = new System.Windows.Forms.NumericUpDown(); + this.label5 = new System.Windows.Forms.Label(); + this.label4 = new System.Windows.Forms.Label(); + this.cbEnableScaling = new System.Windows.Forms.CheckBox(); + this.groupBox2 = new System.Windows.Forms.GroupBox(); + this.flowLayoutButtons.SuspendLayout(); + this.groupBox1.SuspendLayout(); + this.flowLayoutSmallObjects.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.numericUpDown2)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.numSmallObjMax)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.numSmallObjPercent)).BeginInit(); + this.flowLayoutMidObjects.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.numMidObjMin)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.numMidObjMax)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.numMidObjPercent)).BeginInit(); + this.groupBox2.SuspendLayout(); + this.SuspendLayout(); + // + // flowLayoutButtons + // + this.flowLayoutButtons.Controls.Add(this.btnCancel); + this.flowLayoutButtons.Controls.Add(this.btnOk); + this.flowLayoutButtons.FlowDirection = System.Windows.Forms.FlowDirection.RightToLeft; + this.flowLayoutButtons.Location = new System.Drawing.Point(25, 307); + this.flowLayoutButtons.Margin = new System.Windows.Forms.Padding(6); + this.flowLayoutButtons.Name = "flowLayoutButtons"; + this.flowLayoutButtons.Size = new System.Drawing.Size(798, 44); + this.flowLayoutButtons.TabIndex = 1; + // + // btnCancel + // + this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.btnCancel.Location = new System.Drawing.Point(722, 6); + this.btnCancel.Margin = new System.Windows.Forms.Padding(6); + this.btnCancel.Name = "btnCancel"; + this.btnCancel.Size = new System.Drawing.Size(70, 30); + this.btnCancel.TabIndex = 7; + this.btnCancel.Text = "Cancel"; + this.btnCancel.UseVisualStyleBackColor = true; + // + // btnOk + // + this.btnOk.DialogResult = System.Windows.Forms.DialogResult.OK; + this.btnOk.Location = new System.Drawing.Point(640, 6); + this.btnOk.Margin = new System.Windows.Forms.Padding(6); + this.btnOk.Name = "btnOk"; + this.btnOk.Size = new System.Drawing.Size(70, 30); + this.btnOk.TabIndex = 6; + this.btnOk.Text = "OK"; + this.btnOk.UseVisualStyleBackColor = true; + // + // groupBox1 + // + this.groupBox1.Controls.Add(this.flowLayoutSmallObjects); + this.groupBox1.Location = new System.Drawing.Point(25, 76); + this.groupBox1.Margin = new System.Windows.Forms.Padding(6); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.Padding = new System.Windows.Forms.Padding(6); + this.groupBox1.Size = new System.Drawing.Size(795, 109); + this.groupBox1.TabIndex = 11; + this.groupBox1.TabStop = false; + this.groupBox1.Text = "Small Objects"; + // + // flowLayoutSmallObjects + // + this.flowLayoutSmallObjects.Controls.Add(this.numericUpDown2); + this.flowLayoutSmallObjects.Controls.Add(this.label3); + this.flowLayoutSmallObjects.Controls.Add(this.label1); + this.flowLayoutSmallObjects.Controls.Add(this.numSmallObjMax); + this.flowLayoutSmallObjects.Controls.Add(this.label2); + this.flowLayoutSmallObjects.Controls.Add(this.numSmallObjPercent); + this.flowLayoutSmallObjects.Controls.Add(this.label11); + this.flowLayoutSmallObjects.Location = new System.Drawing.Point(17, 35); + this.flowLayoutSmallObjects.Margin = new System.Windows.Forms.Padding(6); + this.flowLayoutSmallObjects.Name = "flowLayoutSmallObjects"; + this.flowLayoutSmallObjects.Size = new System.Drawing.Size(764, 44); + this.flowLayoutSmallObjects.TabIndex = 38; + // + // numericUpDown2 + // + this.numericUpDown2.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.numericUpDown2.Location = new System.Drawing.Point(6, 6); + this.numericUpDown2.Margin = new System.Windows.Forms.Padding(6); + this.numericUpDown2.Name = "numericUpDown2"; + this.numericUpDown2.Size = new System.Drawing.Size(0, 22); + this.numericUpDown2.TabIndex = 10; + // + // label3 + // + this.label3.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(12, 10); + this.label3.Margin = new System.Windows.Forms.Padding(0); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(0, 13); + this.label3.TabIndex = 12; + // + // label1 + // + this.label1.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(12, 10); + this.label1.Margin = new System.Windows.Forms.Padding(0); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(109, 13); + this.label1.TabIndex = 21; + this.label1.Text = "If object is less than"; + // + // numSmallObjMax + // + this.numSmallObjMax.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.numSmallObjMax.Location = new System.Drawing.Point(121, 6); + this.numSmallObjMax.Margin = new System.Windows.Forms.Padding(0, 6, 0, 6); + this.numSmallObjMax.Maximum = new decimal(new int[] { + 50, + 0, + 0, + 0}); + this.numSmallObjMax.Minimum = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.numSmallObjMax.Name = "numSmallObjMax"; + this.numSmallObjMax.Size = new System.Drawing.Size(40, 22); + this.numSmallObjMax.TabIndex = 1; + this.numSmallObjMax.Value = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.numSmallObjMax.ValueChanged += new System.EventHandler(this.numSmallObjMax_ValueChanged); + this.numSmallObjMax.Leave += new System.EventHandler(this.numSmallObjMax_Leave); + // + // label2 + // + this.label2.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(161, 10); + this.label2.Margin = new System.Windows.Forms.Padding(0); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(172, 13); + this.label2.TabIndex = 27; + this.label2.Text = "% of image set percent match to"; + // + // numSmallObjPercent + // + this.numSmallObjPercent.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.numSmallObjPercent.Location = new System.Drawing.Point(333, 6); + this.numSmallObjPercent.Margin = new System.Windows.Forms.Padding(0, 6, 0, 6); + this.numSmallObjPercent.Minimum = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.numSmallObjPercent.Name = "numSmallObjPercent"; + this.numSmallObjPercent.Size = new System.Drawing.Size(40, 22); + this.numSmallObjPercent.TabIndex = 2; + this.numSmallObjPercent.Value = new decimal(new int[] { + 5, + 0, + 0, + 0}); + this.numSmallObjPercent.Leave += new System.EventHandler(this.numSmallObjPercent_Leave); + // + // label11 + // + this.label11.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.label11.AutoSize = true; + this.label11.Location = new System.Drawing.Point(373, 10); + this.label11.Margin = new System.Windows.Forms.Padding(0); + this.label11.Name = "label11"; + this.label11.Size = new System.Drawing.Size(16, 13); + this.label11.TabIndex = 28; + this.label11.Text = "%"; + // + // flowLayoutMidObjects + // + this.flowLayoutMidObjects.Controls.Add(this.numericUpDown1); + this.flowLayoutMidObjects.Controls.Add(this.label12); + this.flowLayoutMidObjects.Controls.Add(this.label7); + this.flowLayoutMidObjects.Controls.Add(this.numMidObjMin); + this.flowLayoutMidObjects.Controls.Add(this.label10); + this.flowLayoutMidObjects.Controls.Add(this.numMidObjMax); + this.flowLayoutMidObjects.Controls.Add(this.label6); + this.flowLayoutMidObjects.Controls.Add(this.numMidObjPercent); + this.flowLayoutMidObjects.Controls.Add(this.label5); + this.flowLayoutMidObjects.Location = new System.Drawing.Point(17, 35); + this.flowLayoutMidObjects.Margin = new System.Windows.Forms.Padding(6); + this.flowLayoutMidObjects.Name = "flowLayoutMidObjects"; + this.flowLayoutMidObjects.Size = new System.Drawing.Size(764, 44); + this.flowLayoutMidObjects.TabIndex = 39; + // + // numericUpDown1 + // + this.numericUpDown1.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.numericUpDown1.Location = new System.Drawing.Point(6, 6); + this.numericUpDown1.Margin = new System.Windows.Forms.Padding(6); + this.numericUpDown1.Name = "numericUpDown1"; + this.numericUpDown1.Size = new System.Drawing.Size(0, 22); + this.numericUpDown1.TabIndex = 10; + // + // label12 + // + this.label12.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.label12.AutoSize = true; + this.label12.Location = new System.Drawing.Point(12, 10); + this.label12.Margin = new System.Windows.Forms.Padding(0); + this.label12.Name = "label12"; + this.label12.Size = new System.Drawing.Size(0, 13); + this.label12.TabIndex = 12; + // + // label7 + // + this.label7.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.label7.AutoSize = true; + this.label7.Location = new System.Drawing.Point(12, 10); + this.label7.Margin = new System.Windows.Forms.Padding(0); + this.label7.Name = "label7"; + this.label7.Size = new System.Drawing.Size(111, 13); + this.label7.TabIndex = 35; + this.label7.Text = "If object is between "; + // + // numMidObjMin + // + this.numMidObjMin.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.numMidObjMin.Location = new System.Drawing.Point(123, 6); + this.numMidObjMin.Margin = new System.Windows.Forms.Padding(0, 6, 0, 6); + this.numMidObjMin.Maximum = new decimal(new int[] { + 99, + 0, + 0, + 0}); + this.numMidObjMin.Minimum = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.numMidObjMin.Name = "numMidObjMin"; + this.numMidObjMin.Size = new System.Drawing.Size(40, 22); + this.numMidObjMin.TabIndex = 3; + this.numMidObjMin.Value = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.numMidObjMin.ValueChanged += new System.EventHandler(this.numMidObjMin_ValueChanged); + this.numMidObjMin.Leave += new System.EventHandler(this.numMidObjMin_Leave); + // + // label10 + // + this.label10.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.label10.AutoSize = true; + this.label10.Location = new System.Drawing.Point(163, 10); + this.label10.Margin = new System.Windows.Forms.Padding(0); + this.label10.Name = "label10"; + this.label10.Size = new System.Drawing.Size(27, 13); + this.label10.TabIndex = 38; + this.label10.Text = "and"; + // + // numMidObjMax + // + this.numMidObjMax.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.numMidObjMax.Location = new System.Drawing.Point(190, 6); + this.numMidObjMax.Margin = new System.Windows.Forms.Padding(0, 6, 0, 6); + this.numMidObjMax.Maximum = new decimal(new int[] { + 99, + 0, + 0, + 0}); + this.numMidObjMax.Minimum = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.numMidObjMax.Name = "numMidObjMax"; + this.numMidObjMax.Size = new System.Drawing.Size(40, 22); + this.numMidObjMax.TabIndex = 4; + this.numMidObjMax.Value = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.numMidObjMax.Leave += new System.EventHandler(this.numMidObjMax_Leave); + // + // label6 + // + this.label6.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.label6.AutoSize = true; + this.label6.Location = new System.Drawing.Point(230, 10); + this.label6.Margin = new System.Windows.Forms.Padding(0); + this.label6.Name = "label6"; + this.label6.Size = new System.Drawing.Size(175, 13); + this.label6.TabIndex = 39; + this.label6.Text = "% of image set percent match to "; + // + // numMidObjPercent + // + this.numMidObjPercent.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.numMidObjPercent.Location = new System.Drawing.Point(405, 6); + this.numMidObjPercent.Margin = new System.Windows.Forms.Padding(0, 6, 2, 6); + this.numMidObjPercent.Name = "numMidObjPercent"; + this.numMidObjPercent.Size = new System.Drawing.Size(40, 22); + this.numMidObjPercent.TabIndex = 5; + this.numMidObjPercent.Value = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.numMidObjPercent.Leave += new System.EventHandler(this.numMidObjPercent_Leave); + // + // label5 + // + this.label5.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.label5.AutoSize = true; + this.label5.Location = new System.Drawing.Point(447, 10); + this.label5.Margin = new System.Windows.Forms.Padding(0); + this.label5.Name = "label5"; + this.label5.Size = new System.Drawing.Size(16, 13); + this.label5.TabIndex = 37; + this.label5.Text = "%"; + // + // label4 + // + this.label4.Location = new System.Drawing.Point(22, 17); + this.label4.Margin = new System.Windows.Forms.Padding(6, 0, 6, 0); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(801, 22); + this.label4.TabIndex = 10; + this.label4.Text = "Objects in the distance are more sensitive to changes in detected positions. This" + + " setting allows custom percentage match settings for distant objects."; + // + // cbEnableScaling + // + this.cbEnableScaling.AutoSize = true; + this.cbEnableScaling.Location = new System.Drawing.Point(25, 45); + this.cbEnableScaling.Margin = new System.Windows.Forms.Padding(6); + this.cbEnableScaling.Name = "cbEnableScaling"; + this.cbEnableScaling.Size = new System.Drawing.Size(135, 17); + this.cbEnableScaling.TabIndex = 0; + this.cbEnableScaling.Text = "Enable object scaling"; + this.cbEnableScaling.UseVisualStyleBackColor = true; + // + // groupBox2 + // + this.groupBox2.Controls.Add(this.flowLayoutMidObjects); + this.groupBox2.Location = new System.Drawing.Point(25, 197); + this.groupBox2.Margin = new System.Windows.Forms.Padding(6); + this.groupBox2.Name = "groupBox2"; + this.groupBox2.Padding = new System.Windows.Forms.Padding(6); + this.groupBox2.Size = new System.Drawing.Size(795, 98); + this.groupBox2.TabIndex = 40; + this.groupBox2.TabStop = false; + this.groupBox2.Text = "Midsize Objects (optional)"; + // + // Frm_DynamicMaskingAdvanced + // + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Inherit; + this.ClientSize = new System.Drawing.Size(831, 362); + this.Controls.Add(this.groupBox2); + this.Controls.Add(this.groupBox1); + this.Controls.Add(this.label4); + this.Controls.Add(this.cbEnableScaling); + this.Controls.Add(this.flowLayoutButtons); + this.Font = new System.Drawing.Font("Segoe UI", 8.25F); + this.Margin = new System.Windows.Forms.Padding(6); + this.Name = "Frm_DynamicMaskingAdvanced"; + this.Text = "Dynamic Masking Advanced"; + this.Load += new System.EventHandler(this.Frm_DynamicMaskingAdvanced_Load); + this.flowLayoutButtons.ResumeLayout(false); + this.groupBox1.ResumeLayout(false); + this.flowLayoutSmallObjects.ResumeLayout(false); + this.flowLayoutSmallObjects.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.numericUpDown2)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.numSmallObjMax)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.numSmallObjPercent)).EndInit(); + this.flowLayoutMidObjects.ResumeLayout(false); + this.flowLayoutMidObjects.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.numMidObjMin)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.numMidObjMax)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.numMidObjPercent)).EndInit(); + this.groupBox2.ResumeLayout(false); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + private System.Windows.Forms.FlowLayoutPanel flowLayoutButtons; + private System.Windows.Forms.Button btnCancel; + private System.Windows.Forms.Button btnOk; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.FlowLayoutPanel flowLayoutMidObjects; + private System.Windows.Forms.NumericUpDown numericUpDown1; + private System.Windows.Forms.Label label12; + private System.Windows.Forms.Label label7; + public System.Windows.Forms.NumericUpDown numMidObjMin; + private System.Windows.Forms.Label label10; + public System.Windows.Forms.NumericUpDown numMidObjMax; + private System.Windows.Forms.Label label6; + public System.Windows.Forms.NumericUpDown numMidObjPercent; + private System.Windows.Forms.Label label5; + private System.Windows.Forms.FlowLayoutPanel flowLayoutSmallObjects; + private System.Windows.Forms.NumericUpDown numericUpDown2; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.Label label1; + public System.Windows.Forms.NumericUpDown numSmallObjMax; + private System.Windows.Forms.Label label2; + public System.Windows.Forms.NumericUpDown numSmallObjPercent; + private System.Windows.Forms.Label label11; + private System.Windows.Forms.Label label4; + public System.Windows.Forms.CheckBox cbEnableScaling; + private System.Windows.Forms.GroupBox groupBox2; + } +} \ No newline at end of file diff --git a/src/UI/Frm_DynamicMaskingAdvanced.cs b/src/UI/Frm_DynamicMaskingAdvanced.cs new file mode 100644 index 00000000..fb71b808 --- /dev/null +++ b/src/UI/Frm_DynamicMaskingAdvanced.cs @@ -0,0 +1,81 @@ +using System; +using System.Windows.Forms; + +namespace AITool +{ + public partial class Frm_DynamicMaskingAdvanced : Form + { + public Frm_DynamicMaskingAdvanced() + { + this.InitializeComponent(); + } + + private void numSmallObjMax_Leave(object sender, EventArgs e) + { + if (this.numSmallObjMax.Text == "") + { + this.numSmallObjMax.Text = this.numSmallObjMax.Value.ToString(); + } + } + + private void numSmallObjPercent_Leave(object sender, EventArgs e) + { + if (this.numSmallObjPercent.Text == "") + { + this.numSmallObjPercent.Text = this.numSmallObjPercent.Value.ToString(); + } + } + + private void numMidObjMax_Leave(object sender, EventArgs e) + { + if (this.numMidObjMax.Text == "") + { + this.numMidObjMax.Text = this.numMidObjMax.Value.ToString(); + } + } + + private void numMidObjPercent_Leave(object sender, EventArgs e) + { + if (this.numMidObjPercent.Text == "") + { + this.numMidObjPercent.Text = this.numMidObjPercent.Value.ToString(); + } + } + + private void Frm_DynamicMaskingAdvanced_Load(object sender, EventArgs e) + { + this.CenterToParent(); + } + + private void numSmallObjMax_ValueChanged(object sender, EventArgs e) + { + this.numMidObjMin.Minimum = this.numSmallObjMax.Value; + + if (this.numMidObjMin.Value < this.numSmallObjMax.Value) + { + this.numMidObjMin.Value = this.numSmallObjMax.Value; + } + + this.numMidObjMax.Minimum = this.numMidObjMin.Minimum + 1; + } + + private void numMidObjMin_ValueChanged(object sender, EventArgs e) + { + if (this.numMidObjMin.Value == this.numMidObjMax.Value) + { + if (this.numMidObjMin.Value > this.numMidObjMin.Minimum) + { + this.numMidObjMin.Value = this.numMidObjMin.Value - 1; + } + } + } + + private void numMidObjMin_Leave(object sender, EventArgs e) + { + if (this.numMidObjMin.Text == "") + { + this.numMidObjMin.Text = this.numMidObjMin.Value.ToString(); + } + } + } +} diff --git a/src/UI/Frm_DynamicMaskingAdvanced.resx b/src/UI/Frm_DynamicMaskingAdvanced.resx new file mode 100644 index 00000000..1af7de15 --- /dev/null +++ b/src/UI/Frm_DynamicMaskingAdvanced.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/UI/Frm_Errors.Designer.cs b/src/UI/Frm_Errors.Designer.cs new file mode 100644 index 00000000..e8ec8cff --- /dev/null +++ b/src/UI/Frm_Errors.Designer.cs @@ -0,0 +1,98 @@ +namespace AITool +{ + partial class Frm_Errors + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.button1 = new System.Windows.Forms.Button(); + this.folv_errors = new BrightIdeasSoftware.FastObjectListView(); + this.button2 = new System.Windows.Forms.Button(); + ((System.ComponentModel.ISupportInitialize)(this.folv_errors)).BeginInit(); + this.SuspendLayout(); + // + // button1 + // + this.button1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.button1.Location = new System.Drawing.Point(640, 247); + this.button1.Margin = new System.Windows.Forms.Padding(5); + this.button1.Name = "button1"; + this.button1.Size = new System.Drawing.Size(70, 30); + this.button1.TabIndex = 4; + this.button1.Text = "Open Log"; + this.button1.UseVisualStyleBackColor = true; + this.button1.Click += new System.EventHandler(this.button1_Click); + // + // folv_errors + // + this.folv_errors.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.folv_errors.HideSelection = false; + this.folv_errors.Location = new System.Drawing.Point(12, 12); + this.folv_errors.Name = "folv_errors"; + this.folv_errors.ShowGroups = false; + this.folv_errors.Size = new System.Drawing.Size(776, 227); + this.folv_errors.TabIndex = 5; + this.folv_errors.UseCompatibleStateImageBehavior = false; + this.folv_errors.View = System.Windows.Forms.View.Details; + this.folv_errors.VirtualMode = true; + // + // button2 + // + this.button2.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.button2.Location = new System.Drawing.Point(718, 247); + this.button2.Name = "button2"; + this.button2.Size = new System.Drawing.Size(70, 30); + this.button2.TabIndex = 6; + this.button2.Text = "Clear"; + this.button2.UseVisualStyleBackColor = true; + this.button2.Click += new System.EventHandler(this.button2_Click); + // + // Frm_Errors + // + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Inherit; + this.ClientSize = new System.Drawing.Size(800, 289); + this.Controls.Add(this.button2); + this.Controls.Add(this.folv_errors); + this.Controls.Add(this.button1); + this.Name = "Frm_Errors"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Errors"; + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.Frm_Errors_FormClosing); + this.Load += new System.EventHandler(this.Frm_Errors_Load); + ((System.ComponentModel.ISupportInitialize)(this.folv_errors)).EndInit(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Button button1; + public BrightIdeasSoftware.FastObjectListView folv_errors; + private System.Windows.Forms.Button button2; + } +} \ No newline at end of file diff --git a/src/UI/Frm_Errors.cs b/src/UI/Frm_Errors.cs new file mode 100644 index 00000000..e8fa0ca6 --- /dev/null +++ b/src/UI/Frm_Errors.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Windows.Forms; + +namespace AITool +{ + public partial class Frm_Errors : Form + { + public List errors = null; + public Frm_Errors() + { + this.InitializeComponent(); + } + + private void button1_Click(object sender, EventArgs e) + { + if (System.IO.File.Exists(AITOOL.LogMan.GetCurrentLogFileName())) + { + System.Diagnostics.Process.Start(AITOOL.LogMan.GetCurrentLogFileName()); + //this.lbl_errors.Text = ""; + } + else + { + MessageBox.Show("log missing"); + } + + } + + private void Frm_Errors_Load(object sender, EventArgs e) + { + Global_GUI.RestoreWindowState(this); + + this.Show(); + + try + { + Global_GUI.ConfigureFOLV(this.folv_errors, typeof(ClsLogItm), null, null, "Time", SortOrder.Descending); + + Global_GUI.UpdateFOLV(this.folv_errors, this.errors); + + } + catch (Exception) + { + + throw; + } + + } + + private void Frm_Errors_FormClosing(object sender, FormClosingEventArgs e) + { + Global_GUI.SaveWindowState(this); + } + + private void button2_Click(object sender, EventArgs e) + { + this.DialogResult = DialogResult.OK; + this.Close(); + } + } +} diff --git a/src/UI/Frm_Errors.resx b/src/UI/Frm_Errors.resx new file mode 100644 index 00000000..1af7de15 --- /dev/null +++ b/src/UI/Frm_Errors.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/UI/Frm_Faces.Designer.cs b/src/UI/Frm_Faces.Designer.cs new file mode 100644 index 00000000..0c6f458a --- /dev/null +++ b/src/UI/Frm_Faces.Designer.cs @@ -0,0 +1,289 @@ + +namespace AITool +{ + partial class Frm_Faces + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.splitContainer1 = new System.Windows.Forms.SplitContainer(); + this.splitContainer2 = new System.Windows.Forms.SplitContainer(); + this.FOLV_Faces = new BrightIdeasSoftware.FastObjectListView(); + this.pictureBoxMainFace = new System.Windows.Forms.PictureBox(); + this.splitContainer3 = new System.Windows.Forms.SplitContainer(); + this.FOLV_FaceFiles = new BrightIdeasSoftware.FastObjectListView(); + this.pictureBoxCurrentFace = new System.Windows.Forms.PictureBox(); + this.toolTip1 = new System.Windows.Forms.ToolTip(this.components); + this.toolStrip1 = new System.Windows.Forms.ToolStrip(); + this.toolStripLabel1 = new System.Windows.Forms.ToolStripLabel(); + this.toolStripComboBoxFaceServers = new System.Windows.Forms.ToolStripComboBox(); + this.statusStrip1 = new System.Windows.Forms.StatusStrip(); + this.toolStripProgressBar1 = new System.Windows.Forms.ToolStripProgressBar(); + this.toolStripStatusLabel1 = new System.Windows.Forms.ToolStripStatusLabel(); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); + this.splitContainer1.Panel1.SuspendLayout(); + this.splitContainer1.Panel2.SuspendLayout(); + this.splitContainer1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer2)).BeginInit(); + this.splitContainer2.Panel1.SuspendLayout(); + this.splitContainer2.Panel2.SuspendLayout(); + this.splitContainer2.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.FOLV_Faces)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBoxMainFace)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer3)).BeginInit(); + this.splitContainer3.Panel1.SuspendLayout(); + this.splitContainer3.Panel2.SuspendLayout(); + this.splitContainer3.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.FOLV_FaceFiles)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBoxCurrentFace)).BeginInit(); + this.toolStrip1.SuspendLayout(); + this.statusStrip1.SuspendLayout(); + this.SuspendLayout(); + // + // splitContainer1 + // + this.splitContainer1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.splitContainer1.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; + this.splitContainer1.Location = new System.Drawing.Point(0, 43); + this.splitContainer1.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.splitContainer1.Name = "splitContainer1"; + // + // splitContainer1.Panel1 + // + this.splitContainer1.Panel1.Controls.Add(this.splitContainer2); + // + // splitContainer1.Panel2 + // + this.splitContainer1.Panel2.Controls.Add(this.splitContainer3); + this.splitContainer1.Size = new System.Drawing.Size(1562, 691); + this.splitContainer1.SplitterDistance = 430; + this.splitContainer1.SplitterWidth = 6; + this.splitContainer1.TabIndex = 0; + // + // splitContainer2 + // + this.splitContainer2.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; + this.splitContainer2.Dock = System.Windows.Forms.DockStyle.Fill; + this.splitContainer2.Location = new System.Drawing.Point(0, 0); + this.splitContainer2.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.splitContainer2.Name = "splitContainer2"; + this.splitContainer2.Orientation = System.Windows.Forms.Orientation.Horizontal; + // + // splitContainer2.Panel1 + // + this.splitContainer2.Panel1.Controls.Add(this.FOLV_Faces); + // + // splitContainer2.Panel2 + // + this.splitContainer2.Panel2.Controls.Add(this.pictureBoxMainFace); + this.splitContainer2.Size = new System.Drawing.Size(430, 691); + this.splitContainer2.SplitterDistance = 381; + this.splitContainer2.SplitterWidth = 6; + this.splitContainer2.TabIndex = 0; + // + // FOLV_Faces + // + this.FOLV_Faces.Dock = System.Windows.Forms.DockStyle.Fill; + this.FOLV_Faces.HideSelection = false; + this.FOLV_Faces.Location = new System.Drawing.Point(0, 0); + this.FOLV_Faces.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.FOLV_Faces.Name = "FOLV_Faces"; + this.FOLV_Faces.ShowGroups = false; + this.FOLV_Faces.Size = new System.Drawing.Size(426, 377); + this.FOLV_Faces.TabIndex = 0; + this.FOLV_Faces.UseCompatibleStateImageBehavior = false; + this.FOLV_Faces.View = System.Windows.Forms.View.Details; + this.FOLV_Faces.VirtualMode = true; + this.FOLV_Faces.SelectionChanged += new System.EventHandler(this.FOLV_Faces_SelectionChanged); + // + // pictureBoxMainFace + // + this.pictureBoxMainFace.Dock = System.Windows.Forms.DockStyle.Fill; + this.pictureBoxMainFace.Location = new System.Drawing.Point(0, 0); + this.pictureBoxMainFace.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.pictureBoxMainFace.Name = "pictureBoxMainFace"; + this.pictureBoxMainFace.Size = new System.Drawing.Size(426, 300); + this.pictureBoxMainFace.TabIndex = 0; + this.pictureBoxMainFace.TabStop = false; + // + // splitContainer3 + // + this.splitContainer3.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; + this.splitContainer3.Dock = System.Windows.Forms.DockStyle.Fill; + this.splitContainer3.Location = new System.Drawing.Point(0, 0); + this.splitContainer3.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.splitContainer3.Name = "splitContainer3"; + // + // splitContainer3.Panel1 + // + this.splitContainer3.Panel1.Controls.Add(this.FOLV_FaceFiles); + // + // splitContainer3.Panel2 + // + this.splitContainer3.Panel2.Controls.Add(this.pictureBoxCurrentFace); + this.splitContainer3.Size = new System.Drawing.Size(1126, 691); + this.splitContainer3.SplitterDistance = 259; + this.splitContainer3.SplitterWidth = 6; + this.splitContainer3.TabIndex = 0; + // + // FOLV_FaceFiles + // + this.FOLV_FaceFiles.Dock = System.Windows.Forms.DockStyle.Fill; + this.FOLV_FaceFiles.HideSelection = false; + this.FOLV_FaceFiles.Location = new System.Drawing.Point(0, 0); + this.FOLV_FaceFiles.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.FOLV_FaceFiles.Name = "FOLV_FaceFiles"; + this.FOLV_FaceFiles.ShowGroups = false; + this.FOLV_FaceFiles.Size = new System.Drawing.Size(255, 687); + this.FOLV_FaceFiles.TabIndex = 0; + this.FOLV_FaceFiles.UseCompatibleStateImageBehavior = false; + this.FOLV_FaceFiles.View = System.Windows.Forms.View.Details; + this.FOLV_FaceFiles.VirtualMode = true; + // + // pictureBoxCurrentFace + // + this.pictureBoxCurrentFace.Dock = System.Windows.Forms.DockStyle.Fill; + this.pictureBoxCurrentFace.Location = new System.Drawing.Point(0, 0); + this.pictureBoxCurrentFace.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.pictureBoxCurrentFace.Name = "pictureBoxCurrentFace"; + this.pictureBoxCurrentFace.Size = new System.Drawing.Size(857, 687); + this.pictureBoxCurrentFace.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.pictureBoxCurrentFace.TabIndex = 0; + this.pictureBoxCurrentFace.TabStop = false; + // + // toolStrip1 + // + this.toolStrip1.ImageScalingSize = new System.Drawing.Size(24, 24); + this.toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.toolStripLabel1, + this.toolStripComboBoxFaceServers}); + this.toolStrip1.Location = new System.Drawing.Point(0, 0); + this.toolStrip1.Name = "toolStrip1"; + this.toolStrip1.Padding = new System.Windows.Forms.Padding(0, 0, 3, 0); + this.toolStrip1.Size = new System.Drawing.Size(1562, 38); + this.toolStrip1.TabIndex = 1; + this.toolStrip1.Text = "toolStrip1"; + // + // toolStripLabel1 + // + this.toolStripLabel1.Name = "toolStripLabel1"; + this.toolStripLabel1.Size = new System.Drawing.Size(191, 33); + this.toolStripLabel1.Text = "Deepstack Face Server:"; + // + // toolStripComboBoxFaceServers + // + this.toolStripComboBoxFaceServers.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.toolStripComboBoxFaceServers.DropDownWidth = 300; + this.toolStripComboBoxFaceServers.FlatStyle = System.Windows.Forms.FlatStyle.Standard; + this.toolStripComboBoxFaceServers.Name = "toolStripComboBoxFaceServers"; + this.toolStripComboBoxFaceServers.Size = new System.Drawing.Size(298, 38); + // + // statusStrip1 + // + this.statusStrip1.ImageScalingSize = new System.Drawing.Size(24, 24); + this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.toolStripProgressBar1, + this.toolStripStatusLabel1}); + this.statusStrip1.Location = new System.Drawing.Point(0, 740); + this.statusStrip1.Name = "statusStrip1"; + this.statusStrip1.Padding = new System.Windows.Forms.Padding(2, 0, 21, 0); + this.statusStrip1.Size = new System.Drawing.Size(1562, 32); + this.statusStrip1.TabIndex = 2; + this.statusStrip1.Text = "statusStrip1"; + // + // toolStripProgressBar1 + // + this.toolStripProgressBar1.Name = "toolStripProgressBar1"; + this.toolStripProgressBar1.Size = new System.Drawing.Size(300, 24); + // + // toolStripStatusLabel1 + // + this.toolStripStatusLabel1.Name = "toolStripStatusLabel1"; + this.toolStripStatusLabel1.Size = new System.Drawing.Size(60, 25); + this.toolStripStatusLabel1.Text = "Status"; + // + // Frm_Faces + // + this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 20F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(1562, 772); + this.Controls.Add(this.statusStrip1); + this.Controls.Add(this.toolStrip1); + this.Controls.Add(this.splitContainer1); + this.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.Name = "Frm_Faces"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Tag = "SAVE"; + this.Text = "Deepstack Faces"; + this.TopMost = true; + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.Frm_Faces_FormClosing); + this.Load += new System.EventHandler(this.Frm_Faces_Load); + this.splitContainer1.Panel1.ResumeLayout(false); + this.splitContainer1.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit(); + this.splitContainer1.ResumeLayout(false); + this.splitContainer2.Panel1.ResumeLayout(false); + this.splitContainer2.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer2)).EndInit(); + this.splitContainer2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.FOLV_Faces)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBoxMainFace)).EndInit(); + this.splitContainer3.Panel1.ResumeLayout(false); + this.splitContainer3.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer3)).EndInit(); + this.splitContainer3.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.FOLV_FaceFiles)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBoxCurrentFace)).EndInit(); + this.toolStrip1.ResumeLayout(false); + this.toolStrip1.PerformLayout(); + this.statusStrip1.ResumeLayout(false); + this.statusStrip1.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.SplitContainer splitContainer1; + private System.Windows.Forms.SplitContainer splitContainer2; + private BrightIdeasSoftware.FastObjectListView FOLV_Faces; + private System.Windows.Forms.PictureBox pictureBoxMainFace; + private System.Windows.Forms.ToolTip toolTip1; + private System.Windows.Forms.ToolStrip toolStrip1; + private System.Windows.Forms.ToolStripLabel toolStripLabel1; + private System.Windows.Forms.ToolStripComboBox toolStripComboBoxFaceServers; + private System.Windows.Forms.StatusStrip statusStrip1; + private System.Windows.Forms.ToolStripProgressBar toolStripProgressBar1; + private System.Windows.Forms.ToolStripStatusLabel toolStripStatusLabel1; + private System.Windows.Forms.SplitContainer splitContainer3; + private BrightIdeasSoftware.FastObjectListView FOLV_FaceFiles; + private System.Windows.Forms.PictureBox pictureBoxCurrentFace; + } +} \ No newline at end of file diff --git a/src/UI/Frm_Faces.cs b/src/UI/Frm_Faces.cs new file mode 100644 index 00000000..cc9efbff --- /dev/null +++ b/src/UI/Frm_Faces.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace AITool +{ + public partial class Frm_Faces : Form + { + public Frm_Faces() + { + InitializeComponent(); + } + + private void Frm_Faces_Load(object sender, EventArgs e) + { + + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + + Global_GUI.ConfigureFOLV(FOLV_Faces, typeof(ClsFace)); + Global_GUI.ConfigureFOLV(FOLV_FaceFiles, typeof(ClsFaceFile)); + + Global_GUI.UpdateFOLV(FOLV_Faces, AITOOL.FaceMan.FacesDic.Values.OrderByDescending(f => f.Name).ToList(), FullRefresh: true); + + Global_GUI.RestoreWindowState(this); + } + + private void Frm_Faces_FormClosing(object sender, FormClosingEventArgs e) + { + Global_GUI.SaveWindowState(this); + } + + private void FOLV_Faces_SelectionChanged(object sender, EventArgs e) + { + FaceSelectionChanged(); + } + + private void FaceSelectionChanged() + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + try + { + if (this.FOLV_Faces.SelectedObjects != null && this.FOLV_Faces.SelectedObjects.Count > 0) + { + + //set current selected object + ClsFace face = (ClsFace)this.FOLV_Faces.SelectedObjects[0]; + + string mainface = Path.Combine(face.FaceStoragePath, $"{face.Name}.jpg"); + if (File.Exists(mainface)) + { + pictureBoxCurrentFace.BackgroundImage = Image.FromFile(mainface); + } + else + { + pictureBoxCurrentFace.BackgroundImage = null; + } + + Global_GUI.UpdateFOLV(FOLV_FaceFiles, face.FilesDic.Values.OrderByDescending(ff => ff.DateFileModified).ToList(), FullRefresh: true); + } + else + { + + } + + } + catch (Exception ex) + { + AITOOL.Log($"Error: {ex.Msg()}"); + + } + + + } + } +} diff --git a/src/UI/Frm_Faces.resx b/src/UI/Frm_Faces.resx new file mode 100644 index 00000000..2b2e3633 --- /dev/null +++ b/src/UI/Frm_Faces.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + 114, 17 + + + 219, 17 + + \ No newline at end of file diff --git a/src/UI/Frm_ImageAdjust.Designer.cs b/src/UI/Frm_ImageAdjust.Designer.cs new file mode 100644 index 00000000..ff85894c --- /dev/null +++ b/src/UI/Frm_ImageAdjust.Designer.cs @@ -0,0 +1,429 @@ + +namespace AITool +{ + partial class Frm_ImageAdjust + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.label1 = new System.Windows.Forms.Label(); + this.tb_ImageFile = new System.Windows.Forms.TextBox(); + this.tbar_Jpegquality = new System.Windows.Forms.TrackBar(); + this.label2 = new System.Windows.Forms.Label(); + this.tb_jpegquality = new System.Windows.Forms.TextBox(); + this.pictureBox1 = new System.Windows.Forms.PictureBox(); + this.label3 = new System.Windows.Forms.Label(); + this.tbar_ResizePercent = new System.Windows.Forms.TrackBar(); + this.tb_Width = new System.Windows.Forms.TextBox(); + this.textBox1 = new System.Windows.Forms.TextBox(); + this.label4 = new System.Windows.Forms.Label(); + this.label5 = new System.Windows.Forms.Label(); + this.label6 = new System.Windows.Forms.Label(); + this.label7 = new System.Windows.Forms.Label(); + this.comboBox1 = new System.Windows.Forms.ComboBox(); + this.bt_save = new System.Windows.Forms.Button(); + this.toolTip1 = new System.Windows.Forms.ToolTip(this.components); + this.bt_Delete = new System.Windows.Forms.Button(); + this.tb_SizePercent = new System.Windows.Forms.TextBox(); + this.label8 = new System.Windows.Forms.Label(); + this.label9 = new System.Windows.Forms.Label(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.lbl_originalHeight = new System.Windows.Forms.Label(); + this.lbl_originalwidth = new System.Windows.Forms.Label(); + this.label10 = new System.Windows.Forms.Label(); + this.tbar_contrast = new System.Windows.Forms.TrackBar(); + this.tbar_brightness = new System.Windows.Forms.TrackBar(); + this.tb_contrast = new System.Windows.Forms.TextBox(); + this.tb_brightness = new System.Windows.Forms.TextBox(); + this.tb_Height = new System.Windows.Forms.TextBox(); + ((System.ComponentModel.ISupportInitialize)(this.tbar_Jpegquality)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.tbar_ResizePercent)).BeginInit(); + this.groupBox1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.tbar_contrast)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.tbar_brightness)).BeginInit(); + this.SuspendLayout(); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(64, 17); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(64, 13); + this.label1.TabIndex = 1; + this.label1.Text = "Test Image:"; + // + // tb_ImageFile + // + this.tb_ImageFile.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tb_ImageFile.AutoCompleteMode = System.Windows.Forms.AutoCompleteMode.SuggestAppend; + this.tb_ImageFile.AutoCompleteSource = System.Windows.Forms.AutoCompleteSource.FileSystem; + this.tb_ImageFile.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.tb_ImageFile.Location = new System.Drawing.Point(133, 15); + this.tb_ImageFile.Name = "tb_ImageFile"; + this.tb_ImageFile.Size = new System.Drawing.Size(871, 20); + this.tb_ImageFile.TabIndex = 2; + // + // tbar_Jpegquality + // + this.tbar_Jpegquality.Location = new System.Drawing.Point(133, 41); + this.tbar_Jpegquality.Maximum = 100; + this.tbar_Jpegquality.Minimum = 5; + this.tbar_Jpegquality.Name = "tbar_Jpegquality"; + this.tbar_Jpegquality.Size = new System.Drawing.Size(168, 45); + this.tbar_Jpegquality.TabIndex = 3; + this.tbar_Jpegquality.TickFrequency = 5; + this.tbar_Jpegquality.Value = 100; + this.tbar_Jpegquality.Scroll += new System.EventHandler(this.trackBar1_Scroll); + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(15, 44); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(114, 13); + this.label2.TabIndex = 4; + this.label2.Text = "JPEG Quality Percent:"; + // + // tb_jpegquality + // + this.tb_jpegquality.Location = new System.Drawing.Point(307, 44); + this.tb_jpegquality.Name = "tb_jpegquality"; + this.tb_jpegquality.Size = new System.Drawing.Size(51, 22); + this.tb_jpegquality.TabIndex = 5; + // + // pictureBox1 + // + this.pictureBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.pictureBox1.Location = new System.Drawing.Point(12, 39); + this.pictureBox1.Name = "pictureBox1"; + this.pictureBox1.Size = new System.Drawing.Size(1020, 324); + this.pictureBox1.TabIndex = 0; + this.pictureBox1.TabStop = false; + // + // label3 + // + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(364, 88); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(77, 13); + this.label3.TabIndex = 6; + this.label3.Text = "Resize Width:"; + // + // tbar_ResizePercent + // + this.tbar_ResizePercent.Location = new System.Drawing.Point(133, 84); + this.tbar_ResizePercent.Maximum = 100; + this.tbar_ResizePercent.Minimum = 5; + this.tbar_ResizePercent.Name = "tbar_ResizePercent"; + this.tbar_ResizePercent.Size = new System.Drawing.Size(168, 45); + this.tbar_ResizePercent.TabIndex = 3; + this.tbar_ResizePercent.TickFrequency = 5; + this.tbar_ResizePercent.Value = 100; + this.tbar_ResizePercent.Scroll += new System.EventHandler(this.trackBar1_Scroll); + // + // tb_Width + // + this.tb_Width.Location = new System.Drawing.Point(443, 84); + this.tb_Width.Name = "tb_Width"; + this.tb_Width.Size = new System.Drawing.Size(51, 22); + this.tb_Width.TabIndex = 5; + // + // textBox1 + // + this.textBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.textBox1.Location = new System.Drawing.Point(1042, 77); + this.textBox1.Name = "textBox1"; + this.textBox1.Size = new System.Drawing.Size(51, 22); + this.textBox1.TabIndex = 5; + // + // label4 + // + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(500, 88); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(80, 13); + this.label4.TabIndex = 6; + this.label4.Text = "Resize Height:"; + // + // label5 + // + this.label5.AutoSize = true; + this.label5.Location = new System.Drawing.Point(349, 16); + this.label5.Name = "label5"; + this.label5.Size = new System.Drawing.Size(51, 13); + this.label5.TabIndex = 4; + this.label5.Text = "File Size:"; + // + // label6 + // + this.label6.AutoSize = true; + this.label6.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.label6.Location = new System.Drawing.Point(404, 14); + this.label6.Name = "label6"; + this.label6.Size = new System.Drawing.Size(12, 17); + this.label6.TabIndex = 7; + this.label6.Text = "."; + // + // label7 + // + this.label7.AutoSize = true; + this.label7.Location = new System.Drawing.Point(12, 15); + this.label7.Name = "label7"; + this.label7.Size = new System.Drawing.Size(43, 13); + this.label7.TabIndex = 8; + this.label7.Text = "Profile:"; + // + // comboBox1 + // + this.comboBox1.FormattingEnabled = true; + this.comboBox1.Location = new System.Drawing.Point(57, 12); + this.comboBox1.Name = "comboBox1"; + this.comboBox1.Size = new System.Drawing.Size(150, 21); + this.comboBox1.TabIndex = 9; + // + // bt_save + // + this.bt_save.Location = new System.Drawing.Point(220, 10); + this.bt_save.Name = "bt_save"; + this.bt_save.Size = new System.Drawing.Size(54, 24); + this.bt_save.TabIndex = 10; + this.bt_save.Text = "Save"; + this.bt_save.UseVisualStyleBackColor = true; + this.bt_save.Click += new System.EventHandler(this.bt_save_Click); + // + // bt_Delete + // + this.bt_Delete.Location = new System.Drawing.Point(280, 10); + this.bt_Delete.Name = "bt_Delete"; + this.bt_Delete.Size = new System.Drawing.Size(54, 24); + this.bt_Delete.TabIndex = 10; + this.bt_Delete.Text = "Delete"; + this.bt_Delete.UseVisualStyleBackColor = true; + this.bt_Delete.Click += new System.EventHandler(this.bt_Delete_Click); + // + // tb_SizePercent + // + this.tb_SizePercent.Location = new System.Drawing.Point(307, 84); + this.tb_SizePercent.Name = "tb_SizePercent"; + this.tb_SizePercent.Size = new System.Drawing.Size(51, 22); + this.tb_SizePercent.TabIndex = 5; + // + // label8 + // + this.label8.AutoSize = true; + this.label8.Location = new System.Drawing.Point(45, 87); + this.label8.Name = "label8"; + this.label8.Size = new System.Drawing.Size(83, 13); + this.label8.TabIndex = 11; + this.label8.Text = "Resize Percent:"; + // + // label9 + // + this.label9.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.label9.AutoSize = true; + this.label9.Location = new System.Drawing.Point(714, 44); + this.label9.Name = "label9"; + this.label9.Size = new System.Drawing.Size(64, 13); + this.label9.TabIndex = 12; + this.label9.Text = "Brightness:"; + // + // groupBox1 + // + this.groupBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.groupBox1.Controls.Add(this.lbl_originalHeight); + this.groupBox1.Controls.Add(this.lbl_originalwidth); + this.groupBox1.Controls.Add(this.tb_ImageFile); + this.groupBox1.Controls.Add(this.label10); + this.groupBox1.Controls.Add(this.label9); + this.groupBox1.Controls.Add(this.label1); + this.groupBox1.Controls.Add(this.label8); + this.groupBox1.Controls.Add(this.tbar_Jpegquality); + this.groupBox1.Controls.Add(this.tbar_contrast); + this.groupBox1.Controls.Add(this.tbar_brightness); + this.groupBox1.Controls.Add(this.tbar_ResizePercent); + this.groupBox1.Controls.Add(this.label4); + this.groupBox1.Controls.Add(this.label2); + this.groupBox1.Controls.Add(this.tb_jpegquality); + this.groupBox1.Controls.Add(this.tb_contrast); + this.groupBox1.Controls.Add(this.tb_brightness); + this.groupBox1.Controls.Add(this.tb_SizePercent); + this.groupBox1.Controls.Add(this.tb_Height); + this.groupBox1.Controls.Add(this.tb_Width); + this.groupBox1.Controls.Add(this.textBox1); + this.groupBox1.Controls.Add(this.label3); + this.groupBox1.Location = new System.Drawing.Point(12, 366); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.Size = new System.Drawing.Size(1020, 138); + this.groupBox1.TabIndex = 13; + this.groupBox1.TabStop = false; + // + // lbl_originalHeight + // + this.lbl_originalHeight.AutoSize = true; + this.lbl_originalHeight.Location = new System.Drawing.Point(579, 68); + this.lbl_originalHeight.Name = "lbl_originalHeight"; + this.lbl_originalHeight.Size = new System.Drawing.Size(13, 13); + this.lbl_originalHeight.TabIndex = 13; + this.lbl_originalHeight.Text = "0"; + // + // lbl_originalwidth + // + this.lbl_originalwidth.AutoSize = true; + this.lbl_originalwidth.Location = new System.Drawing.Point(440, 68); + this.lbl_originalwidth.Name = "lbl_originalwidth"; + this.lbl_originalwidth.Size = new System.Drawing.Size(13, 13); + this.lbl_originalwidth.TabIndex = 13; + this.lbl_originalwidth.Text = "0"; + // + // label10 + // + this.label10.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.label10.AutoSize = true; + this.label10.Location = new System.Drawing.Point(724, 87); + this.label10.Name = "label10"; + this.label10.Size = new System.Drawing.Size(54, 13); + this.label10.TabIndex = 12; + this.label10.Text = "Contrast:"; + // + // tbar_contrast + // + this.tbar_contrast.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.tbar_contrast.Location = new System.Drawing.Point(779, 84); + this.tbar_contrast.Maximum = 100; + this.tbar_contrast.Minimum = 1; + this.tbar_contrast.Name = "tbar_contrast"; + this.tbar_contrast.Size = new System.Drawing.Size(168, 45); + this.tbar_contrast.TabIndex = 3; + this.tbar_contrast.TickFrequency = 5; + this.tbar_contrast.Value = 100; + this.tbar_contrast.Scroll += new System.EventHandler(this.trackBar1_Scroll); + this.tbar_contrast.ValueChanged += new System.EventHandler(this.tbar_contrast_ValueChanged); + // + // tbar_brightness + // + this.tbar_brightness.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.tbar_brightness.Location = new System.Drawing.Point(779, 38); + this.tbar_brightness.Maximum = 100; + this.tbar_brightness.Minimum = 1; + this.tbar_brightness.Name = "tbar_brightness"; + this.tbar_brightness.Size = new System.Drawing.Size(168, 45); + this.tbar_brightness.TabIndex = 3; + this.tbar_brightness.TickFrequency = 5; + this.tbar_brightness.Value = 100; + this.tbar_brightness.Scroll += new System.EventHandler(this.trackBar1_Scroll); + this.tbar_brightness.ValueChanged += new System.EventHandler(this.tbar_brightness_ValueChanged); + // + // tb_contrast + // + this.tb_contrast.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.tb_contrast.Location = new System.Drawing.Point(953, 84); + this.tb_contrast.Name = "tb_contrast"; + this.tb_contrast.Size = new System.Drawing.Size(51, 22); + this.tb_contrast.TabIndex = 5; + // + // tb_brightness + // + this.tb_brightness.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.tb_brightness.Location = new System.Drawing.Point(953, 41); + this.tb_brightness.Name = "tb_brightness"; + this.tb_brightness.Size = new System.Drawing.Size(51, 22); + this.tb_brightness.TabIndex = 5; + // + // tb_Height + // + this.tb_Height.Location = new System.Drawing.Point(582, 84); + this.tb_Height.Name = "tb_Height"; + this.tb_Height.Size = new System.Drawing.Size(51, 22); + this.tb_Height.TabIndex = 5; + // + // Frm_ImageAdjust + // + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Inherit; + this.ClientSize = new System.Drawing.Size(1044, 514); + this.Controls.Add(this.groupBox1); + this.Controls.Add(this.bt_Delete); + this.Controls.Add(this.bt_save); + this.Controls.Add(this.comboBox1); + this.Controls.Add(this.label7); + this.Controls.Add(this.label6); + this.Controls.Add(this.label5); + this.Controls.Add(this.pictureBox1); + this.Font = new System.Drawing.Font("Segoe UI", 8.25F); + this.Name = "Frm_ImageAdjust"; + this.Text = "Image Adjust"; + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.Frm_ImageAdjust_FormClosing); + this.Load += new System.EventHandler(this.Frm_ImageAdjust_Load); + ((System.ComponentModel.ISupportInitialize)(this.tbar_Jpegquality)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.tbar_ResizePercent)).EndInit(); + this.groupBox1.ResumeLayout(false); + this.groupBox1.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.tbar_contrast)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.tbar_brightness)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.PictureBox pictureBox1; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.TextBox tb_ImageFile; + private System.Windows.Forms.TrackBar tbar_Jpegquality; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.TextBox tb_jpegquality; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.TrackBar tbar_ResizePercent; + private System.Windows.Forms.TextBox tb_Width; + private System.Windows.Forms.TextBox textBox1; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.Label label5; + private System.Windows.Forms.Label label6; + private System.Windows.Forms.Label label7; + private System.Windows.Forms.ComboBox comboBox1; + private System.Windows.Forms.Button bt_save; + private System.Windows.Forms.ToolTip toolTip1; + private System.Windows.Forms.Button bt_Delete; + private System.Windows.Forms.TextBox tb_SizePercent; + private System.Windows.Forms.Label label8; + private System.Windows.Forms.Label label9; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.Label lbl_originalHeight; + private System.Windows.Forms.Label lbl_originalwidth; + private System.Windows.Forms.Label label10; + private System.Windows.Forms.TrackBar tbar_contrast; + private System.Windows.Forms.TrackBar tbar_brightness; + private System.Windows.Forms.TextBox tb_contrast; + private System.Windows.Forms.TextBox tb_brightness; + private System.Windows.Forms.TextBox tb_Height; + } +} \ No newline at end of file diff --git a/src/UI/Frm_ImageAdjust.cs b/src/UI/Frm_ImageAdjust.cs new file mode 100644 index 00000000..71091554 --- /dev/null +++ b/src/UI/Frm_ImageAdjust.cs @@ -0,0 +1,268 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using static AITool.AITOOL; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using Image = System.Drawing.Image; + +namespace AITool +{ + public partial class Frm_ImageAdjust : Form + { + + public Camera cam = null; + + private ClsImageAdjust CurIA = null; + + public Frm_ImageAdjust() + { + InitializeComponent(); + } + + private void Frm_ImageAdjust_Load(object sender, EventArgs e) + { + Global_GUI.RestoreWindowState(this); + UpdateProfile(); + + this.tb_ImageFile.Text = GetBestImage(); + + } + + + private void UpdateProfile() + { + string last = "Default"; + + if (AppSettings.Settings.ImageAdjustProfiles.Count == 0) + { + AppSettings.Settings.ImageAdjustProfiles.Add(new ClsImageAdjust("Default")); + } + + + if (!string.IsNullOrEmpty(this.comboBox1.Text.Trim())) + { + last = this.comboBox1.Text.Trim(); + + this.CurIA = AITOOL.GetImageAdjustProfileByName(this.comboBox1.Text.Trim(), false); + + if (this.CurIA == null) + { + this.CurIA = new ClsImageAdjust(this.comboBox1.Text, tb_jpegquality.Text, tb_SizePercent.Text, tb_Width.Text, tb_Height.Text, tb_brightness.Text, tb_contrast.Text); + AppSettings.Settings.ImageAdjustProfiles.Add(this.CurIA); + } + else + { + this.CurIA.Update(this.comboBox1.Text, tb_jpegquality.Text, tb_SizePercent.Text, tb_Width.Text, tb_Height.Text, tb_brightness.Text, tb_contrast.Text); + } + + } + + this.comboBox1.Items.Clear(); + + int i = 0; + int oldidx = 0; + foreach (ClsImageAdjust ia in AppSettings.Settings.ImageAdjustProfiles) + { + this.comboBox1.Items.Add(ia.Name); + if (string.Equals(last.Trim(), ia.Name.Trim(), StringComparison.OrdinalIgnoreCase)) + { + oldidx = i + 1; + } + i++; + } + + if (this.comboBox1.Items.Count > 0) + this.comboBox1.SelectedIndex = oldidx; + + this.CurIA = AITOOL.GetImageAdjustProfileByName(this.comboBox1.Text.Trim(), false); + + + + } + + public string GetBestImage() + { + //try to prioritize the images from actual detections first + //goal is to always try to have an image to display + // IS THIS OVERKILL OR CONFUSING TO USER??? + + try + { + string lastfolder = ""; + + //See if we have an image stored that had ANY detections and use it + if (this.cam != null) + { + if (!string.IsNullOrEmpty(this.cam.last_image_file_with_detections)) + { + lastfolder = Path.GetDirectoryName(this.cam.last_image_file_with_detections); + if (File.Exists(this.cam.last_image_file_with_detections)) + { + Log($" (Found image from -last- detected object: {this.cam.last_image_file_with_detections})"); + return this.cam.last_image_file_with_detections; + } + else + { + //extra debugging + Log(" >CAM.last_image_file_with_detections file no longer exists."); + } + + } + else + { + //extra debugging + Log($" >No CAM.last_image_file_with_detections for '{this.cam.Name}'"); + } + + + //Just take the last image processed by the camera even if no detections + if (!string.IsNullOrEmpty(this.cam.last_image_file)) + { + lastfolder = Path.GetDirectoryName(this.cam.last_image_file); + if (File.Exists(this.cam.last_image_file)) + { + Log($" (Found image from last processed image (no detections): {this.cam.last_image_file})"); + return this.cam.last_image_file; + } + else + { + //extra debugging + Log(" >CAM.last_image_file file no longer exists."); + } + } + else + { + //extra debugging + Log($" >No CAM.last_image_file for '{this.cam.Name}'"); + } + + //try to use the LastCamImages version + string fldr = Path.Combine(Path.GetDirectoryName(AppSettings.Settings.SettingsFileName), "LastCamImages"); + string file = Path.Combine(fldr, $"{cam.Name}-Last.jpg"); + + if (File.Exists(file)) + { + Log($" (Found image from LastCamImages folder: ({file})"); + return file; + } + + + if (string.IsNullOrEmpty(lastfolder)) + { + lastfolder = this.cam.input_path; + } + } + else + { + //extra debugging + Log(" >No CAM selected."); + } + + //FAIL, scan the camera folder for the most recent image file + if (!string.IsNullOrEmpty(lastfolder) && Directory.Exists(lastfolder)) + { + //I expect this may take a few seconds if folder is huge + DirectoryInfo dirinfo = new DirectoryInfo(lastfolder); + FileInfo myFile; + if (this.cam != null && !string.IsNullOrEmpty(this.cam.Prefix)) + { + myFile = dirinfo.GetFiles($"{this.cam.Prefix.Trim()}*.jpg").OrderByDescending(f => f.LastWriteTime).First(); + if (myFile != null) + { + Log($" (Found most recent image in camera folder for prefix '{this.cam.Prefix}' (no detections): {myFile.FullName})"); + return myFile.FullName; + } + else + { + //extra debugging + Log($" >No files found starting with '{this.cam.Prefix}' in {lastfolder}'"); + } + + } + else + { + myFile = dirinfo.GetFiles("*.jpg").OrderByDescending(f => f.LastWriteTime).First(); + if (myFile != null) + { + Log($" (Found most recent image in camera folder (no detections): {myFile.FullName})"); + return myFile.FullName; + } + else + { + //extra debugging + Log($" >No files found in {lastfolder}'"); + } + } + } + else + { + //extra debugging + Log($">Lastfolder not found or doesnt exist - '{lastfolder}'"); + } + + + } + catch (Exception ex) + { + + Log("Error: " + ex.Msg()); + } + + return ""; + + } + private void Frm_ImageAdjust_FormClosing(object sender, FormClosingEventArgs e) + { + Global_GUI.SaveWindowState(this); + } + + private void trackBar1_Scroll(object sender, EventArgs e) + { + + } + + private void bt_save_Click(object sender, EventArgs e) + { + UpdateProfile(); + } + + private void bt_Delete_Click(object sender, EventArgs e) + { + if (AITOOL.HasImageAdjustProfile(this.comboBox1.Text.Trim())) + { + AppSettings.Settings.ImageAdjustProfiles.Remove(AITOOL.GetImageAdjustProfileByName(this.comboBox1.Text.Trim(), false)); + this.comboBox1.Items.Clear(); + this.comboBox1.Text = ""; + UpdateProfile(); + } + } + + private void tbar_brightness_ValueChanged(object sender, EventArgs e) + { + tb_brightness.Text = tbar_brightness.Value.ToString() ; + } + + private void tbar_contrast_ValueChanged(object sender, EventArgs e) + { + tb_contrast.Text = tbar_contrast.Value.ToString(); + } + + private void bt_Apply_Click(object sender, EventArgs e) + { + //this.CurIA = AITOOL.GetImageAdjustProfileByName(this.comboBox1.Text.Trim()); + } + } +} diff --git a/src/UI/Frm_ImageAdjust.resx b/src/UI/Frm_ImageAdjust.resx new file mode 100644 index 00000000..df8339b6 --- /dev/null +++ b/src/UI/Frm_ImageAdjust.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + \ No newline at end of file diff --git a/src/UI/Frm_LegacyActions.Designer.cs b/src/UI/Frm_LegacyActions.Designer.cs new file mode 100644 index 00000000..b5cbc5c7 --- /dev/null +++ b/src/UI/Frm_LegacyActions.Designer.cs @@ -0,0 +1,1148 @@ +namespace AITool +{ + partial class Frm_LegacyActions + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Frm_LegacyActions)); + this.btnCancel = new System.Windows.Forms.Button(); + this.btnSave = new System.Windows.Forms.Button(); + this.groupBoxMQTT = new System.Windows.Forms.GroupBox(); + this.linkLabel1 = new System.Windows.Forms.LinkLabel(); + this.cb_MQTT_enabled = new System.Windows.Forms.CheckBox(); + this.linkLabelMqttSettings = new System.Windows.Forms.LinkLabel(); + this.label1 = new System.Windows.Forms.Label(); + this.tb_MQTT_Topic = new System.Windows.Forms.TextBox(); + this.label9 = new System.Windows.Forms.Label(); + this.tb_MQTT_Topic_Cancel = new System.Windows.Forms.TextBox(); + this.tb_MQTT_Payload = new System.Windows.Forms.TextBox(); + this.cb_MQTT_SendImage = new System.Windows.Forms.CheckBox(); + this.tb_MQTT_Payload_cancel = new System.Windows.Forms.TextBox(); + this.label12 = new System.Windows.Forms.Label(); + this.label13 = new System.Windows.Forms.Label(); + this.groupBoxTelegram = new System.Windows.Forms.GroupBox(); + this.lnkTelegramTriggeringObjects = new System.Windows.Forms.LinkLabel(); + this.cb_telegram = new System.Windows.Forms.CheckBox(); + this.label7 = new System.Windows.Forms.Label(); + this.tb_telegram_caption = new System.Windows.Forms.TextBox(); + this.label20 = new System.Windows.Forms.Label(); + this.cb_telegram_active_time = new System.Windows.Forms.TextBox(); + this.tb_RunExternalProgram = new System.Windows.Forms.TextBox(); + this.label15 = new System.Windows.Forms.Label(); + this.tb_network_folder = new System.Windows.Forms.TextBox(); + this.groupBoxPushover = new System.Windows.Forms.GroupBox(); + this.label11 = new System.Windows.Forms.Label(); + this.LnkPushoverObjects = new System.Windows.Forms.LinkLabel(); + this.cb_Pushover_Enabled = new System.Windows.Forms.CheckBox(); + this.label8 = new System.Windows.Forms.Label(); + this.label21 = new System.Windows.Forms.Label(); + this.tb_Pushover_Title = new System.Windows.Forms.TextBox(); + this.cb_pushover_active_time = new System.Windows.Forms.TextBox(); + this.tb_Pushover_Message = new System.Windows.Forms.TextBox(); + this.tb_Pushover_Device = new System.Windows.Forms.TextBox(); + this.tb_Pushover_sound = new System.Windows.Forms.TextBox(); + this.tb_Pushover_Priority = new System.Windows.Forms.TextBox(); + this.label10 = new System.Windows.Forms.Label(); + this.label16 = new System.Windows.Forms.Label(); + this.label18 = new System.Windows.Forms.Label(); + this.label19 = new System.Windows.Forms.Label(); + this.label3 = new System.Windows.Forms.Label(); + this.label14 = new System.Windows.Forms.Label(); + this.tb_ActionCancelSecs = new System.Windows.Forms.TextBox(); + this.tb_jpeg_merge_quality = new System.Windows.Forms.TextBox(); + this.label23 = new System.Windows.Forms.Label(); + this.label2 = new System.Windows.Forms.Label(); + this.cb_ShowOnlyRelevant = new System.Windows.Forms.CheckBox(); + this.cb_queue_actions = new System.Windows.Forms.CheckBox(); + this.cb_mergeannotations = new System.Windows.Forms.CheckBox(); + this.tb_network_folder_filename = new System.Windows.Forms.TextBox(); + this.tb_Sounds = new System.Windows.Forms.TextBox(); + this.cb_PlaySound = new System.Windows.Forms.CheckBox(); + this.tb_RunExternalProgramArgs = new System.Windows.Forms.TextBox(); + this.cb_RunProgram = new System.Windows.Forms.CheckBox(); + this.cb_copyAlertImages = new System.Windows.Forms.CheckBox(); + this.tbCancelUrl = new System.Windows.Forms.TextBox(); + this.tbTriggerUrl = new System.Windows.Forms.TextBox(); + this.label24 = new System.Windows.Forms.Label(); + this.label22 = new System.Windows.Forms.Label(); + this.label5 = new System.Windows.Forms.Label(); + this.lbl_Confidence = new System.Windows.Forms.Label(); + this.lbl_DetectionFormat = new System.Windows.Forms.Label(); + this.tb_ConfidenceFormat = new System.Windows.Forms.TextBox(); + this.tb_DetectionFormat = new System.Windows.Forms.TextBox(); + this.tb_sound_cooldown = new System.Windows.Forms.TextBox(); + this.tb_cooldown = new System.Windows.Forms.TextBox(); + this.toolTip1 = new System.Windows.Forms.ToolTip(this.components); + this.bt_variables = new System.Windows.Forms.Button(); + this.tb_ActionDelayMS = new System.Windows.Forms.TextBox(); + this.tb_NetworkFolderCleanupDays = new System.Windows.Forms.TextBox(); + this.cb_ActivateBlueIrisWindow = new System.Windows.Forms.CheckBox(); + this.btTest = new System.Windows.Forms.Button(); + this.panel1 = new System.Windows.Forms.Panel(); + this.groupBoxUrlCancel = new System.Windows.Forms.GroupBox(); + this.cb_UrlCancelEnabled = new System.Windows.Forms.CheckBox(); + this.groupBoxUrlTrigger = new System.Windows.Forms.GroupBox(); + this.cb_UrlTriggerEnabled = new System.Windows.Forms.CheckBox(); + this.label6 = new System.Windows.Forms.Label(); + this.label4 = new System.Windows.Forms.Label(); + this.groupBoxMQTT.SuspendLayout(); + this.groupBoxTelegram.SuspendLayout(); + this.groupBoxPushover.SuspendLayout(); + this.panel1.SuspendLayout(); + this.groupBoxUrlCancel.SuspendLayout(); + this.groupBoxUrlTrigger.SuspendLayout(); + this.SuspendLayout(); + // + // btnCancel + // + this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.btnCancel.Location = new System.Drawing.Point(810, 701); + this.btnCancel.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.btnCancel.Name = "btnCancel"; + this.btnCancel.Size = new System.Drawing.Size(70, 30); + this.btnCancel.TabIndex = 37; + this.btnCancel.Text = "Cancel"; + this.btnCancel.UseVisualStyleBackColor = true; + this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click); + // + // btnSave + // + this.btnSave.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.btnSave.Location = new System.Drawing.Point(732, 701); + this.btnSave.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.btnSave.Name = "btnSave"; + this.btnSave.Size = new System.Drawing.Size(70, 30); + this.btnSave.TabIndex = 36; + this.btnSave.Text = "Save"; + this.btnSave.UseVisualStyleBackColor = true; + this.btnSave.Click += new System.EventHandler(this.btnSave_Click); + // + // groupBoxMQTT + // + this.groupBoxMQTT.Controls.Add(this.linkLabel1); + this.groupBoxMQTT.Controls.Add(this.cb_MQTT_enabled); + this.groupBoxMQTT.Controls.Add(this.linkLabelMqttSettings); + this.groupBoxMQTT.Controls.Add(this.label1); + this.groupBoxMQTT.Controls.Add(this.tb_MQTT_Topic); + this.groupBoxMQTT.Controls.Add(this.label9); + this.groupBoxMQTT.Controls.Add(this.tb_MQTT_Topic_Cancel); + this.groupBoxMQTT.Controls.Add(this.tb_MQTT_Payload); + this.groupBoxMQTT.Controls.Add(this.cb_MQTT_SendImage); + this.groupBoxMQTT.Controls.Add(this.tb_MQTT_Payload_cancel); + this.groupBoxMQTT.Controls.Add(this.label12); + this.groupBoxMQTT.Controls.Add(this.label13); + this.groupBoxMQTT.Location = new System.Drawing.Point(6, 296); + this.groupBoxMQTT.Name = "groupBoxMQTT"; + this.groupBoxMQTT.Size = new System.Drawing.Size(852, 87); + this.groupBoxMQTT.TabIndex = 53; + this.groupBoxMQTT.TabStop = false; + // + // linkLabel1 + // + this.linkLabel1.AutoSize = true; + this.linkLabel1.Location = new System.Drawing.Point(738, 54); + this.linkLabel1.Name = "linkLabel1"; + this.linkLabel1.Size = new System.Drawing.Size(102, 13); + this.linkLabel1.TabIndex = 46; + this.linkLabel1.TabStop = true; + this.linkLabel1.Text = "Triggering Objects"; + this.linkLabel1.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.linkLabel1_LinkClicked); + // + // cb_MQTT_enabled + // + this.cb_MQTT_enabled.AutoSize = true; + this.cb_MQTT_enabled.ForeColor = System.Drawing.Color.DodgerBlue; + this.cb_MQTT_enabled.Location = new System.Drawing.Point(6, 0); + this.cb_MQTT_enabled.Name = "cb_MQTT_enabled"; + this.cb_MQTT_enabled.Size = new System.Drawing.Size(136, 17); + this.cb_MQTT_enabled.TabIndex = 17; + this.cb_MQTT_enabled.Text = "Send MQTT Message:"; + this.cb_MQTT_enabled.UseVisualStyleBackColor = true; + this.cb_MQTT_enabled.CheckedChanged += new System.EventHandler(this.cb_MQTT_enabled_CheckedChanged); + // + // linkLabelMqttSettings + // + this.linkLabelMqttSettings.AutoSize = true; + this.linkLabelMqttSettings.Location = new System.Drawing.Point(150, 1); + this.linkLabelMqttSettings.Name = "linkLabelMqttSettings"; + this.linkLabelMqttSettings.Size = new System.Drawing.Size(49, 13); + this.linkLabelMqttSettings.TabIndex = 41; + this.linkLabelMqttSettings.TabStop = true; + this.linkLabelMqttSettings.Text = "Settings"; + this.toolTip1.SetToolTip(this.linkLabelMqttSettings, "Global MQTT Settings"); + this.linkLabelMqttSettings.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.linkLabelMqttSettings_LinkClicked); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(4, 29); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(76, 13); + this.label1.TabIndex = 36; + this.label1.Text = "Trigger Topic:"; + // + // tb_MQTT_Topic + // + this.tb_MQTT_Topic.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.tb_MQTT_Topic.Location = new System.Drawing.Point(87, 25); + this.tb_MQTT_Topic.Name = "tb_MQTT_Topic"; + this.tb_MQTT_Topic.Size = new System.Drawing.Size(219, 20); + this.tb_MQTT_Topic.TabIndex = 18; + this.tb_MQTT_Topic.Tag = ""; + this.toolTip1.SetToolTip(this.tb_MQTT_Topic, "Specify more than one topic/payload by using the PIPE | symbol between each."); + // + // label9 + // + this.label9.AutoSize = true; + this.label9.ForeColor = System.Drawing.Color.DarkRed; + this.label9.Location = new System.Drawing.Point(4, 57); + this.label9.Name = "label9"; + this.label9.Size = new System.Drawing.Size(74, 13); + this.label9.TabIndex = 36; + this.label9.Text = "Cancel Topic:"; + // + // tb_MQTT_Topic_Cancel + // + this.tb_MQTT_Topic_Cancel.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.tb_MQTT_Topic_Cancel.Location = new System.Drawing.Point(87, 53); + this.tb_MQTT_Topic_Cancel.Name = "tb_MQTT_Topic_Cancel"; + this.tb_MQTT_Topic_Cancel.Size = new System.Drawing.Size(219, 20); + this.tb_MQTT_Topic_Cancel.TabIndex = 21; + this.tb_MQTT_Topic_Cancel.Tag = ""; + this.toolTip1.SetToolTip(this.tb_MQTT_Topic_Cancel, "Specify more than one topic/payload by using the PIPE | symbol between each."); + // + // tb_MQTT_Payload + // + this.tb_MQTT_Payload.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.tb_MQTT_Payload.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.tb_MQTT_Payload.Location = new System.Drawing.Point(370, 25); + this.tb_MQTT_Payload.Name = "tb_MQTT_Payload"; + this.tb_MQTT_Payload.Size = new System.Drawing.Size(340, 20); + this.tb_MQTT_Payload.TabIndex = 19; + this.tb_MQTT_Payload.Tag = ""; + this.toolTip1.SetToolTip(this.tb_MQTT_Payload, "Specify more than one topic/payload by using the PIPE | symbol between each."); + // + // cb_MQTT_SendImage + // + this.cb_MQTT_SendImage.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.cb_MQTT_SendImage.AutoSize = true; + this.cb_MQTT_SendImage.Location = new System.Drawing.Point(758, 27); + this.cb_MQTT_SendImage.Name = "cb_MQTT_SendImage"; + this.cb_MQTT_SendImage.Size = new System.Drawing.Size(86, 17); + this.cb_MQTT_SendImage.TabIndex = 20; + this.cb_MQTT_SendImage.Text = "Send Image"; + this.toolTip1.SetToolTip(this.cb_MQTT_SendImage, "If one of the topics has /image and the image checkbox is checked, then the actua" + + "l image with the detection will be sent.\r\n (use | to Separate multiple topics or" + + " payloads.)"); + this.cb_MQTT_SendImage.UseVisualStyleBackColor = true; + // + // tb_MQTT_Payload_cancel + // + this.tb_MQTT_Payload_cancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.tb_MQTT_Payload_cancel.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.tb_MQTT_Payload_cancel.Location = new System.Drawing.Point(370, 51); + this.tb_MQTT_Payload_cancel.Name = "tb_MQTT_Payload_cancel"; + this.tb_MQTT_Payload_cancel.Size = new System.Drawing.Size(340, 20); + this.tb_MQTT_Payload_cancel.TabIndex = 22; + this.tb_MQTT_Payload_cancel.Tag = ""; + this.toolTip1.SetToolTip(this.tb_MQTT_Payload_cancel, "Specify more than one topic/payload by using the PIPE | symbol between each."); + // + // label12 + // + this.label12.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.label12.AutoSize = true; + this.label12.Location = new System.Drawing.Point(312, 28); + this.label12.Name = "label12"; + this.label12.Size = new System.Drawing.Size(50, 13); + this.label12.TabIndex = 39; + this.label12.Text = "Payload:"; + // + // label13 + // + this.label13.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.label13.AutoSize = true; + this.label13.ForeColor = System.Drawing.Color.DarkRed; + this.label13.Location = new System.Drawing.Point(312, 54); + this.label13.Name = "label13"; + this.label13.Size = new System.Drawing.Size(50, 13); + this.label13.TabIndex = 39; + this.label13.Text = "Payload:"; + // + // groupBoxTelegram + // + this.groupBoxTelegram.Controls.Add(this.lnkTelegramTriggeringObjects); + this.groupBoxTelegram.Controls.Add(this.cb_telegram); + this.groupBoxTelegram.Controls.Add(this.label7); + this.groupBoxTelegram.Controls.Add(this.tb_telegram_caption); + this.groupBoxTelegram.Controls.Add(this.label20); + this.groupBoxTelegram.Controls.Add(this.cb_telegram_active_time); + this.groupBoxTelegram.Location = new System.Drawing.Point(6, 125); + this.groupBoxTelegram.Name = "groupBoxTelegram"; + this.groupBoxTelegram.Size = new System.Drawing.Size(852, 58); + this.groupBoxTelegram.TabIndex = 52; + this.groupBoxTelegram.TabStop = false; + // + // lnkTelegramTriggeringObjects + // + this.lnkTelegramTriggeringObjects.AutoSize = true; + this.lnkTelegramTriggeringObjects.Location = new System.Drawing.Point(738, 28); + this.lnkTelegramTriggeringObjects.Name = "lnkTelegramTriggeringObjects"; + this.lnkTelegramTriggeringObjects.Size = new System.Drawing.Size(102, 13); + this.lnkTelegramTriggeringObjects.TabIndex = 46; + this.lnkTelegramTriggeringObjects.TabStop = true; + this.lnkTelegramTriggeringObjects.Text = "Triggering Objects"; + this.lnkTelegramTriggeringObjects.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.lnkTelegramTriggeringObjects_LinkClicked); + // + // cb_telegram + // + this.cb_telegram.AutoSize = true; + this.cb_telegram.ForeColor = System.Drawing.Color.DodgerBlue; + this.cb_telegram.Location = new System.Drawing.Point(8, 0); + this.cb_telegram.Margin = new System.Windows.Forms.Padding(10, 6, 5, 6); + this.cb_telegram.Name = "cb_telegram"; + this.cb_telegram.Size = new System.Drawing.Size(184, 19); + this.cb_telegram.TabIndex = 6; + this.cb_telegram.Text = "Send alert images to Telegram"; + this.cb_telegram.UseVisualStyleBackColor = false; + this.cb_telegram.CheckedChanged += new System.EventHandler(this.cb_telegram_CheckedChanged); + // + // label7 + // + this.label7.AutoSize = true; + this.label7.Location = new System.Drawing.Point(17, 28); + this.label7.Name = "label7"; + this.label7.Size = new System.Drawing.Size(32, 13); + this.label7.TabIndex = 43; + this.label7.Text = "Title:"; + // + // tb_telegram_caption + // + this.tb_telegram_caption.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.tb_telegram_caption.Location = new System.Drawing.Point(55, 25); + this.tb_telegram_caption.Name = "tb_telegram_caption"; + this.tb_telegram_caption.Size = new System.Drawing.Size(219, 20); + this.tb_telegram_caption.TabIndex = 7; + // + // label20 + // + this.label20.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.label20.AutoSize = true; + this.label20.Location = new System.Drawing.Point(298, 28); + this.label20.Name = "label20"; + this.label20.Size = new System.Drawing.Size(34, 13); + this.label20.TabIndex = 44; + this.label20.Text = "Time:"; + // + // cb_telegram_active_time + // + this.cb_telegram_active_time.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.cb_telegram_active_time.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.cb_telegram_active_time.Location = new System.Drawing.Point(340, 25); + this.cb_telegram_active_time.Name = "cb_telegram_active_time"; + this.cb_telegram_active_time.Size = new System.Drawing.Size(219, 20); + this.cb_telegram_active_time.TabIndex = 9; + this.toolTip1.SetToolTip(this.cb_telegram_active_time, "Time range (24 hr) when sending to Telegram is active"); + // + // tb_RunExternalProgram + // + this.tb_RunExternalProgram.AutoCompleteMode = System.Windows.Forms.AutoCompleteMode.SuggestAppend; + this.tb_RunExternalProgram.AutoCompleteSource = System.Windows.Forms.AutoCompleteSource.FileSystem; + this.tb_RunExternalProgram.Location = new System.Drawing.Point(191, 619); + this.tb_RunExternalProgram.Name = "tb_RunExternalProgram"; + this.tb_RunExternalProgram.Size = new System.Drawing.Size(295, 22); + this.tb_RunExternalProgram.TabIndex = 27; + this.toolTip1.SetToolTip(this.tb_RunExternalProgram, "Path to EXE, BAT, etc"); + // + // label15 + // + this.label15.AutoSize = true; + this.label15.Location = new System.Drawing.Point(488, 591); + this.label15.Name = "label15"; + this.label15.Size = new System.Drawing.Size(56, 13); + this.label15.TabIndex = 44; + this.label15.Text = "Filename:"; + // + // tb_network_folder + // + this.tb_network_folder.AutoCompleteMode = System.Windows.Forms.AutoCompleteMode.SuggestAppend; + this.tb_network_folder.AutoCompleteSource = System.Windows.Forms.AutoCompleteSource.FileSystem; + this.tb_network_folder.Location = new System.Drawing.Point(191, 588); + this.tb_network_folder.Name = "tb_network_folder"; + this.tb_network_folder.Size = new System.Drawing.Size(295, 22); + this.tb_network_folder.TabIndex = 24; + // + // groupBoxPushover + // + this.groupBoxPushover.Controls.Add(this.label11); + this.groupBoxPushover.Controls.Add(this.LnkPushoverObjects); + this.groupBoxPushover.Controls.Add(this.cb_Pushover_Enabled); + this.groupBoxPushover.Controls.Add(this.label8); + this.groupBoxPushover.Controls.Add(this.label21); + this.groupBoxPushover.Controls.Add(this.tb_Pushover_Title); + this.groupBoxPushover.Controls.Add(this.cb_pushover_active_time); + this.groupBoxPushover.Controls.Add(this.tb_Pushover_Message); + this.groupBoxPushover.Controls.Add(this.tb_Pushover_Device); + this.groupBoxPushover.Controls.Add(this.tb_Pushover_sound); + this.groupBoxPushover.Controls.Add(this.tb_Pushover_Priority); + this.groupBoxPushover.Controls.Add(this.label10); + this.groupBoxPushover.Controls.Add(this.label16); + this.groupBoxPushover.Controls.Add(this.label18); + this.groupBoxPushover.Controls.Add(this.label19); + this.groupBoxPushover.Location = new System.Drawing.Point(6, 189); + this.groupBoxPushover.Name = "groupBoxPushover"; + this.groupBoxPushover.Size = new System.Drawing.Size(852, 101); + this.groupBoxPushover.TabIndex = 51; + this.groupBoxPushover.TabStop = false; + // + // label11 + // + this.label11.AutoSize = true; + this.label11.ForeColor = System.Drawing.Color.Gray; + this.label11.Location = new System.Drawing.Point(5, 79); + this.label11.Name = "label11"; + this.label11.Size = new System.Drawing.Size(639, 13); + this.label11.TabIndex = 47; + this.label11.Text = "Specify more than one Sound, Prioirty and Time by using the PIPE symbol. Use of" + + " SUNRISE-SUNSET is allowed for all TIMES"; + // + // LnkPushoverObjects + // + this.LnkPushoverObjects.AutoSize = true; + this.LnkPushoverObjects.Location = new System.Drawing.Point(738, 79); + this.LnkPushoverObjects.Name = "LnkPushoverObjects"; + this.LnkPushoverObjects.Size = new System.Drawing.Size(102, 13); + this.LnkPushoverObjects.TabIndex = 46; + this.LnkPushoverObjects.TabStop = true; + this.LnkPushoverObjects.Text = "Triggering Objects"; + this.LnkPushoverObjects.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.LnkPushoverObjects_LinkClicked); + // + // cb_Pushover_Enabled + // + this.cb_Pushover_Enabled.AutoSize = true; + this.cb_Pushover_Enabled.ForeColor = System.Drawing.Color.DodgerBlue; + this.cb_Pushover_Enabled.Location = new System.Drawing.Point(8, 0); + this.cb_Pushover_Enabled.Name = "cb_Pushover_Enabled"; + this.cb_Pushover_Enabled.Size = new System.Drawing.Size(181, 17); + this.cb_Pushover_Enabled.TabIndex = 10; + this.cb_Pushover_Enabled.Text = "Send alert images to Pushover"; + this.cb_Pushover_Enabled.UseVisualStyleBackColor = true; + this.cb_Pushover_Enabled.CheckedChanged += new System.EventHandler(this.cb_Pushover_Enabled_CheckedChanged); + // + // label8 + // + this.label8.AutoSize = true; + this.label8.Location = new System.Drawing.Point(17, 23); + this.label8.Name = "label8"; + this.label8.Size = new System.Drawing.Size(32, 13); + this.label8.TabIndex = 36; + this.label8.Text = "Title:"; + // + // label21 + // + this.label21.AutoSize = true; + this.label21.Location = new System.Drawing.Point(580, 50); + this.label21.Name = "label21"; + this.label21.Size = new System.Drawing.Size(34, 13); + this.label21.TabIndex = 44; + this.label21.Text = "Time:"; + // + // tb_Pushover_Title + // + this.tb_Pushover_Title.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.tb_Pushover_Title.Location = new System.Drawing.Point(55, 20); + this.tb_Pushover_Title.Name = "tb_Pushover_Title"; + this.tb_Pushover_Title.Size = new System.Drawing.Size(219, 20); + this.tb_Pushover_Title.TabIndex = 11; + this.tb_Pushover_Title.Tag = ""; + this.toolTip1.SetToolTip(this.tb_Pushover_Title, "Specify more than one title/message/device by using the PIPE | symbol between eac" + + "h."); + // + // cb_pushover_active_time + // + this.cb_pushover_active_time.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.cb_pushover_active_time.Location = new System.Drawing.Point(622, 49); + this.cb_pushover_active_time.Name = "cb_pushover_active_time"; + this.cb_pushover_active_time.Size = new System.Drawing.Size(219, 20); + this.cb_pushover_active_time.TabIndex = 45; + this.toolTip1.SetToolTip(this.cb_pushover_active_time, "Time range (24 hr) when sending to Pushover is active"); + // + // tb_Pushover_Message + // + this.tb_Pushover_Message.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.tb_Pushover_Message.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.tb_Pushover_Message.Location = new System.Drawing.Point(340, 20); + this.tb_Pushover_Message.Name = "tb_Pushover_Message"; + this.tb_Pushover_Message.Size = new System.Drawing.Size(219, 20); + this.tb_Pushover_Message.TabIndex = 12; + this.tb_Pushover_Message.Tag = ""; + this.toolTip1.SetToolTip(this.tb_Pushover_Message, "Specify more than one title/message/device by using the PIPE | symbol between eac" + + "h."); + // + // tb_Pushover_Device + // + this.tb_Pushover_Device.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.tb_Pushover_Device.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.tb_Pushover_Device.Location = new System.Drawing.Point(622, 20); + this.tb_Pushover_Device.Name = "tb_Pushover_Device"; + this.tb_Pushover_Device.Size = new System.Drawing.Size(219, 20); + this.tb_Pushover_Device.TabIndex = 13; + this.tb_Pushover_Device.Tag = ""; + this.toolTip1.SetToolTip(this.tb_Pushover_Device, "Specify more than one title/message/device by using the PIPE | symbol between eac" + + "h."); + // + // tb_Pushover_sound + // + this.tb_Pushover_sound.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.tb_Pushover_sound.Location = new System.Drawing.Point(55, 49); + this.tb_Pushover_sound.Name = "tb_Pushover_sound"; + this.tb_Pushover_sound.Size = new System.Drawing.Size(219, 20); + this.tb_Pushover_sound.TabIndex = 14; + this.tb_Pushover_sound.Tag = ""; + this.toolTip1.SetToolTip(this.tb_Pushover_sound, "specify a sound name Pushover supports such as bike, bugle, cosmic, etc"); + // + // tb_Pushover_Priority + // + this.tb_Pushover_Priority.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.tb_Pushover_Priority.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.tb_Pushover_Priority.Location = new System.Drawing.Point(340, 49); + this.tb_Pushover_Priority.Name = "tb_Pushover_Priority"; + this.tb_Pushover_Priority.Size = new System.Drawing.Size(219, 20); + this.tb_Pushover_Priority.TabIndex = 15; + this.tb_Pushover_Priority.Tag = ""; + this.toolTip1.SetToolTip(this.tb_Pushover_Priority, "Lowest, Low, Normal, High, Emergency"); + // + // label10 + // + this.label10.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.label10.AutoSize = true; + this.label10.Location = new System.Drawing.Point(278, 23); + this.label10.Name = "label10"; + this.label10.Size = new System.Drawing.Size(55, 13); + this.label10.TabIndex = 39; + this.label10.Text = "Message:"; + // + // label16 + // + this.label16.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.label16.AutoSize = true; + this.label16.Location = new System.Drawing.Point(562, 23); + this.label16.Name = "label16"; + this.label16.Size = new System.Drawing.Size(54, 13); + this.label16.TabIndex = 39; + this.label16.Text = "Device(s):"; + // + // label18 + // + this.label18.AutoSize = true; + this.label18.Location = new System.Drawing.Point(5, 52); + this.label18.Name = "label18"; + this.label18.Size = new System.Drawing.Size(44, 13); + this.label18.TabIndex = 39; + this.label18.Text = "Sound:"; + this.toolTip1.SetToolTip(this.label18, "The name of the sound you want to play"); + // + // label19 + // + this.label19.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.label19.AutoSize = true; + this.label19.Location = new System.Drawing.Point(286, 52); + this.label19.Name = "label19"; + this.label19.Size = new System.Drawing.Size(46, 13); + this.label19.TabIndex = 39; + this.label19.Text = "Priority:"; + // + // label3 + // + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(682, 652); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(110, 13); + this.label3.TabIndex = 40; + this.label3.Text = "Cooldown Seconds:"; + // + // label14 + // + this.label14.AutoSize = true; + this.label14.Location = new System.Drawing.Point(497, 623); + this.label14.Name = "label14"; + this.label14.Size = new System.Drawing.Size(46, 13); + this.label14.TabIndex = 40; + this.label14.Text = "Params:"; + // + // tb_ActionCancelSecs + // + this.tb_ActionCancelSecs.Location = new System.Drawing.Point(803, 41); + this.tb_ActionCancelSecs.Name = "tb_ActionCancelSecs"; + this.tb_ActionCancelSecs.Size = new System.Drawing.Size(44, 22); + this.tb_ActionCancelSecs.TabIndex = 3; + this.toolTip1.SetToolTip(this.tb_ActionCancelSecs, resources.GetString("tb_ActionCancelSecs.ToolTip")); + // + // tb_jpeg_merge_quality + // + this.tb_jpeg_merge_quality.Location = new System.Drawing.Point(803, 13); + this.tb_jpeg_merge_quality.Name = "tb_jpeg_merge_quality"; + this.tb_jpeg_merge_quality.Size = new System.Drawing.Size(44, 22); + this.tb_jpeg_merge_quality.TabIndex = 3; + this.toolTip1.SetToolTip(this.tb_jpeg_merge_quality, "The larger the number, the higher the image quality AND SIZE. If you lower this" + + " number to\r\n50 or below, images will be smaller and sent to Telegram faster."); + // + // label23 + // + this.label23.AutoSize = true; + this.label23.Location = new System.Drawing.Point(666, 45); + this.label23.Name = "label23"; + this.label23.Size = new System.Drawing.Size(126, 13); + this.label23.TabIndex = 48; + this.label23.Text = "Action Cancel Seconds:"; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(620, 17); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(172, 13); + this.label2.TabIndex = 48; + this.label2.Text = "Merge JPEG Save Quality (1-100):"; + // + // cb_ShowOnlyRelevant + // + this.cb_ShowOnlyRelevant.AutoSize = true; + this.cb_ShowOnlyRelevant.ForeColor = System.Drawing.Color.DodgerBlue; + this.cb_ShowOnlyRelevant.Location = new System.Drawing.Point(14, 13); + this.cb_ShowOnlyRelevant.Name = "cb_ShowOnlyRelevant"; + this.cb_ShowOnlyRelevant.Size = new System.Drawing.Size(171, 17); + this.cb_ShowOnlyRelevant.TabIndex = 1; + this.cb_ShowOnlyRelevant.Text = "Show Only Relevant Objects"; + this.toolTip1.SetToolTip(this.cb_ShowOnlyRelevant, "(Applies to ALL cameras) - If checked, only Relevant Objects will be shown for ac" + + "tions. Note this also effects the History tab."); + this.cb_ShowOnlyRelevant.UseVisualStyleBackColor = true; + // + // cb_queue_actions + // + this.cb_queue_actions.AutoSize = true; + this.cb_queue_actions.ForeColor = System.Drawing.Color.DodgerBlue; + this.cb_queue_actions.Location = new System.Drawing.Point(237, 13); + this.cb_queue_actions.Name = "cb_queue_actions"; + this.cb_queue_actions.Size = new System.Drawing.Size(101, 17); + this.cb_queue_actions.TabIndex = 1; + this.cb_queue_actions.Text = "Queue Actions"; + this.toolTip1.SetToolTip(this.cb_queue_actions, resources.GetString("cb_queue_actions.ToolTip")); + this.cb_queue_actions.UseVisualStyleBackColor = true; + this.cb_queue_actions.CheckedChanged += new System.EventHandler(this.cb_queue_actions_CheckedChanged); + // + // cb_mergeannotations + // + this.cb_mergeannotations.AutoSize = true; + this.cb_mergeannotations.ForeColor = System.Drawing.Color.DodgerBlue; + this.cb_mergeannotations.Location = new System.Drawing.Point(395, 13); + this.cb_mergeannotations.Name = "cb_mergeannotations"; + this.cb_mergeannotations.Size = new System.Drawing.Size(189, 17); + this.cb_mergeannotations.TabIndex = 2; + this.cb_mergeannotations.Text = "Merge Annotations Into Images"; + this.toolTip1.SetToolTip(this.cb_mergeannotations, "Merge detected object text and rectangles into actual image."); + this.cb_mergeannotations.UseVisualStyleBackColor = true; + this.cb_mergeannotations.CheckedChanged += new System.EventHandler(this.cb_mergeannotations_CheckedChanged); + // + // tb_network_folder_filename + // + this.tb_network_folder_filename.Location = new System.Drawing.Point(549, 588); + this.tb_network_folder_filename.Name = "tb_network_folder_filename"; + this.tb_network_folder_filename.Size = new System.Drawing.Size(156, 22); + this.tb_network_folder_filename.TabIndex = 25; + this.toolTip1.SetToolTip(this.tb_network_folder_filename, "The filename to be created in the network folder NOT including file extension. F" + + "or example, [camera] would be saved as MYCAMERA.JPG"); + // + // tb_Sounds + // + this.tb_Sounds.Location = new System.Drawing.Point(191, 647); + this.tb_Sounds.Name = "tb_Sounds"; + this.tb_Sounds.Size = new System.Drawing.Size(465, 22); + this.tb_Sounds.TabIndex = 30; + this.toolTip1.SetToolTip(this.tb_Sounds, resources.GetString("tb_Sounds.ToolTip")); + // + // cb_PlaySound + // + this.cb_PlaySound.AutoSize = true; + this.cb_PlaySound.ForeColor = System.Drawing.Color.DodgerBlue; + this.cb_PlaySound.Location = new System.Drawing.Point(9, 649); + this.cb_PlaySound.Name = "cb_PlaySound"; + this.cb_PlaySound.Size = new System.Drawing.Size(86, 17); + this.cb_PlaySound.TabIndex = 29; + this.cb_PlaySound.Text = "Play Sound:"; + this.cb_PlaySound.UseVisualStyleBackColor = true; + this.cb_PlaySound.CheckedChanged += new System.EventHandler(this.cb_PlaySound_CheckedChanged); + // + // tb_RunExternalProgramArgs + // + this.tb_RunExternalProgramArgs.Location = new System.Drawing.Point(549, 620); + this.tb_RunExternalProgramArgs.Name = "tb_RunExternalProgramArgs"; + this.tb_RunExternalProgramArgs.Size = new System.Drawing.Size(295, 22); + this.tb_RunExternalProgramArgs.TabIndex = 28; + this.toolTip1.SetToolTip(this.tb_RunExternalProgramArgs, "Command line arguments to run the external app or script"); + // + // cb_RunProgram + // + this.cb_RunProgram.AutoSize = true; + this.cb_RunProgram.ForeColor = System.Drawing.Color.DodgerBlue; + this.cb_RunProgram.Location = new System.Drawing.Point(9, 620); + this.cb_RunProgram.Name = "cb_RunProgram"; + this.cb_RunProgram.Size = new System.Drawing.Size(141, 17); + this.cb_RunProgram.TabIndex = 26; + this.cb_RunProgram.Text = "Run external program:"; + this.cb_RunProgram.UseVisualStyleBackColor = true; + this.cb_RunProgram.CheckedChanged += new System.EventHandler(this.cb_RunProgram_CheckedChanged); + // + // cb_copyAlertImages + // + this.cb_copyAlertImages.AutoSize = true; + this.cb_copyAlertImages.ForeColor = System.Drawing.Color.DodgerBlue; + this.cb_copyAlertImages.Location = new System.Drawing.Point(9, 589); + this.cb_copyAlertImages.Margin = new System.Windows.Forms.Padding(40, 8, 7, 8); + this.cb_copyAlertImages.Name = "cb_copyAlertImages"; + this.cb_copyAlertImages.Size = new System.Drawing.Size(168, 17); + this.cb_copyAlertImages.TabIndex = 23; + this.cb_copyAlertImages.Text = "Copy alert images to folder:"; + this.toolTip1.SetToolTip(this.cb_copyAlertImages, "When an object in an image is detected, copy the image to the\r\n folder specified"); + this.cb_copyAlertImages.UseVisualStyleBackColor = false; + this.cb_copyAlertImages.CheckedChanged += new System.EventHandler(this.cb_copyAlertImages_CheckedChanged); + // + // tbCancelUrl + // + this.tbCancelUrl.Dock = System.Windows.Forms.DockStyle.Fill; + this.tbCancelUrl.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.tbCancelUrl.Location = new System.Drawing.Point(3, 18); + this.tbCancelUrl.Margin = new System.Windows.Forms.Padding(10, 6, 10, 6); + this.tbCancelUrl.Multiline = true; + this.tbCancelUrl.Name = "tbCancelUrl"; + this.tbCancelUrl.ScrollBars = System.Windows.Forms.ScrollBars.Both; + this.tbCancelUrl.Size = new System.Drawing.Size(846, 71); + this.tbCancelUrl.TabIndex = 33; + this.toolTip1.SetToolTip(this.tbCancelUrl, "URLs that cancel the alert - For BI, use "); + this.tbCancelUrl.WordWrap = false; + // + // tbTriggerUrl + // + this.tbTriggerUrl.Dock = System.Windows.Forms.DockStyle.Fill; + this.tbTriggerUrl.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.tbTriggerUrl.Location = new System.Drawing.Point(3, 18); + this.tbTriggerUrl.Margin = new System.Windows.Forms.Padding(10, 6, 10, 6); + this.tbTriggerUrl.Multiline = true; + this.tbTriggerUrl.Name = "tbTriggerUrl"; + this.tbTriggerUrl.ScrollBars = System.Windows.Forms.ScrollBars.Both; + this.tbTriggerUrl.Size = new System.Drawing.Size(846, 71); + this.tbTriggerUrl.TabIndex = 32; + this.tbTriggerUrl.Text = "test\r\ntest2\r\ntest3"; + this.toolTip1.SetToolTip(this.tbTriggerUrl, "A list of URLs each on their own line OR seperated with commas that will be trigg" + + "ered on an alert"); + this.tbTriggerUrl.WordWrap = false; + // + // label24 + // + this.label24.AutoSize = true; + this.label24.Location = new System.Drawing.Point(13, 98); + this.label24.Margin = new System.Windows.Forms.Padding(35, 0, 5, 0); + this.label24.Name = "label24"; + this.label24.Size = new System.Drawing.Size(112, 13); + this.label24.TabIndex = 0; + this.label24.Text = "[Confidence] format:"; + this.label24.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // label22 + // + this.label22.AutoSize = true; + this.label22.Location = new System.Drawing.Point(23, 70); + this.label22.Margin = new System.Windows.Forms.Padding(35, 0, 5, 0); + this.label22.Name = "label22"; + this.label22.Size = new System.Drawing.Size(103, 13); + this.label22.TabIndex = 0; + this.label22.Text = "[Detection] format:"; + this.label22.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // label5 + // + this.label5.AutoSize = true; + this.label5.Location = new System.Drawing.Point(682, 73); + this.label5.Margin = new System.Windows.Forms.Padding(35, 0, 5, 0); + this.label5.Name = "label5"; + this.label5.Size = new System.Drawing.Size(110, 13); + this.label5.TabIndex = 0; + this.label5.Text = "Cooldown Seconds:"; + this.label5.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // lbl_Confidence + // + this.lbl_Confidence.AutoSize = true; + this.lbl_Confidence.Location = new System.Drawing.Point(357, 98); + this.lbl_Confidence.Margin = new System.Windows.Forms.Padding(2, 0, 5, 0); + this.lbl_Confidence.Name = "lbl_Confidence"; + this.lbl_Confidence.Size = new System.Drawing.Size(10, 13); + this.lbl_Confidence.TabIndex = 2; + this.lbl_Confidence.Text = "."; + // + // lbl_DetectionFormat + // + this.lbl_DetectionFormat.AutoSize = true; + this.lbl_DetectionFormat.Location = new System.Drawing.Point(357, 70); + this.lbl_DetectionFormat.Margin = new System.Windows.Forms.Padding(2, 0, 5, 0); + this.lbl_DetectionFormat.Name = "lbl_DetectionFormat"; + this.lbl_DetectionFormat.Size = new System.Drawing.Size(10, 13); + this.lbl_DetectionFormat.TabIndex = 2; + this.lbl_DetectionFormat.Text = "."; + this.lbl_DetectionFormat.Click += new System.EventHandler(this.lbl_DetectionFormat_Click); + // + // tb_ConfidenceFormat + // + this.tb_ConfidenceFormat.Location = new System.Drawing.Point(134, 95); + this.tb_ConfidenceFormat.Margin = new System.Windows.Forms.Padding(10, 6, 10, 6); + this.tb_ConfidenceFormat.Name = "tb_ConfidenceFormat"; + this.tb_ConfidenceFormat.Size = new System.Drawing.Size(211, 22); + this.tb_ConfidenceFormat.TabIndex = 5; + this.toolTip1.SetToolTip(this.tb_ConfidenceFormat, "(Applies to ALL cameras)"); + this.tb_ConfidenceFormat.TextChanged += new System.EventHandler(this.tb_ConfidenceFormat_TextChanged); + // + // tb_DetectionFormat + // + this.tb_DetectionFormat.Location = new System.Drawing.Point(134, 67); + this.tb_DetectionFormat.Margin = new System.Windows.Forms.Padding(10, 6, 10, 6); + this.tb_DetectionFormat.Name = "tb_DetectionFormat"; + this.tb_DetectionFormat.Size = new System.Drawing.Size(211, 22); + this.tb_DetectionFormat.TabIndex = 4; + this.toolTip1.SetToolTip(this.tb_DetectionFormat, "This is the format for each individual detection"); + this.tb_DetectionFormat.TextChanged += new System.EventHandler(this.tb_DetectionFormat_TextChanged); + // + // tb_sound_cooldown + // + this.tb_sound_cooldown.Location = new System.Drawing.Point(800, 647); + this.tb_sound_cooldown.Margin = new System.Windows.Forms.Padding(10, 6, 10, 6); + this.tb_sound_cooldown.Name = "tb_sound_cooldown"; + this.tb_sound_cooldown.Size = new System.Drawing.Size(44, 22); + this.tb_sound_cooldown.TabIndex = 31; + // + // tb_cooldown + // + this.tb_cooldown.Location = new System.Drawing.Point(803, 70); + this.tb_cooldown.Margin = new System.Windows.Forms.Padding(10, 6, 10, 6); + this.tb_cooldown.Name = "tb_cooldown"; + this.tb_cooldown.Size = new System.Drawing.Size(44, 22); + this.tb_cooldown.TabIndex = 0; + this.toolTip1.SetToolTip(this.tb_cooldown, "Minimum time between actions. Note the actual value used is HALF of this value."); + // + // bt_variables + // + this.bt_variables.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.bt_variables.BackColor = System.Drawing.SystemColors.Info; + this.bt_variables.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.bt_variables.Location = new System.Drawing.Point(8, 700); + this.bt_variables.Name = "bt_variables"; + this.bt_variables.Size = new System.Drawing.Size(70, 30); + this.bt_variables.TabIndex = 34; + this.bt_variables.Text = "Variables"; + this.toolTip1.SetToolTip(this.bt_variables, "A list of [variable] names you can use in the settings below. This list is read" + + "-only."); + this.bt_variables.UseVisualStyleBackColor = false; + this.bt_variables.Click += new System.EventHandler(this.bt_variables_Click); + // + // tb_ActionDelayMS + // + this.tb_ActionDelayMS.Location = new System.Drawing.Point(803, 97); + this.tb_ActionDelayMS.Margin = new System.Windows.Forms.Padding(10, 6, 10, 6); + this.tb_ActionDelayMS.Name = "tb_ActionDelayMS"; + this.tb_ActionDelayMS.Size = new System.Drawing.Size(44, 22); + this.tb_ActionDelayMS.TabIndex = 0; + this.toolTip1.SetToolTip(this.tb_ActionDelayMS, "(Applies to ALL cameras) - Millisecond delay between each Action, including betwe" + + "en each trigger URL if you have more than one."); + // + // tb_NetworkFolderCleanupDays + // + this.tb_NetworkFolderCleanupDays.Location = new System.Drawing.Point(800, 588); + this.tb_NetworkFolderCleanupDays.Margin = new System.Windows.Forms.Padding(10, 6, 10, 6); + this.tb_NetworkFolderCleanupDays.Name = "tb_NetworkFolderCleanupDays"; + this.tb_NetworkFolderCleanupDays.Size = new System.Drawing.Size(44, 22); + this.tb_NetworkFolderCleanupDays.TabIndex = 31; + this.toolTip1.SetToolTip(this.tb_NetworkFolderCleanupDays, "Files in this folder than this many days will be deleted. This cleanup will hap" + + "pen only once a day."); + // + // cb_ActivateBlueIrisWindow + // + this.cb_ActivateBlueIrisWindow.AutoSize = true; + this.cb_ActivateBlueIrisWindow.ForeColor = System.Drawing.Color.DodgerBlue; + this.cb_ActivateBlueIrisWindow.Location = new System.Drawing.Point(14, 38); + this.cb_ActivateBlueIrisWindow.Name = "cb_ActivateBlueIrisWindow"; + this.cb_ActivateBlueIrisWindow.Size = new System.Drawing.Size(156, 17); + this.cb_ActivateBlueIrisWindow.TabIndex = 1; + this.cb_ActivateBlueIrisWindow.Text = "Activate Blue Iris Window"; + this.toolTip1.SetToolTip(this.cb_ActivateBlueIrisWindow, "If BlueIris is installed on the same machine it will activate its window and max" + + "imize it."); + this.cb_ActivateBlueIrisWindow.UseVisualStyleBackColor = true; + this.cb_ActivateBlueIrisWindow.CheckedChanged += new System.EventHandler(this.checkBox1_CheckedChanged); + // + // btTest + // + this.btTest.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.btTest.Location = new System.Drawing.Point(655, 701); + this.btTest.Name = "btTest"; + this.btTest.Size = new System.Drawing.Size(70, 30); + this.btTest.TabIndex = 35; + this.btTest.Text = "Test"; + this.btTest.UseVisualStyleBackColor = true; + this.btTest.Click += new System.EventHandler(this.btTest_Click); + // + // panel1 + // + this.panel1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.panel1.AutoScroll = true; + this.panel1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this.panel1.Controls.Add(this.groupBoxUrlCancel); + this.panel1.Controls.Add(this.groupBoxUrlTrigger); + this.panel1.Controls.Add(this.cb_ActivateBlueIrisWindow); + this.panel1.Controls.Add(this.cb_ShowOnlyRelevant); + this.panel1.Controls.Add(this.groupBoxMQTT); + this.panel1.Controls.Add(this.tb_ActionDelayMS); + this.panel1.Controls.Add(this.tb_cooldown); + this.panel1.Controls.Add(this.groupBoxTelegram); + this.panel1.Controls.Add(this.tb_NetworkFolderCleanupDays); + this.panel1.Controls.Add(this.tb_sound_cooldown); + this.panel1.Controls.Add(this.tb_RunExternalProgram); + this.panel1.Controls.Add(this.tb_DetectionFormat); + this.panel1.Controls.Add(this.label15); + this.panel1.Controls.Add(this.tb_ConfidenceFormat); + this.panel1.Controls.Add(this.tb_network_folder); + this.panel1.Controls.Add(this.lbl_DetectionFormat); + this.panel1.Controls.Add(this.groupBoxPushover); + this.panel1.Controls.Add(this.label6); + this.panel1.Controls.Add(this.lbl_Confidence); + this.panel1.Controls.Add(this.label3); + this.panel1.Controls.Add(this.label4); + this.panel1.Controls.Add(this.label5); + this.panel1.Controls.Add(this.label14); + this.panel1.Controls.Add(this.label22); + this.panel1.Controls.Add(this.tb_ActionCancelSecs); + this.panel1.Controls.Add(this.label24); + this.panel1.Controls.Add(this.tb_jpeg_merge_quality); + this.panel1.Controls.Add(this.label23); + this.panel1.Controls.Add(this.label2); + this.panel1.Controls.Add(this.cb_queue_actions); + this.panel1.Controls.Add(this.cb_copyAlertImages); + this.panel1.Controls.Add(this.cb_mergeannotations); + this.panel1.Controls.Add(this.cb_RunProgram); + this.panel1.Controls.Add(this.tb_network_folder_filename); + this.panel1.Controls.Add(this.tb_RunExternalProgramArgs); + this.panel1.Controls.Add(this.tb_Sounds); + this.panel1.Controls.Add(this.cb_PlaySound); + this.panel1.Location = new System.Drawing.Point(12, 12); + this.panel1.Name = "panel1"; + this.panel1.Size = new System.Drawing.Size(867, 681); + this.panel1.TabIndex = 54; + // + // groupBoxUrlCancel + // + this.groupBoxUrlCancel.Controls.Add(this.tbCancelUrl); + this.groupBoxUrlCancel.Controls.Add(this.cb_UrlCancelEnabled); + this.groupBoxUrlCancel.Location = new System.Drawing.Point(6, 487); + this.groupBoxUrlCancel.Name = "groupBoxUrlCancel"; + this.groupBoxUrlCancel.Size = new System.Drawing.Size(852, 92); + this.groupBoxUrlCancel.TabIndex = 55; + this.groupBoxUrlCancel.TabStop = false; + // + // cb_UrlCancelEnabled + // + this.cb_UrlCancelEnabled.AutoSize = true; + this.cb_UrlCancelEnabled.ForeColor = System.Drawing.Color.DodgerBlue; + this.cb_UrlCancelEnabled.Location = new System.Drawing.Point(6, 0); + this.cb_UrlCancelEnabled.Name = "cb_UrlCancelEnabled"; + this.cb_UrlCancelEnabled.Size = new System.Drawing.Size(94, 17); + this.cb_UrlCancelEnabled.TabIndex = 17; + this.cb_UrlCancelEnabled.Text = "Cancel URL(s)"; + this.cb_UrlCancelEnabled.UseVisualStyleBackColor = true; + this.cb_UrlCancelEnabled.CheckedChanged += new System.EventHandler(this.cb_CancelURLEnabled_CheckedChanged); + // + // groupBoxUrlTrigger + // + this.groupBoxUrlTrigger.Controls.Add(this.tbTriggerUrl); + this.groupBoxUrlTrigger.Controls.Add(this.cb_UrlTriggerEnabled); + this.groupBoxUrlTrigger.Location = new System.Drawing.Point(6, 389); + this.groupBoxUrlTrigger.Name = "groupBoxUrlTrigger"; + this.groupBoxUrlTrigger.Size = new System.Drawing.Size(852, 92); + this.groupBoxUrlTrigger.TabIndex = 54; + this.groupBoxUrlTrigger.TabStop = false; + // + // cb_UrlTriggerEnabled + // + this.cb_UrlTriggerEnabled.AutoSize = true; + this.cb_UrlTriggerEnabled.ForeColor = System.Drawing.Color.DodgerBlue; + this.cb_UrlTriggerEnabled.Location = new System.Drawing.Point(5, 0); + this.cb_UrlTriggerEnabled.Name = "cb_UrlTriggerEnabled"; + this.cb_UrlTriggerEnabled.Size = new System.Drawing.Size(96, 17); + this.cb_UrlTriggerEnabled.TabIndex = 17; + this.cb_UrlTriggerEnabled.Text = "Trigger URL(s)"; + this.cb_UrlTriggerEnabled.UseVisualStyleBackColor = true; + this.cb_UrlTriggerEnabled.CheckedChanged += new System.EventHandler(this.cb_TriggerURLEnabled_CheckedChanged); + // + // label6 + // + this.label6.AutoSize = true; + this.label6.Location = new System.Drawing.Point(711, 591); + this.label6.Name = "label6"; + this.label6.Size = new System.Drawing.Size(85, 13); + this.label6.TabIndex = 40; + this.label6.Text = "Max age (days):"; + // + // label4 + // + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(647, 100); + this.label4.Margin = new System.Windows.Forms.Padding(35, 0, 5, 0); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(145, 13); + this.label4.TabIndex = 0; + this.label4.Text = "Delay Between Actions MS:"; + this.label4.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // Frm_LegacyActions + // + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Inherit; + this.AutoScroll = true; + this.BackColor = System.Drawing.Color.WhiteSmoke; + this.CancelButton = this.btnCancel; + this.ClientSize = new System.Drawing.Size(888, 739); + this.Controls.Add(this.panel1); + this.Controls.Add(this.bt_variables); + this.Controls.Add(this.btnCancel); + this.Controls.Add(this.btnSave); + this.Controls.Add(this.btTest); + this.Font = new System.Drawing.Font("Segoe UI", 8.25F); + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); + this.Name = "Frm_LegacyActions"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Camera Actions"; + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.Frm_LegacyActions_FormClosing); + this.Load += new System.EventHandler(this.Frm_LegacyActions_Load); + this.groupBoxMQTT.ResumeLayout(false); + this.groupBoxMQTT.PerformLayout(); + this.groupBoxTelegram.ResumeLayout(false); + this.groupBoxTelegram.PerformLayout(); + this.groupBoxPushover.ResumeLayout(false); + this.groupBoxPushover.PerformLayout(); + this.panel1.ResumeLayout(false); + this.panel1.PerformLayout(); + this.groupBoxUrlCancel.ResumeLayout(false); + this.groupBoxUrlCancel.PerformLayout(); + this.groupBoxUrlTrigger.ResumeLayout(false); + this.groupBoxUrlTrigger.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Button btnCancel; + private System.Windows.Forms.Button btnSave; + private System.Windows.Forms.Label label5; + private System.Windows.Forms.ToolTip toolTip1; + public System.Windows.Forms.TextBox tbTriggerUrl; + public System.Windows.Forms.CheckBox cb_telegram; + public System.Windows.Forms.TextBox tb_cooldown; + public System.Windows.Forms.CheckBox cb_copyAlertImages; + public System.Windows.Forms.TextBox tb_network_folder; + public System.Windows.Forms.CheckBox cb_RunProgram; + public System.Windows.Forms.TextBox tb_RunExternalProgram; + public System.Windows.Forms.TextBox tb_RunExternalProgramArgs; + public System.Windows.Forms.TextBox tb_Sounds; + public System.Windows.Forms.CheckBox cb_PlaySound; + private System.Windows.Forms.Label label1; + public System.Windows.Forms.TextBox tb_MQTT_Payload; + public System.Windows.Forms.TextBox tb_MQTT_Topic; + public System.Windows.Forms.CheckBox cb_MQTT_enabled; + private System.Windows.Forms.LinkLabel linkLabelMqttSettings; + private System.Windows.Forms.Button btTest; + private System.Windows.Forms.Label label7; + public System.Windows.Forms.TextBox tb_telegram_caption; + public System.Windows.Forms.TextBox tb_network_folder_filename; + public System.Windows.Forms.CheckBox cb_mergeannotations; + public System.Windows.Forms.TextBox tb_MQTT_Payload_cancel; + public System.Windows.Forms.TextBox tb_MQTT_Topic_Cancel; + private System.Windows.Forms.Label label9; + public System.Windows.Forms.TextBox tbCancelUrl; + private System.Windows.Forms.Label label15; + private System.Windows.Forms.Label label14; + private System.Windows.Forms.Label label13; + private System.Windows.Forms.Label label12; + public System.Windows.Forms.CheckBox cb_queue_actions; + private System.Windows.Forms.Label label2; + public System.Windows.Forms.TextBox tb_jpeg_merge_quality; + public System.Windows.Forms.CheckBox cb_MQTT_SendImage; + private System.Windows.Forms.Label label10; + public System.Windows.Forms.TextBox tb_Pushover_Message; + public System.Windows.Forms.TextBox tb_Pushover_Title; + private System.Windows.Forms.Label label8; + public System.Windows.Forms.CheckBox cb_Pushover_Enabled; + private System.Windows.Forms.Label label16; + public System.Windows.Forms.TextBox tb_Pushover_Device; + private System.Windows.Forms.Label label18; + public System.Windows.Forms.TextBox tb_Pushover_sound; + private System.Windows.Forms.Label label19; + public System.Windows.Forms.TextBox tb_Pushover_Priority; + public System.Windows.Forms.GroupBox groupBoxPushover; + public System.Windows.Forms.GroupBox groupBoxTelegram; + public System.Windows.Forms.GroupBox groupBoxMQTT; + private System.Windows.Forms.Label label20; + public System.Windows.Forms.TextBox cb_telegram_active_time; + private System.Windows.Forms.Label label21; + public System.Windows.Forms.TextBox cb_pushover_active_time; + private System.Windows.Forms.Label label22; + public System.Windows.Forms.TextBox tb_DetectionFormat; + public System.Windows.Forms.Label lbl_DetectionFormat; + private System.Windows.Forms.Label label24; + public System.Windows.Forms.Label lbl_Confidence; + public System.Windows.Forms.TextBox tb_ConfidenceFormat; + private System.Windows.Forms.Button bt_variables; + private System.Windows.Forms.Label label3; + public System.Windows.Forms.TextBox tb_sound_cooldown; + public System.Windows.Forms.TextBox tb_ActionCancelSecs; + private System.Windows.Forms.Label label23; + public System.Windows.Forms.CheckBox cb_ShowOnlyRelevant; + private System.Windows.Forms.Panel panel1; + private System.Windows.Forms.LinkLabel LnkPushoverObjects; + private System.Windows.Forms.LinkLabel lnkTelegramTriggeringObjects; + private System.Windows.Forms.LinkLabel linkLabel1; + public System.Windows.Forms.CheckBox cb_UrlCancelEnabled; + public System.Windows.Forms.CheckBox cb_UrlTriggerEnabled; + public System.Windows.Forms.GroupBox groupBoxUrlCancel; + public System.Windows.Forms.GroupBox groupBoxUrlTrigger; + public System.Windows.Forms.TextBox tb_ActionDelayMS; + private System.Windows.Forms.Label label4; + public System.Windows.Forms.TextBox tb_NetworkFolderCleanupDays; + private System.Windows.Forms.Label label6; + private System.Windows.Forms.Label label11; + public System.Windows.Forms.CheckBox cb_ActivateBlueIrisWindow; + } +} \ No newline at end of file diff --git a/src/UI/Frm_LegacyActions.cs b/src/UI/Frm_LegacyActions.cs new file mode 100644 index 00000000..7870a6ae --- /dev/null +++ b/src/UI/Frm_LegacyActions.cs @@ -0,0 +1,296 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Windows.Forms; + +using static AITool.AITOOL; + +namespace AITool +{ + public partial class Frm_LegacyActions : Form + { + public Camera cam; + + public Frm_LegacyActions() + { + this.InitializeComponent(); + } + + private void Frm_LegacyActions_Load(object sender, EventArgs e) + { + Global_GUI.RestoreWindowState(this); + } + + private void Frm_LegacyActions_FormClosing(object sender, FormClosingEventArgs e) + { + Global_GUI.SaveWindowState(this); + } + + private void btnCancel_Click(object sender, EventArgs e) + { + this.Close(); + } + + private void btnSave_Click(object sender, EventArgs e) + { + this.DialogResult = DialogResult.OK; + this.Close(); + } + + private void linkLabelMqttSettings_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) + { + using (Frm_MQTTSettings frm = new Frm_MQTTSettings()) + { + + frm.cam = this.cam; + + frm.tb_ServerPort.Text = AppSettings.Settings.mqtt_serverandport; + frm.cb_UseTLS.Checked = AppSettings.Settings.mqtt_UseTLS; + frm.tb_Password.Text = AppSettings.Settings.mqtt_password; + frm.tb_Username.Text = AppSettings.Settings.mqtt_username; + frm.tb_ClientID.Text = AppSettings.Settings.mqtt_clientid; + + frm.tb_LWTTopic.Text = AppSettings.Settings.mqtt_LastWillTopic; + frm.tb_LWTPayload.Text = AppSettings.Settings.mqtt_LastWillPayload; + + frm.tb_Topic.Text = this.tb_MQTT_Topic.Text.Trim(); + frm.tb_Payload.Text = this.tb_MQTT_Payload.Text.Trim(); + + frm.cb_Retain.Checked = this.cam.Action_mqtt_retain_message; + + if (frm.ShowDialog() == DialogResult.OK) + { + + AppSettings.Settings.mqtt_UseTLS = frm.cb_UseTLS.Checked; + AppSettings.Settings.mqtt_username = frm.tb_Username.Text.Trim(); + AppSettings.Settings.mqtt_serverandport = frm.tb_ServerPort.Text.Trim(); + AppSettings.Settings.mqtt_password = frm.tb_Password.Text.Trim(); + AppSettings.Settings.mqtt_clientid = frm.tb_ClientID.Text.Trim(); + + AppSettings.Settings.mqtt_LastWillTopic = frm.tb_LWTTopic.Text.Trim(); + AppSettings.Settings.mqtt_LastWillPayload = frm.tb_LWTPayload.Text.Trim(); + + this.tb_MQTT_Payload.Text = frm.tb_Payload.Text.Trim(); + this.tb_MQTT_Topic.Text = frm.tb_Topic.Text.Trim(); + + this.cam.Action_mqtt_retain_message = frm.cb_Retain.Checked; + + AppSettings.SaveAsync(); + + } + } + } + + private async void btTest_Click(object sender, EventArgs e) + { + this.btnCancel.Enabled = false; + this.btnSave.Enabled = false; + this.btTest.Enabled = false; + try + { + using (Global_GUI.CursorWait cw = new Global_GUI.CursorWait()) + { + Log("----------------------- TESTING TRIGGERS ----------------------------"); + + if (!string.IsNullOrEmpty(this.cam.last_image_file_with_detections) && File.Exists(this.cam.last_image_file_with_detections)) + { + //test by copying the file as a new file into the watched folder' + string folder = Path.GetDirectoryName(this.cam.last_image_file_with_detections); + string filename = Path.GetFileNameWithoutExtension(this.cam.last_image_file_with_detections); + filename = filename.GetWord("", "_AITOOLTEST_"); + + string ext = Path.GetExtension(this.cam.last_image_file_with_detections); + string testfile = Path.Combine(folder, $"{filename}_AITOOLTEST_{DateTime.Now.TimeOfDay.TotalSeconds.Round(0)}{ext}"); + File.Copy(this.cam.last_image_file_with_detections, testfile, true); + string str = "Created test image file based on last detected object for the camera: " + testfile; + Log(str); + MessageBox.Show(str, "", MessageBoxButtons.OK, MessageBoxIcon.Information); + } + else + { + //do a generic test of the trigger + //bool result = await AITOOL.Trigger(cam, null, true); + bool result = await AITOOL.TriggerActionQueue.AddTriggerActionAsync(TriggerType.All, this.cam, null, null, true, true, null, ""); + + if (result) + { + MessageBox.Show($"Succeeded! See log for details.", "", MessageBoxButtons.OK, MessageBoxIcon.Information); + } + else + { + MessageBox.Show($"Failed. See log for details.", "", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + + } + + Log("---------------------- DONE TESTING TRIGGERS -------------------------"); + } + } + catch { } + finally + { + this.btnCancel.Enabled = true; + this.btnSave.Enabled = true; + this.btTest.Enabled = true; + + } + + } + + private void label3_Click(object sender, EventArgs e) + { + //MessageBox.Show(AITOOL.ReplaceParams(this.cam, null, null, this.label3.Text)); + } + + private void cb_mergeannotations_CheckedChanged(object sender, EventArgs e) + { + if (this.cb_mergeannotations.Checked) + this.tb_jpeg_merge_quality.Enabled = true; + else + this.tb_jpeg_merge_quality.Enabled = false; + + } + + private void cb_Pushover_Enabled_CheckedChanged(object sender, EventArgs e) + { + Global_GUI.GroupboxEnableDisable(groupBoxPushover, (CheckBox)sender); + } + + private void cb_telegram_CheckedChanged(object sender, EventArgs e) + { + Global_GUI.GroupboxEnableDisable(groupBoxTelegram, (CheckBox)sender); + } + + private void cb_MQTT_enabled_CheckedChanged(object sender, EventArgs e) + { + Global_GUI.GroupboxEnableDisable(groupBoxMQTT, (CheckBox)sender); + } + + private void cb_PlaySound_CheckedChanged(object sender, EventArgs e) + { + tb_Sounds.Enabled = cb_PlaySound.Checked; + tb_sound_cooldown.Enabled = cb_PlaySound.Checked; + } + + private void cb_RunProgram_CheckedChanged(object sender, EventArgs e) + { + tb_RunExternalProgram.Enabled = cb_RunProgram.Checked; + tb_RunExternalProgramArgs.Enabled = cb_RunProgram.Checked; + } + + private void cb_copyAlertImages_CheckedChanged(object sender, EventArgs e) + { + tb_network_folder.Enabled = cb_copyAlertImages.Checked; + tb_network_folder_filename.Enabled = cb_copyAlertImages.Checked; + } + + private void tb_DetectionFormat_TextChanged(object sender, EventArgs e) + { + Updateformat(); + } + + private void tb_ConfidenceFormat_TextChanged(object sender, EventArgs e) + { + Updateformat(); + } + + private void Updateformat() + { + try + { + lbl_Confidence.Text = string.Format(tb_ConfidenceFormat.Text.Trim(), 99.123); + if (!string.IsNullOrEmpty(tb_ConfidenceFormat.Text.Trim())) + AppSettings.Settings.DisplayPercentageFormat = tb_ConfidenceFormat.Text.Trim(); + + lbl_Confidence.ForeColor = Color.DarkGreen; + lbl_DetectionFormat.Text = AITOOL.ReplaceParams(cam, null, null, tb_DetectionFormat.Text, Global.IPType.Path); + lbl_DetectionFormat.Text = lbl_DetectionFormat.Text.Replace("[]", "").Replace("()", "").Replace(" ", " ").Replace(" ", " "); + } + catch (Exception) + { + lbl_Confidence.ForeColor = Color.Red; + } + } + + private void bt_variables_Click(object sender, EventArgs e) + { + try + { + string vars = "[Camera];[Prefix];[CamInputFolder];[InputFolder];[ImagePath];[ImagePathEscaped];[ImageFilename];[ImageFilenameNoExt];[Username];[Password];[BlueIrisServerIP];[BlueIrisURL];[SummaryNonEscaped];[Summary];[Detection];[Label];[Detail];[DetailEscaped];[Result];[Position];[Confidence];[Detections];[Confidences];[SummaryJson];[DetectionsJson];[AllJson];[PercentOfImage];%DATE%;%TIME%;%DATETIME%;%TEMP%;%APPDATA%;%USERPROFILE%;%USERNAME%"; + List varlist = vars.SplitStr(";"); + List props = new List(); + foreach (var varitm in varlist) + { + string value = AITOOL.ReplaceParams(cam, null, null, varitm, Global.IPType.URL); + props.Add(new ClsProp(varitm, value)); + } + using (Frm_Variables frm = new Frm_Variables()) + { + frm.props = props; + frm.ShowDialog(); + } + + } + catch (Exception ex) + { + + MessageBox.Show("Error: " + ex.Message); + } + } + + private void lbl_DetectionFormat_Click(object sender, EventArgs e) + { + + } + + private void LnkPushoverObjects_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) + { + using (Frm_RelevantObjects frm = new Frm_RelevantObjects()) + { + frm.ROMName = $"{cam.Name}\\{cam.PushoverTriggeringObjects.TypeName}"; + frm.ShowDialog(this); + } + } + + private void lnkTelegramTriggeringObjects_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) + { + using (Frm_RelevantObjects frm = new Frm_RelevantObjects()) + { + frm.ROMName = $"{cam.Name}\\{cam.TelegramTriggeringObjects.TypeName}"; + + frm.ShowDialog(this); + } + } + + private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) + { + using (Frm_RelevantObjects frm = new Frm_RelevantObjects()) + { + frm.ROMName = $"{cam.Name}\\{cam.MQTTTriggeringObjects.TypeName}"; + frm.ShowDialog(this); + } + } + + private void cb_TriggerURLEnabled_CheckedChanged(object sender, EventArgs e) + { + Global_GUI.GroupboxEnableDisable(groupBoxUrlTrigger, cb_UrlTriggerEnabled); + } + + private void cb_CancelURLEnabled_CheckedChanged(object sender, EventArgs e) + { + Global_GUI.GroupboxEnableDisable(groupBoxUrlCancel, cb_UrlCancelEnabled); + + } + + private void checkBox1_CheckedChanged(object sender, EventArgs e) + { + + } + + private void cb_queue_actions_CheckedChanged(object sender, EventArgs e) + { + + } + } +} diff --git a/src/UI/Frm_LegacyActions.resx b/src/UI/Frm_LegacyActions.resx new file mode 100644 index 00000000..e2ce72bd --- /dev/null +++ b/src/UI/Frm_LegacyActions.resx @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + (Applies to ALL cameras) - If you have any Cancel actions configured, this is the amount +of time before the Cancel action will be performed if there WAS +a detection but no other since then. (Note that +cancel will always be called immediately on false alerts) + + + This will send any actions to a background thread. It will allow the main detection routine +to finish without waiting for Telegram, mqtt, etc. It is possible this will affect how the +telegram cooldown feature works. + + + Play a sound based on detected object. + +Place a common between object names, a semi colon +between object names and the file, and a pipe symbol +between multiple entries. + +Examples: + +person ; C:\DADA.WAV | bird ; C:\GOBBLE.WAV +dog, cat, bird ; C:\WOOF.WAV +* ; C:\DEFAULT.WAV + + + 33 + + + + + AAABAAEAEBAAAAAAAABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAA + AAD///8A////AP///wAnqNw3////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8AKaze1Sep3OklptsM////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////ACuv3wwtrt//K6ve/yWm24r///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8AK6/fz0+85/9Muub/Jqfc7SSk2xf///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wArr9//hNPy/1W95/8uqt7/JKTbkP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8ALbLgwHDM7v+D0vL/fs7x/0q2 + 5P8kpdvxIqLaF////wD///8A////AP///wA4wufwNsDm+DW95fgzu+T4Mbjj+C+24viG1/P/L7br/0q8 + 7P+AzvH/Ubnm/yyo3f8jotqd////AP///wD///8AOsTo6XXa8v+T5vj/keP3/43g9v+K3PX/itv1/4jX + 9P+E0/L/f8/x/3zM8P96ye//SLTj/yOj2vUgntgj////ADvH6TdPzez/mOn5/0rV8/9Fz/H/QMrw/zjC + 7v+J2fT/LrTh/iyx4Pgrrt/4Kaze+Cep3Pglptv4I6Pa6f///wD///8AO8fp9IDh9f+O5vj/Q9Lz/z/N + 8f85x+//jNz1/1jG6v8utOF0////AP///wD///8A////AP///wD///8A////AD3J6lRb1O//mer5/0fW + 9P9C0PL/Pcvw/27V8/9/1/P/SsDn/y+04Ur///8A////AP///wD///8A////AP///wD///8APcrq+ZPp + +f9y4ff/RdTz/0HO8v88yfD/itz1/3DQ7/89u+T/L7XhI////wD///8A////AP///wD///8A////AD/M + 64Nk2fH/muv6/0jY9P9E0/P/P87x/zrI8P+M3PX/Ysvt/zG44/z///8A////AP///wD///8A////AP// + /wD///8AP8zr/Zrt+v+Z6/n/l+j5/5Tl+P+R4vf/jt/2/4vb9f9Wx+v/Mbjj8////wD///8A////AP// + /wD///8A////AEDO7KI/zOv/Psvq/zzJ6f87x+n/OsTo/zjC5/82wOb/Nb3l/zO75P8xuOPh////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A//8AAOf/AADx/wAA8P8AAPg/AAD4HwAAAAcAAAADAACAAQAAgH8AAMA/AADAHwAAwA8AAOAH + AADgAwAA//8AAA== + + + \ No newline at end of file diff --git a/src/UI/Frm_MQTTSettings.Designer.cs b/src/UI/Frm_MQTTSettings.Designer.cs new file mode 100644 index 00000000..5f16f827 --- /dev/null +++ b/src/UI/Frm_MQTTSettings.Designer.cs @@ -0,0 +1,343 @@ +namespace AITool +{ + partial class Frm_MQTTSettings + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.btnCancel = new System.Windows.Forms.Button(); + this.btnSave = new System.Windows.Forms.Button(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.groupBox2 = new System.Windows.Forms.GroupBox(); + this.label6 = new System.Windows.Forms.Label(); + this.tb_Payload = new System.Windows.Forms.TextBox(); + this.tb_Topic = new System.Windows.Forms.TextBox(); + this.label4 = new System.Windows.Forms.Label(); + this.label5 = new System.Windows.Forms.Label(); + this.tb_LWTPayload = new System.Windows.Forms.TextBox(); + this.tb_Password = new System.Windows.Forms.TextBox(); + this.tb_ClientID = new System.Windows.Forms.TextBox(); + this.tb_LWTTopic = new System.Windows.Forms.TextBox(); + this.tb_Username = new System.Windows.Forms.TextBox(); + this.tb_ServerPort = new System.Windows.Forms.TextBox(); + this.cb_Retain = new System.Windows.Forms.CheckBox(); + this.cb_UseTLS = new System.Windows.Forms.CheckBox(); + this.label8 = new System.Windows.Forms.Label(); + this.label3 = new System.Windows.Forms.Label(); + this.label9 = new System.Windows.Forms.Label(); + this.label7 = new System.Windows.Forms.Label(); + this.label2 = new System.Windows.Forms.Label(); + this.label1 = new System.Windows.Forms.Label(); + this.btTest = new System.Windows.Forms.Button(); + this.groupBox1.SuspendLayout(); + this.groupBox2.SuspendLayout(); + this.SuspendLayout(); + // + // btnCancel + // + this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.btnCancel.Location = new System.Drawing.Point(431, 322); + this.btnCancel.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.btnCancel.Name = "btnCancel"; + this.btnCancel.Size = new System.Drawing.Size(70, 30); + this.btnCancel.TabIndex = 8; + this.btnCancel.Text = "Cancel"; + this.btnCancel.UseVisualStyleBackColor = true; + this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click); + // + // btnSave + // + this.btnSave.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.btnSave.Location = new System.Drawing.Point(353, 322); + this.btnSave.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.btnSave.Name = "btnSave"; + this.btnSave.Size = new System.Drawing.Size(70, 30); + this.btnSave.TabIndex = 7; + this.btnSave.Text = "Save"; + this.btnSave.UseVisualStyleBackColor = true; + this.btnSave.Click += new System.EventHandler(this.btnSave_Click); + // + // groupBox1 + // + this.groupBox1.Controls.Add(this.groupBox2); + this.groupBox1.Controls.Add(this.tb_LWTPayload); + this.groupBox1.Controls.Add(this.tb_Password); + this.groupBox1.Controls.Add(this.tb_ClientID); + this.groupBox1.Controls.Add(this.tb_LWTTopic); + this.groupBox1.Controls.Add(this.tb_Username); + this.groupBox1.Controls.Add(this.tb_ServerPort); + this.groupBox1.Controls.Add(this.cb_Retain); + this.groupBox1.Controls.Add(this.cb_UseTLS); + this.groupBox1.Controls.Add(this.label8); + this.groupBox1.Controls.Add(this.label3); + this.groupBox1.Controls.Add(this.label9); + this.groupBox1.Controls.Add(this.label7); + this.groupBox1.Controls.Add(this.label2); + this.groupBox1.Controls.Add(this.label1); + this.groupBox1.Location = new System.Drawing.Point(8, 12); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.Size = new System.Drawing.Size(493, 302); + this.groupBox1.TabIndex = 6; + this.groupBox1.TabStop = false; + // + // groupBox2 + // + this.groupBox2.Controls.Add(this.label6); + this.groupBox2.Controls.Add(this.tb_Payload); + this.groupBox2.Controls.Add(this.tb_Topic); + this.groupBox2.Controls.Add(this.label4); + this.groupBox2.Controls.Add(this.label5); + this.groupBox2.Location = new System.Drawing.Point(10, 168); + this.groupBox2.Name = "groupBox2"; + this.groupBox2.Size = new System.Drawing.Size(471, 128); + this.groupBox2.TabIndex = 5; + this.groupBox2.TabStop = false; + this.groupBox2.Text = "Testing"; + // + // label6 + // + this.label6.AutoSize = true; + this.label6.ForeColor = System.Drawing.Color.DodgerBlue; + this.label6.Location = new System.Drawing.Point(9, 19); + this.label6.Name = "label6"; + this.label6.Size = new System.Drawing.Size(424, 15); + this.label6.TabIndex = 6; + this.label6.Text = "Specify more than one topic/payload by using the PIPE | symbol between each."; + // + // tb_Payload + // + this.tb_Payload.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.tb_Payload.Location = new System.Drawing.Point(69, 69); + this.tb_Payload.Multiline = true; + this.tb_Payload.Name = "tb_Payload"; + this.tb_Payload.Size = new System.Drawing.Size(395, 51); + this.tb_Payload.TabIndex = 5; + // + // tb_Topic + // + this.tb_Topic.Location = new System.Drawing.Point(70, 40); + this.tb_Topic.Name = "tb_Topic"; + this.tb_Topic.Size = new System.Drawing.Size(395, 23); + this.tb_Topic.TabIndex = 4; + // + // label4 + // + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(23, 43); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(38, 15); + this.label4.TabIndex = 0; + this.label4.Text = "Topic:"; + // + // label5 + // + this.label5.AutoSize = true; + this.label5.Location = new System.Drawing.Point(9, 69); + this.label5.Name = "label5"; + this.label5.Size = new System.Drawing.Size(52, 15); + this.label5.TabIndex = 0; + this.label5.Text = "Payload:"; + // + // tb_LWTPayload + // + this.tb_LWTPayload.Location = new System.Drawing.Point(311, 78); + this.tb_LWTPayload.Name = "tb_LWTPayload"; + this.tb_LWTPayload.Size = new System.Drawing.Size(169, 23); + this.tb_LWTPayload.TabIndex = 2; + // + // tb_Password + // + this.tb_Password.Location = new System.Drawing.Point(312, 50); + this.tb_Password.Name = "tb_Password"; + this.tb_Password.Size = new System.Drawing.Size(169, 23); + this.tb_Password.TabIndex = 2; + // + // tb_ClientID + // + this.tb_ClientID.Location = new System.Drawing.Point(78, 107); + this.tb_ClientID.Name = "tb_ClientID"; + this.tb_ClientID.Size = new System.Drawing.Size(161, 23); + this.tb_ClientID.TabIndex = 1; + // + // tb_LWTTopic + // + this.tb_LWTTopic.Location = new System.Drawing.Point(78, 79); + this.tb_LWTTopic.Name = "tb_LWTTopic"; + this.tb_LWTTopic.Size = new System.Drawing.Size(161, 23); + this.tb_LWTTopic.TabIndex = 1; + // + // tb_Username + // + this.tb_Username.Location = new System.Drawing.Point(79, 51); + this.tb_Username.Name = "tb_Username"; + this.tb_Username.Size = new System.Drawing.Size(161, 23); + this.tb_Username.TabIndex = 1; + // + // tb_ServerPort + // + this.tb_ServerPort.Location = new System.Drawing.Point(79, 22); + this.tb_ServerPort.Name = "tb_ServerPort"; + this.tb_ServerPort.Size = new System.Drawing.Size(402, 23); + this.tb_ServerPort.TabIndex = 0; + // + // cb_Retain + // + this.cb_Retain.AutoSize = true; + this.cb_Retain.Location = new System.Drawing.Point(77, 143); + this.cb_Retain.Name = "cb_Retain"; + this.cb_Retain.Size = new System.Drawing.Size(59, 19); + this.cb_Retain.TabIndex = 3; + this.cb_Retain.Text = "Retain"; + this.cb_Retain.UseVisualStyleBackColor = true; + this.cb_Retain.CheckedChanged += new System.EventHandler(this.cb_UseTLS_CheckedChanged); + // + // cb_UseTLS + // + this.cb_UseTLS.AutoSize = true; + this.cb_UseTLS.Location = new System.Drawing.Point(9, 143); + this.cb_UseTLS.Name = "cb_UseTLS"; + this.cb_UseTLS.Size = new System.Drawing.Size(66, 19); + this.cb_UseTLS.TabIndex = 3; + this.cb_UseTLS.Text = "Use TLS"; + this.cb_UseTLS.UseVisualStyleBackColor = true; + this.cb_UseTLS.CheckedChanged += new System.EventHandler(this.cb_UseTLS_CheckedChanged); + // + // label8 + // + this.label8.AutoSize = true; + this.label8.Location = new System.Drawing.Point(253, 82); + this.label8.Name = "label8"; + this.label8.Size = new System.Drawing.Size(52, 15); + this.label8.TabIndex = 0; + this.label8.Text = "Payload:"; + // + // label3 + // + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(246, 54); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(60, 15); + this.label3.TabIndex = 0; + this.label3.Text = "Password:"; + // + // label9 + // + this.label9.AutoSize = true; + this.label9.Location = new System.Drawing.Point(18, 110); + this.label9.Name = "label9"; + this.label9.Size = new System.Drawing.Size(55, 15); + this.label9.TabIndex = 0; + this.label9.Text = "Client ID:"; + // + // label7 + // + this.label7.AutoSize = true; + this.label7.Location = new System.Drawing.Point(10, 82); + this.label7.Name = "label7"; + this.label7.Size = new System.Drawing.Size(63, 15); + this.label7.TabIndex = 0; + this.label7.Text = "LWT Topic:"; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(10, 54); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(63, 15); + this.label2.TabIndex = 0; + this.label2.Text = "Username:"; + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(6, 25); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(67, 15); + this.label1.TabIndex = 0; + this.label1.Text = "Server:Port:"; + // + // btTest + // + this.btTest.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.btTest.Location = new System.Drawing.Point(276, 322); + this.btTest.Name = "btTest"; + this.btTest.Size = new System.Drawing.Size(70, 30); + this.btTest.TabIndex = 6; + this.btTest.Text = "Test"; + this.btTest.UseVisualStyleBackColor = true; + this.btTest.Click += new System.EventHandler(this.btTest_ClickAsync); + // + // Frm_MQTTSettings + // + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Inherit; + this.CancelButton = this.btnCancel; + this.ClientSize = new System.Drawing.Size(513, 366); + this.Controls.Add(this.btTest); + this.Controls.Add(this.groupBox1); + this.Controls.Add(this.btnCancel); + this.Controls.Add(this.btnSave); + this.Font = new System.Drawing.Font("Segoe UI", 8.25F); + this.Name = "Frm_MQTTSettings"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "MQTT Settings"; + this.Load += new System.EventHandler(this.Frm_MQTTSettings_Load); + this.groupBox1.ResumeLayout(false); + this.groupBox1.PerformLayout(); + this.groupBox2.ResumeLayout(false); + this.groupBox2.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Button btnCancel; + private System.Windows.Forms.Button btnSave; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.GroupBox groupBox2; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Button btTest; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.Label label5; + public System.Windows.Forms.TextBox tb_Payload; + public System.Windows.Forms.TextBox tb_Topic; + public System.Windows.Forms.TextBox tb_Password; + public System.Windows.Forms.TextBox tb_Username; + public System.Windows.Forms.TextBox tb_ServerPort; + public System.Windows.Forms.CheckBox cb_UseTLS; + private System.Windows.Forms.Label label6; + public System.Windows.Forms.TextBox tb_LWTPayload; + public System.Windows.Forms.TextBox tb_LWTTopic; + private System.Windows.Forms.Label label8; + private System.Windows.Forms.Label label7; + public System.Windows.Forms.CheckBox cb_Retain; + public System.Windows.Forms.TextBox tb_ClientID; + private System.Windows.Forms.Label label9; + } +} \ No newline at end of file diff --git a/src/UI/Frm_MQTTSettings.cs b/src/UI/Frm_MQTTSettings.cs new file mode 100644 index 00000000..3e275171 --- /dev/null +++ b/src/UI/Frm_MQTTSettings.cs @@ -0,0 +1,132 @@ +using MQTTnet.Client; //.Publishing; + +using System; +using System.Collections.Generic; +using System.IO; +using System.Windows.Forms; + +using static AITool.AITOOL; + +namespace AITool +{ + public partial class Frm_MQTTSettings : Form + { + + public Camera cam; + + public Frm_MQTTSettings() + { + this.InitializeComponent(); + } + + private void btnSave_Click(object sender, EventArgs e) + { + this.DialogResult = DialogResult.OK; + this.Close(); + } + + private void btnCancel_Click(object sender, EventArgs e) + { + this.Close(); + } + + private async void btTest_ClickAsync(object sender, EventArgs e) + { + this.btTest.Enabled = false; + this.btnSave.Enabled = false; + this.btnCancel.Enabled = false; + + try + { + AppSettings.Settings.mqtt_serverandport = this.tb_ServerPort.Text.Trim(); + AppSettings.Settings.mqtt_password = this.tb_Password.Text.Trim(); + AppSettings.Settings.mqtt_username = this.tb_Username.Text.Trim(); + AppSettings.Settings.mqtt_UseTLS = this.cb_UseTLS.Checked; + AppSettings.Settings.mqtt_clientid = this.tb_ClientID.Text.Trim(); + + this.cam.Action_mqtt_retain_message = this.cb_Retain.Checked; + + using (Global_GUI.CursorWait cw = new Global_GUI.CursorWait()) + { + Log("------ TESTING MQTT --------"); + + + string topic = AITOOL.ReplaceParams(this.cam, null, null, this.tb_Topic.Text.Trim(), Global.IPType.Path); + string payload = AITOOL.ReplaceParams(this.cam, null, null, this.tb_Payload.Text.Trim(), Global.IPType.Path); + + List topics = topic.SplitStr("|"); + List payloads = payload.SplitStr("|"); + + MqttClientPublishResult pr = null; + ClsImageQueueItem CurImg = null; + + + for (int i = 0; i < topics.Count; i++) + { + if (this.cam.Action_mqtt_send_image) + { + if (topics[i].IndexOf("/image", StringComparison.OrdinalIgnoreCase) >= 0) + { + if (!string.IsNullOrEmpty(this.cam.last_image_file_with_detections) && File.Exists(this.cam.last_image_file_with_detections)) + { + CurImg = new ClsImageQueueItem(this.cam.last_image_file_with_detections, 0); + } + else if (!string.IsNullOrEmpty(this.cam.last_image_file) && File.Exists(this.cam.last_image_file)) + { + CurImg = new ClsImageQueueItem(this.cam.last_image_file, 0); + } + else + CurImg = null; + } + else + CurImg = null; + + } + + pr = await mqttClient.PublishAsync(topics[i], payloads[i], this.cam.Action_mqtt_retain_message, CurImg); + + } + + Log("------ DONE TESTING MQTT --------"); + + if (pr != null && (pr.ReasonCode == MqttClientPublishReasonCode.Success)) + { + MessageBox.Show("Success! See Log for details."); + } + else if (pr != null) + { + MessageBox.Show($"Failed. See log for details. Reason={pr.ReasonCode}", "", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + else + { + MessageBox.Show($"Failed. See log for details.", "", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + + } + + + } + catch + { + + } + finally + { + this.btTest.Enabled = true; + this.btnSave.Enabled = true; + this.btnCancel.Enabled = true; + } + + } + + private void Frm_MQTTSettings_Load(object sender, EventArgs e) + { + + } + + private void cb_UseTLS_CheckedChanged(object sender, EventArgs e) + { + + } + } +} diff --git a/src/UI/Frm_MQTTSettings.resx b/src/UI/Frm_MQTTSettings.resx new file mode 100644 index 00000000..1af7de15 --- /dev/null +++ b/src/UI/Frm_MQTTSettings.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/UI/Frm_ObjectDetail.Designer.cs b/src/UI/Frm_ObjectDetail.Designer.cs new file mode 100644 index 00000000..1f106619 --- /dev/null +++ b/src/UI/Frm_ObjectDetail.Designer.cs @@ -0,0 +1,180 @@ +namespace AITool +{ + partial class Frm_ObjectDetail + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + components = new System.ComponentModel.Container(); + folv_ObjectDetail = new BrightIdeasSoftware.FastObjectListView(); + contextMenuStripMask = new System.Windows.Forms.ContextMenuStrip(components); + createStaticMasksToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + splitContainer1 = new System.Windows.Forms.SplitContainer(); + splitContainer2 = new System.Windows.Forms.SplitContainer(); + pictureBox2 = new System.Windows.Forms.PictureBox(); + pictureBox1 = new System.Windows.Forms.PictureBox(); + ((System.ComponentModel.ISupportInitialize)folv_ObjectDetail).BeginInit(); + contextMenuStripMask.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)splitContainer1).BeginInit(); + splitContainer1.Panel1.SuspendLayout(); + splitContainer1.Panel2.SuspendLayout(); + splitContainer1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)splitContainer2).BeginInit(); + splitContainer2.Panel1.SuspendLayout(); + splitContainer2.Panel2.SuspendLayout(); + splitContainer2.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)pictureBox2).BeginInit(); + ((System.ComponentModel.ISupportInitialize)pictureBox1).BeginInit(); + SuspendLayout(); + // + // folv_ObjectDetail + // + folv_ObjectDetail.ContextMenuStrip = contextMenuStripMask; + folv_ObjectDetail.Dock = System.Windows.Forms.DockStyle.Fill; + folv_ObjectDetail.Location = new System.Drawing.Point(0, 0); + folv_ObjectDetail.Name = "folv_ObjectDetail"; + folv_ObjectDetail.ShowGroups = false; + folv_ObjectDetail.Size = new System.Drawing.Size(316, 232); + folv_ObjectDetail.TabIndex = 5; + folv_ObjectDetail.UseCompatibleStateImageBehavior = false; + folv_ObjectDetail.View = System.Windows.Forms.View.Details; + folv_ObjectDetail.VirtualMode = true; + folv_ObjectDetail.FormatRow += folv_ObjectDetail_FormatRow; + folv_ObjectDetail.SelectionChanged += folv_ObjectDetail_SelectionChanged; + folv_ObjectDetail.SelectedIndexChanged += folv_ObjectDetail_SelectedIndexChanged; + // + // contextMenuStripMask + // + contextMenuStripMask.ImageScalingSize = new System.Drawing.Size(24, 24); + contextMenuStripMask.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { createStaticMasksToolStripMenuItem }); + contextMenuStripMask.Name = "contextMenuStripMask"; + contextMenuStripMask.Size = new System.Drawing.Size(185, 26); + // + // createStaticMasksToolStripMenuItem + // + createStaticMasksToolStripMenuItem.Name = "createStaticMasksToolStripMenuItem"; + createStaticMasksToolStripMenuItem.Size = new System.Drawing.Size(184, 22); + createStaticMasksToolStripMenuItem.Text = "Create Static Mask(s)"; + createStaticMasksToolStripMenuItem.Click += createStaticMasksToolStripMenuItem_Click; + // + // splitContainer1 + // + splitContainer1.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; + splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill; + splitContainer1.Location = new System.Drawing.Point(0, 0); + splitContainer1.Name = "splitContainer1"; + // + // splitContainer1.Panel1 + // + splitContainer1.Panel1.Controls.Add(splitContainer2); + // + // splitContainer1.Panel2 + // + splitContainer1.Panel2.AutoScroll = true; + splitContainer1.Panel2.Controls.Add(pictureBox1); + splitContainer1.Size = new System.Drawing.Size(962, 512); + splitContainer1.SplitterDistance = 320; + splitContainer1.TabIndex = 6; + // + // splitContainer2 + // + splitContainer2.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; + splitContainer2.Dock = System.Windows.Forms.DockStyle.Fill; + splitContainer2.Location = new System.Drawing.Point(0, 0); + splitContainer2.Name = "splitContainer2"; + splitContainer2.Orientation = System.Windows.Forms.Orientation.Horizontal; + // + // splitContainer2.Panel1 + // + splitContainer2.Panel1.Controls.Add(folv_ObjectDetail); + // + // splitContainer2.Panel2 + // + splitContainer2.Panel2.Controls.Add(pictureBox2); + splitContainer2.Size = new System.Drawing.Size(320, 512); + splitContainer2.SplitterDistance = 236; + splitContainer2.TabIndex = 6; + // + // pictureBox2 + // + pictureBox2.Dock = System.Windows.Forms.DockStyle.Fill; + pictureBox2.Location = new System.Drawing.Point(0, 0); + pictureBox2.Name = "pictureBox2"; + pictureBox2.Size = new System.Drawing.Size(316, 268); + pictureBox2.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + pictureBox2.TabIndex = 0; + pictureBox2.TabStop = false; + // + // pictureBox1 + // + pictureBox1.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + pictureBox1.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Zoom; + pictureBox1.Location = new System.Drawing.Point(0, 0); + pictureBox1.Name = "pictureBox1"; + pictureBox1.Size = new System.Drawing.Size(634, 508); + pictureBox1.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + pictureBox1.TabIndex = 0; + pictureBox1.TabStop = false; + pictureBox1.Click += pictureBox1_Click; + pictureBox1.Paint += pictureBox1_Paint; + // + // Frm_ObjectDetail + // + AutoScaleMode = System.Windows.Forms.AutoScaleMode.Inherit; + ClientSize = new System.Drawing.Size(962, 512); + Controls.Add(splitContainer1); + Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); + Name = "Frm_ObjectDetail"; + StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + Tag = "SAVE"; + Text = "Prediction Object Detail"; + FormClosing += Frm_ObjectDetail_FormClosing; + Load += Frm_ObjectDetail_Load; + ((System.ComponentModel.ISupportInitialize)folv_ObjectDetail).EndInit(); + contextMenuStripMask.ResumeLayout(false); + splitContainer1.Panel1.ResumeLayout(false); + splitContainer1.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)splitContainer1).EndInit(); + splitContainer1.ResumeLayout(false); + splitContainer2.Panel1.ResumeLayout(false); + splitContainer2.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)splitContainer2).EndInit(); + splitContainer2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)pictureBox2).EndInit(); + ((System.ComponentModel.ISupportInitialize)pictureBox1).EndInit(); + ResumeLayout(false); + } + + #endregion + public BrightIdeasSoftware.FastObjectListView folv_ObjectDetail; + private System.Windows.Forms.ContextMenuStrip contextMenuStripMask; + private System.Windows.Forms.ToolStripMenuItem createStaticMasksToolStripMenuItem; + private System.Windows.Forms.SplitContainer splitContainer1; + private System.Windows.Forms.PictureBox pictureBox1; + private System.Windows.Forms.SplitContainer splitContainer2; + private System.Windows.Forms.PictureBox pictureBox2; + } +} \ No newline at end of file diff --git a/src/UI/Frm_ObjectDetail.cs b/src/UI/Frm_ObjectDetail.cs new file mode 100644 index 00000000..e81313f5 --- /dev/null +++ b/src/UI/Frm_ObjectDetail.cs @@ -0,0 +1,438 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Runtime.InteropServices.ComTypes; +using System.Windows.Forms; + + +using static AITool.AITOOL; + +namespace AITool +{ + public partial class Frm_ObjectDetail:Form + { + public List PredictionObjectDetailsList = null; + public ClsPrediction CurPred = null; + public string ImageFileName = ""; + // this tracks the transformation applied to the PictureBox's Graphics + private Matrix transform = new Matrix(); + private float m_dZoomscale = 1.0f; + public const float s_dScrollValue = 0.1f; + private ClsImageQueueItem OriginalBMP = null; + public Frm_ObjectDetail() + { + this.InitializeComponent(); + } + + private void button1_Click(object sender, EventArgs e) + { + + + } + + private void Frm_ObjectDetail_Load(object sender, EventArgs e) + { + Global_GUI.RestoreWindowState(this); + + + this.Show(); + + try + { + Global_GUI.ConfigureFOLV(this.folv_ObjectDetail, typeof(ClsPrediction), null, null); + + Global_GUI.UpdateFOLV(this.folv_ObjectDetail, this.PredictionObjectDetailsList); + + if (this.PredictionObjectDetailsList.Count > 0) + CurPred = this.PredictionObjectDetailsList[0]; + + UpdateImage(); + } + catch (Exception) + { + + throw; + } + + } + + private string _lastimage = ""; + private void UpdateImage() + { + if (this.ImageFileName == _lastimage) + return; + + if (!String.IsNullOrEmpty(this.ImageFileName) && this.ImageFileName.Contains("\\") && File.Exists(this.ImageFileName)) + { + OriginalBMP = new ClsImageQueueItem(this.ImageFileName, 0); + this.pictureBox1.Image = OriginalBMP.ToImage(); //load actual image as background, so that an overlay can be added as the image + _lastimage = this.ImageFileName; + } + + } + + private void Frm_ObjectDetail_FormClosing(object sender, FormClosingEventArgs e) + { + Global_GUI.SaveWindowState(this); + } + + private void folv_ObjectDetail_FormatRow(object sender, BrightIdeasSoftware.FormatRowEventArgs e) + { + this.FormatRow(sender, e); + } + + private async void FormatRow(object Sender, BrightIdeasSoftware.FormatRowEventArgs e) + { + try + { + ClsPrediction OP = (ClsPrediction)e.Model; + + // If SPI IsNot Nothing Then + if (OP.Result == ResultType.Relevant) + e.Item.ForeColor = Color.DarkGreen; //AppSettings.Settings.RectRelevantColor; + else if (OP.Result == ResultType.DynamicMasked || OP.Result == ResultType.ImageMasked || OP.Result == ResultType.StaticMasked) + e.Item.ForeColor = AppSettings.Settings.RectMaskedColor; + else if (OP.Result == ResultType.Error) + { + e.Item.ForeColor = Color.Black; + e.Item.BackColor = Color.Red; + } + else + e.Item.ForeColor = AppSettings.Settings.RectIrrelevantColor; + } + + + + catch (Exception) + { + } + finally + { + } + } + + private void createStaticMasksToolStripMenuItem_Click(object sender, EventArgs e) + { + + int cnt = 0; + if (this.folv_ObjectDetail.SelectedObjects != null && this.folv_ObjectDetail.SelectedObjects.Count > 0) + { + foreach (ClsPrediction CP in this.folv_ObjectDetail.SelectedObjects) + { + if (string.IsNullOrEmpty(CP.Camera)) + Log("Error: Can only add newer history prediction items that include cameraname, imagewidth, imageheight."); + else + { + ObjectPosition OP = new ObjectPosition(CP.XMin, CP.XMax, CP.YMin, CP.YMax, CP.Label, CP.ImageHeight, CP.ImageWidth, CP.Camera, CP.Filename); + Camera cam = GetCamera(CP.Camera); + cam.maskManager.CreateDynamicMask(OP, true); + cnt++; + } + + } + } + Log($"Added/updated {cnt} masks."); + } + + private void pictureBox1_Paint(object sender, PaintEventArgs e) + { + + if (this.pictureBox1.Image.IsNull()) + return; + + if (this.folv_ObjectDetail.SelectedObjects != null && this.folv_ObjectDetail.SelectedObjects.Count > 0) //if checkbox button is enabled + { + + try + { + + foreach (ClsPrediction pred in this.folv_ObjectDetail.SelectedObjects) + { + if (pred != null) + { + AITOOL.DrawAnnotation(e.Graphics, + pred, + this.pictureBox1.Image.Width, + this.pictureBox1.Image.Height, + this.pictureBox1.Width, + this.pictureBox1.Height); + + pictureBox2.Image = AITOOL.CropImage(OriginalBMP, pred.GetRectangle()); + + } + + } + + + + } + catch (Exception ex) + { + + } + + } + + } + + // private void showObject(PaintEventArgs e, ClsPrediction pred) + // { + // try + // { + // if ((this.pictureBox1 != null) && (this.pictureBox1.Image != null)) + // { + + // e.Graphics.Transform = transform; + + // System.Drawing.Color color = new System.Drawing.Color(); + // int BorderWidth = AppSettings.Settings.RectBorderWidth + //; + + // if (pred.Result == ResultType.Relevant) + // { + // color = System.Drawing.Color.FromArgb(AppSettings.Settings.RectRelevantColorAlpha, AppSettings.Settings.RectRelevantColor); + // } + // else if (pred.Result == ResultType.DynamicMasked || pred.Result == ResultType.ImageMasked || pred.Result == ResultType.StaticMasked) + // { + // color = System.Drawing.Color.FromArgb(AppSettings.Settings.RectMaskedColorAlpha, AppSettings.Settings.RectMaskedColor); + // } + // else + // { + // color = System.Drawing.Color.FromArgb(AppSettings.Settings.RectIrrelevantColorAlpha, AppSettings.Settings.RectIrrelevantColor); + // } + + // //1. get the padding between the image and the picturebox border + + // //get dimensions of the image and the picturebox + // double imgWidth = this.pictureBox1.Image.Width; + // double imgHeight = this.pictureBox1.Image.Height; + // double boxWidth = this.pictureBox1.Width; + // double boxHeight = this.pictureBox1.Height; + // double clnWidth = this.pictureBox1.ClientSize.Width; + // double clnHeight = this.pictureBox1.ClientSize.Height; + // double rctWidth = this.pictureBox1.ClientRectangle.Width; + // double rctHeight = this.pictureBox1.ClientRectangle.Height; + + // //these variables store the padding between image border and picturebox border + // double absX = 0; + // double absY = 0; + + // //because the sizemode of the picturebox is set to 'zoom', the image is scaled down + // double scale = 1; + + + // //Comparing the aspect ratio of both the control and the image itself. + // if (imgWidth / imgHeight > boxWidth / boxHeight) //if the image is p.e. 16:9 and the picturebox is 4:3 + // { + // scale = boxWidth / imgWidth; //get scale factor + // absY = (boxHeight - scale * imgHeight) / 2; //padding on top and below the image + // } + // else //if the image is p.e. 4:3 and the picturebox is widescreen 16:9 + // { + // scale = boxHeight / imgHeight; //get scale factor + // absX = (boxWidth - scale * imgWidth) / 2; //padding left and right of the image + // } + + // //2. inputted position values are for the original image size. As the image is probably smaller in the picturebox, the positions must be adapted. + // double xmin = (scale * pred.XMin) + absX; + // double xmax = (scale * pred.XMax) + absX; + // double ymin = (scale * pred.YMin) + absY; + // double ymax = (scale * pred.YMax) + absY; + + // double sclWidth = xmax - xmin; + // double sclHeight = ymax - ymin; + + // double sclxmax = boxWidth - (absX * 2); + // double sclymax = boxHeight - (absY * 2); + // double sclxmin = absX; + // double sclymin = absY; + + // //3. paint rectangle + // System.Drawing.Rectangle rect = new System.Drawing.Rectangle(xmin.ToInt(), + // ymin.ToInt(), + // sclWidth.ToInt(), + // sclHeight.ToInt()); + + // //pictureBox2.Image = AITOOL.CropImage(OriginalBMP, pred.GetRectangle()); + + // using (Pen pen = new Pen(color, BorderWidth)) + // { + // e.Graphics.DrawRectangle(pen, rect); //draw rectangle + // } + + + // ///testing================================================= + // //3. paint rectangle + // //rect = new System.Drawing.Rectangle(absX + 5, + // // absY + 5, + // // sclxmax - 10, + // // sclymax - 10); + + // //using (Pen pen = new Pen(Color.Red, BorderWidth)) + // //{ + // // e.Graphics.DrawRectangle(pen, rect); //draw rectangle + // //} + // ///testing================================================= + + // //we need this since people can change the border width in the json file + // double halfbrd = BorderWidth / 2; + + + + // System.Drawing.SizeF TextSize = e.Graphics.MeasureString(pred.ToString(), new Font(AppSettings.Settings.RectDetectionTextFont, AppSettings.Settings.RectDetectionTextSize)); //finds size of text to draw the background rectangle + + + // //object name text below rectangle + + // double x = xmin - halfbrd; + // double y = ymax + halfbrd; + + // //just for debugging: + // //int timgWidth = (int)imgWidth; + // //int tboxWidth = (int)boxWidth; + // //int tsclWidth = (int)sclWidth; + + // //int timgHeight = (int)imgHeight; + // //int tboxHeight = (int)boxHeight; + // //int tsclHeight = (int)sclHeight; + + + // //adjust the x / width label so it doesnt go off screen + // double EndX = x + TextSize.Width; + // if (EndX > sclxmax) + // { + // //int diffx = x - sclxmax; + // x = xmax - TextSize.Width + halfbrd; + // } + + // if (x < sclxmin) + // x = sclxmin; + + // if (x < 0) + // x = 0; + + // //adjust the y / height label so it doesnt go off screen + // double EndY = y + TextSize.Height; + // if (EndY > sclymax) + // { + // //float diffy = EndY - sclymax; + // y = ymax - TextSize.Height - halfbrd; + // } + + // if (y < 0) + // y = 0; + + + // rect = new System.Drawing.Rectangle(x.ToInt(), + // y.ToInt(), + // boxWidth.ToInt(), + // boxHeight.ToInt()); //sets bounding box for drawn text + + // Brush brush = new SolidBrush(color); //sets background rectangle color + // if (AppSettings.Settings.RectDetectionTextBackColor != System.Drawing.Color.Gainsboro) + // brush = new SolidBrush(AppSettings.Settings.RectDetectionTextBackColor); + + // Brush forecolor = Brushes.Black; + // if (AppSettings.Settings.RectDetectionTextForeColor != System.Drawing.Color.Gainsboro) + // forecolor = new SolidBrush(AppSettings.Settings.RectDetectionTextForeColor); + + // e.Graphics.FillRectangle(brush, + // x.ToInt(), + // y.ToInt(), + // TextSize.Width, + // TextSize.Height); //draw grey background rectangle for detection text + + // e.Graphics.DrawString(pred.ToString(), new Font(AppSettings.Settings.RectDetectionTextFont, AppSettings.Settings.RectDetectionTextSize), forecolor, rect); //draw detection text + + + // } + + // } + // catch (Exception ex) + // { + + // Log("Error: " + ex.Msg()); + // } + // } + + private void folv_ObjectDetail_SelectionChanged(object sender, EventArgs e) + { + if (folv_ObjectDetail.SelectedObject != null) + { + CurPred = folv_ObjectDetail.SelectedObject as ClsPrediction; + this.ImageFileName = CurPred.Filename; + UpdateImage(); + } + this.pictureBox1.Refresh(); + } + //protected override void OnMouseWheel(MouseEventArgs mea) + //{ + // pictureBox1.Focus(); + // if (pictureBox1.Focused == true && mea.Delta != 0) + // { + // // Map the Form-centric mouse location to the PictureBox client coordinate system + // Point pictureBoxPoint = pictureBox1.PointToClient(this.PointToScreen(mea.Location)); + // ZoomScroll(pictureBoxPoint, mea.Delta > 0); + // } + //} + + //private void ZoomScroll(Point location, bool zoomIn) + //{ + // // Figure out what the new scale will be. Ensure the scale factor remains between + // // 1% and 1000% + // float newScale = Math.Min(Math.Max(m_dZoomscale + (zoomIn ? s_dScrollValue : -s_dScrollValue), 0.1f), 10); + + // if (newScale != m_dZoomscale) + // { + // float adjust = newScale / m_dZoomscale; + // m_dZoomscale = newScale; + + // // Translate mouse point to origin + // transform.Translate(-location.X, -location.Y, MatrixOrder.Append); + + // // Scale view + // transform.Scale(adjust, adjust, MatrixOrder.Append); + + // // Translate origin back to original mouse point. + // transform.Translate(location.X, location.Y, MatrixOrder.Append); + // Size newSize = new Size((int)(OriginalBMP.Width * m_dZoomscale), (int)(OriginalBMP.Height * m_dZoomscale)); + // Bitmap bmp = new Bitmap(OriginalBMP, newSize); + // this.pictureBox1.Image = bmp; //load actual image as background, so that an overlay can be added as the image + + // pictureBox1.Invalidate(); + // pictureBox1.Refresh(); + // } + //} + + private void pictureBox1_Click(object sender, EventArgs e) + { + + } + + private void folv_ObjectDetail_SelectedIndexChanged(object sender, EventArgs e) + { + + } + + //FUNCTION FOR MOUSE SCROL ZOOM-IN + //private void ZoomScroll(Point location, bool zoomIn) + //{ + // // make zoom-point (cursor location) our origin + // transform.Translate(-location.X, -location.Y); + + // // perform zoom (at origin) + // if (zoomIn) + // transform.Scale(s_dScrollValue, s_dScrollValue); + // else + // transform.Scale(1 / s_dScrollValue, 1 / s_dScrollValue); + + // // translate origin back to cursor + // transform.Translate(location.X, location.Y); + // this.pictureBox1.Invalidate(); + // this.pictureBox1.Refresh(); + // //m_Picturebox_Canvas.Invalidate(); + //} + } +} diff --git a/src/UI/Frm_ObjectDetail.resx b/src/UI/Frm_ObjectDetail.resx new file mode 100644 index 00000000..c7aa5fd4 --- /dev/null +++ b/src/UI/Frm_ObjectDetail.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + \ No newline at end of file diff --git a/src/UI/Frm_Pause.Designer.cs b/src/UI/Frm_Pause.Designer.cs new file mode 100644 index 00000000..f8b7ee06 --- /dev/null +++ b/src/UI/Frm_Pause.Designer.cs @@ -0,0 +1,239 @@ + +namespace AITool +{ + partial class Frm_Pause + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.cmb_cameras = new System.Windows.Forms.ComboBox(); + this.label1 = new System.Windows.Forms.Label(); + this.label2 = new System.Windows.Forms.Label(); + this.tb_minutes = new System.Windows.Forms.TextBox(); + this.label3 = new System.Windows.Forms.Label(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.cb_URL = new System.Windows.Forms.CheckBox(); + this.cb_Pushover = new System.Windows.Forms.CheckBox(); + this.cb_MQTT = new System.Windows.Forms.CheckBox(); + this.cb_Telegram = new System.Windows.Forms.CheckBox(); + this.cb_FileMonitoring = new System.Windows.Forms.CheckBox(); + this.lbl_resumingtime = new System.Windows.Forms.Label(); + this.timer1 = new System.Windows.Forms.Timer(this.components); + this.toolTip1 = new System.Windows.Forms.ToolTip(this.components); + this.cb_paused = new System.Windows.Forms.CheckBox(); + this.groupBox1.SuspendLayout(); + this.SuspendLayout(); + // + // cmb_cameras + // + this.cmb_cameras.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cmb_cameras.FormattingEnabled = true; + this.cmb_cameras.Location = new System.Drawing.Point(72, 12); + this.cmb_cameras.Name = "cmb_cameras"; + this.cmb_cameras.Size = new System.Drawing.Size(155, 21); + this.cmb_cameras.TabIndex = 0; + this.cmb_cameras.SelectedIndexChanged += new System.EventHandler(this.cmb_cameras_SelectedIndexChanged); + this.cmb_cameras.SelectionChangeCommitted += new System.EventHandler(this.cmb_cameras_SelectionChangeCommitted); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(23, 15); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(45, 13); + this.label1.TabIndex = 1; + this.label1.Text = "Camera"; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(14, 42); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(55, 13); + this.label2.TabIndex = 3; + this.label2.Text = "Pause for"; + // + // tb_minutes + // + this.tb_minutes.Location = new System.Drawing.Point(72, 39); + this.tb_minutes.Name = "tb_minutes"; + this.tb_minutes.Size = new System.Drawing.Size(55, 22); + this.tb_minutes.TabIndex = 4; + // + // label3 + // + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(133, 42); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(49, 13); + this.label3.TabIndex = 3; + this.label3.Text = "Minutes"; + // + // groupBox1 + // + this.groupBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.groupBox1.Controls.Add(this.cb_URL); + this.groupBox1.Controls.Add(this.cb_Pushover); + this.groupBox1.Controls.Add(this.cb_MQTT); + this.groupBox1.Controls.Add(this.cb_Telegram); + this.groupBox1.Controls.Add(this.cb_FileMonitoring); + this.groupBox1.Location = new System.Drawing.Point(19, 70); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.Size = new System.Drawing.Size(301, 73); + this.groupBox1.TabIndex = 5; + this.groupBox1.TabStop = false; + this.groupBox1.Text = "Pause the following Services"; + // + // cb_URL + // + this.cb_URL.AutoSize = true; + this.cb_URL.Location = new System.Drawing.Point(146, 45); + this.cb_URL.Name = "cb_URL"; + this.cb_URL.Size = new System.Drawing.Size(129, 17); + this.cb_URL.TabIndex = 0; + this.cb_URL.Text = "Trigger / Cancel URL"; + this.toolTip1.SetToolTip(this.cb_URL, "Prevent URL actions"); + this.cb_URL.UseVisualStyleBackColor = true; + // + // cb_Pushover + // + this.cb_Pushover.AutoSize = true; + this.cb_Pushover.Location = new System.Drawing.Point(222, 22); + this.cb_Pushover.Name = "cb_Pushover"; + this.cb_Pushover.Size = new System.Drawing.Size(73, 17); + this.cb_Pushover.TabIndex = 0; + this.cb_Pushover.Text = "Pushover"; + this.toolTip1.SetToolTip(this.cb_Pushover, "Prevent pushover actions"); + this.cb_Pushover.UseVisualStyleBackColor = true; + // + // cb_MQTT + // + this.cb_MQTT.AutoSize = true; + this.cb_MQTT.Location = new System.Drawing.Point(13, 45); + this.cb_MQTT.Name = "cb_MQTT"; + this.cb_MQTT.Size = new System.Drawing.Size(56, 17); + this.cb_MQTT.TabIndex = 0; + this.cb_MQTT.Text = "MQTT"; + this.toolTip1.SetToolTip(this.cb_MQTT, "Prevent MQTT actions"); + this.cb_MQTT.UseVisualStyleBackColor = true; + // + // cb_Telegram + // + this.cb_Telegram.AutoSize = true; + this.cb_Telegram.Location = new System.Drawing.Point(146, 22); + this.cb_Telegram.Name = "cb_Telegram"; + this.cb_Telegram.Size = new System.Drawing.Size(72, 17); + this.cb_Telegram.TabIndex = 0; + this.cb_Telegram.Text = "Telegram"; + this.toolTip1.SetToolTip(this.cb_Telegram, "Prevent Telegram actions"); + this.cb_Telegram.UseVisualStyleBackColor = true; + // + // cb_FileMonitoring + // + this.cb_FileMonitoring.AutoSize = true; + this.cb_FileMonitoring.Location = new System.Drawing.Point(13, 22); + this.cb_FileMonitoring.Name = "cb_FileMonitoring"; + this.cb_FileMonitoring.Size = new System.Drawing.Size(132, 17); + this.cb_FileMonitoring.TabIndex = 0; + this.cb_FileMonitoring.Text = "ALL (File Monitoring)"; + this.toolTip1.SetToolTip(this.cb_FileMonitoring, "This will prevent any new images from being processed by the AI server"); + this.cb_FileMonitoring.UseVisualStyleBackColor = true; + // + // lbl_resumingtime + // + this.lbl_resumingtime.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.lbl_resumingtime.AutoSize = true; + this.lbl_resumingtime.Location = new System.Drawing.Point(16, 156); + this.lbl_resumingtime.Name = "lbl_resumingtime"; + this.lbl_resumingtime.Size = new System.Drawing.Size(128, 13); + this.lbl_resumingtime.TabIndex = 6; + this.lbl_resumingtime.Text = "Resuming in xx minutes"; + // + // timer1 + // + this.timer1.Enabled = true; + this.timer1.Interval = 1000; + this.timer1.Tick += new System.EventHandler(this.timer1_Tick); + // + // cb_paused + // + this.cb_paused.Appearance = System.Windows.Forms.Appearance.Button; + this.cb_paused.Location = new System.Drawing.Point(247, 148); + this.cb_paused.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.cb_paused.Name = "cb_paused"; + this.cb_paused.Size = new System.Drawing.Size(73, 31); + this.cb_paused.TabIndex = 9; + this.cb_paused.Text = "Pause"; + this.cb_paused.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + this.cb_paused.UseVisualStyleBackColor = true; + this.cb_paused.CheckedChanged += new System.EventHandler(this.cb_paused_CheckedChanged); + // + // Frm_Pause + // + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Inherit; + this.ClientSize = new System.Drawing.Size(333, 187); + this.Controls.Add(this.cb_paused); + this.Controls.Add(this.lbl_resumingtime); + this.Controls.Add(this.groupBox1); + this.Controls.Add(this.tb_minutes); + this.Controls.Add(this.label3); + this.Controls.Add(this.label2); + this.Controls.Add(this.label1); + this.Controls.Add(this.cmb_cameras); + this.Font = new System.Drawing.Font("Segoe UI", 8.25F); + this.Name = "Frm_Pause"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Pause"; + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.Frm_Pause_FormClosing); + this.Load += new System.EventHandler(this.Frm_Pause_Load); + this.groupBox1.ResumeLayout(false); + this.groupBox1.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.ComboBox cmb_cameras; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.TextBox tb_minutes; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.CheckBox cb_URL; + private System.Windows.Forms.CheckBox cb_Pushover; + private System.Windows.Forms.CheckBox cb_MQTT; + private System.Windows.Forms.CheckBox cb_Telegram; + private System.Windows.Forms.CheckBox cb_FileMonitoring; + private System.Windows.Forms.Label lbl_resumingtime; + private System.Windows.Forms.Timer timer1; + private System.Windows.Forms.ToolTip toolTip1; + private System.Windows.Forms.CheckBox cb_paused; + } +} \ No newline at end of file diff --git a/src/UI/Frm_Pause.cs b/src/UI/Frm_Pause.cs new file mode 100644 index 00000000..39a75705 --- /dev/null +++ b/src/UI/Frm_Pause.cs @@ -0,0 +1,184 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace AITool +{ + public partial class Frm_Pause : Form + { + public Camera CurrentCam = null; + + public Frm_Pause() + { + InitializeComponent(); + } + + private void Frm_Pause_Load(object sender, EventArgs e) + { + this.cmb_cameras.Items.Add("All Cameras"); //add all cameras stats entry + int i = 0; + int oldidxstats = 0; + + if (CurrentCam != null) + { + foreach (Camera cam in AppSettings.Settings.CameraList) + { + this.cmb_cameras.Items.Add($" {cam.Name}"); + if (this.CurrentCam.Name.EqualsIgnoreCase(cam.Name)) + oldidxstats = i + 1; + i++; + + } + } + + if (this.cmb_cameras.Items.Count > 0) + this.cmb_cameras.SelectedIndex = oldidxstats; + + this.ShowCamera(); + + Global_GUI.RestoreWindowState(this); + //timer1.Start(); + + + } + + private void timer1_Tick(object sender, EventArgs e) + { + UpdateButton(); + } + + private void UpdateButton() + { + if (!this.CurrentCam.IsNull()) + { + if (this.CurrentCam.Paused) + { + if (!timer1.Enabled) + timer1.Start(); + + this.lbl_resumingtime.Text = $"Resuming in {(this.CurrentCam.ResumeTime - DateTime.Now).TotalMinutes.Round()} minutes..."; + cb_paused.Text = "UN-pause"; + } + else + { + + //this.cb_Paused.Checked = this.CurrentCam.Paused; + cb_paused.Text = "Pause"; + this.lbl_resumingtime.Text = "Not paused."; + if (timer1.Enabled) + timer1.Stop(); + } + } + + } + + + private void cmb_cameras_SelectionChangeCommitted(object sender, EventArgs e) + { + ShowCamera(); + + } + + public void ShowCamera() + { + if (cmb_cameras.Text.Trim().EqualsIgnoreCase("all cameras")) + this.CurrentCam = AITOOL.GetCamera("default", true); + else + this.CurrentCam = AITOOL.GetCamera(cmb_cameras.Text.Trim()); + + cb_paused.Checked = this.CurrentCam.Paused; + if (this.CurrentCam.Paused) + cb_paused.Text = "UN-pause"; + else + cb_paused.Text = "Pause"; + + tb_minutes.Text = this.CurrentCam.PauseMinutes.ToString(); + cb_FileMonitoring.Checked = this.CurrentCam.PauseFileMon; + cb_MQTT.Checked = this.CurrentCam.PauseMQTT; + cb_Pushover.Checked = this.CurrentCam.PausePushover; + cb_Telegram.Checked = this.CurrentCam.PauseTelegram; + cb_URL.Checked = this.CurrentCam.PauseURL; + + UpdateButton(); + + } + + private void SaveCamera() + { + + if (this.cmb_cameras.Text.Trim().EqualsIgnoreCase("All Cameras")) + { + foreach (var cam in AppSettings.Settings.CameraList) + { + cam.PauseMinutes = tb_minutes.Text.ToDouble(); + cam.PauseFileMon = cb_FileMonitoring.Checked; + cam.PauseMQTT = cb_MQTT.Checked; + cam.PausePushover = cb_Pushover.Checked; + cam.PauseTelegram = cb_Telegram.Checked; + cam.PauseURL = cb_URL.Checked; + if (cam.Paused && !cb_paused.Checked) + { + cam.Resume(); + } + else if (!cam.Paused && cb_paused.Checked) + { + cam.Pause(); + } + } + } + else + { + this.CurrentCam.PauseMinutes = tb_minutes.Text.ToDouble(); + this.CurrentCam.PauseFileMon = cb_FileMonitoring.Checked; + this.CurrentCam.PauseMQTT = cb_MQTT.Checked; + this.CurrentCam.PausePushover = cb_Pushover.Checked; + this.CurrentCam.PauseTelegram = cb_Telegram.Checked; + this.CurrentCam.PauseURL = cb_URL.Checked; + if (this.CurrentCam.Paused && !cb_paused.Checked) + { + this.CurrentCam.Resume(); + } + else if (!this.CurrentCam.Paused && cb_paused.Checked) + { + this.CurrentCam.Pause(); + } + } + + UpdateButton(); + + } + + private void bt_pause_Click(object sender, EventArgs e) + { + + + } + + private void bt_save_Click(object sender, EventArgs e) + { + + } + + private void Frm_Pause_FormClosing(object sender, FormClosingEventArgs e) + { + Global_GUI.SaveWindowState(this); + + } + + private void cb_paused_CheckedChanged(object sender, EventArgs e) + { + SaveCamera(); + } + + private void cmb_cameras_SelectedIndexChanged(object sender, EventArgs e) + { + + } + } +} diff --git a/src/UI/Frm_Pause.resx b/src/UI/Frm_Pause.resx new file mode 100644 index 00000000..df095294 --- /dev/null +++ b/src/UI/Frm_Pause.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 104, 17 + + + 17, 17 + + + 104, 17 + + \ No newline at end of file diff --git a/src/UI/Frm_PredSizeLimits.Designer.cs b/src/UI/Frm_PredSizeLimits.Designer.cs new file mode 100644 index 00000000..b8c5f732 --- /dev/null +++ b/src/UI/Frm_PredSizeLimits.Designer.cs @@ -0,0 +1,358 @@ + +namespace AITool +{ + partial class Frm_PredSizeLimits + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + label1 = new System.Windows.Forms.Label(); + groupBox1 = new System.Windows.Forms.GroupBox(); + label3 = new System.Windows.Forms.Label(); + tb_maxpercent = new System.Windows.Forms.TextBox(); + label2 = new System.Windows.Forms.Label(); + tb_MinPercent = new System.Windows.Forms.TextBox(); + groupBox2 = new System.Windows.Forms.GroupBox(); + label7 = new System.Windows.Forms.Label(); + label5 = new System.Windows.Forms.Label(); + label6 = new System.Windows.Forms.Label(); + tb_maxheight = new System.Windows.Forms.TextBox(); + label4 = new System.Windows.Forms.Label(); + tb_maxwidth = new System.Windows.Forms.TextBox(); + tb_minheight = new System.Windows.Forms.TextBox(); + tb_minwidth = new System.Windows.Forms.TextBox(); + BtnSave = new System.Windows.Forms.Button(); + label8 = new System.Windows.Forms.Label(); + groupBox3 = new System.Windows.Forms.GroupBox(); + label10 = new System.Windows.Forms.Label(); + label9 = new System.Windows.Forms.Label(); + tb_ConfidenceUpper = new System.Windows.Forms.TextBox(); + tb_ConfidenceLower = new System.Windows.Forms.TextBox(); + groupBox4 = new System.Windows.Forms.GroupBox(); + label11 = new System.Windows.Forms.Label(); + tb_duplicatepercent = new System.Windows.Forms.TextBox(); + groupBox1.SuspendLayout(); + groupBox2.SuspendLayout(); + groupBox3.SuspendLayout(); + groupBox4.SuspendLayout(); + SuspendLayout(); + // + // label1 + // + label1.AutoSize = true; + label1.Location = new System.Drawing.Point(42, 44); + label1.Name = "label1"; + label1.Size = new System.Drawing.Size(42, 13); + label1.TabIndex = 0; + label1.Text = "Min %:"; + // + // groupBox1 + // + groupBox1.Controls.Add(label3); + groupBox1.Controls.Add(tb_maxpercent); + groupBox1.Controls.Add(label2); + groupBox1.Controls.Add(tb_MinPercent); + groupBox1.Controls.Add(label1); + groupBox1.Location = new System.Drawing.Point(14, 59); + groupBox1.Name = "groupBox1"; + groupBox1.Size = new System.Drawing.Size(263, 70); + groupBox1.TabIndex = 1; + groupBox1.TabStop = false; + groupBox1.Text = "Limit Size Percentage"; + // + // label3 + // + label3.AutoSize = true; + label3.ForeColor = System.Drawing.Color.DimGray; + label3.Location = new System.Drawing.Point(9, 20); + label3.Name = "label3"; + label3.Size = new System.Drawing.Size(203, 13); + label3.TabIndex = 2; + label3.Text = "Percentage of prediction size vs image"; + // + // tb_maxpercent + // + tb_maxpercent.Location = new System.Drawing.Point(199, 41); + tb_maxpercent.Name = "tb_maxpercent"; + tb_maxpercent.Size = new System.Drawing.Size(33, 22); + tb_maxpercent.TabIndex = 3; + tb_maxpercent.Text = "100"; + // + // label2 + // + label2.AutoSize = true; + label2.Location = new System.Drawing.Point(157, 44); + label2.Name = "label2"; + label2.Size = new System.Drawing.Size(43, 13); + label2.TabIndex = 0; + label2.Text = "Max %:"; + // + // tb_MinPercent + // + tb_MinPercent.Location = new System.Drawing.Point(86, 41); + tb_MinPercent.Name = "tb_MinPercent"; + tb_MinPercent.Size = new System.Drawing.Size(33, 22); + tb_MinPercent.TabIndex = 2; + tb_MinPercent.Text = "100"; + // + // groupBox2 + // + groupBox2.Controls.Add(label7); + groupBox2.Controls.Add(label5); + groupBox2.Controls.Add(label6); + groupBox2.Controls.Add(tb_maxheight); + groupBox2.Controls.Add(label4); + groupBox2.Controls.Add(tb_maxwidth); + groupBox2.Controls.Add(tb_minheight); + groupBox2.Controls.Add(tb_minwidth); + groupBox2.Location = new System.Drawing.Point(12, 151); + groupBox2.Name = "groupBox2"; + groupBox2.Size = new System.Drawing.Size(264, 77); + groupBox2.TabIndex = 2; + groupBox2.TabStop = false; + groupBox2.Text = "Limit Size in Pixels (0 disables)"; + // + // label7 + // + label7.AutoSize = true; + label7.Location = new System.Drawing.Point(134, 50); + label7.Name = "label7"; + label7.Size = new System.Drawing.Size(69, 13); + label7.TabIndex = 0; + label7.Text = "Max Height:"; + // + // label5 + // + label5.AutoSize = true; + label5.Location = new System.Drawing.Point(19, 50); + label5.Name = "label5"; + label5.Size = new System.Drawing.Size(68, 13); + label5.TabIndex = 0; + label5.Text = "Min Height:"; + // + // label6 + // + label6.AutoSize = true; + label6.Location = new System.Drawing.Point(137, 26); + label6.Name = "label6"; + label6.Size = new System.Drawing.Size(66, 13); + label6.TabIndex = 0; + label6.Text = "Max Width:"; + // + // tb_maxheight + // + tb_maxheight.Location = new System.Drawing.Point(201, 47); + tb_maxheight.Name = "tb_maxheight"; + tb_maxheight.Size = new System.Drawing.Size(33, 22); + tb_maxheight.TabIndex = 7; + tb_maxheight.Text = "100"; + // + // label4 + // + label4.AutoSize = true; + label4.Location = new System.Drawing.Point(22, 26); + label4.Name = "label4"; + label4.Size = new System.Drawing.Size(65, 13); + label4.TabIndex = 0; + label4.Text = "Min Width:"; + // + // tb_maxwidth + // + tb_maxwidth.Location = new System.Drawing.Point(201, 23); + tb_maxwidth.Name = "tb_maxwidth"; + tb_maxwidth.Size = new System.Drawing.Size(33, 22); + tb_maxwidth.TabIndex = 5; + tb_maxwidth.Text = "100"; + // + // tb_minheight + // + tb_minheight.Location = new System.Drawing.Point(86, 47); + tb_minheight.Name = "tb_minheight"; + tb_minheight.Size = new System.Drawing.Size(33, 22); + tb_minheight.TabIndex = 6; + tb_minheight.Text = "100"; + // + // tb_minwidth + // + tb_minwidth.Location = new System.Drawing.Point(86, 23); + tb_minwidth.Name = "tb_minwidth"; + tb_minwidth.Size = new System.Drawing.Size(33, 22); + tb_minwidth.TabIndex = 4; + tb_minwidth.Text = "100"; + // + // BtnSave + // + BtnSave.Location = new System.Drawing.Point(211, 292); + BtnSave.Name = "BtnSave"; + BtnSave.Size = new System.Drawing.Size(70, 30); + BtnSave.TabIndex = 9; + BtnSave.Text = "Save"; + BtnSave.UseVisualStyleBackColor = true; + BtnSave.Click += BtnSave_Click; + // + // label8 + // + label8.AutoSize = true; + label8.ForeColor = System.Drawing.Color.DimGray; + label8.Location = new System.Drawing.Point(12, 132); + label8.Name = "label8"; + label8.Size = new System.Drawing.Size(261, 13); + label8.TabIndex = 2; + label8.Text = "(See History tab > Prediction Details, for size info)"; + // + // groupBox3 + // + groupBox3.Controls.Add(label10); + groupBox3.Controls.Add(label9); + groupBox3.Controls.Add(tb_ConfidenceUpper); + groupBox3.Controls.Add(tb_ConfidenceLower); + groupBox3.Location = new System.Drawing.Point(14, 12); + groupBox3.Name = "groupBox3"; + groupBox3.Size = new System.Drawing.Size(263, 43); + groupBox3.TabIndex = 4; + groupBox3.TabStop = false; + groupBox3.Text = "Object Confidence limits"; + // + // label10 + // + label10.AutoSize = true; + label10.Location = new System.Drawing.Point(157, 19); + label10.Name = "label10"; + label10.Size = new System.Drawing.Size(42, 13); + label10.TabIndex = 0; + label10.Text = "Upper:"; + // + // label9 + // + label9.AutoSize = true; + label9.Location = new System.Drawing.Point(41, 19); + label9.Name = "label9"; + label9.Size = new System.Drawing.Size(41, 13); + label9.TabIndex = 0; + label9.Text = "Lower:"; + // + // tb_ConfidenceUpper + // + tb_ConfidenceUpper.Location = new System.Drawing.Point(199, 16); + tb_ConfidenceUpper.Name = "tb_ConfidenceUpper"; + tb_ConfidenceUpper.Size = new System.Drawing.Size(33, 22); + tb_ConfidenceUpper.TabIndex = 1; + tb_ConfidenceUpper.Text = "100"; + // + // tb_ConfidenceLower + // + tb_ConfidenceLower.Location = new System.Drawing.Point(86, 16); + tb_ConfidenceLower.Name = "tb_ConfidenceLower"; + tb_ConfidenceLower.Size = new System.Drawing.Size(33, 22); + tb_ConfidenceLower.TabIndex = 0; + tb_ConfidenceLower.Text = "100"; + // + // groupBox4 + // + groupBox4.Controls.Add(label11); + groupBox4.Controls.Add(tb_duplicatepercent); + groupBox4.Location = new System.Drawing.Point(12, 234); + groupBox4.Name = "groupBox4"; + groupBox4.Size = new System.Drawing.Size(265, 49); + groupBox4.TabIndex = 5; + groupBox4.TabStop = false; + groupBox4.Text = "Duplicate object detection"; + // + // label11 + // + label11.AutoSize = true; + label11.Location = new System.Drawing.Point(6, 22); + label11.Name = "label11"; + label11.Size = new System.Drawing.Size(77, 13); + label11.TabIndex = 2; + label11.Text = "Match Size %:"; + // + // tb_duplicatepercent + // + tb_duplicatepercent.Location = new System.Drawing.Point(86, 19); + tb_duplicatepercent.Name = "tb_duplicatepercent"; + tb_duplicatepercent.Size = new System.Drawing.Size(33, 22); + tb_duplicatepercent.TabIndex = 8; + tb_duplicatepercent.Text = "100"; + // + // Frm_PredSizeLimits + // + AutoScaleMode = System.Windows.Forms.AutoScaleMode.Inherit; + AutoScroll = true; + ClientSize = new System.Drawing.Size(288, 329); + Controls.Add(groupBox4); + Controls.Add(groupBox3); + Controls.Add(label8); + Controls.Add(BtnSave); + Controls.Add(groupBox2); + Controls.Add(groupBox1); + Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); + Name = "Frm_PredSizeLimits"; + StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + Text = "Prediction Tolerances"; + FormClosed += Frm_PredSizeLimits_FormClosed; + Load += Frm_PredSizeLimits_Load; + groupBox1.ResumeLayout(false); + groupBox1.PerformLayout(); + groupBox2.ResumeLayout(false); + groupBox2.PerformLayout(); + groupBox3.ResumeLayout(false); + groupBox3.PerformLayout(); + groupBox4.ResumeLayout(false); + groupBox4.PerformLayout(); + ResumeLayout(false); + PerformLayout(); + } + + #endregion + + private System.Windows.Forms.Label label1; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.GroupBox groupBox2; + private System.Windows.Forms.Label label7; + private System.Windows.Forms.Label label5; + private System.Windows.Forms.Label label6; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.Button BtnSave; + public System.Windows.Forms.TextBox tb_maxpercent; + public System.Windows.Forms.TextBox tb_MinPercent; + public System.Windows.Forms.TextBox tb_maxheight; + public System.Windows.Forms.TextBox tb_maxwidth; + public System.Windows.Forms.TextBox tb_minheight; + public System.Windows.Forms.TextBox tb_minwidth; + private System.Windows.Forms.Label label8; + private System.Windows.Forms.GroupBox groupBox3; + private System.Windows.Forms.Label label10; + private System.Windows.Forms.Label label9; + public System.Windows.Forms.TextBox tb_ConfidenceUpper; + public System.Windows.Forms.TextBox tb_ConfidenceLower; + private System.Windows.Forms.GroupBox groupBox4; + private System.Windows.Forms.Label label11; + public System.Windows.Forms.TextBox tb_duplicatepercent; + } +} \ No newline at end of file diff --git a/src/UI/Frm_PredSizeLimits.cs b/src/UI/Frm_PredSizeLimits.cs new file mode 100644 index 00000000..48b62093 --- /dev/null +++ b/src/UI/Frm_PredSizeLimits.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace AITool +{ + public partial class Frm_PredSizeLimits : Form + { + public Frm_PredSizeLimits() + { + InitializeComponent(); + } + + private void BtnSave_Click(object sender, EventArgs e) + { + this.DialogResult = DialogResult.OK; + this.Close(); + } + + private void Frm_PredSizeLimits_Load(object sender, EventArgs e) + { + Global_GUI.RestoreWindowState(this); + } + + private void Frm_PredSizeLimits_FormClosed(object sender, FormClosedEventArgs e) + { + Global_GUI.SaveWindowState(this); + } + } +} diff --git a/src/UI/Frm_PredSizeLimits.resx b/src/UI/Frm_PredSizeLimits.resx new file mode 100644 index 00000000..a395bffc --- /dev/null +++ b/src/UI/Frm_PredSizeLimits.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/UI/Frm_RelevantObjects.Designer.cs b/src/UI/Frm_RelevantObjects.Designer.cs new file mode 100644 index 00000000..2789fc1d --- /dev/null +++ b/src/UI/Frm_RelevantObjects.Designer.cs @@ -0,0 +1,565 @@ + +namespace AITool +{ + partial class Frm_RelevantObjects + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Frm_RelevantObjects)); + this.splitContainer1 = new System.Windows.Forms.SplitContainer(); + this.FOLV_RelevantObjects = new BrightIdeasSoftware.FastObjectListView(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.groupBox4 = new System.Windows.Forms.GroupBox(); + this.cb_ObjectIgnoreDynamicMask = new System.Windows.Forms.CheckBox(); + this.tb_Name = new System.Windows.Forms.TextBox(); + this.cb_ObjectIgnoreImageMask = new System.Windows.Forms.CheckBox(); + this.label1 = new System.Windows.Forms.Label(); + this.cb_ObjectTriggers = new System.Windows.Forms.CheckBox(); + this.label2 = new System.Windows.Forms.Label(); + this.tb_Time = new System.Windows.Forms.TextBox(); + this.groupBox3 = new System.Windows.Forms.GroupBox(); + this.label4 = new System.Windows.Forms.Label(); + this.label10 = new System.Windows.Forms.Label(); + this.label9 = new System.Windows.Forms.Label(); + this.tb_ConfidenceUpper = new System.Windows.Forms.TextBox(); + this.tb_ConfidenceLower = new System.Windows.Forms.TextBox(); + this.label3 = new System.Windows.Forms.Label(); + this.cb_enabled = new System.Windows.Forms.CheckBox(); + this.groupBox5 = new System.Windows.Forms.GroupBox(); + this.label5 = new System.Windows.Forms.Label(); + this.tb_maxpercent = new System.Windows.Forms.TextBox(); + this.label6 = new System.Windows.Forms.Label(); + this.tb_MinPercent = new System.Windows.Forms.TextBox(); + this.label7 = new System.Windows.Forms.Label(); + this.toolStrip1 = new System.Windows.Forms.ToolStrip(); + this.toolStripComboBoxCameras = new System.Windows.Forms.ToolStripComboBox(); + this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator(); + this.toolStripButtonAdd = new System.Windows.Forms.ToolStripButton(); + this.toolStripButtonDelete = new System.Windows.Forms.ToolStripButton(); + this.toolStripButtonUp = new System.Windows.Forms.ToolStripButton(); + this.toolStripButtonDown = new System.Windows.Forms.ToolStripButton(); + this.btnSave = new System.Windows.Forms.Button(); + this.toolTip1 = new System.Windows.Forms.ToolTip(this.components); + this.btnReset = new System.Windows.Forms.Button(); + this.btn_adddefaults = new System.Windows.Forms.Button(); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); + this.splitContainer1.Panel1.SuspendLayout(); + this.splitContainer1.Panel2.SuspendLayout(); + this.splitContainer1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.FOLV_RelevantObjects)).BeginInit(); + this.groupBox1.SuspendLayout(); + this.groupBox4.SuspendLayout(); + this.groupBox3.SuspendLayout(); + this.groupBox5.SuspendLayout(); + this.toolStrip1.SuspendLayout(); + this.SuspendLayout(); + // + // splitContainer1 + // + this.splitContainer1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.splitContainer1.BackColor = System.Drawing.Color.NavajoWhite; + this.splitContainer1.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; + this.splitContainer1.FixedPanel = System.Windows.Forms.FixedPanel.Panel2; + this.splitContainer1.Location = new System.Drawing.Point(0, 34); + this.splitContainer1.Name = "splitContainer1"; + this.splitContainer1.Orientation = System.Windows.Forms.Orientation.Horizontal; + // + // splitContainer1.Panel1 + // + this.splitContainer1.Panel1.Controls.Add(this.FOLV_RelevantObjects); + // + // splitContainer1.Panel2 + // + this.splitContainer1.Panel2.AutoScroll = true; + this.splitContainer1.Panel2.BackColor = System.Drawing.SystemColors.Control; + this.splitContainer1.Panel2.Controls.Add(this.groupBox1); + this.splitContainer1.Size = new System.Drawing.Size(685, 546); + this.splitContainer1.SplitterDistance = 292; + this.splitContainer1.TabIndex = 0; + // + // FOLV_RelevantObjects + // + this.FOLV_RelevantObjects.CheckBoxes = true; + this.FOLV_RelevantObjects.Dock = System.Windows.Forms.DockStyle.Fill; + this.FOLV_RelevantObjects.HideSelection = false; + this.FOLV_RelevantObjects.Location = new System.Drawing.Point(0, 0); + this.FOLV_RelevantObjects.Name = "FOLV_RelevantObjects"; + this.FOLV_RelevantObjects.ShowGroups = false; + this.FOLV_RelevantObjects.ShowImagesOnSubItems = true; + this.FOLV_RelevantObjects.Size = new System.Drawing.Size(681, 288); + this.FOLV_RelevantObjects.TabIndex = 0; + this.FOLV_RelevantObjects.UseCompatibleStateImageBehavior = false; + this.FOLV_RelevantObjects.View = System.Windows.Forms.View.Details; + this.FOLV_RelevantObjects.VirtualMode = true; + this.FOLV_RelevantObjects.FormatCell += new System.EventHandler(this.FOLV_RelevantObjects_FormatCell); + this.FOLV_RelevantObjects.FormatRow += new System.EventHandler(this.FOLV_RelevantObjects_FormatRow); + this.FOLV_RelevantObjects.SelectionChanged += new System.EventHandler(this.FOLV_RelevantObjects_SelectionChanged); + this.FOLV_RelevantObjects.ItemChecked += new System.Windows.Forms.ItemCheckedEventHandler(this.FOLV_RelevantObjects_ItemChecked); + this.FOLV_RelevantObjects.SelectedIndexChanged += new System.EventHandler(this.FOLV_RelevantObjects_SelectedIndexChanged); + // + // groupBox1 + // + this.groupBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.groupBox1.Controls.Add(this.groupBox4); + this.groupBox1.Controls.Add(this.groupBox3); + this.groupBox1.Controls.Add(this.label3); + this.groupBox1.Controls.Add(this.cb_enabled); + this.groupBox1.Controls.Add(this.groupBox5); + this.groupBox1.Location = new System.Drawing.Point(9, 9); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.Size = new System.Drawing.Size(630, 225); + this.groupBox1.TabIndex = 2; + this.groupBox1.TabStop = false; + this.groupBox1.Enter += new System.EventHandler(this.groupBox1_Enter); + this.groupBox1.Leave += new System.EventHandler(this.groupBox1_Leave); + // + // groupBox4 + // + this.groupBox4.Controls.Add(this.cb_ObjectIgnoreDynamicMask); + this.groupBox4.Controls.Add(this.tb_Name); + this.groupBox4.Controls.Add(this.cb_ObjectIgnoreImageMask); + this.groupBox4.Controls.Add(this.label1); + this.groupBox4.Controls.Add(this.cb_ObjectTriggers); + this.groupBox4.Controls.Add(this.label2); + this.groupBox4.Controls.Add(this.tb_Time); + this.groupBox4.Location = new System.Drawing.Point(6, 23); + this.groupBox4.Name = "groupBox4"; + this.groupBox4.Size = new System.Drawing.Size(204, 153); + this.groupBox4.TabIndex = 6; + this.groupBox4.TabStop = false; + // + // cb_ObjectIgnoreDynamicMask + // + this.cb_ObjectIgnoreDynamicMask.AutoSize = true; + this.cb_ObjectIgnoreDynamicMask.Location = new System.Drawing.Point(5, 120); + this.cb_ObjectIgnoreDynamicMask.Name = "cb_ObjectIgnoreDynamicMask"; + this.cb_ObjectIgnoreDynamicMask.Size = new System.Drawing.Size(178, 17); + this.cb_ObjectIgnoreDynamicMask.TabIndex = 0; + this.cb_ObjectIgnoreDynamicMask.Text = "Object Ignores Dynamic Mask"; + this.toolTip1.SetToolTip(this.cb_ObjectIgnoreDynamicMask, resources.GetString("cb_ObjectIgnoreDynamicMask.ToolTip")); + this.cb_ObjectIgnoreDynamicMask.UseVisualStyleBackColor = true; + this.cb_ObjectIgnoreDynamicMask.CheckedChanged += new System.EventHandler(this.cb_IgnoreMask_CheckedChanged); + // + // tb_Name + // + this.tb_Name.Location = new System.Drawing.Point(46, 22); + this.tb_Name.Name = "tb_Name"; + this.tb_Name.Size = new System.Drawing.Size(149, 22); + this.tb_Name.TabIndex = 1; + this.tb_Name.TextChanged += new System.EventHandler(this.tb_Name_TextChanged); + this.tb_Name.Leave += new System.EventHandler(this.tb_Name_Leave); + // + // cb_ObjectIgnoreImageMask + // + this.cb_ObjectIgnoreImageMask.AutoSize = true; + this.cb_ObjectIgnoreImageMask.Location = new System.Drawing.Point(5, 97); + this.cb_ObjectIgnoreImageMask.Name = "cb_ObjectIgnoreImageMask"; + this.cb_ObjectIgnoreImageMask.Size = new System.Drawing.Size(166, 17); + this.cb_ObjectIgnoreImageMask.TabIndex = 0; + this.cb_ObjectIgnoreImageMask.Text = "Object Ignores Image Mask"; + this.toolTip1.SetToolTip(this.cb_ObjectIgnoreImageMask, resources.GetString("cb_ObjectIgnoreImageMask.ToolTip")); + this.cb_ObjectIgnoreImageMask.UseVisualStyleBackColor = true; + this.cb_ObjectIgnoreImageMask.CheckedChanged += new System.EventHandler(this.cb_IgnoreMask_CheckedChanged); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(2, 25); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(39, 13); + this.label1.TabIndex = 0; + this.label1.Text = "Name:"; + // + // cb_ObjectTriggers + // + this.cb_ObjectTriggers.AutoSize = true; + this.cb_ObjectTriggers.Location = new System.Drawing.Point(5, 74); + this.cb_ObjectTriggers.Name = "cb_ObjectTriggers"; + this.cb_ObjectTriggers.Size = new System.Drawing.Size(104, 17); + this.cb_ObjectTriggers.TabIndex = 0; + this.cb_ObjectTriggers.Text = "Object Triggers"; + this.toolTip1.SetToolTip(this.cb_ObjectTriggers, "If you uncheck this, the detection of this object will prevent a trigger NO MATTE" + + "R what other objects are detected. (For example, the object name could be a per" + + "sons name used used for face detection)"); + this.cb_ObjectTriggers.UseVisualStyleBackColor = true; + this.cb_ObjectTriggers.CheckedChanged += new System.EventHandler(this.cb_ObjectTriggers_CheckedChanged); + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(7, 51); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(34, 13); + this.label2.TabIndex = 0; + this.label2.Text = "Time:"; + // + // tb_Time + // + this.tb_Time.Location = new System.Drawing.Point(46, 48); + this.tb_Time.Name = "tb_Time"; + this.tb_Time.Size = new System.Drawing.Size(149, 22); + this.tb_Time.TabIndex = 1; + this.tb_Time.TextChanged += new System.EventHandler(this.tb_Time_TextChanged); + this.tb_Time.Leave += new System.EventHandler(this.tb_Time_Leave); + // + // groupBox3 + // + this.groupBox3.Controls.Add(this.label4); + this.groupBox3.Controls.Add(this.label10); + this.groupBox3.Controls.Add(this.label9); + this.groupBox3.Controls.Add(this.tb_ConfidenceUpper); + this.groupBox3.Controls.Add(this.tb_ConfidenceLower); + this.groupBox3.Location = new System.Drawing.Point(216, 23); + this.groupBox3.Name = "groupBox3"; + this.groupBox3.Size = new System.Drawing.Size(223, 70); + this.groupBox3.TabIndex = 5; + this.groupBox3.TabStop = false; + this.groupBox3.Text = "Object Confidence limits"; + // + // label4 + // + this.label4.AutoSize = true; + this.label4.Font = new System.Drawing.Font("Consolas", 6.75F); + this.label4.ForeColor = System.Drawing.Color.Gray; + this.label4.Location = new System.Drawing.Point(9, 43); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(195, 10); + this.label4.TabIndex = 2; + this.label4.Text = "(Range limited by CAM\\Default objects)"; + // + // label10 + // + this.label10.AutoSize = true; + this.label10.Location = new System.Drawing.Point(90, 20); + this.label10.Name = "label10"; + this.label10.Size = new System.Drawing.Size(42, 13); + this.label10.TabIndex = 0; + this.label10.Text = "Upper:"; + // + // label9 + // + this.label9.AutoSize = true; + this.label9.Location = new System.Drawing.Point(6, 20); + this.label9.Name = "label9"; + this.label9.Size = new System.Drawing.Size(41, 13); + this.label9.TabIndex = 0; + this.label9.Text = "Lower:"; + // + // tb_ConfidenceUpper + // + this.tb_ConfidenceUpper.Location = new System.Drawing.Point(132, 16); + this.tb_ConfidenceUpper.Name = "tb_ConfidenceUpper"; + this.tb_ConfidenceUpper.Size = new System.Drawing.Size(33, 22); + this.tb_ConfidenceUpper.TabIndex = 1; + this.tb_ConfidenceUpper.Text = "100"; + this.toolTip1.SetToolTip(this.tb_ConfidenceUpper, "MQTT, PUSHOVER, TELEGRAM, DYNAMIC MASK objects cannot be set lower or higher than" + + " \\DEFAULT objects"); + this.tb_ConfidenceUpper.TextChanged += new System.EventHandler(this.tb_ConfidenceUpper_TextChanged); + this.tb_ConfidenceUpper.Leave += new System.EventHandler(this.tb_ConfidenceUpper_Leave); + // + // tb_ConfidenceLower + // + this.tb_ConfidenceLower.Location = new System.Drawing.Point(51, 16); + this.tb_ConfidenceLower.Name = "tb_ConfidenceLower"; + this.tb_ConfidenceLower.Size = new System.Drawing.Size(33, 22); + this.tb_ConfidenceLower.TabIndex = 0; + this.tb_ConfidenceLower.Text = "100"; + this.toolTip1.SetToolTip(this.tb_ConfidenceLower, "MQTT, PUSHOVER, TELEGRAM, DYNAMIC MASK objects cannot be set lower or higher than" + + " \\DEFAULT objects"); + this.tb_ConfidenceLower.TextChanged += new System.EventHandler(this.tb_ConfidenceLower_TextChanged); + this.tb_ConfidenceLower.Leave += new System.EventHandler(this.tb_ConfidenceLower_Leave); + // + // label3 + // + this.label3.Font = new System.Drawing.Font("Consolas", 6.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.label3.ForeColor = System.Drawing.Color.Gray; + this.label3.Location = new System.Drawing.Point(3, 179); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(436, 38); + this.label3.TabIndex = 4; + this.label3.Text = "Example Time Reanges - \"00:01:00-02:59:59, 06:00:00-11:59:59\". Semicolon Hour li" + + "st: \"22;23;0;1;2;3;4;5\". or Dusk-Dawn, Dawn-Dusk, Sunrise-Sunset, Sunset-Sunris" + + "e"; + // + // cb_enabled + // + this.cb_enabled.AutoSize = true; + this.cb_enabled.Enabled = false; + this.cb_enabled.Location = new System.Drawing.Point(6, 0); + this.cb_enabled.Name = "cb_enabled"; + this.cb_enabled.Size = new System.Drawing.Size(68, 17); + this.cb_enabled.TabIndex = 2; + this.cb_enabled.Text = "Enabled"; + this.cb_enabled.UseVisualStyleBackColor = true; + this.cb_enabled.CheckedChanged += new System.EventHandler(this.cb_enabled_CheckedChanged); + // + // groupBox5 + // + this.groupBox5.Controls.Add(this.label5); + this.groupBox5.Controls.Add(this.tb_maxpercent); + this.groupBox5.Controls.Add(this.label6); + this.groupBox5.Controls.Add(this.tb_MinPercent); + this.groupBox5.Controls.Add(this.label7); + this.groupBox5.Location = new System.Drawing.Point(216, 106); + this.groupBox5.Name = "groupBox5"; + this.groupBox5.Size = new System.Drawing.Size(223, 70); + this.groupBox5.TabIndex = 1; + this.groupBox5.TabStop = false; + this.groupBox5.Text = "Limit Size Percentage"; + // + // label5 + // + this.label5.AutoSize = true; + this.label5.ForeColor = System.Drawing.Color.DimGray; + this.label5.Location = new System.Drawing.Point(9, 20); + this.label5.Name = "label5"; + this.label5.Size = new System.Drawing.Size(203, 13); + this.label5.TabIndex = 2; + this.label5.Text = "Percentage of prediction size vs image"; + // + // tb_maxpercent + // + this.tb_maxpercent.Location = new System.Drawing.Point(132, 41); + this.tb_maxpercent.Name = "tb_maxpercent"; + this.tb_maxpercent.Size = new System.Drawing.Size(33, 22); + this.tb_maxpercent.TabIndex = 3; + this.tb_maxpercent.Text = "100"; + this.tb_maxpercent.TextChanged += new System.EventHandler(this.tb_maxpercent_TextChanged); + // + // label6 + // + this.label6.AutoSize = true; + this.label6.Location = new System.Drawing.Point(88, 44); + this.label6.Name = "label6"; + this.label6.Size = new System.Drawing.Size(43, 13); + this.label6.TabIndex = 0; + this.label6.Text = "Max %:"; + // + // tb_MinPercent + // + this.tb_MinPercent.Location = new System.Drawing.Point(51, 41); + this.tb_MinPercent.Name = "tb_MinPercent"; + this.tb_MinPercent.Size = new System.Drawing.Size(33, 22); + this.tb_MinPercent.TabIndex = 2; + this.tb_MinPercent.Text = "100"; + this.tb_MinPercent.TextChanged += new System.EventHandler(this.tb_MinPercent_TextChanged); + // + // label7 + // + this.label7.AutoSize = true; + this.label7.Location = new System.Drawing.Point(7, 44); + this.label7.Name = "label7"; + this.label7.Size = new System.Drawing.Size(42, 13); + this.label7.TabIndex = 0; + this.label7.Text = "Min %:"; + // + // toolStrip1 + // + this.toolStrip1.ImageScalingSize = new System.Drawing.Size(24, 24); + this.toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.toolStripComboBoxCameras, + this.toolStripSeparator1, + this.toolStripButtonAdd, + this.toolStripButtonDelete, + this.toolStripButtonUp, + this.toolStripButtonDown}); + this.toolStrip1.Location = new System.Drawing.Point(0, 0); + this.toolStrip1.Name = "toolStrip1"; + this.toolStrip1.Padding = new System.Windows.Forms.Padding(0, 0, 2, 0); + this.toolStrip1.Size = new System.Drawing.Size(685, 31); + this.toolStrip1.TabIndex = 1; + this.toolStrip1.Text = "toolStrip1"; + // + // toolStripComboBoxCameras + // + this.toolStripComboBoxCameras.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.toolStripComboBoxCameras.FlatStyle = System.Windows.Forms.FlatStyle.Standard; + this.toolStripComboBoxCameras.Name = "toolStripComboBoxCameras"; + this.toolStripComboBoxCameras.Size = new System.Drawing.Size(225, 31); + this.toolStripComboBoxCameras.SelectedIndexChanged += new System.EventHandler(this.toolStripComboBoxCameras_SelectedIndexChanged); + this.toolStripComboBoxCameras.Click += new System.EventHandler(this.toolStripComboBoxCameras_Click); + // + // toolStripSeparator1 + // + this.toolStripSeparator1.Name = "toolStripSeparator1"; + this.toolStripSeparator1.Size = new System.Drawing.Size(6, 31); + // + // toolStripButtonAdd + // + this.toolStripButtonAdd.Image = global::AITool.Properties.Resources.image_x_generic; + this.toolStripButtonAdd.ImageTransparentColor = System.Drawing.Color.Magenta; + this.toolStripButtonAdd.Name = "toolStripButtonAdd"; + this.toolStripButtonAdd.Size = new System.Drawing.Size(57, 28); + this.toolStripButtonAdd.Text = "Add"; + this.toolStripButtonAdd.Click += new System.EventHandler(this.toolStripButtonAdd_Click); + // + // toolStripButtonDelete + // + this.toolStripButtonDelete.Enabled = false; + this.toolStripButtonDelete.Image = global::AITool.Properties.Resources.edit_delete_5; + this.toolStripButtonDelete.ImageTransparentColor = System.Drawing.Color.Magenta; + this.toolStripButtonDelete.Name = "toolStripButtonDelete"; + this.toolStripButtonDelete.Size = new System.Drawing.Size(68, 28); + this.toolStripButtonDelete.Text = "Delete"; + this.toolStripButtonDelete.Click += new System.EventHandler(this.toolStripButtonDelete_Click); + // + // toolStripButtonUp + // + this.toolStripButtonUp.Enabled = false; + this.toolStripButtonUp.Image = global::AITool.Properties.Resources.arrow_up_double_3; + this.toolStripButtonUp.ImageTransparentColor = System.Drawing.Color.Magenta; + this.toolStripButtonUp.Name = "toolStripButtonUp"; + this.toolStripButtonUp.Size = new System.Drawing.Size(50, 28); + this.toolStripButtonUp.Text = "Up"; + this.toolStripButtonUp.Click += new System.EventHandler(this.toolStripButtonUp_Click); + // + // toolStripButtonDown + // + this.toolStripButtonDown.Enabled = false; + this.toolStripButtonDown.Image = global::AITool.Properties.Resources.arrow_down_double_3; + this.toolStripButtonDown.ImageTransparentColor = System.Drawing.Color.Magenta; + this.toolStripButtonDown.Name = "toolStripButtonDown"; + this.toolStripButtonDown.Size = new System.Drawing.Size(66, 28); + this.toolStripButtonDown.Text = "Down"; + this.toolStripButtonDown.Click += new System.EventHandler(this.toolStripButtonDown_Click); + // + // btnSave + // + this.btnSave.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.btnSave.Location = new System.Drawing.Point(615, 587); + this.btnSave.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); + this.btnSave.Name = "btnSave"; + this.btnSave.Size = new System.Drawing.Size(70, 30); + this.btnSave.TabIndex = 10; + this.btnSave.Text = "OK"; + this.btnSave.UseVisualStyleBackColor = true; + this.btnSave.Click += new System.EventHandler(this.btnSave_Click); + // + // btnReset + // + this.btnReset.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.btnReset.Location = new System.Drawing.Point(9, 586); + this.btnReset.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); + this.btnReset.Name = "btnReset"; + this.btnReset.Size = new System.Drawing.Size(70, 30); + this.btnReset.TabIndex = 10; + this.btnReset.Text = "Reset"; + this.btnReset.UseVisualStyleBackColor = true; + this.btnReset.Click += new System.EventHandler(this.btnReset_Click); + // + // btn_adddefaults + // + this.btn_adddefaults.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.btn_adddefaults.Location = new System.Drawing.Point(87, 586); + this.btn_adddefaults.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); + this.btn_adddefaults.Name = "btn_adddefaults"; + this.btn_adddefaults.Size = new System.Drawing.Size(80, 30); + this.btn_adddefaults.TabIndex = 10; + this.btn_adddefaults.Text = "Add Defaults"; + this.btn_adddefaults.UseVisualStyleBackColor = true; + this.btn_adddefaults.Click += new System.EventHandler(this.btn_adddefaults_Click); + // + // Frm_RelevantObjects + // + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Inherit; + this.ClientSize = new System.Drawing.Size(685, 624); + this.Controls.Add(this.btn_adddefaults); + this.Controls.Add(this.btnReset); + this.Controls.Add(this.btnSave); + this.Controls.Add(this.toolStrip1); + this.Controls.Add(this.splitContainer1); + this.Font = new System.Drawing.Font("Segoe UI", 8.25F); + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.Name = "Frm_RelevantObjects"; + this.Tag = "SAVE"; + this.Text = "Relevant Objects"; + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.Frm_RelevantObjects_FormClosing); + this.Load += new System.EventHandler(this.Frm_RelevantObjects_Load); + this.splitContainer1.Panel1.ResumeLayout(false); + this.splitContainer1.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit(); + this.splitContainer1.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.FOLV_RelevantObjects)).EndInit(); + this.groupBox1.ResumeLayout(false); + this.groupBox1.PerformLayout(); + this.groupBox4.ResumeLayout(false); + this.groupBox4.PerformLayout(); + this.groupBox3.ResumeLayout(false); + this.groupBox3.PerformLayout(); + this.groupBox5.ResumeLayout(false); + this.groupBox5.PerformLayout(); + this.toolStrip1.ResumeLayout(false); + this.toolStrip1.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.SplitContainer splitContainer1; + private BrightIdeasSoftware.FastObjectListView FOLV_RelevantObjects; + private System.Windows.Forms.TextBox tb_Name; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.ToolStrip toolStrip1; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.CheckBox cb_enabled; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.ToolTip toolTip1; + private System.Windows.Forms.TextBox tb_Time; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Button btnSave; + private System.Windows.Forms.GroupBox groupBox4; + private System.Windows.Forms.GroupBox groupBox3; + private System.Windows.Forms.Label label10; + private System.Windows.Forms.Label label9; + public System.Windows.Forms.TextBox tb_ConfidenceUpper; + public System.Windows.Forms.TextBox tb_ConfidenceLower; + private System.Windows.Forms.ToolStripButton toolStripButtonAdd; + private System.Windows.Forms.ToolStripButton toolStripButtonDelete; + private System.Windows.Forms.ToolStripButton toolStripButtonUp; + private System.Windows.Forms.ToolStripButton toolStripButtonDown; + private System.Windows.Forms.Button btnReset; + private System.Windows.Forms.CheckBox cb_ObjectIgnoreImageMask; + private System.Windows.Forms.CheckBox cb_ObjectTriggers; + private System.Windows.Forms.Button btn_adddefaults; + private System.Windows.Forms.ToolStripComboBox toolStripComboBoxCameras; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator1; + private System.Windows.Forms.CheckBox cb_ObjectIgnoreDynamicMask; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.GroupBox groupBox5; + private System.Windows.Forms.Label label5; + private System.Windows.Forms.TextBox tb_maxpercent; + private System.Windows.Forms.Label label6; + private System.Windows.Forms.TextBox tb_MinPercent; + private System.Windows.Forms.Label label7; + } +} \ No newline at end of file diff --git a/src/UI/Frm_RelevantObjects.cs b/src/UI/Frm_RelevantObjects.cs new file mode 100644 index 00000000..97779d20 --- /dev/null +++ b/src/UI/Frm_RelevantObjects.cs @@ -0,0 +1,665 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +using BrightIdeasSoftware; + +using SQLite; + +namespace AITool +{ + public partial class Frm_RelevantObjects:Form + { + private ClsRelevantObjectManager ObjectManager = null; + private ClsRelevantObjectManager TempObjectManager = null; + public string ROMName = ""; + private ClsRelevantObject ro = null; + private bool NeedsSaving = false; + private bool Loading = false; + + public Frm_RelevantObjects() + { + InitializeComponent(); + } + + private void Frm_RelevantObjects_Load(object sender, EventArgs e) + { + Loading = true; + + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + + Global_GUI.ConfigureFOLV(FOLV_RelevantObjects, typeof(ClsRelevantObject)); + + this.FOLV_RelevantObjects.BooleanCheckStateGetter = delegate (Object rowObject) + { + return !rowObject.IsNull() && ((ClsRelevantObject)rowObject).Enabled; + }; + + this.FOLV_RelevantObjects.BooleanCheckStatePutter = delegate (Object rowObject, bool newValue) + { + if (rowObject.IsNull()) + return false; + ((ClsRelevantObject)rowObject).Enabled = newValue; + return newValue; + }; + + this.FillCombo(); + this.SetROM(); + this.LoadROMList(); + + Global_GUI.RestoreWindowState(this); + + Loading = false; + + } + + private void LoadROMList() + { + //Global_GUI.UpdateFOLV(FOLV_RelevantObjects, this.TempObjectManager.ObjectList, UseSelected: true, SelectObject: this.ro, FullRefresh: true, ForcedSelection: true); + Global_GUI.UpdateFOLV(FOLV_RelevantObjects, this.TempObjectManager.ObjectList, FullRefresh: true); + + Global_GUI.GroupboxEnableDisable(groupBox1, cb_enabled); + } + + private void FillCombo() + { + try + { + toolStripComboBoxCameras.Items.Clear(); + int idx = 0; + int fnd = -1; + foreach (Camera cam in AppSettings.Settings.CameraList) + { + foreach (PropertyInfo prop in cam.GetType().GetProperties()) + { + if (prop.PropertyType == typeof(ClsRelevantObjectManager)) + { + ClsRelevantObjectManager rom = (ClsRelevantObjectManager)prop.GetValue(cam); + string item = $"{cam.Name}\\{rom.TypeName}"; + toolStripComboBoxCameras.Items.Add(item); + if (item.EqualsIgnoreCase(this.ROMName)) + fnd = idx; + idx++; + } + } + string item2 = $"{cam.Name}\\{cam.maskManager.MaskTriggeringObjects.TypeName}"; + toolStripComboBoxCameras.Items.Add(item2); + if (item2.EqualsIgnoreCase(this.ROMName)) + fnd = idx; + idx++; + + } + + if (fnd != -1) + toolStripComboBoxCameras.SelectedIndex = fnd; + else if (idx > 0) + toolStripComboBoxCameras.SelectedIndex = 0; + + this.Text = $"Relevant Objects - {this.ROMName}"; + + } + catch (Exception ex) + { + + AITOOL.Log($"Error: {ex.Message}"); + } + } + + private void SetROM() + { + try + { + if (this.ROMName.IsEmpty()) + return; + + this.TempObjectManager = null; + + foreach (Camera cam in AppSettings.Settings.CameraList) + { + foreach (PropertyInfo prop in cam.GetType().GetProperties()) + { + if (prop.PropertyType == typeof(ClsRelevantObjectManager)) + { + ClsRelevantObjectManager rom = (ClsRelevantObjectManager)prop.GetValue(cam); + if ($"{cam.Name}\\{rom.TypeName}".EqualsIgnoreCase(this.ROMName)) + { + this.TempObjectManager = rom; + break; + } + } + } + + string item = $"{cam.Name}\\{cam.maskManager.MaskTriggeringObjects.TypeName}"; + if (item.EqualsIgnoreCase(this.ROMName)) + { + this.TempObjectManager = cam.maskManager.MaskTriggeringObjects; + break; + } + } + + if (this.TempObjectManager.IsNull()) + MessageBox.Show($"Error: Could not match '{this.ROMName}' to existing RelevantObjectManager?"); + else + this.TempObjectManager.Update(); + } + catch (Exception ex) + { + + AITOOL.Log($"Error: {ex.Message}"); + } + } + + private void SaveROM() + { + try + { + if (this.ROMName.IsEmpty()) + return; + + + foreach (Camera cam in AppSettings.Settings.CameraList) + { + foreach (PropertyInfo prop in cam.GetType().GetProperties()) + { + if (prop.PropertyType == typeof(ClsRelevantObjectManager)) + { + ClsRelevantObjectManager rom = (ClsRelevantObjectManager)prop.GetValue(cam); + if ($"{cam.Name}\\{rom.TypeName}".EqualsIgnoreCase(this.ROMName)) + { + rom.ObjectList = this.TempObjectManager.ObjectList; + //rom = this.TempObjectManager; + break; + } + } + } + + string item = $"{cam.Name}\\{cam.maskManager.MaskTriggeringObjects.TypeName}"; + if (item.EqualsIgnoreCase(this.ROMName)) + { + cam.maskManager.MaskTriggeringObjects.ObjectList = this.TempObjectManager.ObjectList; + break; + } + } + + + } + catch (Exception ex) + { + + AITOOL.Log($"Error: {ex.Message}"); + } + } + + private void Frm_RelevantObjects_FormClosing(object sender, FormClosingEventArgs e) + { + Global_GUI.SaveWindowState(this); + } + + private void cb_enabled_CheckedChanged(object sender, EventArgs e) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + Global_GUI.GroupboxEnableDisable(groupBox1, cb_enabled); + + NeedsSaving = true; + + this.SaveRO(); + } + + private void FOLV_RelevantObjects_SelectionChanged(object sender, EventArgs e) + { + + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + SelectionChanged(); + } + + private void SelectionChanged() + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + try + { + if (this.FOLV_RelevantObjects.SelectedObjects != null && this.FOLV_RelevantObjects.SelectedObjects.Count > 0) + { + ////save the last one just in case + this.SaveRO(); + + //set current selected object + this.ro = (ClsRelevantObject)this.FOLV_RelevantObjects.SelectedObjects[0]; + + //enable toolstrip buttons + toolStripButtonDelete.Enabled = true; + toolStripButtonDown.Enabled = true; + toolStripButtonUp.Enabled = true; + } + else + { + toolStripButtonDelete.Enabled = false; + toolStripButtonDown.Enabled = false; + toolStripButtonUp.Enabled = false; + this.ro = null; + } + + } + catch (Exception ex) + { + AITOOL.Log($"Error: {ex.Msg()}"); + + } + + LoadRO(); + + } + + private void LoadRO([CallerMemberName()] string memberName = null) + { + Loading = true; + + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + try + { + if (this.ro.IsNull()) + { + this.cb_enabled.Checked = false; + this.cb_enabled.Enabled = false; + this.tb_Name.Text = ""; + this.tb_Time.Text = ""; + this.tb_ConfidenceLower.Text = ""; + this.tb_ConfidenceUpper.Text = ""; + this.tb_MinPercent.Text = ""; + this.tb_maxpercent.Text = ""; + + } + else + { + this.cb_enabled.Checked = this.ro.Enabled; + this.cb_enabled.Enabled = true; + this.tb_Name.Text = this.ro.Name; + this.tb_Time.Text = this.ro.ActiveTimeRange; + this.tb_ConfidenceLower.Text = this.ro.Threshold_lower.ToString(); + this.tb_ConfidenceUpper.Text = this.ro.Threshold_upper.ToString(); + + this.tb_MinPercent.Text = this.ro.PredSizeMinPercentOfImage.ToString(); + this.tb_maxpercent.Text = this.ro.PredSizeMaxPercentOfImage.ToString(); + + if (this.tb_Name.Text.EqualsIgnoreCase("NEW OBJECT") || this.tb_Name.Text.IsEmpty()) + this.tb_Name.Enabled = true; + else + this.tb_Name.Enabled = false; + + this.cb_ObjectTriggers.Checked = this.ro.Trigger; + + this.cb_ObjectIgnoreImageMask.Checked = this.ro.IgnoreImageMask; + if (this.ro.IgnoreDynamicMask.HasValue) + this.cb_ObjectIgnoreDynamicMask.Checked = this.ro.IgnoreDynamicMask.Value; + + if (this.ROMName.Has("\\default")) + { + this.cb_ObjectIgnoreDynamicMask.Enabled = true; + this.cb_ObjectIgnoreImageMask.Enabled = true; + } + else + { + this.cb_ObjectIgnoreDynamicMask.Enabled = false; + this.cb_ObjectIgnoreImageMask.Enabled = false; + } + + } + + Global_GUI.GroupboxEnableDisable(groupBox1, cb_enabled); + + + } + catch (Exception ex) + { + + AITOOL.Log("Error: " + ex.Msg()); + } + finally + { + NeedsSaving = false; + Loading = false; + + } + } + private void SaveRO([CallerMemberName()] string memberName = null) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + try + { + if (!Loading && NeedsSaving && !this.ro.IsNull() && !this.tb_Name.Text.IsEmpty()) + { + this.ro.Enabled = this.cb_enabled.Checked; + this.ro.Name = this.tb_Name.Text; + this.ro.ActiveTimeRange = this.tb_Time.Text; + this.ro.Trigger = this.cb_ObjectTriggers.Checked; + this.ro.IgnoreImageMask = this.cb_ObjectIgnoreImageMask.Checked; + this.ro.IgnoreDynamicMask = this.cb_ObjectIgnoreDynamicMask.Checked; + double lower = this.tb_ConfidenceLower.Text.ToDouble(); + double upper = this.tb_ConfidenceUpper.Text.ToDouble(); + //if (!this.TempObjectManager.TypeName.EqualsIgnoreCase("default")) + //{ + // ClsRelevantObject ro = this.TempObjectManager.Get(this.ro.Name, false, out int FoundIDX, UseMainCamList: true); + // if (!ro.IsNull() && lower < ro.Threshold_lower || upper > ro.Threshold_upper) + // { + // MessageBox.Show($"The threshold cannot be lower or higher than {this.TempObjectManager.CameraName}\\Default setting: ({ro.Threshold_lower}-{ro.Threshold_upper}%)"); + // return; + // } + //} + this.ro.Threshold_lower = lower; + this.ro.Threshold_upper = upper; + + this.ro.PredSizeMinPercentOfImage = tb_MinPercent.Text.ToDouble(); + this.ro.PredSizeMaxPercentOfImage = tb_maxpercent.Text.ToDouble(); + } + + } + catch (Exception ex) + { + + AITOOL.Log("Error: " + ex.Msg()); + } + finally + { + this.FOLV_RelevantObjects.Refresh(); + NeedsSaving = false; + } + } + + private void FOLV_RelevantObjects_ItemChecked(object sender, ItemCheckedEventArgs e) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + this.LoadRO(); + //SelectionChanged(); + } + + private void FOLV_RelevantObjects_FormatRow(object sender, BrightIdeasSoftware.FormatRowEventArgs e) + { + FormatRow(sender, e); + } + + private void FormatRow(object Sender, BrightIdeasSoftware.FormatRowEventArgs e) + { + try + { + ClsRelevantObject ro = (ClsRelevantObject)e.Model; + + if (ro.IsNull()) + return; + + // If SPI IsNot Nothing Then + if (ro.Enabled && ro.Trigger) + e.Item.ForeColor = Color.Green; + + else if (ro.Enabled && !ro.Trigger) + e.Item.ForeColor = Color.DarkOrange; + + else if (!ro.Enabled) + e.Item.ForeColor = Color.Gray; + } + catch (Exception) { } + + } + + private void btnSave_Click(object sender, EventArgs e) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + try + { + + //this.Save(); + this.DialogResult = DialogResult.OK; + this.Close(); + + } + catch (Exception ex) + { + + AITOOL.Log("Error: " + ex.Msg()); + } + } + + private void Save() + { + SaveRO(); + + List rolist = new List(); + foreach (ClsRelevantObject ro in FOLV_RelevantObjects.Objects) + { + rolist.Add(ro); + } + + this.TempObjectManager.ObjectList = this.TempObjectManager.FromList(rolist, true, true); + this.TempObjectManager.Update(); + + //this.SaveROM(); + } + + private void toolStripButtonUp_Click(object sender, EventArgs e) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + if (this.ro == null) + return; + + this.ro = this.TempObjectManager.MoveUp(ro, out int NewIDX); + + Global_GUI.UpdateFOLV(FOLV_RelevantObjects, this.TempObjectManager.ObjectList, UseSelected: true, SelectObject: this.ro, FullRefresh: true, ForcedSelection: true); + + } + + private void toolStripButtonDown_Click(object sender, EventArgs e) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + if (this.ro == null) + return; + + this.ro = this.TempObjectManager.MoveDown(ro, out int NewIDX); + + Global_GUI.UpdateFOLV(FOLV_RelevantObjects, this.TempObjectManager.ObjectList, UseSelected: true, SelectObject: this.ro, FullRefresh: true, ForcedSelection: true); + + } + + private void toolStripButtonDelete_Click(object sender, EventArgs e) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + this.ro = this.TempObjectManager.Delete(ro, out int NewIDX); + + Global_GUI.UpdateFOLV(FOLV_RelevantObjects, this.TempObjectManager.ObjectList, UseSelected: true, SelectObject: this.ro, FullRefresh: true, ForcedSelection: true); + } + + private void toolStripButtonAdd_Click(object sender, EventArgs e) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + SaveRO(); + + ClsRelevantObject ro = new ClsRelevantObject("NEW OBJECT"); + this.ro = ro; + + if (this.TempObjectManager.TryAdd(ro, true, out int NewIDX)) + Global_GUI.UpdateFOLV(FOLV_RelevantObjects, this.TempObjectManager.ObjectList, UseSelected: true, SelectObject: this.ro, FullRefresh: true, ForcedSelection: true); + + } + + private void btnReset_Click(object sender, EventArgs e) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + this.TempObjectManager.Reset(); // = new ClsRelevantObjectManager(AppSettings.Settings.ObjectPriority, this.ObjectManager.TypeName, this.ObjectManager.Camera); + this.ro = null; + Global_GUI.UpdateFOLV(FOLV_RelevantObjects, this.TempObjectManager.ObjectList, UseSelected: true, SelectObject: this.ro, FullRefresh: true, ForcedSelection: true); + + } + + private void tb_Name_Leave(object sender, EventArgs e) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + this.SaveRO(); + + } + + private void rb_trigger_CheckedChanged(object sender, EventArgs e) + { + NeedsSaving = true; + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + this.SaveRO(); + + } + + private void rb_ignore_CheckedChanged(object sender, EventArgs e) + { + NeedsSaving = true; + this.SaveRO(); + + } + + private void tb_ConfidenceLower_Leave(object sender, EventArgs e) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + this.SaveRO(); + + } + + private void tb_ConfidenceUpper_TextChanged(object sender, EventArgs e) + { + NeedsSaving = true; + this.SaveRO(); + + } + + private void tb_ConfidenceUpper_Leave(object sender, EventArgs e) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + this.SaveRO(); + + } + + private void tb_Time_Leave(object sender, EventArgs e) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + this.SaveRO(); + + } + + private void groupBox1_Enter(object sender, EventArgs e) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + this.SaveRO(); + } + + private void groupBox1_Leave(object sender, EventArgs e) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + this.SaveRO(); + + } + + private void tb_Name_TextChanged(object sender, EventArgs e) + { + NeedsSaving = true; + this.SaveRO(); + + } + + private void tb_ConfidenceLower_TextChanged(object sender, EventArgs e) + { + NeedsSaving = true; + this.SaveRO(); + + } + + private void FOLV_RelevantObjects_SelectedIndexChanged(object sender, EventArgs e) + { + + } + + private void tb_Time_TextChanged(object sender, EventArgs e) + { + NeedsSaving = true; + this.SaveRO(); + } + + private void cb_ObjectTriggers_CheckedChanged(object sender, EventArgs e) + { + NeedsSaving = true; + this.SaveRO(); + } + + private void cb_IgnoreMask_CheckedChanged(object sender, EventArgs e) + { + NeedsSaving = true; + this.SaveRO(); + } + + private void btn_adddefaults_Click(object sender, EventArgs e) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + this.TempObjectManager.AddDefaults(); // = new ClsRelevantObjectManager(AppSettings.Settings.ObjectPriority, this.ObjectManager.TypeName, this.ObjectManager.Camera); + this.ro = null; + Global_GUI.UpdateFOLV(FOLV_RelevantObjects, this.TempObjectManager.ObjectList, UseSelected: true, SelectObject: this.ro, FullRefresh: true, ForcedSelection: true); + } + + private void toolStripComboBoxCameras_SelectedIndexChanged(object sender, EventArgs e) + { + if (toolStripComboBoxCameras.SelectedItem.ToString().IsNotEmpty()) + { + this.ROMName = toolStripComboBoxCameras.SelectedItem.ToString(); + this.Text = $"Relevant Objects - {this.ROMName}"; + this.SetROM(); + this.LoadROMList(); + this.SelectionChanged(); + } + } + + private void button1_Click(object sender, EventArgs e) + { + this.Save(); + } + + private void toolStripComboBoxCameras_Click(object sender, EventArgs e) + { + + } + + private void btnCancel_Click(object sender, EventArgs e) + { + + } + + private void FOLV_RelevantObjects_FormatCell(object sender, BrightIdeasSoftware.FormatCellEventArgs e) + { + FormatCell(sender, e); + } + + private void FormatCell(object sender, BrightIdeasSoftware.FormatCellEventArgs e) + { + if (e.Column.Name.Contains("Ignore")) + { + if (!this.ROMName.Has("\\default")) + e.SubItem.ForeColor = Color.Gray; + + } + } + + private void tb_MinPercent_TextChanged(object sender, EventArgs e) + { + NeedsSaving = true; + this.SaveRO(); + } + + private void tb_maxpercent_TextChanged(object sender, EventArgs e) + { + NeedsSaving = true; + this.SaveRO(); + } + } +} diff --git a/src/UI/Frm_RelevantObjects.resx b/src/UI/Frm_RelevantObjects.resx new file mode 100644 index 00000000..b64eb79f --- /dev/null +++ b/src/UI/Frm_RelevantObjects.resx @@ -0,0 +1,187 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 122, 17 + + + If you enable this and the object is behind an DYNAMIC mask, it will still trigger. +For example, if you mask the road but still want to know when PEOPLE walk by. + +Also note that the ignore settings only work on the \DEFAULT objects for each camera. + + + If you enable this and the object is behind an IMAGE mask, it will still trigger. +For example, if you mask the road but still want to know when PEOPLE walk by. + +Note that the IMAGE mask is tested before the DYNAMIC mask. + +Also note that the ignore settings only work on the \DEFAULT objects for each camera. + + + 17, 17 + + + + + AAABAAEAGBgAAAAAAACICQAAFgAAACgAAAAYAAAAMAAAAAEAIAAAAAAAAAkAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAgAAAAMAAAADAAAAAwAA + AAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAv///wBnZ2cAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAJPT09LFRZXkBUW2FBVVxhQVVcYkFVXWNBVl1jQVZdY0FWXmNBVl5jQVde + ZEFXXmRBVV1iQVNaYEFSWV9AX2duNGtzehP///8ADhASAAAAAAAAAAAAAAAAAAAAAAAAAAAQwsLClO7b + zPDs0bvw7dK98O7TvvDv1L/w8NW/8O/Vv/Dw1cDw8NXA8PDWwfDx1sHw7tS+8OrQu/DmzLfw07eg2bOm + m5qSk5Qp////AAAAAAAAAAAAAAAAAAAAAAAAAAASxcXEn+uqdP/lhzv/5Yo//+WKP//Wgj3/z389/+WJ + P//lij//5Yo//+WKP//lij//5Yo//+WKP//liT//3IM8/8eadPnAvbu7mpqbOAAAAABubm0AAAAAAAAA + AAAAAAATxMTDnuqncf/jgzj/44Y7/9iAO/9iTj7/TElF/7d2Qf/khjv/44Y7/+OGO//jhjv/44Y7/+OG + O//hhDv/1X03/798Rf/b0sr/xsfHy52cnDz///8AAAAAAAAAAAAAAAATxMTDnuikcP/gfzb/4YI5/9N9 + O/9GTFD/MV19/4dwXP/cgDr/4YI5/+GCOv/hgjr/4YI6/+GCOv/egDn/0nk1/79zOP/Qu6v/4+Lh/8TG + xsmUlZYw////AAAAAAAAAAATxMTDnuahbv/efDP/3n83/9p8N/+YZD7/XldQ/4hgQ//bgT7/34E7/95+ + N//efjf/3n83/95/N//dfjf/1Hg0/8VxMv+5fVD/yJt5/8urlP24qp+xi4yMJAAAAAAAAAATxMTDnuSf + bP/ceTH/3Hs0/959NP+6bC3/WzgZ/1s9JP+wbDr/14NC/9d/Ov/dezP/3Hs0/9x7NP/cezT/2Hk0/890 + Mf/Hbi3/w2sr/8dyNP/LkWbvpaWlbQUFBQkAAAATxMTDnuOcav/ZdS7/13gy/7ZmPv9RSU3/JjtO/yQ9 + ev9FQUf/jXh//6edj//Cglj/1nY3/9p3Mf/adzL/2ncy/9d2Mf/TdDD/03Qw/9NxLP/hmmj+ycnJmgAA + ABAAAAATw8PCnuGaaf/Yciz/rGk6/1smX/8sFoP/Kxhv/z0Ref8uKjr/dDOO/6Zgzv+iksj/v4F1/9V0 + M//XdDD/13Qw/9d0MP/XdDD/13Qw/9ZxLP/knWz/zMzLngAAABIAAAATwsLCnuCXZv/LaC//ezhR/0wF + Y/9CCGT/PxBF/14pJf9WK0b/iCWm/6MYz/+3VN3/u6G3/81/Tv/WcCv/1XEt/9VxLf/VcS3/1XEt/9Ru + Kf/immr/zMvLngAAABMAAAATwsLBnt2WZP+pVTH/YhJZ/08JW/9RBF7/UQ9P/2IkXP99JI7/lCO1/6Ak + x/+uIMr/tYyo/72pff/QczX/020r/9NtK//TbSv/020r/9JrJ//gmGj/y8vLngAAABMAAAATwsHBnsSP + av9rRzb/YR9O/0EwNP9GGUn/UAJh/2ERdv9+IZj/jhyw/6I9tf+bZZT/jXd7/424p/+6inL/0Gop/9Bq + Kf/Qain/0Gop/89nJf/elWf/y8vLngAAABMAAAATwcHBno+Agf9FST//PUJM/ytHPP85Mi7/VQhc/1sL + b/97IpL/iCGe/2pSYv97qXT/caSZ/1eapv+Si6L/y2g2/85mJf/OZyb/zmcm/81kIv/dkmT/ysrKngAA + ABMAAAATwMC/nnp0jf8vRmH/MEhh/xlNZ/81Ok//VAhd/1QFaP96Iov/YjZe/zxXLv9WoZH/dM7d/0yL + vP98c6z/wGVE/8xjIv/MYyT/zGMk/8tgIP/bj2L/ycnJngAAABMAAAATv7+/nodrjP8yMmj/JDdu/xZB + cP8tNmb/UAVh/1ECZP9yHIf/YTZv/zZqZ/8vlq3/V6rL/2x8tv+HWYH/wmAw/8lfIf/JXyL/yV8i/8hc + Hv/ZjWH/ycnJngAAABMAAAATv7++nq1yb/9KImL/GyBw/xowa/8mJ2X/TwBb/04AYP9oE3r/ayyJ/yxY + gv8ZYZX/LV+m/2xdtv+gT0X/xlka/8ZZG//GWRv/xlkb/8VWF//WiFz/yMjIngAAABMAAAARwsLBodah + iP+QY4P/aFuY/1Valv9rYob/iFh9/4VMif+UWpH/jW+I/193lP9gd6L/Z2ym/4xvt/+7fHD/1Idc/9SH + XP/Uh1z/1Idc/9SFWf/gqYr/ysrKoQAAABEAAAALqaioW9DQz5POzs2Tz87Ok9DQz5PR0NCT0dHQk9LS + 0ZPT09KT09PSk9PT05PU1NOT1NTUk9XV1JPV1dWT1dXVk9XV1ZPV1dWT1dXVk9XV1ZPY2NiTrq6uWwAA + AAsAAAACAAAABQAAAAcAAAAHAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAA + AAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAHAAAABQAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8A////AP///wCAAD8AgAAfAIAADwCAAAcAgAADAIAA + AwCAAAEAgAABAIAAAQCAAAEAgAABAIAAAQCAAAEAgAABAIAAAQCAAAEAwAADAP///wD///8A////AP// + /wA= + + + \ No newline at end of file diff --git a/src/UI/Frm_UpdateCheck.Designer.cs b/src/UI/Frm_UpdateCheck.Designer.cs new file mode 100644 index 00000000..4859ddcb --- /dev/null +++ b/src/UI/Frm_UpdateCheck.Designer.cs @@ -0,0 +1,325 @@ + +namespace AITool +{ + partial class Frm_UpdateCheck + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.bt_check = new System.Windows.Forms.Button(); + this.label1 = new System.Windows.Forms.Label(); + this.label2 = new System.Windows.Forms.Label(); + this.label3 = new System.Windows.Forms.Label(); + this.lbl_CurrentVersion = new System.Windows.Forms.Label(); + this.bt_InstallBeta = new System.Windows.Forms.Button(); + this.linkLabelRelease = new System.Windows.Forms.LinkLabel(); + this.linkLabelBeta = new System.Windows.Forms.LinkLabel(); + this.bt_installRelease = new System.Windows.Forms.Button(); + this.toolTip1 = new System.Windows.Forms.ToolTip(this.components); + this.splitContainer1 = new System.Windows.Forms.SplitContainer(); + this.btn_Donate = new System.Windows.Forms.Button(); + this.linkLabelGithub = new System.Windows.Forms.LinkLabel(); + this.linkLabelIPCam = new System.Windows.Forms.LinkLabel(); + this.linkLabelReportIssue = new System.Windows.Forms.LinkLabel(); + this.lbl_message = new System.Windows.Forms.Label(); + this.webBrowser1 = new System.Windows.Forms.WebBrowser(); + this.label4 = new System.Windows.Forms.Label(); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); + this.splitContainer1.Panel1.SuspendLayout(); + this.splitContainer1.Panel2.SuspendLayout(); + this.splitContainer1.SuspendLayout(); + this.SuspendLayout(); + // + // bt_check + // + this.bt_check.Location = new System.Drawing.Point(9, 94); + this.bt_check.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.bt_check.Name = "bt_check"; + this.bt_check.Size = new System.Drawing.Size(85, 24); + this.bt_check.TabIndex = 0; + this.bt_check.Text = "Check"; + this.bt_check.UseVisualStyleBackColor = true; + this.bt_check.Click += new System.EventHandler(this.bt_check_Click); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(50, 14); + this.label1.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(90, 13); + this.label1.TabIndex = 1; + this.label1.Text = "Current Version:"; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(12, 66); + this.label2.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(131, 13); + this.label2.TabIndex = 1; + this.label2.Text = "Current Official Release:"; + // + // label3 + // + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(21, 39); + this.label3.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(116, 13); + this.label3.TabIndex = 1; + this.label3.Text = "Current Beta Release:"; + // + // lbl_CurrentVersion + // + this.lbl_CurrentVersion.AutoSize = true; + this.lbl_CurrentVersion.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.lbl_CurrentVersion.Location = new System.Drawing.Point(141, 14); + this.lbl_CurrentVersion.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + this.lbl_CurrentVersion.Name = "lbl_CurrentVersion"; + this.lbl_CurrentVersion.Size = new System.Drawing.Size(91, 13); + this.lbl_CurrentVersion.TabIndex = 2; + this.lbl_CurrentVersion.Text = "1.1.1 (2/2/22)"; + // + // bt_InstallBeta + // + this.bt_InstallBeta.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.bt_InstallBeta.Enabled = false; + this.bt_InstallBeta.Location = new System.Drawing.Point(271, 33); + this.bt_InstallBeta.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.bt_InstallBeta.Name = "bt_InstallBeta"; + this.bt_InstallBeta.Size = new System.Drawing.Size(69, 23); + this.bt_InstallBeta.TabIndex = 0; + this.bt_InstallBeta.Text = "Install"; + this.toolTip1.SetToolTip(this.bt_InstallBeta, "Download latest beta installer"); + this.bt_InstallBeta.UseVisualStyleBackColor = true; + this.bt_InstallBeta.Click += new System.EventHandler(this.bt_InstallBeta_Click); + // + // linkLabelRelease + // + this.linkLabelRelease.AutoSize = true; + this.linkLabelRelease.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.linkLabelRelease.Location = new System.Drawing.Point(141, 66); + this.linkLabelRelease.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + this.linkLabelRelease.Name = "linkLabelRelease"; + this.linkLabelRelease.Size = new System.Drawing.Size(13, 13); + this.linkLabelRelease.TabIndex = 3; + this.linkLabelRelease.TabStop = true; + this.linkLabelRelease.Text = "."; + this.toolTip1.SetToolTip(this.linkLabelRelease, "Click to go to release page"); + this.linkLabelRelease.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.linkLabelRelease_LinkClicked); + // + // linkLabelBeta + // + this.linkLabelBeta.AutoSize = true; + this.linkLabelBeta.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.linkLabelBeta.Location = new System.Drawing.Point(141, 39); + this.linkLabelBeta.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + this.linkLabelBeta.Name = "linkLabelBeta"; + this.linkLabelBeta.Size = new System.Drawing.Size(13, 13); + this.linkLabelBeta.TabIndex = 3; + this.linkLabelBeta.TabStop = true; + this.linkLabelBeta.Text = "."; + this.toolTip1.SetToolTip(this.linkLabelBeta, "Click to go to beta installer page"); + this.linkLabelBeta.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.linkLabelBeta_LinkClicked); + // + // bt_installRelease + // + this.bt_installRelease.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.bt_installRelease.Enabled = false; + this.bt_installRelease.Location = new System.Drawing.Point(271, 61); + this.bt_installRelease.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.bt_installRelease.Name = "bt_installRelease"; + this.bt_installRelease.Size = new System.Drawing.Size(69, 22); + this.bt_installRelease.TabIndex = 4; + this.bt_installRelease.Text = "Install"; + this.toolTip1.SetToolTip(this.bt_installRelease, "Download latest official release version"); + this.bt_installRelease.UseVisualStyleBackColor = true; + this.bt_installRelease.Click += new System.EventHandler(this.bt_installRelease_Click); + // + // splitContainer1 + // + this.splitContainer1.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; + this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill; + this.splitContainer1.FixedPanel = System.Windows.Forms.FixedPanel.Panel1; + this.splitContainer1.Location = new System.Drawing.Point(0, 0); + this.splitContainer1.Name = "splitContainer1"; + // + // splitContainer1.Panel1 + // + this.splitContainer1.Panel1.Controls.Add(this.btn_Donate); + this.splitContainer1.Panel1.Controls.Add(this.linkLabelGithub); + this.splitContainer1.Panel1.Controls.Add(this.linkLabelIPCam); + this.splitContainer1.Panel1.Controls.Add(this.linkLabelReportIssue); + this.splitContainer1.Panel1.Controls.Add(this.lbl_message); + this.splitContainer1.Panel1.Controls.Add(this.label1); + this.splitContainer1.Panel1.Controls.Add(this.bt_installRelease); + this.splitContainer1.Panel1.Controls.Add(this.bt_check); + this.splitContainer1.Panel1.Controls.Add(this.linkLabelBeta); + this.splitContainer1.Panel1.Controls.Add(this.bt_InstallBeta); + this.splitContainer1.Panel1.Controls.Add(this.linkLabelRelease); + this.splitContainer1.Panel1.Controls.Add(this.label2); + this.splitContainer1.Panel1.Controls.Add(this.lbl_CurrentVersion); + this.splitContainer1.Panel1.Controls.Add(this.label3); + // + // splitContainer1.Panel2 + // + this.splitContainer1.Panel2.Controls.Add(this.webBrowser1); + this.splitContainer1.Panel2.Controls.Add(this.label4); + this.splitContainer1.Size = new System.Drawing.Size(752, 275); + this.splitContainer1.SplitterDistance = 348; + this.splitContainer1.TabIndex = 5; + // + // btn_Donate + // + this.btn_Donate.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.btn_Donate.Image = global::AITool.Properties.Resources.donate_button; + this.btn_Donate.Location = new System.Drawing.Point(9, 208); + this.btn_Donate.Name = "btn_Donate"; + this.btn_Donate.Size = new System.Drawing.Size(79, 29); + this.btn_Donate.TabIndex = 9; + this.btn_Donate.UseVisualStyleBackColor = true; + this.btn_Donate.Click += new System.EventHandler(this.btn_Donate_Click); + // + // linkLabelGithub + // + this.linkLabelGithub.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.linkLabelGithub.AutoSize = true; + this.linkLabelGithub.Location = new System.Drawing.Point(226, 251); + this.linkLabelGithub.Name = "linkLabelGithub"; + this.linkLabelGithub.Size = new System.Drawing.Size(104, 13); + this.linkLabelGithub.TabIndex = 8; + this.linkLabelGithub.TabStop = true; + this.linkLabelGithub.Text = "Github Discussion "; + // + // linkLabelIPCam + // + this.linkLabelIPCam.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.linkLabelIPCam.AutoSize = true; + this.linkLabelIPCam.Location = new System.Drawing.Point(107, 251); + this.linkLabelIPCam.Name = "linkLabelIPCam"; + this.linkLabelIPCam.Size = new System.Drawing.Size(94, 13); + this.linkLabelIPCam.TabIndex = 7; + this.linkLabelIPCam.TabStop = true; + this.linkLabelIPCam.Text = "IPCamTalk Forum"; + this.linkLabelIPCam.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.linkLabelIPCam_LinkClicked); + // + // linkLabelReportIssue + // + this.linkLabelReportIssue.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.linkLabelReportIssue.AutoSize = true; + this.linkLabelReportIssue.Location = new System.Drawing.Point(12, 251); + this.linkLabelReportIssue.Name = "linkLabelReportIssue"; + this.linkLabelReportIssue.Size = new System.Drawing.Size(71, 13); + this.linkLabelReportIssue.TabIndex = 6; + this.linkLabelReportIssue.TabStop = true; + this.linkLabelReportIssue.Text = "Report Issue"; + this.linkLabelReportIssue.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.linkLabelReportIssue_LinkClicked); + // + // lbl_message + // + this.lbl_message.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.lbl_message.ForeColor = System.Drawing.Color.Green; + this.lbl_message.Location = new System.Drawing.Point(137, 100); + this.lbl_message.Name = "lbl_message"; + this.lbl_message.Size = new System.Drawing.Size(203, 52); + this.lbl_message.TabIndex = 5; + this.lbl_message.Text = "A newer version is available. "; + this.lbl_message.Visible = false; + // + // webBrowser1 + // + this.webBrowser1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.webBrowser1.Location = new System.Drawing.Point(3, 29); + this.webBrowser1.MinimumSize = new System.Drawing.Size(20, 20); + this.webBrowser1.Name = "webBrowser1"; + this.webBrowser1.Size = new System.Drawing.Size(389, 239); + this.webBrowser1.TabIndex = 1; + this.webBrowser1.DocumentCompleted += new System.Windows.Forms.WebBrowserDocumentCompletedEventHandler(this.webBrowser1_DocumentCompleted); + this.webBrowser1.Navigated += new System.Windows.Forms.WebBrowserNavigatedEventHandler(this.webBrowser1_Navigated); + // + // label4 + // + this.label4.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.label4.BackColor = System.Drawing.SystemColors.Info; + this.label4.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.label4.Location = new System.Drawing.Point(3, 0); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(394, 26); + this.label4.TabIndex = 0; + this.label4.Text = "Release Notes"; + this.label4.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // Frm_UpdateCheck + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(752, 275); + this.Controls.Add(this.splitContainer1); + this.Font = new System.Drawing.Font("Segoe UI", 8.25F); + this.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.Name = "Frm_UpdateCheck"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Tag = "SAVE"; + this.Text = "Check for updates"; + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.Frm_UpdateCheck_FormClosing); + this.Load += new System.EventHandler(this.Frm_UpdateCheck_Load); + this.splitContainer1.Panel1.ResumeLayout(false); + this.splitContainer1.Panel1.PerformLayout(); + this.splitContainer1.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit(); + this.splitContainer1.ResumeLayout(false); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Button bt_check; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.Label lbl_CurrentVersion; + private System.Windows.Forms.Button bt_InstallBeta; + private System.Windows.Forms.LinkLabel linkLabelRelease; + private System.Windows.Forms.LinkLabel linkLabelBeta; + private System.Windows.Forms.Button bt_installRelease; + private System.Windows.Forms.ToolTip toolTip1; + private System.Windows.Forms.SplitContainer splitContainer1; + private System.Windows.Forms.WebBrowser webBrowser1; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.Label lbl_message; + private System.Windows.Forms.LinkLabel linkLabelReportIssue; + private System.Windows.Forms.LinkLabel linkLabelGithub; + private System.Windows.Forms.LinkLabel linkLabelIPCam; + private System.Windows.Forms.Button btn_Donate; + } +} \ No newline at end of file diff --git a/src/UI/Frm_UpdateCheck.cs b/src/UI/Frm_UpdateCheck.cs new file mode 100644 index 00000000..28da8f0b --- /dev/null +++ b/src/UI/Frm_UpdateCheck.cs @@ -0,0 +1,388 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Diagnostics; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using System.Xml; + +using Octokit; +using Octokit.Helpers; +using Octokit.Internal; + +using Telegram.Bot.Types; + +using static AITool.AITOOL; + +namespace AITool +{ + public partial class Frm_UpdateCheck:Form + { + private DateTime CurrentVerTime = DateTime.MinValue; + private GitHubClient client = null; + private IReadOnlyList releases = null; + + public Frm_UpdateCheck() + { + InitializeComponent(); + } + + private void Frm_UpdateCheck_Load(object sender, EventArgs e) + { + Global_GUI.RestoreWindowState(this); + + + + } + + private void Frm_UpdateCheck_FormClosing(object sender, FormClosingEventArgs e) + { + Global_GUI.SaveWindowState(this); + } + + + private class ReleaseNote + { + public DateTime Date; + public String Title = ""; + public String Body = ""; + public string Type = ""; + public string Version = ""; + } + + private async void bt_check_Click(object sender, EventArgs e) + { + + try + { + + this.ControlBox = false; + + Assembly CurAssm = Assembly.GetExecutingAssembly(); + string AssemVer = CurAssm.GetName().Version.Major + "." + CurAssm.GetName().Version.Minor + "." + CurAssm.GetName().Version.Build; + CurrentVerTime = Global.RetrieveLinkerTimestamp(); + lbl_CurrentVersion.Text = $"{AssemVer} ({CurrentVerTime.ToShortDateString()})"; + + List notes = new List(); + + using Global_GUI.CursorWait cw = new Global_GUI.CursorWait(); + bt_check.Enabled = false; + bt_check.Text = "Checking..."; + + //first get the most recent release: + if (client.IsNull()) + client = new GitHubClient(new ProductHeaderValue("AITOOL-VORLONCD")); + + Log("Getting Github rate limits..."); + + var miscellaneousRateLimit = await client.Miscellaneous.GetRateLimits(); + + // The "core" object provides your rate limit status except for the Search API. + var coreRateLimit = miscellaneousRateLimit.Resources.Core; + + var howManyCoreRequestsCanIMakePerHour = coreRateLimit?.Limit; + var howManyCoreRequestsDoIHaveLeft = coreRateLimit?.Remaining; + var whenDoesTheCoreLimitReset = coreRateLimit?.Reset.ToLocalTime(); // UTC time + + // the "search" object provides your rate limit status for the Search API. + var searchRateLimit = miscellaneousRateLimit.Resources.Search; + + var howManySearchRequestsCanIMakePerMinute = searchRateLimit?.Limit; + var howManySearchRequestsDoIHaveLeft = searchRateLimit?.Remaining; + var whenDoesTheSearchLimitReset = searchRateLimit?.Reset.ToLocalTime(); // UTC time + + Log($"Github>> Max Core Requests per hour: {howManyCoreRequestsCanIMakePerHour} (remaining={howManyCoreRequestsDoIHaveLeft} left)"); + Log($"Github>> Max Search Requests per minute: {howManySearchRequestsCanIMakePerMinute} (remaining={howManySearchRequestsDoIHaveLeft} left)"); + + //------------------------------ + //Max Core=60/hr + //Max search=10/min + //WE USE ABOUT 20 CORE REQUESTS EACH UPDATE CHECK + //doesnt look like we actually use "SEARCH" requests for this update check -Vorlon + //------------------------------ + + if (howManyCoreRequestsDoIHaveLeft <= 1 && whenDoesTheCoreLimitReset.IsNotNull()) + { + string err = $"GitHub>> Please wait until after {whenDoesTheSearchLimitReset} to check for updates again.\r\n\r\n(CORE Limit={howManyCoreRequestsCanIMakePerHour}/hr, remaining={howManyCoreRequestsDoIHaveLeft})"; + Log(err); + MessageBox.Show(err, "GITHUB Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + + if (howManySearchRequestsDoIHaveLeft <= 1 && whenDoesTheSearchLimitReset.IsNotNull()) + { + string err = $"GitHub>> Please wait until after {whenDoesTheSearchLimitReset} to check for updates again.\r\n\r\n(SEARCH Limit={howManySearchRequestsCanIMakePerMinute}/min, remaining={howManySearchRequestsDoIHaveLeft})"; + Log(err); + MessageBox.Show(err, "GITHUB Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + + Log("Loading latest Github release..."); + + releases = await client.Repository.Release.GetAll("VorlonCD", "bi-aidetection"); + + //var assets = await client.rel Release.GetAllAssets("VorlonCD", "bi-aidetection", releases[0]); + //var myAsset_zipFile = assets[0]; + + string releasever = $"{releases[0].TagName} ({releases[0].PublishedAt.Value.LocalDateTime.ToShortDateString()})"; + linkLabelRelease.Text = releasever; + + ReleaseNote rn = new ReleaseNote(); + rn.Date = releases[0].PublishedAt.Value.LocalDateTime; + rn.Title = releases[0].Name; + rn.Body = releases[0].Body; + rn.Version = releases[0].TagName; + rn.Type = "Release"; + notes.Add(rn); + + + + //get the latest beta version installer file + var repo = await client.Repository.Get("VorlonCD", "bi-aidetection"); + + var contents = await client.Repository.Content.GetAllContents("VorlonCD", "bi-aidetection", "src/UI/Installer"); + + //get the date on the file + var path = contents[0].Path; //"src/UI/Installer"; + var branch = "master"; + + var request = new CommitRequest { Path = path, Sha = branch }; + + // find the latest commit to the file on a specific branch + var commitsForFile = await client.Repository.Commit.GetAll(repo.Id, request); + var mostRecentCommit = commitsForFile[0]; + var authorDate = mostRecentCommit.Commit.Author.Date; + var fileEditDate = authorDate.LocalDateTime; + + string ver = contents[0].Name.GetWord(".", ".exe"); + string betaver = $"{ver} ({fileEditDate.ToShortDateString()})"; + linkLabelBeta.Text = betaver; + + if (CurAssm.GetName().Version < new Version(ver)) + { + lbl_message.Visible = true; + } + else + { + lbl_message.Visible = false; + } + + var commits = await client.Repository.Commit.GetAll("VorlonCD", "bi-aidetection"); + + for (int i = 0; i < commits.Count; i++) + { + if (commits[i].Commit.Author.Date > releases[0].PublishedAt.Value.LocalDateTime) + { + rn = new ReleaseNote(); + rn.Date = commits[i].Commit.Author.Date.LocalDateTime; + rn.Title = !commits[i].Label.IsNull() ? rn.Title : ""; + rn.Body = commits[i].Commit.Message; + bool hasdash = rn.Body.TrimStart().StartsWith("-"); + bool hasstar = rn.Body.TrimStart().StartsWith("*"); + if (!hasdash && !hasstar) + rn.Body = "* " + rn.Body.Trim(); + + if (i == 0) + rn.Version = ver; + else + rn.Version = ""; + + rn.Type = "Commit"; + notes.Insert(0, rn); + } + } + + + //sort by date + notes = notes.OrderByDescending((d) => d.Date).ToList(); + + StringBuilder Markup = new StringBuilder(); + + + foreach (var note in notes) + { + Markup.AppendLine($"{note.Version} ({note.Date}) {note.Title}"); + Markup.AppendLine(""); + Markup.AppendLine($"{note.Body}"); + Markup.AppendLine(""); + Markup.AppendLine(""); + } + + var html = Markdig.Markdown.ToHtml(Markup.ToString()); + + + webBrowser1.DocumentText = html; + + + + // get the download URL for this file on a specific branch + //var file = await client.Repository.Content.GetAllContentsByRef(repo.Id, path, branch); + + bt_InstallBeta.Enabled = true; + bt_installRelease.Enabled = true; + + //Setup the versions + //Version latestGitHubVersion = new Version(releases[0].TagName); + //Version localVersion = new Version("X.X.X"); + + + } + catch (Exception ex) + { + + Log("Error: " + ex.Msg()); + MessageBox.Show("Error: " + ex.Message, "GITHUB API Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + finally + { + this.ControlBox = true; + bt_check.Enabled = true; + bt_check.Text = "Check"; + } + } + + private void linkLabelRelease_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) + { + Process.Start("https://github.com/VorlonCD/bi-aidetection/releases"); + } + + private void linkLabelBeta_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) + { + Process.Start("https://github.com/VorlonCD/bi-aidetection/tree/master/src/UI/Installer"); + } + + private async void bt_installRelease_Click(object sender, EventArgs e) + { + try + { + using Global_GUI.CursorWait cw = new Global_GUI.CursorWait(); + + bt_installRelease.Enabled = false; + bt_installRelease.Text = "Working"; + + string filename = Path.Combine(Directory.GetCurrentDirectory(), releases[0].Assets[0].Name); + + if (!System.IO.File.Exists(filename)) + { + var response = await client.Connection.Get(new Uri(releases[0].Assets[0].Url), new Dictionary(), "application/octet-stream"); + System.IO.File.WriteAllBytes(filename, (byte[])response.Body); + + } + + ExploreFile(filename); + Process.Start(filename); + + this.DialogResult = DialogResult.OK; + this.Close(); + //if (filename.Has(".exe")) + + } + catch (Exception ex) + { + + MessageBox.Show("Error: " + ex.Message, "Error downloading", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + finally + { + bt_installRelease.Enabled = true; + bt_installRelease.Text = "Download"; + + } + } + + public bool ExploreFile(string filePath) + { + if (!System.IO.File.Exists(filePath)) + { + return false; + } + //Clean up file path so it can be navigated OK + filePath = System.IO.Path.GetFullPath(filePath); + System.Diagnostics.Process.Start("explorer.exe", string.Format("/select,\"{0}\"", filePath)); + return true; + } + + private async void bt_InstallBeta_Click(object sender, EventArgs e) + { + + + try + { + using Global_GUI.CursorWait cw = new Global_GUI.CursorWait(); + + bt_InstallBeta.Enabled = false; + bt_InstallBeta.Text = "Working"; + + //get the latest beta version installer file + var repo = await client.Repository.Get("VorlonCD", "bi-aidetection"); + + var contents = await client.Repository.Content.GetAllContents("VorlonCD", "bi-aidetection", "src/UI/Installer"); + + //Octokit.ApiException: 'Unsupported 'Accept' header: 'application/octet-stream'. Must accept 'application/json'.' + + string filename = Path.Combine(Directory.GetCurrentDirectory(), contents[0].Name); + + if (!System.IO.File.Exists(filename)) + { + var response = await client.Connection.Get(new Uri(contents[0].DownloadUrl), new Dictionary(), "application/octet-stream"); + + System.IO.File.WriteAllBytes(filename, (byte[])response.Body); + + } + ExploreFile(filename); + Process.Start(filename); + this.DialogResult = DialogResult.OK; + this.Close(); + } + catch (Exception ex) + { + + MessageBox.Show("Error: " + ex.Message, "Error downloading", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + finally + { + bt_InstallBeta.Enabled = true; + bt_InstallBeta.Text = "Download"; + + } + } + + private void linkLabelReportIssue_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) + { + Process.Start("https://github.com/VorlonCD/bi-aidetection/issues"); + } + + private void linkLabelIPCam_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) + { + Process.Start("https://ipcamtalk.com/threads/tool-tutorial-free-ai-person-detection-for-blue-iris.37330/"); + } + + private void btn_Donate_Click(object sender, EventArgs e) + { + Process.Start("https://github.com/sponsors/VorlonCD"); + } + + private void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) + { + + } + + private void webBrowser1_Navigated(object sender, WebBrowserNavigatedEventArgs e) + { + //var doc = webBrowser1.Document; + + //if (doc != null) + //{ + // doc.ExecCommand("FontSize", false, 10); + // doc.ExecCommand("FontFamily", false, "consolas"); + //} + } + } +} diff --git a/src/UI/Frm_UpdateCheck.resx b/src/UI/Frm_UpdateCheck.resx new file mode 100644 index 00000000..df8339b6 --- /dev/null +++ b/src/UI/Frm_UpdateCheck.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + \ No newline at end of file diff --git a/src/UI/Frm_Variables.Designer.cs b/src/UI/Frm_Variables.Designer.cs new file mode 100644 index 00000000..c5bf89b5 --- /dev/null +++ b/src/UI/Frm_Variables.Designer.cs @@ -0,0 +1,86 @@ + +namespace AITool +{ + partial class Frm_Variables + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.FOLV_Vars = new BrightIdeasSoftware.FastObjectListView(); + this.label1 = new System.Windows.Forms.Label(); + ((System.ComponentModel.ISupportInitialize)(this.FOLV_Vars)).BeginInit(); + this.SuspendLayout(); + // + // FOLV_Vars + // + this.FOLV_Vars.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.FOLV_Vars.HideSelection = false; + this.FOLV_Vars.Location = new System.Drawing.Point(2, 25); + this.FOLV_Vars.Name = "FOLV_Vars"; + this.FOLV_Vars.ShowGroups = false; + this.FOLV_Vars.Size = new System.Drawing.Size(382, 450); + this.FOLV_Vars.TabIndex = 0; + this.FOLV_Vars.UseCompatibleStateImageBehavior = false; + this.FOLV_Vars.View = System.Windows.Forms.View.Details; + this.FOLV_Vars.VirtualMode = true; + this.FOLV_Vars.SelectedIndexChanged += new System.EventHandler(this.FOLV_Vars_SelectedIndexChanged); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(-1, 6); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(210, 13); + this.label1.TabIndex = 1; + this.label1.Text = "CTRL-Click copies variables to clipboard"; + // + // Frm_Variables + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(386, 476); + this.Controls.Add(this.label1); + this.Controls.Add(this.FOLV_Vars); + this.Font = new System.Drawing.Font("Segoe UI", 8.25F); + this.Name = "Frm_Variables"; + this.Tag = "SAVE"; + this.Text = "Variables"; + this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.Frm_Variables_FormClosed); + this.Load += new System.EventHandler(this.Frm_Variables_Load); + ((System.ComponentModel.ISupportInitialize)(this.FOLV_Vars)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + public BrightIdeasSoftware.FastObjectListView FOLV_Vars; + private System.Windows.Forms.Label label1; + } +} \ No newline at end of file diff --git a/src/UI/Frm_Variables.cs b/src/UI/Frm_Variables.cs new file mode 100644 index 00000000..e9e44d44 --- /dev/null +++ b/src/UI/Frm_Variables.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace AITool +{ + public partial class Frm_Variables : Form + { + public List props = new List(); + public Frm_Variables() + { + InitializeComponent(); + } + + private void Frm_Variables_Load(object sender, EventArgs e) + { + Global_GUI.ConfigureFOLV(FOLV_Vars, typeof(ClsProp)); + Global_GUI.UpdateFOLV(FOLV_Vars, props, ResizeColsStyle: ColumnHeaderAutoResizeStyle.ColumnContent); + Global_GUI.RestoreWindowState(this); + } + + private void Frm_Variables_FormClosed(object sender, FormClosedEventArgs e) + { + Global_GUI.SaveWindowState(this); + } + + private void FOLV_Vars_SelectedIndexChanged(object sender, EventArgs e) + { + if (this.FOLV_Vars.SelectedObjects != null && this.FOLV_Vars.SelectedObjects.Count > 0) + { + string clip = ""; + foreach (ClsProp prop in this.FOLV_Vars.SelectedObjects) + { + clip += prop.Name + " "; + } + Clipboard.SetText(clip.Trim()); + } + else + { + //Clipboard.SetText(""); + } + } + } +} diff --git a/src/UI/Frm_Variables.resx b/src/UI/Frm_Variables.resx new file mode 100644 index 00000000..1af7de15 --- /dev/null +++ b/src/UI/Frm_Variables.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/UI/Frm_WizardAI.Designer.cs b/src/UI/Frm_WizardAI.Designer.cs new file mode 100644 index 00000000..e7dc0647 --- /dev/null +++ b/src/UI/Frm_WizardAI.Designer.cs @@ -0,0 +1,40 @@ + +namespace AITool +{ + partial class Frm_WizardAI + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(800, 450); + this.Text = "Frm_WizardAI"; + } + + #endregion + } +} \ No newline at end of file diff --git a/src/UI/Frm_WizardAI.cs b/src/UI/Frm_WizardAI.cs new file mode 100644 index 00000000..28392a32 --- /dev/null +++ b/src/UI/Frm_WizardAI.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace AITool +{ + public partial class Frm_WizardAI : Form + { + public Frm_WizardAI() + { + InitializeComponent(); + } + } +} diff --git a/src/UI/Frm_WizardBIServer.Designer.cs b/src/UI/Frm_WizardBIServer.Designer.cs new file mode 100644 index 00000000..2def45dd --- /dev/null +++ b/src/UI/Frm_WizardBIServer.Designer.cs @@ -0,0 +1,132 @@ + +namespace AITool +{ + partial class Frm_WizardBIServer + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Frm_WizardBIServer)); + this.label1 = new System.Windows.Forms.Label(); + this.label2 = new System.Windows.Forms.Label(); + this.txt_BlueIrisServer = new System.Windows.Forms.TextBox(); + this.bt_validate = new System.Windows.Forms.Button(); + this.lblStatus = new System.Windows.Forms.Label(); + this.lblURL = new System.Windows.Forms.Label(); + this.SuspendLayout(); + // + // label1 + // + this.label1.BackColor = System.Drawing.Color.DodgerBlue; + this.label1.Dock = System.Windows.Forms.DockStyle.Top; + this.label1.Font = new System.Drawing.Font("Segoe UI", 15.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.label1.ForeColor = System.Drawing.Color.White; + this.label1.Location = new System.Drawing.Point(0, 0); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(582, 30); + this.label1.TabIndex = 0; + this.label1.Text = "Select BlueIris Server Machine"; + this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // label2 + // + this.label2.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.label2.Location = new System.Drawing.Point(12, 43); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(558, 117); + this.label2.TabIndex = 1; + this.label2.Text = resources.GetString("label2.Text"); + this.label2.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + this.label2.Click += new System.EventHandler(this.label2_Click); + // + // txt_BlueIrisServer + // + this.txt_BlueIrisServer.Location = new System.Drawing.Point(15, 167); + this.txt_BlueIrisServer.Name = "txt_BlueIrisServer"; + this.txt_BlueIrisServer.Size = new System.Drawing.Size(189, 20); + this.txt_BlueIrisServer.TabIndex = 2; + // + // bt_validate + // + this.bt_validate.Location = new System.Drawing.Point(215, 163); + this.bt_validate.Name = "bt_validate"; + this.bt_validate.Size = new System.Drawing.Size(60, 26); + this.bt_validate.TabIndex = 3; + this.bt_validate.Text = "Validate"; + this.bt_validate.UseVisualStyleBackColor = true; + this.bt_validate.Click += new System.EventHandler(this.bt_validate_Click); + // + // lblStatus + // + this.lblStatus.AutoSize = true; + this.lblStatus.Location = new System.Drawing.Point(281, 170); + this.lblStatus.Name = "lblStatus"; + this.lblStatus.Size = new System.Drawing.Size(53, 13); + this.lblStatus.TabIndex = 4; + this.lblStatus.Text = "Unknown"; + // + // lblURL + // + this.lblURL.AutoSize = true; + this.lblURL.Location = new System.Drawing.Point(12, 204); + this.lblURL.Name = "lblURL"; + this.lblURL.Size = new System.Drawing.Size(10, 13); + this.lblURL.TabIndex = 5; + this.lblURL.Text = "."; + // + // Frm_WizardBIServer + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScroll = true; + this.ClientSize = new System.Drawing.Size(582, 277); + this.ControlBox = false; + this.Controls.Add(this.lblURL); + this.Controls.Add(this.lblStatus); + this.Controls.Add(this.bt_validate); + this.Controls.Add(this.txt_BlueIrisServer); + this.Controls.Add(this.label2); + this.Controls.Add(this.label1); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None; + this.Name = "Frm_WizardBIServer"; + this.Text = "Frm_WizardBI"; + this.Load += new System.EventHandler(this.Frm_WizardBIServer_Load); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Label label2; + public System.Windows.Forms.TextBox txt_BlueIrisServer; + private System.Windows.Forms.Button bt_validate; + private System.Windows.Forms.Label lblStatus; + private System.Windows.Forms.Label lblURL; + } +} \ No newline at end of file diff --git a/src/UI/Frm_WizardBIServer.cs b/src/UI/Frm_WizardBIServer.cs new file mode 100644 index 00000000..8303f209 --- /dev/null +++ b/src/UI/Frm_WizardBIServer.cs @@ -0,0 +1,58 @@ +using System; +using System.Drawing; +using System.Linq; +using System.Windows.Forms; +using static AITool.AITOOL; + +namespace AITool +{ + public partial class Frm_WizardBIServer : Form + { + public bool Valid = false; + + public Frm_WizardBIServer() + { + InitializeComponent(); + } + + private void Frm_WizardBIServer_Load(object sender, EventArgs e) + { + this.txt_BlueIrisServer.Text = AITOOL.BlueIrisInfo.ServerName; + this.lblURL.Text = AITOOL.BlueIrisInfo.URL; + } + + private async void bt_validate_Click(object sender, EventArgs e) + { + if (string.IsNullOrEmpty(this.txt_BlueIrisServer.Text)) + { + lblStatus.Text = "Error: Select a server!"; + return; + } + + BlueIrisInfo bi = new BlueIrisInfo(); + + lblStatus.Text = "Validating..."; + + BlueIrisResult bir = await bi.RefreshBIInfoAsync(this.txt_BlueIrisServer.Text); + + if (bir == BlueIrisResult.Valid) + { + lblStatus.ForeColor = Color.DodgerBlue; + lblStatus.Text = $"Validated. Result={bir}. {bi.ClipPaths.Count} clip paths, {bi.Users.Count} users, {bi.Cameras} cameras."; + AITOOL.BlueIrisInfo = bi; + this.Valid = true; + } + else + { + lblStatus.ForeColor = Color.Red; + lblStatus.Text = $"Error: Result={bir}. See log for more details."; + this.Valid = false; + } + } + + private void label2_Click(object sender, EventArgs e) + { + + } + } +} diff --git a/src/UI/Frm_WizardBIServer.resx b/src/UI/Frm_WizardBIServer.resx new file mode 100644 index 00000000..5e633f36 --- /dev/null +++ b/src/UI/Frm_WizardBIServer.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Enter the machine name where BlueIris is running. Use 127.0.0.1 or 'Localhost' for the current machine. + +If not running on the current machine: + +* Make sure the REMOTE REGISTRY service is not disabled and has been started + +* Map a drive letter to the root of the BlueIris CLIPS folder. For example, Z: mapped to \\server\Storage which is actually C:\Storage + + \ No newline at end of file diff --git a/src/UI/Frm_WizzardParent.Designer.cs b/src/UI/Frm_WizzardParent.Designer.cs new file mode 100644 index 00000000..40e53217 --- /dev/null +++ b/src/UI/Frm_WizzardParent.Designer.cs @@ -0,0 +1,168 @@ + +namespace AITool +{ + partial class Frm_WizzardParent + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.panel1 = new System.Windows.Forms.Panel(); + this.pnlContent = new System.Windows.Forms.Panel(); + this.pictureBox1 = new System.Windows.Forms.PictureBox(); + this.panel3 = new System.Windows.Forms.Panel(); + this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); + this.btnBack = new System.Windows.Forms.Button(); + this.btnNext = new System.Windows.Forms.Button(); + this.btnCancel = new System.Windows.Forms.Button(); + this.panel1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit(); + this.panel3.SuspendLayout(); + this.tableLayoutPanel1.SuspendLayout(); + this.SuspendLayout(); + // + // panel1 + // + this.panel1.Controls.Add(this.pictureBox1); + this.panel1.Dock = System.Windows.Forms.DockStyle.Left; + this.panel1.Location = new System.Drawing.Point(0, 0); + this.panel1.Name = "panel1"; + this.panel1.Size = new System.Drawing.Size(155, 450); + this.panel1.TabIndex = 3; + // + // pnlContent + // + this.pnlContent.Dock = System.Windows.Forms.DockStyle.Fill; + this.pnlContent.Location = new System.Drawing.Point(155, 0); + this.pnlContent.Name = "pnlContent"; + this.pnlContent.Size = new System.Drawing.Size(645, 450); + this.pnlContent.TabIndex = 4; + // + // pictureBox1 + // + this.pictureBox1.BackColor = System.Drawing.Color.LightCyan; + this.pictureBox1.Dock = System.Windows.Forms.DockStyle.Fill; + this.pictureBox1.Image = global::AITool.Properties.Resources.Logo; + this.pictureBox1.Location = new System.Drawing.Point(0, 0); + this.pictureBox1.Name = "pictureBox1"; + this.pictureBox1.Size = new System.Drawing.Size(155, 450); + this.pictureBox1.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.pictureBox1.TabIndex = 0; + this.pictureBox1.TabStop = false; + // + // panel3 + // + this.panel3.Controls.Add(this.tableLayoutPanel1); + this.panel3.Dock = System.Windows.Forms.DockStyle.Bottom; + this.panel3.Location = new System.Drawing.Point(155, 401); + this.panel3.Name = "panel3"; + this.panel3.Size = new System.Drawing.Size(645, 49); + this.panel3.TabIndex = 5; + // + // tableLayoutPanel1 + // + this.tableLayoutPanel1.ColumnCount = 4; + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 30F)); + this.tableLayoutPanel1.Controls.Add(this.btnCancel, 0, 0); + this.tableLayoutPanel1.Controls.Add(this.btnNext, 0, 0); + this.tableLayoutPanel1.Controls.Add(this.btnBack, 0, 0); + this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Right; + this.tableLayoutPanel1.Location = new System.Drawing.Point(292, 0); + this.tableLayoutPanel1.Name = "tableLayoutPanel1"; + this.tableLayoutPanel1.RowCount = 1; + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel1.Size = new System.Drawing.Size(353, 49); + this.tableLayoutPanel1.TabIndex = 0; + // + // btnBack + // + this.btnBack.Dock = System.Windows.Forms.DockStyle.Fill; + this.btnBack.Location = new System.Drawing.Point(4, 5); + this.btnBack.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.btnBack.Name = "btnBack"; + this.btnBack.Size = new System.Drawing.Size(105, 39); + this.btnBack.TabIndex = 1; + this.btnBack.Text = "< &Back"; + this.btnBack.UseVisualStyleBackColor = true; + this.btnBack.Click += new System.EventHandler(this.btnBack_Click); + // + // btnNext + // + this.btnNext.Dock = System.Windows.Forms.DockStyle.Fill; + this.btnNext.Location = new System.Drawing.Point(117, 5); + this.btnNext.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.btnNext.Name = "btnNext"; + this.btnNext.Size = new System.Drawing.Size(105, 39); + this.btnNext.TabIndex = 2; + this.btnNext.Text = "&Next >"; + this.btnNext.UseVisualStyleBackColor = true; + this.btnNext.Click += new System.EventHandler(this.btnNext_Click); + // + // btnCancel + // + this.btnCancel.Dock = System.Windows.Forms.DockStyle.Fill; + this.btnCancel.Location = new System.Drawing.Point(230, 5); + this.btnCancel.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.btnCancel.Name = "btnCancel"; + this.btnCancel.Size = new System.Drawing.Size(105, 39); + this.btnCancel.TabIndex = 3; + this.btnCancel.Text = "&Cancel"; + this.btnCancel.UseVisualStyleBackColor = true; + this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click); + // + // Frm_WizzardParent + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(800, 450); + this.Controls.Add(this.panel3); + this.Controls.Add(this.pnlContent); + this.Controls.Add(this.panel1); + this.Name = "Frm_WizzardParent"; + this.ShowIcon = false; + this.Text = "AITOOL Wizard"; + this.Load += new System.EventHandler(this.Frm_WizzardParent_Load); + this.panel1.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit(); + this.panel3.ResumeLayout(false); + this.tableLayoutPanel1.ResumeLayout(false); + this.ResumeLayout(false); + + } + + #endregion + private System.Windows.Forms.Panel panel1; + private System.Windows.Forms.Panel pnlContent; + private System.Windows.Forms.PictureBox pictureBox1; + private System.Windows.Forms.Panel panel3; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; + private System.Windows.Forms.Button btnCancel; + private System.Windows.Forms.Button btnNext; + private System.Windows.Forms.Button btnBack; + } +} \ No newline at end of file diff --git a/src/UI/Frm_WizzardParent.cs b/src/UI/Frm_WizzardParent.cs new file mode 100644 index 00000000..df2fb2ad --- /dev/null +++ b/src/UI/Frm_WizzardParent.cs @@ -0,0 +1,100 @@ +using System; +using System.Linq; +using System.Windows.Forms; + +namespace AITool +{ + public partial class Frm_WizzardParent : Form + { + Form[] frm = { new Frm_WizardBIServer(), new Frm_WizardAI() }; + int top = -1; + int count; + + public Frm_WizzardParent() + { + count = frm.Length; + InitializeComponent(); + } + + + private void LoadNewForm() + { + frm[top].TopLevel = false; + frm[top].AutoScroll = true; + frm[top].Dock = DockStyle.Fill; + this.pnlContent.Controls.Clear(); + this.pnlContent.Controls.Add(frm[top]); + frm[top].Show(); + } + + + private void Back() + { + top--; + + if (top <= -1) + { + return; + } + else + { + btnBack.Enabled = true; + btnNext.Enabled = true; + LoadNewForm(); + if (top - 1 <= -1) + { + btnBack.Enabled = false; + } + } + + if (top >= count) + { + btnNext.Enabled = false; + } + } + private void Next() + { + + top++; + if (top >= count) + { + return; + } + else + { + btnBack.Enabled = true; + btnNext.Enabled = true; + LoadNewForm(); + if (top + 1 == count) + { + btnNext.Enabled = false; + } + } + + if (top <= 0) + { + btnBack.Enabled = false; + } + } + private void btnBack_Click(object sender, EventArgs e) + { + Back(); + } + + private void Frm_WizzardParent_Load(object sender, EventArgs e) + { + Next(); + } + + private void btnCancel_Click(object sender, EventArgs e) + { + this.DialogResult = DialogResult.Cancel; + this.Close(); + } + + private void btnNext_Click(object sender, EventArgs e) + { + Next(); + } + } +} diff --git a/src/UI/Frm_WizzardParent.resx b/src/UI/Frm_WizzardParent.resx new file mode 100644 index 00000000..1af7de15 --- /dev/null +++ b/src/UI/Frm_WizzardParent.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/UI/Global-GUI.cs b/src/UI/Global-GUI.cs new file mode 100644 index 00000000..b68ec1dc --- /dev/null +++ b/src/UI/Global-GUI.cs @@ -0,0 +1,1180 @@ +using BrightIdeasSoftware; + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Windows.Forms; + +using static AITool.AITOOL; + +namespace AITool +{ + public static class Global_GUI + { + + // ==================================================================== + // ALL FUNCTIONS HERE ARE GUI HELPER FUNCTIONS AND NOT UNIQUE TO AITOOL + // NO direct UI interaction + // ==================================================================== + + public static void GroupboxEnableDisable(GroupBox gb, CheckBox MasterCheckBox) + { + + //if (gb.Tag == null || !(gb.Tag is Color)) + // gb.Tag = gb.ForeColor; + + //if (MasterCheckBox.Checked) + // gb.ForeColor = (Color)gb.Tag; + //else + // gb.ForeColor = SystemColors.GrayText; + + foreach (Control cont in gb.Controls) + { + if (!cont.Equals(MasterCheckBox)) + { + if (cont.Enabled) + cont.Tag = cont.ForeColor; + + if (cont.Tag == null || !(cont.Tag is Color)) + cont.Tag = cont.ForeColor; + + cont.Enabled = MasterCheckBox.Checked; + + + if (MasterCheckBox.Checked) + cont.ForeColor = (Color)cont.Tag; + else + cont.ForeColor = SystemColors.GrayText; + } + } + } + + + public static void UpdateFOLV(FastObjectListView olv, IList objs, + bool Follow = false, + ColumnHeaderAutoResizeStyle ResizeColsStyle = ColumnHeaderAutoResizeStyle.None, + bool FullRefresh = false, + bool UseSelected = false, + object SelectObject = null, + bool ForcedSelection = false, + [CallerMemberName()] string memberName = null) + { + + Global_GUI.InvokeIFRequired(olv, () => + { + + try + { + int oldcnt = olv.Items.Count; + + if (oldcnt == 0) + { + olv.EmptyListMsg = "Loading..."; + } + + //Debug.Print($"{DateTime.Now} - {memberName} > UpdateFOLV: {objs.Count} items, Follow={Follow}"); + + olv.Freeze(); //accessed from another thread + + if (FullRefresh) + { + //olv.ClearCachedInfo(); + //olv.ClearObjects(); + } + + if (FullRefresh || olv.Items.Count == 0) //full refresh of new objects + olv.SetObjects(objs, true); + else + olv.AddObjects(objs); //minimize list changes + + if (olv.Items.Count > 0) + { + if (Follow) + { + if (SelectObject != null && UseSelected) + { + //use the given object as selected + olv.SelectedObject = SelectObject; //olv.Items.Count - 1; + } + else if (SelectObject == null && UseSelected && olv.IsFiltering && olv.GetItemCount() > 0) + { + //use the last filtered object as the selected object + olv.SelectedObject = olv.FilteredObjects.Cast().Last(); + } + else if (!olv.IsFiltering) + { + //just use the last object given to us + olv.SelectedObject = objs[objs.Count - 1]; //olv.Items.Count - 1; + } + + + if (olv.SelectedObject != null) + olv.EnsureModelVisible(olv.SelectedObject); + + } + else + { + //only select something if nothing is already selected + if (SelectObject != null && (ForcedSelection || (UseSelected && olv.SelectedObject == null))) + { + //use the given object as selected + olv.SelectedObject = SelectObject; //olv.Items.Count - 1; + olv.EnsureModelVisible(olv.SelectedObject); + } + //else if (SelectObject == null && UseSelected && olv.IsFiltering && olv.GetItemCount() > 0) + //{ + // //use the last filtered object as the selected object + // olv.SelectedObject = olv.FilteredObjects.Cast().First(); + //} + //else if (!olv.IsFiltering && UseSelected) + //{ + // //just use the last object given to us + // olv.SelectedObject = objs[0]; //olv.Items.Count - 1; + //} + } + + + //update column size only if did not restore folv state file or forced + if (olv.Tag == null) + { + olv.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent); + olv.Tag = "resizedcols"; + } + else if (ResizeColsStyle != ColumnHeaderAutoResizeStyle.None) + { + olv.AutoResizeColumns(ResizeColsStyle); + olv.Tag = "resizedcols"; + } + } + else + { + olv.EmptyListMsg = "Empty"; + } + + } + catch (Exception ex) + { + Log("Error: " + ex.Msg()); + } + finally + { + olv.Unfreeze(); + if (FullRefresh) + olv.Refresh(); + } + + }); + + } + + public static void FilterFOLV(FastObjectListView OLV, string FilterText, bool Filter) + { + using var cw = new Global_GUI.CursorWait(); + + try + { + if (!string.IsNullOrEmpty(FilterText)) + { + if (Filter) + { + OLV.UseFiltering = true; + } + else + { + OLV.UseFiltering = false; + } + TextMatchFilter filter = TextMatchFilter.Regex(OLV, FilterText); + OLV.ModelFilter = filter; + HighlightTextRenderer renderererer = new HighlightTextRenderer(filter); + SolidBrush brush = renderererer.FillBrush as SolidBrush ?? new SolidBrush(Color.Transparent); + brush.Color = System.Drawing.Color.FromArgb(100, Color.LightSeaGreen); // + renderererer.FillBrush = brush; + OLV.DefaultRenderer = renderererer; + Global.SaveRegSetting("SearchText", FilterText); + + } + else + { + OLV.ModelFilter = null; + } + OLV.Refresh(); + //Application.DoEvents(); + } + catch { } + } + + public static void ConfigureFOLV(FastObjectListView FOLV, Type Cls, + System.Drawing.Font Fnt = null, + ImageList ImageList = null, + string PrimarySortColumnName = "", + SortOrder PrimarySortOrder = SortOrder.Ascending, + string SecondarySortColumnName = "", + SortOrder SecondarySortOrder = SortOrder.Ascending, + List FilterColumnList = null, + Color Clr = new Color(), + int RowHeight = 0, + bool ShowGroups = false, + bool GridLines = true, + ObjectListView.CellEditActivateMode editmode = ObjectListView.CellEditActivateMode.DoubleClick) + { + + + //Global_GUI.InvokeIFRequired(FOLV, () => + //{ + + try + { + + FOLV.AllowColumnReorder = true; + FOLV.CopySelectionOnControlC = true; + FOLV.FullRowSelect = true; + FOLV.GridLines = GridLines; + FOLV.HideSelection = false; + FOLV.IncludeColumnHeadersInCopy = true; + FOLV.OwnerDraw = true; + FOLV.SelectColumnsOnRightClick = true; + FOLV.SelectColumnsOnRightClickBehaviour = ObjectListView.ColumnSelectBehaviour.InlineMenu; + FOLV.SelectedColumnTint = Color.LawnGreen; + FOLV.ShowCommandMenuOnRightClick = true; + FOLV.ShowFilterMenuOnRightClick = true; + FOLV.ShowGroups = false; + FOLV.ShowImagesOnSubItems = true; + FOLV.ShowItemCountOnGroups = true; + FOLV.ShowItemToolTips = true; + FOLV.ShowSortIndicators = true; + FOLV.SortGroupItemsByPrimaryColumn = true; + FOLV.TintSortColumn = true; + FOLV.UseFiltering = true; + FOLV.UseHyperlinks = true; //may cause column save/restore error? + FOLV.CellEditActivation = editmode; + FOLV.UseCellFormatEvents = true; + FOLV.UseNotifyPropertyChanged = true; + + if (ImageList != null) + { + FOLV.SmallImageList = ImageList; + } + + if (Fnt != null) + { + FOLV.Font = Fnt; + } + else + { + FOLV.Font = new Font("Consolas", 8, FontStyle.Regular); + } + + if (Clr.IsEmpty) + { + FOLV.ForeColor = Clr; + } + + object[] IIProps2 = Cls.GetProperties(); //Cls.GetType().GetProperties + + //if (IIProps2.Length == 0) + // IIProps2 = Cls.GetFields(); + + // Uncomment this to see a fancy cell highlighting while editing + EditingCellBorderDecoration EC = new EditingCellBorderDecoration(true); + EC.UseLightbox = true; + + FOLV.AddDecoration(EC); + FOLV.BuildList(); + int colcnt = 0; + + //for (int i = 0; i < IIProps2.Length; i++) + //{ + // object ei = IIProps2[i]; + + //} + + foreach (PropertyInfo ei in IIProps2) + { + if (typeof(IEnumerable).IsAssignableFrom(ei.PropertyType) && !(ei.PropertyType.Name == "String")) + { + continue; + } + else if (ei.PropertyType.Name == "Object") + { + continue; + } + + colcnt = colcnt + 1; + OLVColumn cl = new OLVColumn(); + if (FOLV.SmallImageList != null) + { + + if (string.Equals(FOLV.Name, "folv_history", StringComparison.OrdinalIgnoreCase)) + { + if (colcnt == 1) + { + cl.ImageGetter = GetImageForHistoryList; + } + else if (ei.Name == "IsPerson") + { + cl.ImageGetter = GetImageForHistoryListPerson; + cl.AspectToStringConverter = delegate (object x) + { + return String.Empty; + }; + } + else if (ei.Name == "Success") + { + cl.ImageGetter = GetImageForHistoryListSuccess; + cl.AspectToStringConverter = delegate (object x) + { + return String.Empty; + }; + } + } + else if (string.Equals(FOLV.Name, "FOLV_AIServers", StringComparison.OrdinalIgnoreCase)) + { + if (colcnt == 1) + { + cl.ImageGetter = GetImageForAIServerList; + } + } + else if (string.Equals(FOLV.Name, "FOLV_Cameras", StringComparison.OrdinalIgnoreCase)) + { + if (colcnt == 1) + { + cl.ImageGetter = GetImageForCameraList; + } + } + //else if (FOLV.Name == "FOLV_BlocklistViewer" && ei.Name == "RegionalInternetRegistry") + //{ + // cl.ImageGetter = GetImageForBlocklistViewerRIR; + //} + //else if (FOLV.Name == "FOLV_Apps" && colcnt == 1) + //{ + // cl.ImageGetter = GetImageForProdList; + //} + } + //cl.AspectName = ei.Name + cl.UseFiltering = true; + cl.Searchable = true; + cl.Text = ei.Name; + cl.Name = ei.Name; + + cl.DataType = ei.PropertyType; + cl.AspectName = ei.Name; + + if (cl.Name == "Func" || ei.PropertyType.Name == "Int64" || ei.PropertyType.Name == "Int32" || ei.PropertyType.Name == "Timespan") + { + cl.TextAlign = HorizontalAlignment.Right; + } + + if (ei.Name == "UniqueID" || ei.Name == "FoundElementList") + { + cl.MinimumWidth = 0; + cl.MaximumWidth = 0; + cl.Width = 0; + } + else + { + cl.MinimumWidth = 50; + //cl.MaximumWidth = 20000 + //cl.Width = -2 + } + + if (ei.Name.IndexOf("url", StringComparison.OrdinalIgnoreCase) >= 0) + { + cl.Hyperlink = true; + } + + if ((ei.Name.Has("percent") || ei.Name.Has("threshold") || ei.Name.Has("confidence")) && (ei.PropertyType == typeof(double) || ei.PropertyType == typeof(int) || ei.PropertyType == typeof(float))) + { + cl.AspectToStringFormat = "{0:0.#}%"; + } + + //if (ei.PropertyType == typeof(ThreadSafe.Boolean)) + //{ + // if (FOLV.Name == "FOLV_AIServers") + // { + // cl.AspectGetter = delegate (object rowObject) + // { + // bool ret = false; + // ClsURLItem obj = (ClsURLItem)rowObject; + // if (ei.Name == "Enabled") + // { + // ret = obj.Enabled; + // } + // else if (ei.Name == "InUse") + // { + // ret = obj.InUse; + // } + // else + // { + // int test = 0; + // } + // return ret; + // }; + + // cl.DataType = typeof(bool); + // cl.CheckBoxes = true; + // } + + //} + //if (ei.Name == "Enabled") + //{ + // int test = 0; + //} + + //if (ei.PropertyType == typeof(Boolean)) + //{ + // cl.CheckBoxes = true; + // cl.IsEditable = false; + //} + + + if (string.Equals(ei.Name, PrimarySortColumnName, StringComparison.OrdinalIgnoreCase)) + { + FOLV.PrimarySortColumn = cl; + FOLV.PrimarySortOrder = PrimarySortOrder; //SortOrder.Descending + + //cl.ImageGetter = AddressOf GetImageFromProd + } + if (string.Equals(ei.Name, SecondarySortColumnName, StringComparison.OrdinalIgnoreCase)) + { + FOLV.SecondarySortColumn = cl; + FOLV.SecondarySortOrder = SecondarySortOrder; //SortOrder.Descending + } + + FOLV.AllColumns.Add(cl); //Columns vs AllColumns? + + } + + //restore column state if it exists + FOLVRestoreState(FOLV); + + //OLV.RebuildColumns() + FOLV.Refresh(); + FOLV.BuildList(); + FOLV.RebuildColumns(); + + //Application.DoEvents(); + + } + catch (Exception ex) + { + MessageBox.Show("Error: " + ex.Message); + } + finally + { + + } + + //}); + + } + + public class FOLVObjectListViewState + { + public int VersionNumber = 1; + public int NumberOfColumns = 1; + public View CurrentView; + public int SortColumn = -1; + public bool IsShowingGroups; + public SortOrder LastSortOrder = SortOrder.None; + public ArrayList ColumnIsVisible = new ArrayList(); + public ArrayList ColumnDisplayIndicies = new ArrayList(); + public ArrayList ColumnWidths = new ArrayList(); + } + + public static bool FOLVRestoreState(FastObjectListView FOLV) + { + bool Ret = false; + + try + { + string Filename = Path.Combine(Path.GetDirectoryName(AppSettings.Settings.SettingsFileName), $"ListSaveState-{FOLV.Name}.JSON"); + + if (!File.Exists(Filename)) + return false; + + FOLVObjectListViewState olvState = Global.ReadFromJsonFile(Filename); + + // The number of columns has changed. We have no way to match old + // columns to the new ones, so we just give up. + if (olvState == null || olvState.NumberOfColumns != FOLV.AllColumns.Count) + { + Log("Debug: olvState Is null OrElse olvState.NumberOfColumns <> FOLV.AllColumns.Count."); + return false; + } + + if (olvState.SortColumn == -1) + { + FOLV.PrimarySortColumn = null; + FOLV.PrimarySortOrder = SortOrder.None; + } + else + { + FOLV.PrimarySortColumn = FOLV.AllColumns[olvState.SortColumn]; // FOLV.AllColumns(olvState.SortColumn) + FOLV.PrimarySortOrder = olvState.LastSortOrder; + } + + for (int i = 0; i <= olvState.NumberOfColumns - 1; i++) + { + OLVColumn column = FOLV.AllColumns[i]; // FOLV.AllColumns(i) + column.Width = System.Convert.ToInt32(olvState.ColumnWidths[i]); + column.IsVisible = System.Convert.ToBoolean(olvState.ColumnIsVisible[i]); + column.LastDisplayIndex = System.Convert.ToInt32(olvState.ColumnDisplayIndicies[i]); + } + + // ReSharper disable RedundantCheckBeforeAssignment + if (olvState.IsShowingGroups != FOLV.ShowGroups) + // ReSharper restore RedundantCheckBeforeAssignment + FOLV.ShowGroups = olvState.IsShowingGroups; + + if (FOLV.View == olvState.CurrentView) + FOLV.RebuildColumns(); + else + FOLV.View = olvState.CurrentView; + + //tell other functions not to resize columns unless forced + FOLV.Tag = "resizedcols"; + + Ret = true; + } + catch (Exception ex) + { + Log("Error: " + ex.Msg()); + } + + return Ret; + } + + public static bool FOLVSaveState(FastObjectListView FOLV) + { + bool ret = false; + + try + { + FOLVObjectListViewState olvState = new FOLVObjectListViewState(); + + olvState.VersionNumber = 1; + olvState.NumberOfColumns = FOLV.AllColumns.Count; // FOLV.AllColumns.Count + olvState.CurrentView = FOLV.View; + + // If we have a sort column, it is possible that it is not currently being shown, in which + // case, it's Index will be -1. So we calculate its index directly. Technically, the sort + // column does not even have to a member of AllColumns, in which case IndexOf will return -1, + // which is works fine since we have no way of restoring such a column anyway. + if (FOLV.PrimarySortColumn != null) + olvState.SortColumn = FOLV.AllColumns.IndexOf(FOLV.PrimarySortColumn);// FOLV.AllColumns.IndexOf(FOLV.PrimarySortColumn) + olvState.LastSortOrder = FOLV.PrimarySortOrder; + olvState.IsShowingGroups = FOLV.ShowGroups; + + // If FOLV.Columns.Count > 0 AndAlso FOLV.Columns(0).LastDisplayIndex = -1 Then 'FOLV.AllColumns(0).LastDisplayIndex = -1 + // 'FOLV.RememberDisplayIndicies() + // End If + + if (FOLV.Columns.Count == 0) + { + Log("Error: No columns to save?"); + return false; + } + + foreach (OLVColumn column in FOLV.AllColumns) // FOLV.AllColumns + { + olvState.ColumnIsVisible.Add(column.IsVisible); + olvState.ColumnDisplayIndicies.Add(column.LastDisplayIndex); + olvState.ColumnWidths.Add(column.Width); + } + + // Now that we have stored our state, save to json + string Filename = Path.Combine(Path.GetDirectoryName(AppSettings.Settings.SettingsFileName), $"ListSaveState-{FOLV.Name}.JSON"); + string json = Global.WriteToJsonFile(Filename, olvState); + + if (json != null && !string.IsNullOrEmpty(json)) + { + ret = true; + } + } + catch (Exception ex) + { + Log("Error: " + ex.Msg()); + } + + return ret; + } + + // Create the FontConverter. + private static System.ComponentModel.TypeConverter Fontconverter = System.ComponentModel.TypeDescriptor.GetConverter(typeof(Font)); + + public static void SetAppDefaultFont(bool firsttime, Control currentform) + { + try + { + + string fnt = AppSettings.Settings.DefaultFont.Trim(); + Font font1; + + if (fnt.IsNotEmpty()) + { + font1 = (Font)Fontconverter.ConvertFromString(fnt); + } + else + { + Log("Debug: Default font not set yet, using Segoe UI, 8.5pt"); + font1 = (Font)Fontconverter.ConvertFromString("Segoe UI, 8.5pt"); + } + +#if NET6_0_OR_GREATER + if (firsttime) + Application.SetDefaultFont(font1); + else + { + if (currentform.IsNotNull()) + { + currentform.Font = font1; + } + } +#else + if (currentform.IsNotNull()) + { + currentform.Font = font1; + } +#endif + + } + catch (Exception ex) + { + + Log($"Error: {ex.Msg()}"); + } + + + } + + public static string GetStringFromFont(Font font) + { + + string ret = ""; + + try + { + if (font.IsNotNull()) + { + ret = Fontconverter.ConvertToString(font); + } + } + catch (Exception ex) + { + Log($"Error: {ex.Msg()}"); + } + + return ret; + } + public static bool IsFontStringValid(string FontString) + { + bool ret = false; + try + { + if (FontString.IsNotEmpty()) + { + using Font font1 = (Font)Fontconverter.ConvertFromString(AppSettings.Settings.DefaultFont.Trim()); + + if (font1.IsNotNull()) + ret = true; + } + } + catch (Exception ex) + { + Log($"Error: {ex.Msg()}"); + } + + return ret; + } + public static Font GetFontFromString(string FontString) + { + Font ret = new Font("Segoe UI", 8.25f); + try + { + if (FontString.IsNotEmpty()) + { + ret = (Font)Fontconverter.ConvertFromString(FontString); + + } + } + catch (Exception ex) + { + Log($"Error: {ex.Msg()}"); + } + + return ret; + } + + public static void RestoreWindowState(Form Frm) + { + + try + { + //Global_GUI.InvokeIFRequired(Frm, () => + //{ + Point SavLocation = (Point)(Global.GetRegSetting(Frm.Name + "_Location", new Point())); //Frm.RestoreBounds.Location + + if (SavLocation.IsEmpty) + return; //we did not previously save settings + + bool SavMaximized = System.Convert.ToBoolean(Global.GetRegSetting(Frm.Name + "_Maximized", false)); + bool SavMinimized = System.Convert.ToBoolean(Global.GetRegSetting(Frm.Name + "_Minimized", false)); + object ObjSize = Global.GetRegSetting(Frm.Name + "_Size", Frm.RestoreBounds.Size); + Size SavSize = (Size)ObjSize; //CType(ObjSize, System.Drawing.Size) + + //============================================================================================================ + //No reliable way of detecting if a screen has been turned off! This will only detect if fully disconnected + //============================================================================================================ + + Rectangle tstrect = new Rectangle(SavLocation, SavSize); + if (!SavLocation.IsEmpty && SavLocation.X > 0 && IsVisibleOnAnyScreen(tstrect)) + { + Frm.Location = SavLocation; + if (!SavSize.IsEmpty && SavSize.Width > 5 && SavSize.Height > 5) + Frm.Size = SavSize; + //else + //Debug.Print("Saved size not valid."); + } + //else + //Debug.Print("Saved location not valid."); + + + if (SavMaximized) + Frm.WindowState = FormWindowState.Maximized; + else if (SavMinimized) + Frm.WindowState = FormWindowState.Normal; //FormWindowState.Minimized + + if (Frm.Tag != null && string.Equals(Frm.Tag.ToString(), "SAVE", StringComparison.OrdinalIgnoreCase)) + { + List ctls = GetControls(Frm); + + foreach (Control ctl in ctls) + if (ctl is SplitContainer) + { + //SplitContainer sc = (SplitContainer)ctl; + //sc.SplitterDistance = System.Convert.ToInt32(Global.GetRegSetting($"{Frm.Name}.SplitContainer.{sc.Name}.SplitterDistance", sc.SplitterDistance)); + SplitContainer sc = (SplitContainer)ctl; + + string name = sc.Name; + + var fullDistance = new Func(c => c.Orientation == Orientation.Horizontal ? c.Size.Height : c.Size.Width); + // calculate splitter distance with regard to current control size + int storedDistance = System.Convert.ToInt32(Global.GetRegSetting($"{Frm.Name}.SplitContainer.{sc.Name}.SplitterDistance", sc.SplitterDistance)); + //int distanceToRestore = + // sc.FixedPanel == FixedPanel.Panel1 ? storedDistance : + // sc.FixedPanel == FixedPanel.Panel2 ? fullDistance(sc) - storedDistance : + // storedDistance * fullDistance(sc) / 100; + + sc.SplitterDistance = storedDistance; + } + else if (ctl is TabControl) + { + TabControl tc = (TabControl)ctl; + tc.SelectedIndex = System.Convert.ToInt32(Global.GetRegSetting($"{Frm.Name}.TabControl.{tc.Name}.SelectedIndex", tc.SelectedIndex)); + } + //else if (ctl is ComboBox) + //{ + // ComboBox cc = (ComboBox)ctl; + // if (cc.Items.Count == 0 && string.IsNullOrEmpty(System.Convert.ToString(cc.Text))) + // { + // List lst = new List(); + // lst = (List)(GetSetting($"{Frm.Name}.ComboBox.{cc.Name}.Items", lst)); + // foreach (string st in lst) + // cc.Items.Add(st); + // cc.Text = System.Convert.ToString(GetSetting($"{Frm.Name}.ComboBox.{cc.Name}.Text", cc.Text)); + // } + //} + //else if (ctl is TextBox) + //{ + // TextBox tc = (TextBox)ctl; + // if (string.IsNullOrEmpty(System.Convert.ToString(tc.Text))) + // tc.Text = System.Convert.ToString(GetSetting($"{Frm.Name}.TextBox.{tc.Name}.Text", tc.Text)); + //} + //else if (ctl is CheckBox) + //{ + // CheckBox tc = (CheckBox)ctl; + // if (tc.CheckState == CheckState.Indeterminate) + // { + // tc.Checked = System.Convert.ToBoolean(GetSetting($"{Frm.Name}.CheckBox.{tc.Name}.Checked", tc.Checked)); + // tc.CheckState = (CheckState)(tc.Checked ? CheckState.Checked : CheckState.Unchecked); + // } + //} + + } + + Global_GUI.SetAppDefaultFont(false, Frm); + + //If Screen.AllScreens.Any(Function(screen__1) screen__1.WorkingArea.IntersectsWith(Properties.Settings.[Default].WindowPosition)) Then + // StartPosition = FormStartPosition.Manual + // DesktopBounds = Properties.Settings.[Default].WindowPosition + // Frm.WindowState = FormWindowState.Normal + //End If + + //Debug.Print("Size After: " & Me.Size.ToString) + //Debug.Print("Loc After: " & Me.Location.ToString) + //Debug.Print("WindowState After: " & Me.WindowState.ToString) + + //}); + } + catch (Exception) + { + + } + } + + public static void SaveWindowState(Form Frm) + { + + + try + { + + //Global_GUI.InvokeIFRequired(Frm, () => + //{ + + if (Frm.WindowState == FormWindowState.Maximized) + { + Global.SaveRegSetting(Frm.Name + "_Location", Frm.RestoreBounds.Location); + Global.SaveRegSetting(Frm.Name + "_Size", Frm.RestoreBounds.Size); + Global.SaveRegSetting(Frm.Name + "_Maximized", true); + Global.SaveRegSetting(Frm.Name + "_Minimized", false); + } + else if (Frm.WindowState == FormWindowState.Normal) + { + Global.SaveRegSetting(Frm.Name + "_Location", Frm.Location); + Global.SaveRegSetting(Frm.Name + "_Size", Frm.Size); + Global.SaveRegSetting(Frm.Name + "_Maximized", false); + Global.SaveRegSetting(Frm.Name + "_Minimized", false); + } + else + { + Global.SaveRegSetting(Frm.Name + "_Location", Frm.RestoreBounds.Location); + Global.SaveRegSetting(Frm.Name + "_Size", Frm.RestoreBounds.Size); + Global.SaveRegSetting(Frm.Name + "_Maximized", false); + Global.SaveRegSetting(Frm.Name + "_Minimized", true); + } + + if (Frm.Tag != null && string.Equals(Frm.Tag.ToString(), "SAVE", StringComparison.OrdinalIgnoreCase)) + { + List ctls = GetControls(Frm); + + foreach (Control ctl in ctls) + { + if (ctl is SplitContainer) + { + //SplitContainer sc = (SplitContainer)ctl; + //Global.SaveRegSetting($"{Frm.Name}.SplitContainer.{sc.Name}.SplitterDistance", sc.SplitterDistance); + SplitContainer sc = (SplitContainer)ctl; + + string name = sc.Name; + + var fullDistance = new Func(c => c.Orientation == Orientation.Horizontal ? c.Size.Height : c.Size.Width); + + // Store as percentage if FixedPanel.None + //int distanceToStore = + // sc.FixedPanel == FixedPanel.Panel1 ? sc.SplitterDistance : + // sc.FixedPanel == FixedPanel.Panel2 ? fullDistance(sc) - sc.SplitterDistance : + // (int)(((double)sc.SplitterDistance) / ((double)fullDistance(sc))) * 100; + + Global.SaveRegSetting($"{Frm.Name}.SplitContainer.{sc.Name}.SplitterDistance", sc.SplitterDistance); + } + else if (ctl is TabControl) + { + TabControl tc = (TabControl)ctl; + Global.SaveRegSetting($"{Frm.Name}.TabControl.{tc.Name}.SelectedIndex", tc.SelectedIndex); + } + else if (ctl is FastObjectListView) + { + FastObjectListView folv = (FastObjectListView)ctl; + FOLVSaveState(folv); + } + + } + + } + + + //}); + + } + catch (Exception) + { + + } + } + + + public static List GetControls(Control frm) + { + List ctls = new List(); + + foreach (Control c in frm.Controls) + { + if (!c.IsDisposed && c.Name != "" && c.Enabled) + { + ctls.Add(c); + } + if (c.HasChildren) + { + ctls.AddRange(GetControls(c)); + } + } + + return ctls; + } + private static bool IsVisibleOnAnyScreen(Rectangle rect) + { + bool Ret = false; + int I = 0; + foreach (Screen screen in Screen.AllScreens) + { + I++; + //Debug.Print($"Screen {I}: Name={screen.DeviceName}, Primary={screen.Primary}, Working={screen.WorkingArea}, Bounds={screen.Bounds}"); + if (screen.WorkingArea.IntersectsWith(rect)) + { + //Debug.Print("...IntersectsWith"); + Ret = true; + } + } + + return Ret; + } + + public static void InvokeIFRequired(Control control, System.Windows.Forms.MethodInvoker action) + { + // This will let you update any control from another thread - It only invokes IF NEEDED for better performance + // See TextBoxLogger.Log for example + + if (control != null && !control.IsDisposed && !control.Disposing && control.IsHandleCreated && control.FindForm().IsHandleCreated && !IsClosing) + { + if (control.InvokeRequired) + { + control.Invoke(action); + } + else + { + action(); + } + + } + } + + public static string GetImageForHistoryList(object row) + { + string RetKey = ""; + + try + { + + string png = "32.png"; //make empty string to avoid using the png version of the images + + History hist = (History)row; + if (hist.Success) + { + RetKey = "success" + png; + } + else if (hist.WasSkipped) + { + RetKey = "error" + png; + } + else if (!hist.Success && hist.Detections.IndexOf("false alert", StringComparison.OrdinalIgnoreCase) >= 0) + { + RetKey = "nothing" + png; + } + else + { + RetKey = "detection" + png; + } + + + } + catch (Exception) + { + } + + return RetKey; + + } + public static string GetImageForAIServerList(object row) + { + string RetKey = ""; + + try + { + + string png = ".png"; //make empty string to avoid using the png version of the images + + ClsURLItem url = (ClsURLItem)row; + if (url.Type == URLTypeEnum.AWSRekognition_Objects || url.Type == URLTypeEnum.AWSRekognition_Faces) + { + RetKey = "AWSRekognition" + png; + } + else if (url.Type == URLTypeEnum.DeepStack || url.Type == URLTypeEnum.DeepStack_Faces || url.Type == URLTypeEnum.DeepStack_Custom || url.Type == URLTypeEnum.DeepStack_Scene) + { + RetKey = "Deepstack" + png; + } + else if (url.Type == URLTypeEnum.DOODS) + { + RetKey = "DOODS" + png; + } + else if (url.Type == URLTypeEnum.SightHound_Person || url.Type == URLTypeEnum.SightHound_Vehicle) + { + RetKey = "SightHound" + png; + } + else if (url.Type.ToString().Has("codeproject")) + { + RetKey = "Codeproject" + png; + } + + + + } + catch (Exception) + { + } + + return RetKey; + + } + public static string GetImageForCameraList(object row) + { + return "camera-webcam.ico"; + + } + public static string GetImageForHistoryListPerson(object row) + { + string RetKey = ""; + + try + { + string png = "32.png"; //make empty string to avoid using the png version of the images + + History hist = (History)row; + if (hist.IsPerson) + { + RetKey = "person" + png; + } + else + { + RetKey = ""; + } + + + } + catch { } + + return RetKey; + + } + + public static string GetImageForHistoryListSuccess(object row) + { + string RetKey = ""; + + try + { + //"Airplane", "Bear", "Bicycle", "Bird", "Boat", "Bus", "Car", "Cat", "Cow", "Dog", "Horse", "Motorcycle", "Person", "Sheep", "Truck" + + string png = "32.png"; //make empty string to avoid using the png version of the images + + History hist = (History)row; + if (hist.Success) + { + if (hist.IsPerson) + { + RetKey = "person" + png; + } + else if (hist.IsAnimal) + { + if (hist.Detections.IndexOf("bear", StringComparison.OrdinalIgnoreCase) >= 0) + { + RetKey = "bear" + png; + } + else if (hist.Detections.IndexOf("dog", StringComparison.OrdinalIgnoreCase) >= 0) + { + RetKey = "dog" + png; + } + else if (hist.Detections.IndexOf("cat", StringComparison.OrdinalIgnoreCase) >= 0) + { + RetKey = "cat" + png; + } + else if (hist.Detections.IndexOf("bird", StringComparison.OrdinalIgnoreCase) >= 0) + { + RetKey = "bird" + png; + } + else if (hist.Detections.IndexOf("horse", StringComparison.OrdinalIgnoreCase) >= 0) + { + RetKey = "horse" + png; + } + else if (hist.Detections.IndexOf("kangaroo", StringComparison.OrdinalIgnoreCase) >= 0) + { + RetKey = "horse" + png; + } + else + { + RetKey = "alien" + png; + } + + + } + else if (hist.IsVehicle) + { + if (hist.Detections.IndexOf("truck", StringComparison.OrdinalIgnoreCase) >= 0 || hist.Detections.IndexOf("bus", StringComparison.OrdinalIgnoreCase) >= 0) + { + RetKey = "truck" + png; + } + else if (hist.Detections.IndexOf("car", StringComparison.OrdinalIgnoreCase) >= 0) + { + RetKey = "car" + png; + } + else if (hist.Detections.IndexOf("motorcycle", StringComparison.OrdinalIgnoreCase) >= 0) + { + RetKey = "motorcycle" + png; + } + else if (hist.Detections.IndexOf("bicycle", StringComparison.OrdinalIgnoreCase) >= 0) + { + RetKey = "bicycle" + png; + } + } + } + else + { + RetKey = ""; + } + + + } + catch { } + + return RetKey; + + } + + + public class CursorWait:IDisposable + { + public CursorWait(bool appStarting = false, bool applicationCursor = true) + { + Cursor.Current = appStarting ? Cursors.AppStarting : Cursors.WaitCursor; + if (applicationCursor) + { + System.Windows.Forms.Application.UseWaitCursor = true; + } + //System.Windows.Forms.Application.DoEvents(); + } + + public void Dispose() + { + Cursor.Current = Cursors.Default; + System.Windows.Forms.Application.UseWaitCursor = false; + //Windows.Forms.Application.DoEvents() + } + } + + } +} diff --git a/src/UI/Global.cs b/src/UI/Global.cs new file mode 100644 index 00000000..30b0a9ca --- /dev/null +++ b/src/UI/Global.cs @@ -0,0 +1,5149 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Diagnostics; +using System.Drawing; +using System.Drawing.Imaging; +using System.Globalization; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Management; +using System.Media; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security.Principal; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using System.Xml.Linq; + +using Innovative.SolarCalculator; + +using Microsoft.Win32; +using Microsoft.Win32.SafeHandles; + +using NAudio.Wave; + +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Serialization; + +using static AITool.AITOOL; + +namespace AITool +{ + // ============================================================= + // ALL FUNCTIONS HERE ARE GENERIC/SHARED AND NOT UNIQUE TO AITOOL + // NO direct UI interaction + // ============================================================= + + public static class Global + { + public static IProgress progress = null; + + //this may speed up json serialization + public static readonly DefaultContractResolver JSONContractResolver = new DefaultContractResolver + { + NamingStrategy = new CamelCaseNamingStrategy + { + ProcessDictionaryKeys = false, + OverrideSpecifiedNames = false, + ProcessExtensionDataNames = false + } + + //Global.JSONContractResolver.NamingStrategy = new CamelCaseNamingStrategy(); + }; + public static readonly JsonSerializerSettings JSONSettingsPretty = new JsonSerializerSettings + { + Formatting = Formatting.Indented, + TypeNameHandling = TypeNameHandling.All, + PreserveReferencesHandling = PreserveReferencesHandling.Objects, + ContractResolver = JSONContractResolver, + //NullValueHandling = NullValueHandling.Ignore, + //DefaultValueHandling = DefaultValueHandling.Ignore, + //ReferenceLoopHandling = ReferenceLoopHandling.Ignore, + // Add other settings that may improve performance for your specific case + }; + + public static readonly JsonSerializerSettings JSONSettingsPerformance = new JsonSerializerSettings + { + Formatting = Formatting.None, + TypeNameHandling = TypeNameHandling.Auto, + PreserveReferencesHandling = PreserveReferencesHandling.None, + ReferenceLoopHandling = ReferenceLoopHandling.Ignore, + ConstructorHandling = ConstructorHandling.Default, + ObjectCreationHandling = ObjectCreationHandling.Auto, + NullValueHandling = NullValueHandling.Ignore, + MetadataPropertyHandling = MetadataPropertyHandling.Default, + ContractResolver = new CamelCasePropertyNamesContractResolver(), + //NullValueHandling = NullValueHandling.Ignore, + //DefaultValueHandling = DefaultValueHandling.Ignore, + //ReferenceLoopHandling = ReferenceLoopHandling.Ignore, + // Add other settings that may improve performance for your specific case + }; + + /// + /// ''' Gets a value indicating whether the application is a windows service. + /// ''' + /// ''' + /// ''' true if this instance is service; otherwise, false. + + /// ''' + public static bool IsService + { + get + { + // Determining whether or not the host application is a service is + // an expensive operation (it uses reflection), so we cache the + // result of the first call to this method so that we don't have to + // recalculate it every call. + + // If we have not already determined whether or not the application + // is running as a service... + if (!_isService.HasValue) + { + + // Get details of the host assembly. + System.Reflection.Assembly entryAssembly = System.Reflection.Assembly.GetEntryAssembly(); + + // Get the method that was called to enter the host assembly. + System.Reflection.MethodInfo entryPoint = entryAssembly.EntryPoint; + + // If the base type of the host assembly inherits from the + // "ServiceBase" class, it must be a windows service. We store + // the result ready for the next caller of this method. + _isService = (entryPoint.ReflectedType.BaseType.FullName == "System.ServiceProcess.ServiceBase"); + } + + // Return the cached result. + return System.Convert.ToBoolean(_isService); + } + } + + private static Nullable _isService = default(Boolean?); + + public static async Task SafeFileDeleteAsync(string Filename, string Source, long MaxWaitMS = 30000, bool Quiet = false) + { + //run the function in another thread + return await Task.Run(() => SafeFileDelete(Filename, Source, MaxWaitMS, Quiet)); + } + public static bool SafeFileDelete(string Filename, string Source, long MaxWaitMS = 30000, bool Quiet = false) + { + bool ret = false; + + if (!File.Exists(Filename)) + return true; + + WaitFileAccessResult result = Global.WaitForFileAccess(Filename, FileAccess.ReadWrite, FileShare.None, MaxWaitMS, AppSettings.Settings.loop_delay_ms, true, 0); + if (result.Success) + { + try + { + File.Delete(Filename); + ret = true; + } + catch (Exception ex) + { + if (!Quiet) + Log($"Error: Could not delete file after {result.TimeMS} ms. Source='{Source}': {ex.Msg()}"); + } + } + else + { + if (!Quiet) + Log($"Error: Could not delete file after {result.TimeMS} ms. Source='{Source}': {Filename}"); + } + + return ret; + + } + + public static string FindSoundFile(string InputFile) + { + //only return the sound file if we found it + string ret = ""; + try + { + //If the file passed contains a full path, just verify it exists + if (InputFile.Contains("\\")) + { + if (File.Exists(InputFile)) + { + ret = InputFile; + } + } + else //Assume it is just a filename and look for it... + { + //add extension if not found + if (!Path.HasExtension(InputFile)) + InputFile = InputFile.Trim() + ".wav"; + + //generate a list of known media paths + List paths = new List(); + paths.Add(AppDomain.CurrentDomain.BaseDirectory); + paths.Add(Path.GetDirectoryName(AppSettings.Settings.SettingsFileName)); + + if (AITOOL.BlueIrisInfo.IsNotNull() && AITOOL.BlueIrisInfo.AppPath.IsNotNull() && Directory.Exists(AITOOL.BlueIrisInfo.AppPath)) + paths.Add(Path.Combine(AITOOL.BlueIrisInfo.AppPath, "sounds")); + + paths.Add(Environment.ExpandEnvironmentVariables("%SYSTEMROOT%\\Media")); + + if (Directory.Exists(Environment.ExpandEnvironmentVariables("%PROGRAMFILES%\\Microsoft Office\\root\\Office16\\Media"))) + paths.Add(Environment.ExpandEnvironmentVariables("%PROGRAMFILES%\\Microsoft Office\\root\\Office16\\Media")); + + if (Directory.Exists(Environment.ExpandEnvironmentVariables("%PROGRAMFILES%\\Microsoft Office\\root\\Office15\\Media"))) + paths.Add(Environment.ExpandEnvironmentVariables("%PROGRAMFILES%\\Microsoft Office\\root\\Office15\\Media")); + + //search each path for the sound file + foreach (var fldr in paths) + { + string file = Path.Combine(fldr, InputFile); + if (File.Exists(file)) + { + ret = file; + break; + } + } + + } + } + catch (Exception ex) + { + Log($"Error: {ex.Msg()}"); + } + + return ret; + } + + public static string GetTempFolder(bool Clear = false) + { + string ret = Path.GetTempPath(); + + try + { + ret = Path.Combine(Path.GetTempPath(), "_AITOOL"); + + if (Clear && Directory.Exists(ret)) + try { Directory.Delete(ret, true); } catch { } + + if (!Directory.Exists(ret)) + Directory.CreateDirectory(ret); + } + catch (Exception ex) + { + Log("Error: Could not get temp folder? " + ex.Msg()); + } + + return ret; + } + + public static string SafeLoadTextFile(string Filename) + { + + string ret = ""; + + //dont wait very long for access, only grab text if the file is not being used (for stderr.txt, etc) + WaitFileAccessResult result = WaitForFileAccess(Filename, FileAccess.Read, FileShare.Read, 100, AppSettings.Settings.loop_delay_ms, true, 4, MaxErrRetryCnt: 4); + + try + { + if (result.Success) + { + // Open a FileStream object using the passed in safe file handle. + using FileStream fileStream = new FileStream(result.Handle, FileAccess.Read); + using StreamReader sr = new StreamReader(fileStream, Encoding.UTF8); + ret = sr.ReadToEnd(); + } + } + catch { } + finally + { + if (result.Handle != null) + { + if (!result.Handle.IsClosed) + result.Handle.Close(); + + result.Handle.Dispose(); + } + } + + return ret; + + } + + /// + /// Perform a deep Copy of the object, using Json as a serialisation method. + /// + /// The type of object being copied. + /// The object instance to copy. + /// The copied object. + public static T CloneJson(this T source) + { + // Don't serialize a null object, simply return the default for that object + if (Object.ReferenceEquals(source, null)) + { + return default(T); + } + + return JsonConvert.DeserializeObject(JsonConvert.SerializeObject(source, JSONSettingsPerformance)); + + } + + public static async Task IsPortOpenAsync(string Host, int port) + { + Socket socket = null; + + try + { + // make a TCP based socket + socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + + // connect + await Task.Run(() => socket.Connect(Host, port)); + + return true; + } + catch (SocketException ex) + { + if (ex.SocketErrorCode == SocketError.ConnectionRefused) + { + return false; + } + + //An error occurred when attempting to access the socket + Debug.WriteLine(ex.ToString()); + Console.WriteLine(ex); + } + finally + { + if (socket?.Connected ?? false) + { + socket?.Disconnect(false); + } + socket?.Close(); + } + + return false; + } + + public static bool IsPortOpen(string Host, int port) + { + Socket socket = null; + + try + { + // make a TCP based socket + socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + + // connect + socket.Connect(Host, port); + + return true; + } + catch (SocketException ex) + { + if (ex.SocketErrorCode == SocketError.ConnectionRefused) + { + return false; + } + + //An error occurred when attempting to access the socket + //Debug.WriteLine(ex.ToString()); + //Console.WriteLine(ex); + } + finally + { + if (socket?.Connected ?? false) + { + socket?.Disconnect(false); + } + socket?.Close(); + } + + return false; + } + + public static bool IsLocalPortInUse(int port) + { + + try + { + // Evaluate current system tcp connections. This is the same information provided + // by the netstat command line application, just in .Net strongly-typed object + // form. We will look through the list, and if our port we would like to use + // in our TcpClient is occupied, we will set isAvailable to false. + IPGlobalProperties ipGlobalProperties = IPGlobalProperties.GetIPGlobalProperties(); + + TcpConnectionInformation[] tcpConnInfoArray = ipGlobalProperties.GetActiveTcpConnections(); + + foreach (TcpConnectionInformation tcpi in tcpConnInfoArray) + { + if (tcpi.LocalEndPoint.Port == port) + { + return true; + } + } + } + catch (Exception ex) + { + Log($"Error: {ex.Msg()}"); + return true; + } + + return false; + } + + + + public static string UpdateURL(string InURL, int DefaultPort, string DefaultPath, string Host, ref bool WasFixed, ref bool HadError) + { + string ret = InURL; + UriBuilder uriBuilder = new UriBuilder(); + try + { + Uri url = new Uri(InURL); + + if (url != null) + { + + uriBuilder.Scheme = url.Scheme; + uriBuilder.Host = url.Host; + uriBuilder.Path = url.PathAndQuery; + uriBuilder.Port = url.Port; + + if (url.HostNameType == UriHostNameType.IPv6 && url.Host.Contains("[") && !InURL.Contains("[")) + { + //it adds [ ] around an ipv6 address + Log($"Debug: Placed square brackets around IPV6 address: '{url.Host}' for {InURL}"); + WasFixed = true; + } + + + if (!string.IsNullOrEmpty(Host) && !Host.Equals(url.Host, StringComparison.OrdinalIgnoreCase)) + { + uriBuilder.Host = Host; + Log($"Debug: Changed host from '{url.Host}' to '{Host}' for {InURL}"); + WasFixed = true; + } + + if (DefaultPort > 0 && url.Port != DefaultPort) + { + uriBuilder.Port = DefaultPort; + Log($"Debug: Changed port from '{url.Port}' to '{DefaultPort}' for {InURL}"); + WasFixed = true; + } + + //scheme=http or https + if (DefaultPort == 443 && url.Scheme != "https") + { + Log($"Debug: Changed scheme from '{url.Scheme}' to 'https' for {InURL}"); + uriBuilder.Scheme = "https"; + WasFixed = true; + } + else if (DefaultPort == 80 && url.Scheme == "https") + { + Log($"Debug: Changed scheme from '{url.Scheme}' to 'http' for {InURL}"); + uriBuilder.Scheme = "http"; + WasFixed = true; + } + + //if (!InURL.Contains($":{uriBuilder.Port}")) + //{ + // //this doesnt work because URI.ToString does not include the port if it is the default port for the scheme + // Log($"Debug: Port added to URL: '{uriBuilder.Port}' for {InURL}"); + // WasFixed = true; + //} + + if (!string.IsNullOrEmpty(DefaultPath) && string.IsNullOrEmpty(url.PathAndQuery) || url.PathAndQuery == "/" || url.PathAndQuery == "\\" || !url.PathAndQuery.StartsWith(DefaultPath, StringComparison.OrdinalIgnoreCase)) + { + Log($"Debug: Added correct path of '{DefaultPath}' to {InURL}"); + uriBuilder.Path = DefaultPath; + WasFixed = true; + } + + + if (!WasFixed) + { + string newurl = uriBuilder.Uri.GetComponents(UriComponents.AbsoluteUri, UriFormat.Unescaped); + if (!newurl.Equals(ret, StringComparison.OrdinalIgnoreCase)) + { + Log($"Debug: Updated URL '{InURL}' to '{newurl}'"); + WasFixed = true; + } + + } + + } + else + { + HadError = true; + Log($"Error: Bad url '{InURL}'"); + } + + } + catch (Exception ex) + { + HadError = true; + Log($"Error: {InURL}: {ex.Message}"); + } + + if (WasFixed) + { + //ret = uriBuilder.Uri.ToString(); + ret = uriBuilder.Uri.GetComponents(UriComponents.AbsoluteUri, UriFormat.SafeUnescaped); + } + + return ret; + } + + public static string GetURLPath(string InURL) + { + string ret = ""; + try + { + Uri url = new Uri(InURL); + if (url != null && !string.IsNullOrEmpty(url.PathAndQuery) && url.PathAndQuery != "\\" && url.PathAndQuery != "/") + ret = url.PathAndQuery; + } + catch (Exception ex) + { + Log($"Error: {InURL}: {ex.Message}"); + } + return ret; + } + + + public static bool IsValidURL(string InURL) + { + bool ret = false; + try + { + Uri url = new Uri(InURL); + if (url != null && + !string.IsNullOrEmpty(url.PathAndQuery) && + url.PathAndQuery != "/" && + !url.PathAndQuery.Contains("|") && + !url.PathAndQuery.Contains(";") && + url.Port != 0) + ret = true; + } + catch (Exception ex) + { + //Log($"Error: {InURL}: {ex.Message}"); + } + return ret; + } + [DllImport("Shlwapi.dll", CharSet = CharSet.Auto)] + public static extern long StrFormatByteSize(long fileSize, [MarshalAs(UnmanagedType.LPTStr)] StringBuilder buffer, int bufferSize); + + public static string FormatBytes(long filesize) + { + StringBuilder sb = new StringBuilder(); + StrFormatByteSize(filesize, sb, sb.Capacity); + return sb.ToString(); + } + public static bool IsLatLongValid(string lat, string lng) + { + return IsLatLongValid(lat.ToDouble(), lng.ToDouble()); + } + public static bool IsLatLongValid(double lat, double lng) + { + bool ret = false; + if (lat == 0 || lat == 39.809734 || lat < -90 || lat > 90 || lng == 0 || lng == -98.555620 || lng < -180 || lng > 180) + ret = false; + else + ret = true; + return ret; + } + private static DateTime lastlatwarn = DateTime.Now; + [DebuggerStepThrough] + public static bool IsTimeBetween(DateTime time, string span) + { + if (span.IsEmpty()) + return true; //if span is not set, assume its always true + + bool ret = false; + + try + { + // convert datetime to a TimeSpan + TimeSpan now = time.TimeOfDay; + + //first split up multiple ranges: + List spans = span.SplitStr(","); + foreach (var spn in spans) + { + + //support simple format hour1;hour2;hour3 12;1;2;3;4;5;6 + if (spn.Contains(";")) + { + List semsplt = spn.SplitStr(";"); + foreach (string hr in semsplt) + { + //The value of the Hour property is always expressed using a 24-hour clock. + if (hr == time.Hour.ToString()) + { + ret = true; + break; + } + } + + if (ret) + break; + } + else + { + List splt = spn.SplitStr("-", RemoveEmpty: false); + + TimeSpan BeginSpan = now; + TimeSpan EndSpan = now; + + //sunset-sunrise + //sunrise-sunset + //sunset_5-sunrise+5 + + if (splt[0].EqualsIgnoreCase("sunset") || splt[0].EqualsIgnoreCase("sunrise") || splt[0].Has("dusk") || splt[0].Has("dawn")) + { + if (!IsLatLongValid(AppSettings.Settings.LocalLatitude, AppSettings.Settings.LocalLongitude)) + { + if ((DateTime.Now - lastlatwarn).TotalMinutes >= 30) + { + Log($"Warn: The 'LocalLatitude' and 'LocalLongitude' settings in AITOOL.Settings.JSON file is incorrect and needs to be set to your local area. Latitude is currently '{AppSettings.Settings.LocalLatitude}' and Longitude is '{AppSettings.Settings.LocalLongitude}'. If you set those settings in a locally running copy of BlueIris > Settings > Schedule tab, they will be AUTOMATICALLY used."); + lastlatwarn = DateTime.Now; + } + } + else + { + TimeZoneInfo localZone = TimeZoneInfo.Local; + SolarTimes solarTimes = new SolarTimes(time, AppSettings.Settings.LocalLatitude, AppSettings.Settings.LocalLongitude); + + if (splt[0].Has("dusk")) + { + BeginSpan = TimeZoneInfo.ConvertTimeFromUtc(solarTimes.DuskCivil.ToUniversalTime(), localZone).TimeOfDay; + } + else if (splt[0].EqualsIgnoreCase("sunset")) + { + BeginSpan = TimeZoneInfo.ConvertTimeFromUtc(solarTimes.Sunset.ToUniversalTime(), localZone).TimeOfDay; + } + else if (splt[0].EqualsIgnoreCase("sunrise")) + { + BeginSpan = TimeZoneInfo.ConvertTimeFromUtc(solarTimes.Sunrise.ToUniversalTime(), localZone).TimeOfDay; + } + else if (splt[0].Has("dawn")) + { + BeginSpan = TimeZoneInfo.ConvertTimeFromUtc(solarTimes.DawnCivil.ToUniversalTime(), localZone).TimeOfDay; + } + + if (splt[1].Has("dusk")) + { + EndSpan = TimeZoneInfo.ConvertTimeFromUtc(solarTimes.DuskCivil.ToUniversalTime(), localZone).TimeOfDay; + } + else if (splt[1].EqualsIgnoreCase("sunset")) + { + EndSpan = TimeZoneInfo.ConvertTimeFromUtc(solarTimes.Sunset.ToUniversalTime(), localZone).TimeOfDay; + } + else if (splt[1].EqualsIgnoreCase("sunrise")) + { + EndSpan = TimeZoneInfo.ConvertTimeFromUtc(solarTimes.Sunrise.ToUniversalTime(), localZone).TimeOfDay; + } + else if (splt[1].Has("dawn")) + { + EndSpan = TimeZoneInfo.ConvertTimeFromUtc(solarTimes.DawnCivil.ToUniversalTime(), localZone).TimeOfDay; + } + + } + + } + else + { + BeginSpan = TimeSpan.Parse(splt[0]); + EndSpan = TimeSpan.Parse(splt[1]); + + } + + // see if start comes before end + if (BeginSpan < EndSpan) + { + ret = BeginSpan <= now && now <= EndSpan; + //if (ret) + // Console.WriteLine($"Time ({now.TotalHours.Round()}) IS BETWEEN [{span}] BeginSpan ({BeginSpan.TotalHours.Round()}) is LESS THAN EndSpan ({EndSpan.TotalHours.Round()})"); + //else + // Console.WriteLine($"Time ({now.TotalHours.Round()}) IS *NOT* BETWEEN [{span}] BeginSpan ({BeginSpan.TotalHours.Round()}) is LESS THAN EndSpan ({EndSpan.TotalHours.Round()})"); + + } + else + { + // start is after end, so do the inverse comparison + ret = !(EndSpan < now && now < BeginSpan); + //if (ret) + // Console.WriteLine($"Time ({now.TotalHours.Round()}) IS BETWEEN [{span}] BeginSpan ({BeginSpan.TotalHours.Round()}) is GREATER THAN EndSpan ({EndSpan.TotalHours.Round()})"); + //else + // Console.WriteLine($"Time ({now.TotalHours.Round()}) IS *NOT* BETWEEN [{span}] BeginSpan ({BeginSpan.TotalHours.Round()}) is GREATER THAN EndSpan ({EndSpan.TotalHours.Round()})"); + + } + + if (ret) + break; + } + } + + } + catch (Exception ex) + { + + Log($"Error: Range Invalid: '{span}'. Lat='{AppSettings.Settings.LocalLatitude}', Long='{AppSettings.Settings.LocalLongitude}': " + ex.Msg()); + } + + return ret; + } + + public static async Task DirectoryExistsAsync(string directory, int TimeoutMS = 20000) + { + //run the function in another thread + CancellationTokenSource cts = new CancellationTokenSource(TimeoutMS); + try + { + Stopwatch sw = Stopwatch.StartNew(); + bool result = await Task.Run(() => Directory.Exists(directory), cts.Token); + Log($"Trace: Directory exists for '{directory}' took {sw.ElapsedMilliseconds}ms"); + return result; + } + catch (Exception ex) + { + + return false; + } + } + + public static string MappedDriveToUNCPath(string path) + { + if (!path.StartsWith(@"\\")) + { + using (RegistryKey key = Registry.CurrentUser.OpenSubKey("Network\\" + path[0])) + { + if (key != null) + { + return key.GetValue("RemotePath").ToString() + path.Remove(0, 2).ToString(); + } + } + } + return path; + } + + public class MappedDrive + { + public string DriveLetter = ""; + public string Path = ""; + } + + public static async Task GetBestRemotePathAsync(string RemoteLocalPath, string RemoteMachineNameOrIP) + { + //We are taking a path like C:\BlueIris\clips\blah read from a remote computer's registry + //and trying to convert it to an accessible path on the current computer. + string ret = RemoteLocalPath; + string lastremotepathpart = RemoteLocalPath.SplitStr(@"\").Last(); + string ip = ""; + string hostname = ""; + Stopwatch sw = Stopwatch.StartNew(); + + if (!RemoteLocalPath.StartsWith(@"\\")) + { + //Make sure we have the IP address + IPAddress ipa = await GetIPAddressFromHostnameAsync(RemoteMachineNameOrIP); + ip = Global.IP2Str(ipa, IPType.Path); + hostname = await GetHostNameAsync(RemoteMachineNameOrIP); + + //first look for a mapped drive letter: + string letter = RemoteLocalPath.Substring(0, 1); + List mapped = new List(); + using RegistryKey key = Registry.CurrentUser.OpenSubKey("Network"); + if (key != null) + { + List drives = key.GetSubKeyNames().ToList(); + foreach (string drv in drives) + { + using RegistryKey drvkey = key.OpenSubKey(drv); + if (drvkey != null) + { + string remotepath = drvkey.GetValue("RemotePath", "").ToString(); + if (!string.IsNullOrEmpty(remotepath) && remotepath.StartsWith(@"\\")) + { + string mappedserver = remotepath.GetWord(@"\\", @"\"); + + if (mappedserver.EqualsIgnoreCase(ip) || + mappedserver.EqualsIgnoreCase(hostname) || + mappedserver.EqualsIgnoreCase(hostname.GetWord("", "."))) //lop off the SERVER.DOMAIN + { + MappedDrive md = new MappedDrive(); + md.DriveLetter = drv; + md.Path = remotepath; + mapped.Add(md); + } + } + } + } + } + + //first pass, try to get any matches where some of the UNC path matches... + //there would have to be a shared part of the path + // C:\clips\myclippath + //\\server\share\clips\myclippath + foreach (MappedDrive md in mapped) + { + string sharedpath = GetSharedPath(md.Path, RemoteLocalPath, false).TrimEnd(@"\".ToCharArray()); + if (!string.IsNullOrEmpty(sharedpath)) + { + Log($"Debug: Found shared path in {sw.ElapsedMilliseconds}ms on '{RemoteMachineNameOrIP}' for path '{RemoteLocalPath}': {sharedpath}"); + return sharedpath; + } + } + + List spltpth = RemoteLocalPath.SplitStr("\\"); + + //search for last two parts of the path UNDER each of the shares + string lastpath = spltpth[spltpth.Count - 1]; + string nexttolast = ""; + + if (spltpth.Count - 2 > 0) + { + nexttolast = spltpth[spltpth.Count - 2]; + string searchpath = $"{nexttolast}\\{lastpath}"; + foreach (MappedDrive md in mapped) + { + string checkpath = Path.Combine(md.Path, searchpath); + if (await Global.DirectoryExistsAsync(checkpath)) + { + Log($"Debug: Found remote path in {sw.ElapsedMilliseconds}ms on '{RemoteMachineNameOrIP}' for path '{RemoteLocalPath}': {checkpath}"); + return checkpath; + } + } + } + + + // C:\clips\myclippath + //\\server\share\clips\myclippath + + //C:\BlueIrisStorage\Alerts + //\\server\BlueIrisStorage + + foreach (MappedDrive md in mapped) + { + string checkpath = Path.Combine(md.Path, lastpath); + if (await Global.DirectoryExistsAsync(checkpath)) + { + Log($"Debug: Found remote path in {sw.ElapsedMilliseconds}ms on '{RemoteMachineNameOrIP}' for path '{RemoteLocalPath}': {checkpath}"); + return checkpath; + } + } + + + ret = $"\\\\{Global.IP2Str(RemoteMachineNameOrIP, IPType.Path)}\\{RemoteLocalPath.Replace(":", "$")}"; + //resort to using admin shares (have to be enabled through group policy in newer versions of windows) + Log($"Debug: Found ADMIN share in {sw.ElapsedMilliseconds}ms '{RemoteMachineNameOrIP}' for path '{RemoteLocalPath}': {ret}"); + + } + return ret; + } + + public static string GetSharedPath(string BasePath, string SrcPath, bool OnlyPartial) + { + // Dim NetRootPth As String = "\\server\admlibrary\Software\AutoDesk\Test_CAD_State_Kit" + // Dim SrcPth As String = " C:\Test\Test_CAD_State_Kit\C3D 2021\Utilities" + // Output= \\server\admlibrary\Software\AutoDesk\Test_CAD_State_Kit\C3D 2021\Utilities + string Ret = ""; + try + { + // Dim com As String = FindCommonPath(pths) + string[] nps = BasePath.Trim().Split('\\'); + string SharedPth = ""; + bool Fnd = false; + foreach (string pp in nps) + { + if (!OnlyPartial) + { + if (pp == "") + SharedPth = SharedPth + @"\"; + else + SharedPth = SharedPth + pp + @"\"; + } + + string[] rps = SrcPath.Trim().Split('\\'); + foreach (string rp in rps) + { + if (Fnd) + { + if (rp == "") + SharedPth = SharedPth + @"\"; + else + SharedPth = SharedPth + rp + @"\"; + } + else if (pp != "" && string.Equals(pp, rp, StringComparison.OrdinalIgnoreCase)) + { + Fnd = true; + if (OnlyPartial) + SharedPth = SharedPth + rp + @"\"; + } + } + if (Fnd) + break; + } + if (Fnd) + Ret = SharedPth; + } + catch (Exception ex) + { + Ret = ""; + Log("Error: " + ex.Message); + } + return Ret; + } + + public static bool IsRegexPatternValid(string pattern) + { + try + { + if (!string.IsNullOrWhiteSpace(pattern) && pattern.Length > 2) + { + System.Text.RegularExpressions.Regex test = new System.Text.RegularExpressions.Regex(pattern); + return true; + } + } + catch { } + return false; + } + + public static bool OnlyHexInString(string test) + { + if (test.IsEmpty()) + return false; + // For C-style hex notation (0xFF) you can use @"\A\b(0[xX])?[0-9a-fA-F]+\b\Z" + return System.Text.RegularExpressions.Regex.IsMatch(test, @"\A\b[0-9a-fA-F]+\b\Z"); + } + + public static Color ConvertStringToColor(string InString, string InAlphaString = "") + { + Color Ret = Color.FromKnownColor(KnownColor.White); + try + { + if (InString.IsNotEmpty()) + { + int Alpha = 255; + int Red = Ret.R; + int Green = Ret.G; + int Blue = Ret.B; + string[] splt; + if (InString.Contains(",")) + { + splt = InString.Trim().Split(','); + if (splt.Count() == 3) + { + Red = splt[0].ToInt(); + Green = splt[1].ToInt(); + Blue = splt[2].ToInt(); + Ret = Color.FromArgb(Red, Green, Blue); + } + else if (splt.Count() == 4) + { + Alpha = splt[0].ToInt(); + Red = splt[1].ToInt(); + Green = splt[2].ToInt(); + Blue = splt[3].ToInt(); + Ret = Color.FromArgb(Alpha, Red, Green, Blue); + } + else + Log("Error: Problem converting color, not 3 RGB numbers or 4 ARGB: '" + InString + ","); + + } + else + { + if (InString.StartsWith("#") || OnlyHexInString(InString.Trim())) + { + int argb = Int32.Parse(InString.Replace("#", "").Trim(), NumberStyles.HexNumber); + Ret = Color.FromArgb(argb); + } + else + { + Ret = Color.FromName(InString.Trim()); + } + } + + if (InAlphaString.IsNotEmpty()) + { + Ret = Color.FromArgb(InAlphaString.ToInt(), Ret); + } + + } + } + catch (Exception ex) + { + Log($"Error: InString='{InString}': {ex.Message}"); + } + return Ret; + } + + private static string CachedMacAddress = ""; + /// + /// Finds the MAC address of the NIC with maximum speed. + /// + /// The MAC address. + public static string GetMacAddress() + { + + if (CachedMacAddress.IsNotNull()) + return CachedMacAddress; + + const int MIN_MAC_ADDR_LENGTH = 12; + string macAddress = string.Empty; + long maxSpeed = -1; + Stopwatch sw = Stopwatch.StartNew(); + + try + { + foreach (NetworkInterface nic in NetworkInterface.GetAllNetworkInterfaces()) + { + if (nic.OperationalStatus == OperationalStatus.Up && nic.NetworkInterfaceType != NetworkInterfaceType.Loopback) + { + string tempMac = nic.GetPhysicalAddress().ToString(); + + if (nic.Speed > maxSpeed && !string.IsNullOrEmpty(tempMac) && tempMac.Length >= MIN_MAC_ADDR_LENGTH) + { + Log("Trace: New Max Speed = " + nic.Speed + ", MAC: " + tempMac); + maxSpeed = nic.Speed; + macAddress = tempMac; + } + + } + + } + + } + catch (Exception ex) + { + Log("Error: " + ex.Msg()); + } + + if (sw.ElapsedMilliseconds > 500) //should it really take this long?? + Log($"Warn: It tool {sw.ElapsedMilliseconds}ms to get the MAC address?"); + + CachedMacAddress = macAddress; + + return macAddress; + } + public static void ResponsiveSleep(int SleepMS) + { + Stopwatch sw = Stopwatch.StartNew(); + do + { + Thread.Sleep(50); + //I've seen threading errors happen with doevents calls, so wrap in try/catch... + try + { Application.DoEvents(); } //Cover your eyes, nothing to see here. DoEvents isnt that bad. Really. Ok, bye. + catch { } + + } while (sw.ElapsedMilliseconds <= SleepMS); + } + + public static bool DirectoryCopy(string sourceDirName, string destDirName, bool copySubDirs, bool nolog) + { + + bool ret = true; + try + { + // Get the subdirectories for the specified directory. + DirectoryInfo dir = new DirectoryInfo(sourceDirName); + + if (!dir.Exists) + { + ret = false; + if (!nolog) Log($"Error: Source folder does not exist? {sourceDirName}"); + return ret; + } + + DirectoryInfo[] dirs = dir.GetDirectories(); + + // If the destination directory doesn't exist, create it. + Directory.CreateDirectory(destDirName); + + // Get the files in the directory and copy them to the new location. + FileInfo[] files = dir.GetFiles(); + foreach (FileInfo file in files) + { + string tempPath = Path.Combine(destDirName, file.Name); + try + { + file.CopyTo(tempPath, false); + } + catch (Exception ex) + { + ret = false; + if (!nolog) Log($"Error: {tempPath} - {ex.Message}"); + } + } + + // If copying subdirectories, copy them and their contents to new location. + if (copySubDirs) + { + foreach (DirectoryInfo subdir in dirs) + { + string tempPath = Path.Combine(destDirName, subdir.Name); + if (!DirectoryCopy(subdir.FullName, tempPath, copySubDirs, nolog)) + ret = false; + } + } + + } + catch (Exception ex) + { + ret = false; + if (!nolog) Log($"Error: {ex.Message}"); + } + + return ret; + } + + private static Dictionary HostNameCache = new Dictionary(); + public static async Task GetHostNameAsync(string IPAddressOrHostName) + { + + if (HostNameCache.ContainsKey(IPAddressOrHostName.ToLower())) + return HostNameCache[IPAddressOrHostName.ToLower()]; + + Stopwatch sw = Stopwatch.StartNew(); + string ret = IPAddressOrHostName; + + try + { + if (!IsValidIPAddress(IPAddressOrHostName, out IPAddress FoundIP)) + return IPAddressOrHostName; //assume valid hostname if it doesnt look like an ip address + + //why is this taking close to 5 seconds for an internal network dns call?? + + IPHostEntry entry = await Dns.GetHostEntryAsync(IPAddressOrHostName); + if (entry != null) + { + ret = entry.HostName; + } + } + catch (Exception ex) + { + Log("Error: " + ex.Message); + } + finally + { + Log($"Trace: Resolved Host name '{IPAddressOrHostName}' to '{ret}' in {sw.ElapsedMilliseconds}ms"); + } + + HostNameCache.Add(IPAddressOrHostName.ToLower(), ret); + + return ret; + + } + + private static string CurrentIP = ""; + private static string CurrentHost = ""; + public static bool IsLocalHost(string HostNameOrIPAddress) + { + try + { + bool ret = false; + + if (CurrentIP.IsEmpty()) + CurrentIP = GetAllLocalIPs(NetworkInterfaceType.Ethernet)[0].ToString(); + + if (CurrentHost.IsEmpty()) + CurrentHost = Dns.GetHostName(); + + if (string.IsNullOrEmpty(HostNameOrIPAddress) || + HostNameOrIPAddress == "." || + HostNameOrIPAddress.EqualsIgnoreCase("localhost") || + HostNameOrIPAddress == "127.0.0.1" || + HostNameOrIPAddress == "0.0.0.0" || + HostNameOrIPAddress == "::1:" || + HostNameOrIPAddress == "[::1:]" || + HostNameOrIPAddress == "0:0:0:0:0:0:0:1" || + HostNameOrIPAddress == "[0:0:0:0:0:0:0:1]" || + HostNameOrIPAddress.EqualsIgnoreCase(CurrentIP) || + HostNameOrIPAddress.EqualsIgnoreCase(CurrentHost)) + { + ret = true; + } + else + { + IPAddress ip = GetIPAddressFromHostname(HostNameOrIPAddress); + if (IPAddress.IsLoopback(ip)) + { + ret = true; + } + else if (HostNameOrIPAddress.EqualsIgnoreCase(CurrentIP) || + HostNameOrIPAddress.EqualsIgnoreCase(CurrentHost)) + { + ret = true; + } + } + + ////if (!ret) + //// Log($"Host or IP not detected as 'localhost': '{HostNameOrIPAddress}' ThisHost='{CurrentHost}', ThisIP='{CurrentIP}'"); + + return ret; + + } + catch (Exception ex) + { + Log($"Error: {ex.Msg()}"); + return false; + } + + + } + + + public static async Task IsLocalHostAsync(string HostNameOrIPAddress) + { + try + { + bool ret = false; + + if (CurrentIP.IsEmpty()) + { + List ips = GetAllLocalIPs(NetworkInterfaceType.Ethernet); + //was getting index out of bounds on one machine that has never had AITOOL installed + if (ips.Count > 0) + CurrentIP = ips[0].ToString(); + else + { + CurrentIP = "127.0.0.1"; //this should not happen - antivirus/firewall blocking? + } + } + + + if (CurrentHost.IsEmpty()) + CurrentHost = Dns.GetHostName(); + + if (string.IsNullOrEmpty(HostNameOrIPAddress) || + HostNameOrIPAddress == "." || + HostNameOrIPAddress.EqualsIgnoreCase("localhost") || + HostNameOrIPAddress == "127.0.0.1" || + HostNameOrIPAddress == "0.0.0.0" || + HostNameOrIPAddress == "::1:" || + HostNameOrIPAddress == "[::1:]" || + HostNameOrIPAddress == "0:0:0:0:0:0:0:1" || + HostNameOrIPAddress == "[0:0:0:0:0:0:0:1]" || + HostNameOrIPAddress.EqualsIgnoreCase(CurrentIP) || + HostNameOrIPAddress.EqualsIgnoreCase(CurrentHost)) + { + ret = true; + } + else + { + IPAddress ip = await GetIPAddressFromHostnameAsync(HostNameOrIPAddress); + if (IPAddress.IsLoopback(ip)) + { + ret = true; + } + else if (HostNameOrIPAddress.EqualsIgnoreCase(CurrentIP) || + HostNameOrIPAddress.EqualsIgnoreCase(CurrentHost)) + { + ret = true; + } + } + + ////if (!ret) + //// Log($"Host or IP not detected as 'localhost': '{HostNameOrIPAddress}' ThisHost='{CurrentHost}', ThisIP='{CurrentIP}'"); + + return ret; + + } + catch (Exception ex) + { + Log($"Error: {ex.Msg()}"); + return false; + } + + + } + + + public static bool IsLocalNetwork(string HostNameOrIPAddress) + { + + if (IsLocalHost(HostNameOrIPAddress)) + return true; + + IPAddress ip = GetIPAddressFromHostname(HostNameOrIPAddress); + + if (ip != IPAddress.None) + { + if (IPAddress.IsLoopback(ip)) + return true; + + if (ip.AddressFamily == AddressFamily.InterNetwork) + { + byte[] bytes = ip.GetAddressBytes(); + switch (bytes[0]) + { + case 10: + return true; + case 172: + return bytes[1] < 32 && bytes[1] >= 16; + case 192: + return bytes[1] == 168; + default: + return false; + } + } + else if (ip.AddressFamily == AddressFamily.InterNetworkV6) + { + var addressAsString = ip.ToString(); + var firstWord = addressAsString.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries)[0]; + + // Make sure we are dealing with an IPv6 address + if (ip.AddressFamily != AddressFamily.InterNetworkV6) return false; + + // The original IPv6 Site Local addresses (fec0::/10) are deprecated. Unfortunately IsIPv6SiteLocal only checks for the original deprecated version: + else if (ip.IsIPv6SiteLocal) return true; + + // These days Unique Local Addresses (ULA) are used in place of Site Local. + // ULA has two variants: + // fc00::/8 is not defined yet, but might be used in the future for internal-use addresses that are registered in a central place (ULA Central). + // fd00::/8 is in use and does not have to registered anywhere. + else if (firstWord.Substring(0, 2) == "fc" && firstWord.Length >= 4) return true; + else if (firstWord.Substring(0, 2) == "fd" && firstWord.Length >= 4) return true; + + // Link local addresses (prefixed with fe80) are not routable + else if (firstWord == "fe80") return true; + + // Discard Prefix + else if (firstWord == "100") return true; + + // Any other IP address is not Unique Local Address (ULA) + else return false; + } + } + + return false; + + + } + + public enum IPType + { + Path, + URL + } + + public static string IP2Str(IPAddress ip, IPType type) + { + if (ip == IPAddress.None) + return ip.ToString(); //?? + + if (ip.AddressFamily == AddressFamily.InterNetwork) + return ip.ToString(); + + if (ip.AddressFamily == AddressFamily.InterNetworkV6) + { + + if (type == IPType.Path) + { + return $"{ip.ToString().Replace(":::", "::").Replace(":", "-").Replace("%", "s")}.ipv6-literal.net"; //https://devblogs.microsoft.com/oldnewthing/20100915-00/?p=12863 + + } + else if (type == IPType.URL) + { + return $"[{ip.ToString().Replace(":::", "::")}]"; //add square brackets to make the URL valid. Fix where BlueIris returns 3 colons vs 2 correct. + } + } + + return ip.ToString(); + + } + + public static string IP2Str(string HostNameOrIPAddress, IPType type) + { + IPAddress ip = GetIPAddressFromIPString(HostNameOrIPAddress.Replace(":::", "::")); + if (ip == IPAddress.None) + return HostNameOrIPAddress; + + return IP2Str(ip, type); + + + } + + public static string CleanIPV6Address(string IP) + { + //2600-6c64-6b7f-f8d8--1d4.ipv6-literal.net + //2600:6c64:6b7f:f8d8::1d4 + //[2600:6c64:6b7f:f8d8::1d4] + //[2600:6c64:6b7f:f8d8:::1d4] - bad, 3 colons + IP = IP.Trim("[] ".ToCharArray()); + if (IP.IndexOf(".ipv6-literal.net", StringComparison.OrdinalIgnoreCase) >= 0) + { + IP = IP.Replace(".ipv6-literal.net", "").Replace(":::", "::").Replace("-", ":").Replace("s", "%").Trim(); + } + return IP; + } + + static Dictionary DNSErrCache = new Dictionary(); + public static IPAddress GetIPAddressFromHostname(string HostNameOrIPAddress = "") + { + IPAddress ret = IPAddress.None; + try + { + //stop any errors from happening more than once since it may take a long time to resolve an invalid host: + if (DNSErrCache.ContainsKey(HostNameOrIPAddress.ToLower())) + return ret; + + if (IsValidIPAddress(HostNameOrIPAddress, out IPAddress FoundIP)) + return FoundIP; + + IPHostEntry Host; + if (string.IsNullOrWhiteSpace(HostNameOrIPAddress)) + Host = Dns.GetHostEntry(Dns.GetHostName()); + else + Host = Dns.GetHostEntry(HostNameOrIPAddress); + + foreach (IPAddress IP in Host.AddressList) + { + if (IP.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork) + { + // just return the first one ipv4 address + ret = IP; + break; + } + } + if (!IsValidIPAddress(ret, out FoundIP)) + { + // fall back to ipv6 + foreach (IPAddress IP in Host.AddressList) + { + if (IP.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6) + { + // just return the first ipv6 address + ret = IP; + break; + } + } + } + } + catch (Exception ex) + { + ret = IPAddress.None; + if (!DNSErrCache.ContainsKey(HostNameOrIPAddress.ToLower())) + DNSErrCache.Add(HostNameOrIPAddress.ToLower(), ex.Message); + + Log($"Error: Hostname '{HostNameOrIPAddress}': {ex.Msg()}"); + } + + return ret; + } + + public static List GetAllLocalIPs(NetworkInterfaceType _type) + { + List ipAddrList = new List(); + try + { + foreach (NetworkInterface item in NetworkInterface.GetAllNetworkInterfaces()) + { + if (item.NetworkInterfaceType == _type && item.OperationalStatus == OperationalStatus.Up) + { + foreach (UnicastIPAddressInformation ip in item.GetIPProperties().UnicastAddresses) + { + if (ip.Address.AddressFamily == AddressFamily.InterNetwork) + { + ipAddrList.Add(ip.Address); + } + } + } + } + //fall back to IPV6 if needed + if (ipAddrList.Count == 0) + { + foreach (NetworkInterface item in NetworkInterface.GetAllNetworkInterfaces()) + { + if (item.NetworkInterfaceType == _type && item.OperationalStatus == OperationalStatus.Up) + { + foreach (UnicastIPAddressInformation ip in item.GetIPProperties().UnicastAddresses) + { + if (ip.Address.AddressFamily == AddressFamily.InterNetworkV6) + { + ipAddrList.Add(ip.Address); + } + } + } + } + + } + + if (ipAddrList.Count == 0) + Log($"Error: No IP addresses found for NetworkInterfaceType '{_type}' with Operational status = UP????"); + + } + catch (Exception ex) + { + + Log("Error: " + ex.Msg()); + } + + return ipAddrList; + } + + public static async Task GetIPAddressFromHostnameAsync(string HostNameOrIPAddress = "") + { + IPAddress ret = IPAddress.None; + Stopwatch sw = Stopwatch.StartNew(); + + try + { + //stop any errors from happening more than once: + if (DNSErrCache.ContainsKey(HostNameOrIPAddress.ToLower())) + return ret; + + if (IsValidIPAddress(HostNameOrIPAddress, out IPAddress FoundIP)) + return FoundIP; + + IPHostEntry Host; + if (string.IsNullOrWhiteSpace(HostNameOrIPAddress)) + Host = await Dns.GetHostEntryAsync(Dns.GetHostName()); + else + Host = await Dns.GetHostEntryAsync(HostNameOrIPAddress); + + //prefer ipv4 address + foreach (IPAddress IP in Host.AddressList) + { + if (IP.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork) + { + // just return the first ipv4 found + ret = IP; + break; + } + } + if (!IsValidIPAddress(ret, out FoundIP)) + { + // fall back to ipv6 + foreach (IPAddress IP in Host.AddressList) + { + if (IP.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6) + { + // just return the first ipv6 + ret = IP; + break; + } + } + } + } + catch (Exception ex) + { + ret = IPAddress.None; + if (!DNSErrCache.ContainsKey(HostNameOrIPAddress.ToLower())) + DNSErrCache.Add(HostNameOrIPAddress.ToLower(), ex.Message); + + Log($"Error: Hostname '{HostNameOrIPAddress}': {ex.Msg()}"); + } + finally + { + if (ret != IPAddress.None) + Log($"Trace: Resolved host '{HostNameOrIPAddress}' to IP {ret.ToString()} in {sw.ElapsedMilliseconds}ms"); + } + + return ret; + } + public static IPAddress GetIPAddressFromIPString(string IP) + { + IPAddress ret = IPAddress.None; + if (IsValidIPAddress(IP, out IPAddress FoundIP)) + return FoundIP; + + return ret; + } + + public static bool IsValidIPAddress(string IP, out IPAddress FoundIP) + { + FoundIP = IPAddress.None; + if (!string.IsNullOrEmpty(IP)) + { + IP = CleanIPV6Address(IP); + if (IPAddress.TryParse(IP, out FoundIP) && !FoundIP.Equals(IPAddress.None) && (FoundIP.AddressFamily == AddressFamily.InterNetwork || FoundIP.AddressFamily == AddressFamily.InterNetworkV6)) + return true; + } + return false; + } + public static bool IsValidIPAddress(IPAddress IP, out IPAddress FoundIP) + { + FoundIP = IPAddress.None; + if (IP != null && !IP.Equals(IPAddress.None)) + { + if (IsValidIPAddress(IP.ToString(), out FoundIP)) + return true; + } + return false; + } + + public class ClsPingOut + { + public bool Success = false; + public PingReply PingReply = null; + public long DNSResolveMS = 0; + public string PingError = ""; + public int Hops = 0; + public int Retries = 0; + public long AvgTimeMS = 0; + public long MaxTimeMS = 0; + public long MinTimeMS = 0; + public long TotalTimeMS = 0; + public List Pings = new List(); + } + + public static async Task IsConnected(string HostOrIPToPing = "www.google.com", int TimeoutMS = 2000, int RetryCount = 3, int DelayMS = 25, bool AlwaysRetry = false) + { + ClsPingOut ret = new ClsPingOut(); + Stopwatch SW = Stopwatch.StartNew(); + + try + { + IPAddress IP = null; + + if (!IsValidIPAddress(HostOrIPToPing, out IP)) + IP = await GetIPAddressFromHostnameAsync(HostOrIPToPing); + + ret.DNSResolveMS = SW.ElapsedMilliseconds; + + if (!IsValidIPAddress(IP, out IP)) + return ret; + + Log($"Trace: Pinging {HostOrIPToPing} ({IP.ToString()}) With timeout:{TimeoutMS}ms And Ping Retry Count:{RetryCount}..."); + for (int Tries = 1; Tries <= RetryCount; Tries++) + { + ret.Retries = Tries; + byte[] buffer = new byte[32]; + PingOptions pingOptions = new PingOptions(128, false); + + int TTLBefore = pingOptions.Ttl; + try + { + using (Ping Myping = new Ping()) + { + ret.PingReply = await Myping.SendPingAsync(IP, TimeoutMS, buffer, pingOptions); + } + } + catch (Exception ex) + { + ret.PingError = ex.GetBaseException().Message; + } + if (ret.PingReply != null) + { + ret.Success = (ret.PingReply.Status == IPStatus.Success); + ret.PingError = ret.PingReply.Status.ToString(); + if (ret.PingReply.Options != null) //ipv6 returns null + ret.Hops = TTLBefore - ret.PingReply.Options.Ttl; + if (ret.Success) + { + ret.Pings.Add(ret.PingReply.RoundtripTime); + + if (!AlwaysRetry) //If we want to get a true ping average, dont break out of the loop yet + break; + } + } + + // wait before next try + await Task.Delay(DelayMS); + } + } + catch (Exception ex) + { + ret.PingError = $"{ex.Msg()} (Site={HostOrIPToPing} Timeout was {TimeoutMS}ms)"; + Log($"Error: {ret.PingError}"); + } + finally + { + } + + if (ret.Pings.Count > 0) + { + ret.AvgTimeMS = System.Convert.ToInt64(ret.Pings.Average()); + ret.MaxTimeMS = System.Convert.ToInt64(ret.Pings.Max()); + ret.MinTimeMS = System.Convert.ToInt64(ret.Pings.Min()); + } + + SW.Stop(); + ret.TotalTimeMS = SW.ElapsedMilliseconds; + + Log($"Trace: ...Result={ret.Success}, {ret.TotalTimeMS}ms, {ret.PingError}"); + + return ret; + } + + + public static void MoveFiles(string FromFolder, string ToFolder, string FileSpec, bool OnlyIfNewer, bool OnlyCopy = false) + { + //Let us pass a filename so we can be lazy + if (Path.HasExtension(FromFolder)) + FromFolder = Path.GetDirectoryName(FromFolder); + + if (Path.HasExtension(ToFolder)) + ToFolder = Path.GetDirectoryName(ToFolder); + + List files = GetFiles(FromFolder, FileSpec, SearchOption.TopDirectoryOnly); + + int cnt = 0; + + if (files.Count > 0) + { + if (!Directory.Exists(ToFolder)) + Directory.CreateDirectory(ToFolder); + + } + + foreach (FileInfo fi in files) + { + string newfile = Path.Combine(ToFolder, fi.Name); + try + { + bool move = true; + FileInfo nfi = new FileInfo(newfile); + if (nfi.Exists) + { + if (fi.LastWriteTime < nfi.LastWriteTime) + { + //just delete the older file rather than moving it + move = false; + if (!OnlyCopy) + fi.Delete(); + } + } + + if (move) + { + if (!OnlyCopy) + fi.MoveTo(newfile); + else + fi.CopyTo(newfile, true); + + } + + cnt++; + } + catch (Exception ex) + { + + Log($"Error: Could not move {fi.FullName} to {newfile}: {ex.Msg()}"); + } + } + + Log($"Debug: Moved {cnt} '{FileSpec}' files from {FromFolder} to {ToFolder}."); + + } + + public static string GetFrameworkVersion() + { + using (RegistryKey ndpKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full")) + { + if (ndpKey != null) + { + int value = (int)(ndpKey.GetValue("Release") ?? 0); + string ver = ndpKey.GetValue("Version", "Unknown").ToString(); + if (value >= 528040) + return new Version(4, 8, 0).ToString() + $" (v{ver}, Release {value.ToString()})"; + + if (value >= 461808) + return new Version(4, 7, 2).ToString() + $" (v{ver}, Release {value.ToString()})"; + + if (value >= 461308) + return new Version(4, 7, 1).ToString() + $" (v{ver}, Release {value.ToString()})"; + + if (value >= 460798) + return new Version(4, 7, 0).ToString() + $" (v{ver}, Release {value.ToString()})"; + + if (value >= 394802) + return new Version(4, 6, 2).ToString() + $" (v{ver}, Release {value.ToString()})"; + + if (value >= 394254) + return new Version(4, 6, 1).ToString() + $" (v{ver}, Release {value.ToString()})"; + + if (value >= 393295) + return new Version(4, 6, 0).ToString() + $" (v{ver}, Release {value.ToString()})"; + + if (value >= 379893) + return new Version(4, 5, 2).ToString() + $" (v{ver}, Release {value.ToString()})"; + + if (value >= 378675) + return new Version(4, 5, 1).ToString() + $" (v{ver}, Release {value.ToString()})"; + + if (value >= 378389) + return new Version(4, 5, 0).ToString() + $" (v{ver}, Release {value.ToString()})"; + + return $"Unknown release {value}"; + } + + throw new NotSupportedException(@"No registry key found under 'SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full' to determine running framework version"); + } + } + + + public static bool DeleteRegSetting(string Name) + { + + //regkey is built from CompanyName\ProductName\MajorVersion.MinorVersion + Version AN = Assembly.GetExecutingAssembly().GetName().Version; + string Cname = System.Windows.Forms.Application.CompanyName; + string Pname = System.Windows.Forms.Application.ProductName; + string version = AN.Major + "." + AN.Minor; + string SKey = ""; + bool ret = false; + try + { + string RKey = $"Software\\{Cname}\\{Pname}\\{version}{SKey}"; + + using RegistryKey reg = Registry.CurrentUser.OpenSubKey(RKey, false); + + if (reg != null) + { + bool Found = false; + string[] Values = reg.GetValueNames(); + foreach (string valu in Values) + if (string.Equals(valu, Name, StringComparison.OrdinalIgnoreCase)) + { + Found = true; + break; + } + if (Found) + { + reg.DeleteValue(Name, false); + ret = true; + } + } + + + } + catch (Exception) + { + //Log($"Error: {ex.Msg()}"); + } + + return ret; + + } + public static bool DeleteRegSettings() + { + + //regkey is built from CompanyName\ProductName\MajorVersion.MinorVersion + Version AN = Assembly.GetExecutingAssembly().GetName().Version; + string Cname = System.Windows.Forms.Application.CompanyName; + string Pname = System.Windows.Forms.Application.ProductName; + string version = AN.Major + "." + AN.Minor; + bool ret = false; + try + { + string RKey = $"Software\\{Cname}\\{Pname}\\{version}"; + + Registry.CurrentUser.DeleteSubKeyTree(RKey, false); + + ret = true; + + } + catch (Exception) + { + //Log($"Error: {ex.Msg()}"); + } + + return ret; + + } + + public static dynamic GetRegSetting(string Name, object DefaultValue = null, string SubKey = "") + { + + //regkey is built from CompanyName\ProductName\MajorVersion.MinorVersion + Version AN = Assembly.GetExecutingAssembly().GetName().Version; + string Cname = System.Windows.Forms.Application.CompanyName; + string Pname = System.Windows.Forms.Application.ProductName; + string version = AN.Major + "." + AN.Minor; + object RetVal = DefaultValue; + string SKey = ""; + if (!string.IsNullOrWhiteSpace(SubKey)) + SKey = "\\" + SubKey.Trim(); + try + { + string RKey = $"Software\\{Cname}\\{Pname}\\{version}{SKey}"; + + using RegistryKey reg = Registry.CurrentUser.OpenSubKey(RKey, false); + if (reg != null) + { + bool Found = false; + string[] Values = reg.GetValueNames(); + foreach (string valu in Values) + if (string.Equals(valu, Name, StringComparison.OrdinalIgnoreCase)) + { + Found = true; + RetVal = reg.GetValue(Name, DefaultValue); + break; + } + if (Found) + { + if (reg.GetValueKind(Name) == RegistryValueKind.MultiString) + { + if (DefaultValue is List) + RetVal = ((string[])RetVal).ToList(); + else if (DefaultValue is object[]) + RetVal = (string[])RetVal; + else if (DefaultValue is string[]) + RetVal = (string[])RetVal; + } + else if (RetVal is string && DefaultValue is Point) + { + //{X=965,Y=399} + int X = GetNumberInt(RetVal.ToString().GetWord("X=", ",")); + int Y = GetNumberInt(RetVal.ToString().GetWord("Y=", "}")); + RetVal = new Point(X, Y); + + } + else if (RetVal is string && DefaultValue is Size) + { + //{Width=931, Height=592} + int Wid = GetNumberInt(RetVal.ToString().GetWord("Width=", ",")); + int Hei = GetNumberInt(RetVal.ToString().GetWord("Height=", "}")); + RetVal = new Size(Wid, Hei); + } + else if (RetVal is string) + { + if (RetVal.ToString().Length > 256 && IsBase64String(RetVal.ToString())) + { + string base64 = ""; + try + { + base64 = DeCompressFromBase64String(RetVal.ToString()); + } + catch (Exception) + { + } + + if (base64.IsEmpty()) //maybe its not really base 64 + gzip compressed? + base64 = RetVal.ToString(); + + RetVal = Convert.ChangeType(base64, DefaultValue.GetType()); + } + else + { + RetVal = Convert.ChangeType(RetVal, DefaultValue.GetType()); + } + } + else if (DefaultValue != null) + RetVal = Convert.ChangeType(RetVal, DefaultValue.GetType()); + //Else + // RetVal = Convert.ChangeType(RetVal, DefaultValue.GetType) + + + } + } + + + } + catch (Exception) + { + //Log($"Error: {ex.Msg()}"); + } + + return RetVal; + + } + public static Double GetNumberDbl(object Obj) + { + //gets a number from anywhere within a string + double Ret = 0; + if (!Obj.IsNull()) + { + if (Obj is string) + { + string o = System.Convert.ToString(Obj).Trim(); + double outdbl = 0; + + //Take into account that some countries may use 123,45 vs 123.45 + if (double.TryParse(o, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out outdbl)) + Ret = outdbl; + else //try to extract the number from a larger string + { + try + { + //this can grab anything even "The number is 69,9 dude" + string outstrnum = Regex.Match(o.Replace(",", "."), @"[-+]?(?:\b[0-9]+(?:\.[0-9]*)?|\.[0-9]+\b)(?:[eE][-+]?[0-9]+\b)?").Value; + if (double.TryParse(outstrnum, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out outdbl)) + Ret = outdbl; + else + { + Log($"Error: Could not parse to double? '{o}'"); + } + } + catch { } + + } + + + //if (OnlyNums != o) + //{ + // //debug + // int brkpt = 0; + //} + + } + else if (Obj is int) + Ret = Convert.ToDouble(Obj); + else if (Obj is double) + Ret = ((double)Obj); + else if (Obj is float) + Ret = (float)Obj; + } + return Ret; + + } + public static int GetNumberInt(object Obj) + { + //gets a number from anywhere within a string + int Ret = 0; + if (Obj != null) + { + if (Obj is string && !string.IsNullOrWhiteSpace((string)Obj)) + { + string o = System.Convert.ToString(Obj).Trim(); + double outdbl = 0; + string OnlyNums = ""; + + try + { + //this can grab anything even "The number is 69" + OnlyNums = Regex.Match(o, @"[-+]?(?:\b[0-9]+(?:\.[0-9]*)?|\.[0-9]+\b)(?:[eE][-+]?[0-9]+\b)?").Value; + } + catch { } + + if (OnlyNums != o) + { + //debug + int brkpt = 0; + } + + if (double.TryParse(OnlyNums, out outdbl)) + Ret = Convert.ToInt32(Math.Round(outdbl)); + } + else if (Obj is int) + Ret = (int)Obj; + else if (Obj is double) + Ret = ((double)Obj).ToInt(); + else if (Obj is float) + Ret = Convert.ToInt32(Math.Round((float)Obj)); + } + return Ret; + + } + public static bool SaveRegSetting(string name, object value, string SubKey = "") + { + bool ret = false; + //regkey is built from CompanyName\ProductName\MajorVersion.MinorVersion + Version AN = Assembly.GetExecutingAssembly().GetName().Version; + string Cname = System.Windows.Forms.Application.CompanyName; + string Pname = System.Windows.Forms.Application.ProductName; + string version = AN.Major + "." + AN.Minor; + string SKey = ""; + if (!string.IsNullOrWhiteSpace(SubKey)) + SKey = "\\" + SubKey.Trim(); + try + { + string RKey = $"Software\\{Cname}\\{Pname}\\{version}{SKey}"; + using RegistryKey reg = Registry.CurrentUser.CreateSubKey(RKey, RegistryKeyPermissionCheck.ReadWriteSubTree); + if (reg != null) + { + if (value is List) + { + List strlist = (List)value; + reg.SetValue(name, strlist.ToArray(), RegistryValueKind.MultiString); + } + else if (value is object[]) + { + List strlist = new List(); + object[] objects = (object[])value; + foreach (object obj in objects) + strlist.Add(obj.ToString()); + reg.SetValue(name, strlist.ToArray(), RegistryValueKind.MultiString); + } + else if (value is string) + { + //large strings may cause, so compress and base 64 encode + //Insufficient system resources exist to complete the requested service.; [IOException] + string compressed = value.ToString(); + + if (compressed.Length > 2048) + compressed = CompressToBase64String(compressed); + + reg.SetValue(name, compressed); + + } + else + { + reg.SetValue(name, value); + + } + ret = true; + } + + + } + catch (Exception ex) + { + Log($"Error: ({name}={value.ToString().Length} bytes) {ex.Msg()}"); + } + finally + { + + } + + return ret; + } + + public static string CompressToBase64String(string strdata) + { + return Convert.ToBase64String(CompressGzip(Encoding.UTF8.GetBytes(strdata))); + } + public static string DeCompressFromBase64String(string strdata) + { + return Encoding.UTF8.GetString(DecompressGZ(Convert.FromBase64String(strdata))); + } + + public static bool IsBase64String(string value) + { + if (value == null || value.Length == 0 || value.Length % 4 != 0 + || value.Contains(' ') || value.Contains('\t') || value.Contains('\r') || value.Contains('\n')) + return false; + var index = value.Length - 1; + if (value[index] == '=') + index--; + if (value[index] == '=') + index--; + for (var i = 0; i <= index; i++) + if (IsInvalid(value[i])) + return false; + return true; + } + // Make it private as there is the name makes no sense for an outside caller + private static bool IsInvalid(char value) + { + var intValue = (Int32)value; + if (intValue >= 48 && intValue <= 57) + return false; + if (intValue >= 65 && intValue <= 90) + return false; + if (intValue >= 97 && intValue <= 122) + return false; + return intValue != 43 && intValue != 47; + } + public static byte[] CompressGzip(byte[] data) + { + try + { + using (MemoryStream output = new MemoryStream()) + { + using (GZipStream gzip = new GZipStream(output, CompressionMode.Compress, false)) + { + gzip.Write(data, 0, data.Length); + gzip.Close(); + return output.ToArray(); + } + } + } + catch (Exception ex) + { + Log("Error: " + ex.Msg()); + } + + return null; + + } + + public static byte[] DecompressGZ(byte[] data) + { + try + { + Stopwatch sw1 = new Stopwatch(); + sw1.Start(); + using (MemoryStream input = new MemoryStream()) + { + input.Write(data, 0, data.Length); + input.Position = 0; + using (GZipStream gzip = new GZipStream(input, CompressionMode.Decompress, false)) + { + using (MemoryStream output = new MemoryStream()) + { + byte[] buff = new byte[4097]; + int read = -1; + read = gzip.Read(buff, 0, buff.Length); + while (read > 0) + { + output.Write(buff, 0, read); + read = gzip.Read(buff, 0, buff.Length); + } + gzip.Close(); + sw1.Stop(); + //Debug.Print(" (gz: " + sw1.ElapsedMilliseconds + "ms)"); + return output.ToArray(); + } + } + } + } + catch (Exception ex) + { + Log("Error: " + ex.Msg()); + } + + return null; + + } + + public static string ReplaceCaseInsensitive(string input, string search, string replacement) + { + string result = Regex.Replace( + input, + Regex.Escape(search), + replacement.Replace("$", "$$"), + RegexOptions.IgnoreCase + ); + return result; + } + + + public static String WildCardToRegular(String value) + { + return "^" + Regex.Escape(value).Replace("\\?", ".").Replace("\\*", ".*") + "$"; + } + + public static void PlayTick(SoundPlayer player) + { + if (AppSettings.Settings.Tick && player != null) + { + // Play the sound asynchronously + Task.Run(() => + { + // Rewind the player and play the sound + player.PlaySync(); + }); + + } + } + public static void SendMessage(MessageType MT, string Descript = "", object Payload = null, [CallerMemberName] string memberName = null) + { + if (progress == null) + { + //TODO: trying to catch a logging failure, should take out messagebox later + if (!warnedlog && !Global.IsService) + { + warnedlog = true; + string member = "unknown"; + if (memberName != null) + member = memberName; + + MessageBox.Show($"SendMessage {MT.ToString()} Debug warning: Progress event logger is null? calling function='{member}'"); + } + return; + } + + ClsMessage msg = new ClsMessage(MT, Descript, Payload, memberName); + + progress.Report(msg); + + } + + public static async void TelegramControlMessage(string Message, [CallerMemberName] string memberName = null) + { + + try + { + //PAUSE|STOP [CAMNAME] [MINUTES] + //STOP 30 <<---Stops/pauses all cameras for 30 minutes + //PAUSE CAMERANAME 30 + //START|RESUME [CAMNAME] + //RESUME + //RESUME CAMERANAME + + if (Message.StartsWith("play:", StringComparison.OrdinalIgnoreCase)) + { + string file = Message.GetWord("play:", ""); + string soundfile = Global.FindSoundFile(file); + if (soundfile.IsNotNull()) + { + AITOOL.Telegram.SendTextMessageAsync(AppSettings.Settings.telegram_chatids.GetStrAtIndex(0), $"Playing voice file {soundfile}..."); + + if (soundfile.EndsWith(".ogg", StringComparison.OrdinalIgnoreCase)) + { + PlayOOG(soundfile); + } + else + { + Log($"Debug: Playing sound: {soundfile}..."); + using SoundPlayer sp = new SoundPlayer(soundfile); + sp.PlaySync(); + } + } + else + { + Log($"Error: Sound file not found: {soundfile}"); + AITOOL.Telegram.SendTextMessageAsync(AppSettings.Settings.telegram_chatids.GetStrAtIndex(0), $"Error: Could not find {soundfile}..."); + } + return; + } + + List parts = Message.SplitStr(" ", TrimChars: " []"); + + bool pause = false; + bool resume = false; + double howlong = 525600; //let it default to the number of minutes in a year + string camname = ""; + + if (parts.Count > 0) + { + if (parts[0].EqualsIgnoreCase("pause") || parts[0].EqualsIgnoreCase("stop")) + pause = true; + else if (parts[0].EqualsIgnoreCase("resume") || parts[0].EqualsIgnoreCase("start")) + resume = true; + else if (parts[0].EqualsIgnoreCase("restartcomputer") || parts[0].EqualsIgnoreCase("restartpc") || parts[0].EqualsIgnoreCase("reboot") || parts[0].EqualsIgnoreCase("rebootcomputer") || parts[0].EqualsIgnoreCase("rebootpc")) + { + await AITOOL.Telegram.SendTextMessageAsync(AppSettings.Settings.telegram_chatids.GetStrAtIndex(0), $"Computer restarting in 10 seconds..."); + + using (Process prc = System.Diagnostics.Process.Start("shutdown.exe", "-r -f -t 10")) { } + Application.Exit(); + } + else if (parts[0].EqualsIgnoreCase("shutdowncomputer") || parts[0].EqualsIgnoreCase("shutdownpc")) + { + await AITOOL.Telegram.SendTextMessageAsync(AppSettings.Settings.telegram_chatids.GetStrAtIndex(0), $"Computer shutting down in 10 seconds..."); + using (Process prc = System.Diagnostics.Process.Start("shutdown.exe", "-s -f -t 10")) { } + + Application.Exit(); + } + else if (parts[0].EqualsIgnoreCase("restart") || parts[0].EqualsIgnoreCase("restartaitool")) + { + await AITOOL.Telegram.SendTextMessageAsync(AppSettings.Settings.telegram_chatids.GetStrAtIndex(0), $"Restarting AITOOL..."); + Restart = true; + Application.Exit(); + + } + else if (parts[0].EqualsIgnoreCase("mute")) + { + await AITOOL.Telegram.SendTextMessageAsync(AppSettings.Settings.telegram_chatids.GetStrAtIndex(0), $"Muting."); + Log("Muting."); + Mute(); + } + else if (parts[0].EqualsIgnoreCase("unmute")) + { + await AITOOL.Telegram.SendTextMessageAsync(AppSettings.Settings.telegram_chatids.GetStrAtIndex(0), $"UnMuting."); + Log("UnMuting"); + UnMute(); + } + else if (parts[0].EqualsIgnoreCase("volumeup")) + { + await AITOOL.Telegram.SendTextMessageAsync(AppSettings.Settings.telegram_chatids.GetStrAtIndex(0), $"VolumeUp."); + Log("Volume Up"); + VolUp(); + } + else if (parts[0].EqualsIgnoreCase("volumedown")) + { + await AITOOL.Telegram.SendTextMessageAsync(AppSettings.Settings.telegram_chatids.GetStrAtIndex(0), $"VolumeDown."); + Log("Volume Down"); + VolDown(); + } + else if (parts[0].EqualsIgnoreCase("volumeset")) + { + float num = parts.GetStrAtIndex(1).ToFloat(); + await AITOOL.Telegram.SendTextMessageAsync(AppSettings.Settings.telegram_chatids.GetStrAtIndex(0), $"VolumeSet {num}"); + Log($"Volume set {num}"); + VolSet(num); + } + else if (parts[0].EqualsIgnoreCase("screenshot")) + { + await AITOOL.Telegram.SendTextMessageAsync(AppSettings.Settings.telegram_chatids.GetStrAtIndex(0), $"Taking screenshot..."); + Log($"Debug: Taking screenshot of primary screen @ {Screen.PrimaryScreen.Bounds.Width}x{Screen.PrimaryScreen.Bounds.Height}"); + + using (Bitmap bitmap = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height)) + { + using (Graphics g = Graphics.FromImage(bitmap)) + { + g.CopyFromScreen(0, 0, 0, 0, bitmap.Size); + } + + //string outfile = Path.Combine(GetTempFolder(), $"Screenshot_{DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss")}.jpg"); + + + using (MemoryStream ms = new MemoryStream()) + { + bitmap.Save(ms, ImageFormat.Jpeg); + ms.Seek(0, SeekOrigin.Begin); //go back to start + await AITOOL.Telegram.SendPhotoAsync(AppSettings.Settings.telegram_chatids.GetStrAtIndex(0), ms, "", "screenshot.jpg", ""); + } + + //ClsImageQueueItem img = new ClsImageQueueItem(outfile, 0, false); + + } + } + else + { + Log($"Debug: Unknown AITOOL Telegram control command '{parts[0]}'"); + AITOOL.Telegram.SendTextMessageAsync(AppSettings.Settings.telegram_chatids.GetStrAtIndex(0), $"Debug: Unknown Telegram control command '{parts[0]}'"); + } + + if (pause || resume) + { + //check if the second parameter is a camera name or number + if (parts.Count > 1) + { + if (parts[1].IsNumeric()) + { + howlong = parts[1].ToDouble(); + } + else + { + camname = parts[1]; + } + } + + if (parts.Count > 2 && parts[2].IsNumeric()) + { + howlong = parts[2].ToDouble(); + } + + if (camname.IsNotEmpty()) + { + Camera cam = AITOOL.GetCamera(camname); + if (cam != null) + { + if (pause) + { + cam.PauseMinutes = howlong; + cam.PauseFileMon = true; + cam.PauseMQTT = true; + cam.PausePushover = true; + cam.PauseTelegram = true; + cam.PauseURL = true; + cam.Pause(); + AITOOL.Telegram.SendTextMessageAsync(AppSettings.Settings.telegram_chatids.GetStrAtIndex(0), $"Camera '{camname}' paused {howlong} minutes."); + } + else + { + cam.PauseFileMon = false; + cam.PauseMQTT = false; + cam.PausePushover = false; + cam.PauseTelegram = false; + cam.PauseURL = false; + cam.Resume(); + AITOOL.Telegram.SendTextMessageAsync(AppSettings.Settings.telegram_chatids.GetStrAtIndex(0), $"Camera '{camname}' resumed."); + } + } + else + { + Log($"Error: Camera '{camname}' not found."); + AITOOL.Telegram.SendTextMessageAsync(AppSettings.Settings.telegram_chatids.GetStrAtIndex(0), $"Error: Camera '{camname}' not found."); + } + } + else + { + string pausemsg = ""; + if (pause) + pausemsg = $"All cameras paused {howlong} minutes."; + else + pausemsg = $"All cameras resumed."; + + + foreach (var cam in AppSettings.Settings.CameraList) + { + if (pause) + { + cam.PauseMinutes = howlong; + cam.PauseFileMon = true; + cam.PauseMQTT = true; + cam.PausePushover = true; + cam.PauseTelegram = true; + cam.PauseURL = true; + cam.Pause(); + } + else + { + cam.PauseFileMon = false; + cam.PauseMQTT = false; + cam.PausePushover = false; + cam.PauseTelegram = false; + cam.PauseURL = false; + cam.Resume(); + } + } + + AITOOL.Telegram.SendTextMessageAsync(AppSettings.Settings.telegram_chatids.GetStrAtIndex(0), pausemsg); + + } + + } + + } + else + { + Log("Debug: empty Telegram control message?"); + } + + } + catch (Exception ex) + { + Log($"Error: {ex.Msg()}"); + } + + + } + public static void DeleteHistoryItem(string filename, [CallerMemberName] string memberName = null) + { + if (progress == null) + { + //TODO: trying to catch a logging failure, should take out messagebox later + if (!warnedlog && !Global.IsService) + { + warnedlog = true; + string member = "unknown"; + if (memberName != null) + member = memberName; + + MessageBox.Show($"DeleteHistoryItem Debug warning: Progress event logger is null? calling function='{member}'"); + } + return; + } + + ClsMessage msg = new ClsMessage(MessageType.DeleteHistoryItem, filename, null, memberName); + + progress.Report(msg); + + } + + public static void PlayOOG(string filename) + { + //This cannot play .OGG files created by the Telegram.Bot engine? + //Could not load stream 1483939711 due to error: Found OPUS bitstream. + //'System.ArgumentException' in NAudio.Vorbis.dll + //Could not initialize container! + using NAudio.Vorbis.VorbisWaveReader vorbis = new NAudio.Vorbis.VorbisWaveReader(filename); + using NAudio.Wave.WaveOut waveOut = new NAudio.Wave.WaveOut(); + waveOut.Init(vorbis); + waveOut.Play(); + while (waveOut.PlaybackState == PlaybackState.Playing) + { + if (MasterCTS.IsNotNull() && MasterCTS.Token.IsCancellationRequested) + break; + + Task.Delay(100, MasterCTS.Token); + } + + } + + public static void Mute() + { + + //SendMessageW(hand, WM_APPCOMMAND, hand, (IntPtr)APPCOMMAND_VOLUME_MUTE); + + using (var enumerator = new NAudio.CoreAudioApi.MMDeviceEnumerator()) + { + foreach (var device in enumerator.EnumerateAudioEndPoints(NAudio.CoreAudioApi.DataFlow.Render, NAudio.CoreAudioApi.DeviceState.Active)) + { + if (device.AudioEndpointVolume?.HardwareSupport.HasFlag(NAudio.CoreAudioApi.EEndpointHardwareSupport.Mute) == true) + { + Console.WriteLine(device.FriendlyName); + device.AudioEndpointVolume.Mute = true; + } + } + } + } + public static void UnMute() + { + using (var enumerator = new NAudio.CoreAudioApi.MMDeviceEnumerator()) + { + foreach (var device in enumerator.EnumerateAudioEndPoints(NAudio.CoreAudioApi.DataFlow.Render, NAudio.CoreAudioApi.DeviceState.Active)) + { + if (device.AudioEndpointVolume?.HardwareSupport.HasFlag(NAudio.CoreAudioApi.EEndpointHardwareSupport.Mute) == true) + { + device.AudioEndpointVolume.Mute = false; + } + } + } + } + + public static void VolDown() + { + //SendMessageW(hand, WM_APPCOMMAND, hand, (IntPtr)APPCOMMAND_VOLUME_DOWN); + + using (var enumerator = new NAudio.CoreAudioApi.MMDeviceEnumerator()) + { + foreach (var device in enumerator.EnumerateAudioEndPoints(NAudio.CoreAudioApi.DataFlow.Render, NAudio.CoreAudioApi.DeviceState.Active)) + { + if (device.AudioEndpointVolume?.HardwareSupport.HasFlag(NAudio.CoreAudioApi.EEndpointHardwareSupport.Volume) == true) + { + device.AudioEndpointVolume.VolumeStepDown(); + } + } + } + + } + + public static void VolUp() + { + //SendMessageW(hand, WM_APPCOMMAND, hand, (IntPtr)APPCOMMAND_VOLUME_UP); + using (var enumerator = new NAudio.CoreAudioApi.MMDeviceEnumerator()) + { + foreach (var device in enumerator.EnumerateAudioEndPoints(NAudio.CoreAudioApi.DataFlow.Render, NAudio.CoreAudioApi.DeviceState.Active)) + { + if (device.AudioEndpointVolume?.HardwareSupport.HasFlag(NAudio.CoreAudioApi.EEndpointHardwareSupport.Volume) == true) + { + device.AudioEndpointVolume.VolumeStepUp(); + } + } + } + + } + + public static void VolSet(float Level) + { + //SendMessageW(hand, WM_APPCOMMAND, hand, (IntPtr)APPCOMMAND_VOLUME_UP); + using (var enumerator = new NAudio.CoreAudioApi.MMDeviceEnumerator()) + { + foreach (var device in enumerator.EnumerateAudioEndPoints(NAudio.CoreAudioApi.DataFlow.Render, NAudio.CoreAudioApi.DeviceState.Active)) + { + if (device.AudioEndpointVolume?.HardwareSupport.HasFlag(NAudio.CoreAudioApi.EEndpointHardwareSupport.Volume) == true) + { + //device.AudioEndpointVolume.VolumeRange.MaxDecibels + //device.AudioEndpointVolume.VolumeRange.MinDecibels + //The new master volume level. The level is expressed as a normalized value in the range from 0.0 to 1.0. + device.AudioEndpointVolume.MasterVolumeLevelScalar = Level / 100f; + } + } + } + + } + public static void UpdateProgressBar(string label, int CurVal = -1, int MinVal = -1, int MaxVal = -1, [CallerMemberName] string memberName = null) + { + if (progress == null) + { + //TODO: trying to catch a logging failure, should take out messagebox later + if (!warnedlog && !Global.IsService) + { + warnedlog = true; + string member = "unknown"; + if (memberName != null) + member = memberName; + + MessageBox.Show($"UpdateProgressBar Debug warning: Progress event logger is null? calling function='{member}'"); + } + return; + } + + ClsMessage msg = new ClsMessage(MessageType.UpdateProgressBar, label, null, memberName, CurVal, MinVal, MaxVal); + + progress.Report(msg); + + } + + public static void CreateHistoryItem(History hist, [CallerMemberName] string memberName = null) + { + if (progress == null) + { + //TODO: trying to catch a logging failure, should take out messagebox later + if (!warnedlog && !Global.IsService) + { + warnedlog = true; + string member = "unknown"; + if (memberName != null) + member = memberName; + + MessageBox.Show($"CreateHistoryItem Debug warning: Progress event logger is null? calling function='{member}'"); + } + return; + } + + ClsMessage msg = new ClsMessage(MessageType.CreateHistoryItem, "", hist, memberName); + + progress.Report(msg); + + } + + public static void UpdateLabel(string Message, string LabelControlName, [CallerMemberName] string memberName = null) + { + if (progress == null) + { + //TODO: trying to catch a logging failure, should take out messagebox later + if (!warnedlog && !Global.IsService) + { + warnedlog = true; + string member = "unknown"; + if (memberName != null) + member = memberName; + + MessageBox.Show($"UpdateLabel Debug warning: Progress event logger is null? calling function='{member}'"); + } + return; + } + + ClsMessage msg = new ClsMessage(MessageType.UpdateLabel, Message, LabelControlName, memberName); + + progress.Report(msg); + + } + private static bool warnedlog = false; + public static void LogMessage(string Message, [CallerMemberName] string memberName = null) + { + if (progress == null) + { + //TODO: trying to catch a logging failure, should take out messagebox later + if (!warnedlog && !Global.IsService) + { + warnedlog = true; + string member = "unknown"; + if (memberName != null) + member = memberName; + + MessageBox.Show($"Log Debug warning: Progress event logger is null? calling function='{member}'"); + } + return; + } + + ClsMessage msg = new ClsMessage(MessageType.LogEntry, "", null, memberName); + + //this is for logging in non-gui classes. Reports back to real logger + //progress needs to be subscribed to in main gui + string mn = ""; + if (memberName != null && !string.IsNullOrEmpty(memberName)) + { + mn = $"{memberName}>> "; + } + msg.Description = $"{mn}{Message}"; + + SaveRegSetting("LastLogEntry", msg.Description); + Global.SaveRegSetting("LastShutdownState", $"checkpoint: Global.Log: {DateTime.Now}"); + + + progress.Report(msg); + + } + public static Regex RegEx_ValidDate = new Regex("(19|20)[0-9]{2}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])_[0-9][0-9]_[0-9][0-9]_[0-9][0-9]", RegexOptions.IgnoreCase | RegexOptions.Compiled); + + //[DllImport("ntdll.dll")] + //public static extern int RtlNtStatusToDosError(int status); + + /// + /// Flags used by method. + /// + [Flags] + public enum FormatMessageFlags:uint + { + /// + /// The function allocates a buffer large enough to hold the formatted message, and places a pointer to the + /// allocated buffer at the address specified by lpBuffer. The lpBuffer parameter is a pointer + /// to an LPTSTR. The nSize parameter specifies the minimum number of TCHARs to allocate + /// for an output message buffer. The caller should use the LocalFree function to free the buffer when + /// it is no longer needed. + /// If the length of the formatted message exceeds 128K bytes, then FormatMessage will fail and a + /// subsequent call to GetLastError will return ERROR_MORE_DATA. + /// This value is not available for use when compiling Windows Store apps. + /// + FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100, + + /// + /// Insert sequences in the message definition are to be ignored and passed through to the output buffer + /// unchanged. This flag is useful for fetching a message for later formatting. If this flag is set, the + /// Arguments parameter is ignored. + /// + FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200, + + /// + /// The lpSource parameter is a pointer to a null-terminated string that contains a message definition. + /// The message definition may contain insert sequences, just as the message text in a message table resource + /// may. This flag cannot be used with or + /// . + /// + FORMAT_MESSAGE_FROM_STRING = 0x00000400, + + /// + /// The lpSource parameter is a module handle containing the message-table resource(s) to search. If + /// this lpSource handle is null, the current process's application image file will be searched. + /// This flag cannot be used with . + /// If the module has no message table resource, the function fails with ERROR_RESOURCE_TYPE_NOT_FOUND. + /// + FORMAT_MESSAGE_FROM_HMODULE = 0x00000800, + + /// + /// The function should search the system message-table resource(s) for the requested message. If this flag is + /// specified with , the function searches the system message table + /// if the message is not found in the module specified by lpSource. This flag cannot be used with + /// . + /// If this flag is specified, an application can pass the result of the GetLastError function to + /// retrieve the message text for a system-defined error. + /// + FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000, + + /// + /// The Arguments parameter is not a va_list structure, but is a pointer to an array of values that + /// represent the arguments. This flag cannot be used with 64-bit integer values. If you are using a 64-bit + /// integer, you must use the va_list structure. + /// + FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x00002000 + } + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern uint FormatMessage( + FormatMessageFlags dwFlags, + IntPtr lpSource, + uint dwMessageId, + uint dwLanguageId, + StringBuilder lpBuffer, + uint nSize, + IntPtr arguments); + //[DllImport("Kernel32.dll", SetLastError = true)] + //static extern uint FormatMessage(uint dwFlags, IntPtr lpSource, uint dwMessageId, uint dwLanguageId, ref IntPtr lpBuffer, uint nSize, IntPtr pArguments); + //[DllImport("kernel32.dll", CharSet = CharSet.Auto)] + //static extern uint FormatMessage(uint dwFlags, IntPtr lpSource, uint dwMessageId, uint dwLanguageId, StringBuilder lpBuffer, uint nSize, IntPtr Arguments); + public static string FormatMessageFromHRESULT(int errorcode) + { + const int nCapacity = 1024; // max error length + //const uint FORMAT_MSG_FROM_SYS = 0x01000; + + //const uint FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100; + //const uint FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200; + //const uint FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000; + //const uint FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x00002000; + //const uint FORMAT_MESSAGE_FROM_HMODULE = 0x00000800; + //const uint FORMAT_MESSAGE_FROM_STRING = 0x00000400; + //const int HresultWin32Prefix = unchecked((int)0x80070000); + StringBuilder defSb = new StringBuilder(nCapacity); + + const FormatMessageFlags Flags = FormatMessageFlags.FORMAT_MESSAGE_FROM_SYSTEM + | FormatMessageFlags.FORMAT_MESSAGE_IGNORE_INSERTS + | FormatMessageFlags.FORMAT_MESSAGE_ARGUMENT_ARRAY; + + var buffer = new StringBuilder(nCapacity); + uint result = FormatMessage(Flags, IntPtr.Zero, (uint)errorcode, 0, buffer, nCapacity, IntPtr.Zero); + string ret = ""; + if (result != 0) + { + ret = buffer.ToString().Trim(); + } + return ret; + //uint dwChars = FormatMessage( + // FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_HMODULE, + // IntPtr.Zero, + // (uint)hresult, + // 0, // Default language + // ref lpMsgBuf, + // 0, + // IntPtr.Zero); + //must specify the FORMAT_MESSAGE_ARGUMENT_ARRAY flag when pass an array + //uint length = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, IntPtr.Zero, (uint)code, 0, defSb, nCapacity, IntPtr.Zero); + + + + //string sRet = "(unknown)"; + //if (dwChars > 0) + //{ + // sRet = Marshal.PtrToStringAnsi(lpMsgBuf).TrimEnd(' ', '.', '\r', '\n'); + //} + + //string sDefMsg = defSb.ToString().TrimEnd(' ', '.', '\r', '\n'); + ////nothing left to do: + //return sDefMsg; + } + + //[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + //private extern static SafeFileHandle CreateFile( + // string lpFileName, + // FileSystemRights dwDesiredAccess, + // FileShare dwShareMode, + // IntPtr securityAttrs, + // FileMode dwCreationDisposition, + // FileOptions dwFlagsAndAttributes, + // IntPtr hTemplateFile); + + + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] + private extern static SafeFileHandle CreateFile( + string lpFileName, + [MarshalAs(UnmanagedType.U4)] FileAccess dwDesiredAccess, + [MarshalAs(UnmanagedType.U4)] FileShare dwShareMode, + IntPtr lpSecurityAttributes, + [MarshalAs(UnmanagedType.U4)] FileMode dwCreationDisposition, + [MarshalAs(UnmanagedType.U4)] FileAttributes dwFlagsAndAttributes, + IntPtr hTemplateFile); + + private const int ERROR_ACCESS_DENIED = 5; + private const int ERROR_SHARING_VIOLATION = 32; + private const int ERROR_LOCK_VIOLATION = 33; + public static async Task WaitForFileAccessAsync(string filename, FileAccess rights = FileAccess.Read, FileShare share = FileShare.Read, long WaitMS = 30000, int RetryDelayMS = 0) + { + //run the function in another thread + return await Task.Run(() => WaitForFileAccess(filename, rights, share, WaitMS, RetryDelayMS)); + } + + public class WaitFileAccessResult + { + public bool Success = false; + public long TimeMS = 0; + public int ErrRetryCnt = 0; + public string ResultString = ""; + public SafeFileHandle Handle = default(SafeFileHandle); + + } + + public static WaitFileAccessResult WaitForFileAccess(string filename, + FileAccess rights = FileAccess.Read, + FileShare share = FileShare.None, + long MaxWaitMS = 30000, + int RetryDelayMS = 0, + bool ReturnHandle = false, + long MinFileSize = 1, + int MaxErrRetryCnt = 2000) + { + + //using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + WaitFileAccessResult ret = new WaitFileAccessResult(); + string LastFailReason = ""; + Stopwatch SW = Stopwatch.StartNew(); + + if (RetryDelayMS == 0) + RetryDelayMS = AppSettings.Settings.loop_delay_ms; + + try + { + //lets give it an initial tiny wait + //await Task.Delay(RetryDelayMS); + + FileInfo FI = new FileInfo(filename); + + bool NeedsToWrite = rights == FileAccess.ReadWrite || rights == FileAccess.Write; + + if (FI.Exists) + { + + if (NeedsToWrite && FI.IsReadOnly) + { + ret.Success = false; + ret.ResultString = "(ReadOnly)"; + return ret; + } + + long LastLength = FI.Length; //if the file is growing, try to wait for it + + while ((ret.ErrRetryCnt <= MaxErrRetryCnt) && (SW.ElapsedMilliseconds <= MaxWaitMS)) + { + if (FI.Length >= MinFileSize && LastLength == FI.Length) + { + + //SafeFileHandle fileHandle = CreateFile(fileName,FileSystemRights.Modify, FileShare.Write,IntPtr.Zero, FileMode.OpenOrCreate,FileOptions.None, IntPtr.Zero); + ret.Handle = CreateFile(filename, rights, share, IntPtr.Zero, FileMode.Open, FileAttributes.Normal, IntPtr.Zero); + + if (ret.Handle.IsInvalid) + { + int LastErr = Marshal.GetLastWin32Error(); + + if (LastErr == ERROR_SHARING_VIOLATION) + LastFailReason = "(SharingViolation)"; + else if (LastErr == ERROR_LOCK_VIOLATION) + LastFailReason = "(LockViolation)"; + + + + if (LastErr != ERROR_SHARING_VIOLATION && LastErr != ERROR_LOCK_VIOLATION) + { + LastFailReason = $"(Unexpected-{LastErr})"; + //unexpected error, break out + Log($"Error: Unexpected Win32Error waiting for access to {filename}: {LastErr}: {new Win32Exception(LastErr)}"); + break; + } + + } + else + { + //passed the first test + if (NeedsToWrite) + { + //make sure we can read a byte and write the same byte back to verify access + if (IsFileWritable(filename, ref ret.Handle, ref FI, out LastFailReason)) + { + ret.Success = true; + break; + } + } + else + { + ret.Success = true; + break; + } + } + + if (!ret.Handle.IsClosed) + { + ret.Handle.Close(); + ret.Handle.Dispose(); + } + + } + + LastLength = FI.Length; + + ret.ErrRetryCnt += 1; + + Thread.Sleep(RetryDelayMS); + + FI.Refresh(); + } + SW.Stop(); + + ret.TimeMS = SW.ElapsedMilliseconds; + + if (!ret.Success) + { + ret.ResultString = $"Debug: LastFail={LastFailReason}, lock time: {ret.TimeMS}ms (max={MaxWaitMS}), {ret.ErrRetryCnt} retries (max={MaxErrRetryCnt}) with a {RetryDelayMS}ms retry delay: {Path.GetFileName(filename)}"; + if (ret.ErrRetryCnt > 2 || ret.TimeMS > 1000) + { + Log(ret.ResultString); + } + } + + } + else + { + ret.ResultString = $"Error: File not found: " + filename; + Log(ret.ResultString); + } + + } + catch (Exception ex) + { + ret.TimeMS = SW.ElapsedMilliseconds; + ret.ResultString = $"Error: {filename}: {ex.Msg()}"; + Log(ret.ResultString); + } + finally + { + if (!ReturnHandle && ret.Handle != null && !ret.Handle.IsClosed) + { + ret.Handle.Close(); + ret.Handle.Dispose(); + } + } + + + return ret; + + } + + public static bool IsFileWritable(string filename, ref SafeFileHandle handle, ref FileInfo FI, out string FailReason) + { + bool Ret = false; + string err = ""; + FailReason = ""; + + byte[] TestReadByte; + try + { + if (FI == null) + FI = new FileInfo(filename); + + if (FI.IsReadOnly) + { + FailReason = "(ReadOnly)"; + return false; + } + + bool Updated = false; + + using (FileStream fs = new FileStream(handle, FileAccess.ReadWrite)) + { + + using (BinaryReader br = new BinaryReader(fs)) + { + + if (br.BaseStream.CanRead) + { + //read 1 byte + TestReadByte = br.ReadBytes(1); + fs.Position = 0; + using (BinaryWriter bw = new BinaryWriter(fs)) + { + + if (bw.BaseStream.CanWrite) + { + //write same byte back - get exception if file is in use + bw.Write(TestReadByte); + bw.Flush(); + Updated = true; + } + else + { + FailReason = "(CantWrite)"; + } + } + } + else + { + FailReason = "(CantRead)"; + } + } + } + + if (Updated) + { + //reset dates on the file back to what they were before the test write + FileInfo DFI = new FileInfo(filename); + if (DFI.CreationTime != FI.CreationTime) + DFI.CreationTime = FI.CreationTime; //reset date the same as the old file + if (DFI.LastWriteTime != FI.LastWriteTime) + DFI.LastWriteTime = FI.LastWriteTime; + } + + if (String.IsNullOrWhiteSpace(FailReason)) + Ret = true; + + } + catch (Exception ex) + { + err = "(" + ex.Message + ")"; + } + FailReason = err; + return Ret; + } + + public static bool IsInList(List FindStrList, string SearchList, string Separators = ",;|", bool TrueIfEmpty = true) + { + if (TrueIfEmpty && string.IsNullOrWhiteSpace(SearchList)) + return true; //If there is no searchlist, always return true + + return IsInList(FindStrList, SearchList.SplitStr(Separators, true, true, true)); + } + [DebuggerStepThrough] + public static bool IsInList(string FindStr, List SearchList, string Separators = ",;|", bool TrueIfEmpty = true) + { + if (TrueIfEmpty && SearchList.Count == 0) + return true; //If there is no searchlist, always return true + + return IsInList(FindStr.SplitStr(Separators, true, true, true), SearchList); + } + [DebuggerStepThrough] + public static bool IsInList(string FindStr, string SearchList, string Separators = ",;|", bool TrueIfEmpty = true) + { + if (TrueIfEmpty && string.IsNullOrWhiteSpace(SearchList)) + return true; //If there is no searchlist, always return true + + + return IsInList(FindStr.SplitStr(Separators, true, true, true), SearchList.SplitStr(Separators, true, true, true)); + } + [DebuggerStepThrough] + public static bool IsInList(List FindStrsList, List SearchList) + { + foreach (string findstr in FindStrsList) + { + foreach (string searchstr in SearchList) + { + if (findstr.Equals(searchstr, StringComparison.OrdinalIgnoreCase) || searchstr == "*") + return true; + } + } + return false; + } + + [DebuggerStepThrough] + public static string ConvertToBase64(this Stream stream) + { + byte[] bytes; + using (var memoryStream = new MemoryStream()) + { + stream.CopyTo(memoryStream); + bytes = memoryStream.ToArray(); + } + + string base64 = Convert.ToBase64String(bytes); + return base64; + } + + + + public static IEnumerable GetAllExceptions(this Exception exception) + { + yield return exception; + + if (exception is AggregateException aggrEx) + { + foreach (Exception innerEx in aggrEx.InnerExceptions.SelectMany(e => e.GetAllExceptions())) + { + yield return innerEx; + } + } + else if (exception.InnerException != null) + { + foreach (Exception innerEx in exception.InnerException.GetAllExceptions()) + { + yield return innerEx; + } + } + } + + public static void Startup(bool Enable) + { + try + { + + using (RegistryKey RK = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", true)) + { + + string AppName = Path.GetFileNameWithoutExtension(Application.ExecutablePath); + string AppCmd = Application.ExecutablePath + " /min"; + bool Enabled = false; + object CurVal = RK.GetValue(AppName, null); + + if (CurVal == null || string.IsNullOrWhiteSpace(CurVal.ToString())) + { + Log("Application is NOT set to start with Windows: HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"); + + Enabled = false; + } + else + { + if (string.Equals(CurVal.ToString(), AppCmd, StringComparison.OrdinalIgnoreCase)) + { + Log("Application is already set to start with Windows: HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"); + Enabled = true; + } + else + { + Log($"Application is NOT set to start with Windows (bad path={CurVal}): HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"); + } + + } + + if (Enable && !Enabled) + { + Log("Enabling Application startup: " + AppCmd); + if (!Debugger.IsAttached) + { + RK.SetValue(AppName, AppCmd); + } + } + else if (!Enable && Enabled) + { + Log("Disabling Application startup."); + if (!Debugger.IsAttached) + { + RK.DeleteValue(AppName); + } + } + + } + + } + catch (Exception ex) + { + Log(ex.Message); + } + finally + { + } + } + + + + + public static void SerializeJsonIntoStream(object value, Stream stream) + { + using (var sw = new StreamWriter(stream, new UTF8Encoding(false), 32768, true)) + using (var jtw = new JsonTextWriter(sw) { Formatting = Formatting.None }) + { + var js = new JsonSerializer(); + js.Serialize(jtw, value); + jtw.Flush(); + } + } + + public static HttpContent CreateHttpContentString(object content) + { + HttpContent httpContent = null; + + if (content != null) + { + var json = Global.GetJSONString(content); //JsonConvert.SerializeObject(content); + httpContent = new StringContent(json, Encoding.UTF8, "application/json"); + } + + return httpContent; + } + + public static HttpContent CreateHttpContentStream(object content) + { + HttpContent httpContent = null; + + if (content != null) + { + var ms = new MemoryStream(); + SerializeJsonIntoStream(content, ms); + ms.Seek(0, SeekOrigin.Begin); + httpContent = new StreamContent(ms); + httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + + } + + return httpContent; + } + + + + public static bool IsDirWritable(string FolderName, bool CreateIfNotExist) + { + bool Ret = false; + try + { + if (FolderName.IsEmpty()) + return Ret; + + // if actually passed a filename, get folder + string pth = FolderName; + //if (PathEndsWithFilename(FolderName)) + //{ + // pth = Path.GetDirectoryName(pth); + //} + + if (!Directory.Exists(pth)) + { + if (CreateIfNotExist) + { + Directory.CreateDirectory(pth); + Ret = true; + return Ret; + } + else + { + return Ret; + } + } + + string Filename = Path.Combine(pth, Path.GetRandomFileName()); + using (var fs = File.Create(Filename, 1, FileOptions.DeleteOnClose)) + { + } + // make sure temp file really did get deleted... + if (File.Exists(Filename)) + { + Log("Trace: Failed to remove temp file? " + Filename); + File.Delete(Filename); + } + + Ret = true; + } + catch + { + } + + return Ret; + } + + /// + /// Writes the given object instance to a Json file. + /// Object type must have a parameterless constructor. + /// Only Public properties and variables will be written to the file. These can be any type though, even other classes. + /// If there are public properties/variables that you do not want written to the file, decorate them with the [JsonIgnore] attribute. + /// + /// The type of object being written to the file. + /// The file path to write the object instance to. + /// The object instance to write to the file. + /// If false the file will be overwritten if it already exists. If true the contents will be appended to the file. + public static string WriteToJsonFile(string filePath, T objectToWrite, bool append = false) where T : new() + { + string Ret = ""; + TextWriter writer = null; + try + { + + Ret = JsonConvert.SerializeObject(objectToWrite, JSONSettingsPretty); + if (JSONSettingsPretty.Error == null) + { + if (Directory.Exists(Path.GetDirectoryName(filePath))) + { + writer = new StreamWriter(filePath, append); + writer.Write(Ret); + } + } + else + { + Ret = ""; + Log($"Error: While writing '{filePath}', got: " + JSONSettingsPretty.Error.ToString()); + } + } + catch (Exception ex) + { + Ret = ""; + Log($"Error: While writing '{filePath}', got: " + ex.Msg()); + } + finally + { + if (writer != null) + writer.Close(); + } + return Ret; + + } + + /// + /// Reads an object instance from an Json file. + /// Object type must have a parameterless constructor. + /// + /// The type of object to read from the file. + /// The file path to read the object instance from. + /// Returns a new instance of the object read from the Json file. + public static T ReadFromJsonFile(string filePath) where T : new() + { + + + T Ret = default(T); + + TextReader reader = null; + try + { + reader = new StreamReader(filePath); + var fileContents = reader.ReadToEnd(); + + JsonSerializerSettings jset = new JsonSerializerSettings { }; + jset.TypeNameHandling = TypeNameHandling.All; + jset.PreserveReferencesHandling = PreserveReferencesHandling.Objects; + jset.ContractResolver = Global.JSONContractResolver; + + Ret = JsonConvert.DeserializeObject(fileContents, jset); + } + catch (Exception ex) + { + Log($"Error: While reading '{filePath}', got: " + ex.Msg()); + } + finally + { + if (reader != null) + reader.Close(); + } + + return Ret; + + } + + public static string GetJSONString(object cls2, Newtonsoft.Json.Formatting formatting = Formatting.Indented, + Newtonsoft.Json.TypeNameHandling handling = TypeNameHandling.All, + Newtonsoft.Json.PreserveReferencesHandling reference = PreserveReferencesHandling.Objects) + { + + string Ret = ""; + try + { + + JSONSettingsPretty.Formatting = formatting; + JSONSettingsPretty.TypeNameHandling = handling; + JSONSettingsPretty.PreserveReferencesHandling = reference; + JSONSettingsPretty.Error = null; + + string contents2 = JsonConvert.SerializeObject(cls2, formatting, JSONSettingsPretty); + + if (JSONSettingsPretty.Error == null) + { + Ret = contents2; + } + else + { + Log($"Error: " + JSONSettingsPretty.Error.ToString()); + } + + } + catch (Exception ex) + { + Log($"Error: " + ex.Msg()); + } + finally + { + } + + return Ret; + + } + + public static T SetJSONString(string JSONString) where T : new() + { + + + T Ret = default(T); + + try + { + //JsonSerializerSettings jset = new JsonSerializerSettings { }; + //jset.TypeNameHandling = TypeNameHandling.All; + //jset.PreserveReferencesHandling = PreserveReferencesHandling.Objects; + //jset.ContractResolver = Global.JSONContractResolver; + + Ret = JsonConvert.DeserializeObject(JSONString, JSONSettingsPretty); + } + catch (Exception ex) + { + Log($"Error: While converting json string '{JSONString}', got: " + ex.Msg()); + } + finally + { + } + + return Ret; + + } + public static DateTime RetrieveLinkerTimestamp() + { + DateTime dt = new DateTime(1970, 1, 1, 0, 0, 0); + + try + { + string filePath = System.Reflection.Assembly.GetCallingAssembly().Location; + const int c_PeHeaderOffset = 60; + const int c_LinkerTimestampOffset = 8; + byte[] b = new byte[2048]; + using (System.IO.Stream s = new System.IO.FileStream(filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read)) + { + + try + { + //s = New System.IO.FileStream(filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read) + s.Read(b, 0, 2048); + } + finally + { + if (s != null) + { + s.Close(); + } + } + + int i = System.BitConverter.ToInt32(b, c_PeHeaderOffset); + int secondsSince1970 = System.BitConverter.ToInt32(b, i + c_LinkerTimestampOffset); + dt = dt.AddSeconds(secondsSince1970); + dt = dt.AddHours(System.TimeZone.CurrentTimeZone.GetUtcOffset(dt).Hours); + } + + } + catch (System.Exception ex) + { + Log("Error: " + ex.Msg()); + } + + return dt; + + } + + + public static DateTime GetTimeFromFileName(string FileName) + { + DateTime OutDate = DateTime.MinValue; + + try + { + if (string.IsNullOrWhiteSpace(FileName)) + { + return OutDate; + } + string Fil = Path.GetFileNameWithoutExtension(FileName); + string StrDate = ""; + MatchCollection Matches = RegEx_ValidDate.Matches(Fil); + if (Matches != null && Matches.Count > 0) + { + StrDate = Matches[0].Value; + StrDate = StrDate.Replace("_", ":"); + //pos 10=T + StrDate = StrDate.Remove(10, 1).Insert(10, "T"); + if (!GetDateStrict(StrDate, ref OutDate)) + { + Log("Error: There was a problem parsing '" + FileName + "' for a date."); + OutDate = DateTime.MinValue; + } + } + + } + catch (Exception ex) + { + Log("Error: " + ex.Msg()); + } + + return OutDate; + + } + + public class ClsDateFormat + { + public string Fmt = ""; + public long Cnt = 0; + public override string ToString() + { + return $"Cnt='{this.Cnt}', Fmt='{this.Fmt}'"; + } + } + + public static long DateFormatHitCnt = 0; + + public static List DateFormatList = new List(); + + public static void CreateFormatList() + { + //28-Feb-2015 17:21:56.155 + //11/8/2012 09:28:33:941 + //15-May-2018 18:19:20.173 + //15-May-2018 18:05:28.457 + //dd-MMMM-yyyy HH:mm:ss.fff + //7/15/2015 13:10:46:788 + //8/7/2017 13:00:15:330 + //6/14/2016 15:03:01:360 + //2018-04-10T14:32:26 + //yyyy-MM-ddTHH:mm:ss + + //most popular first + DateFormatList.Add(new ClsDateFormat { Fmt = "dd-MMM-yyyy HH:mm:ss.fff" }); + DateFormatList.Add(new ClsDateFormat { Fmt = "yyyy-MM-dd HH:mm:ss.fff" }); + DateFormatList.Add(new ClsDateFormat { Fmt = "M/d/yyyy HH:mm:ss:fff" }); + DateFormatList.Add(new ClsDateFormat { Fmt = "dd-MMMM-yyyy HH:mm:ss.fff" }); + DateFormatList.Add(new ClsDateFormat { Fmt = "yyyy-MM-ddTHH:mm:ss" }); + + DateFormatList.Add(new ClsDateFormat { Fmt = "M/d/yyyy H:mm:ss:fff" }); + DateFormatList.Add(new ClsDateFormat { Fmt = "M/dd/yyyy hh:mm:ss:fff" }); + DateFormatList.Add(new ClsDateFormat { Fmt = "M/dd/yyyy HH:mm:ss:fff" }); + DateFormatList.Add(new ClsDateFormat { Fmt = "M/dd/yyyy H:mm:ss:fff" }); + DateFormatList.Add(new ClsDateFormat { Fmt = "M/dd/yyyy hh:mm:ss:fff" }); + DateFormatList.Add(new ClsDateFormat { Fmt = "M/dd/yyyy HH:mm:ss:fff" }); + DateFormatList.Add(new ClsDateFormat { Fmt = "M/dd/yyyy H:mm:ss:fff" }); + DateFormatList.Add(new ClsDateFormat { Fmt = "MM/dd/yyyy hh:mm:ss:fff" }); + DateFormatList.Add(new ClsDateFormat { Fmt = "MM/dd/yyyy HH:mm:ss:fff" }); + DateFormatList.Add(new ClsDateFormat { Fmt = "MM/dd/yyyy H:mm:ss:fff" }); + DateFormatList.Add(new ClsDateFormat { Fmt = "d-MMMM-yyyy HH:mm:ss.fff" }); + DateFormatList.Add(new ClsDateFormat { Fmt = "d-MMM-yyyy HH:mm:ss.fff" }); + + } + public static bool GetDateStrict(string InpDate, ref DateTime OutDate, string format = "") + { + bool Ret = false; + try + { + if (!string.IsNullOrEmpty(format)) + { + Ret = DateTime.TryParseExact(InpDate, format, null, System.Globalization.DateTimeStyles.None, out OutDate); //New CultureInfo("en-US") + if (Ret) + return Ret; + } + + if (DateFormatList.Count == 0) + { + CreateFormatList(); + } + for (int i = 0; i < DateFormatList.Count; i++) + { + ClsDateFormat df = DateFormatList[i]; + Ret = DateTime.TryParseExact(InpDate, df.Fmt, null, System.Globalization.DateTimeStyles.None, out OutDate); //New CultureInfo("en-US") + if (Ret) + { + //First double check that date is in normal-ish range.. + if (OutDate > new DateTime(2000, 1, 1) && OutDate <= DateTime.Now.AddHours(12)) + { + df.Cnt = df.Cnt + 1; + DateFormatHitCnt = DateFormatHitCnt + 1; + //If DateFormatHitCnt < 15 OrElse (CurCnt > 1 AndAlso (DateFormatHitCnt Mod 25 = 0)) Then + // 'Sort the list by most frequent found cnt + // DateFormatList = DateFormatList.OrderByDescending(Function(d) d.Cnt).ToList + //End If + break; + } + else + { + Ret = false; + } + } + + } + + if (!Ret) + { + //last ditch + Ret = DateTime.TryParse(InpDate, out OutDate); + if (Ret) + { + if (OutDate > new DateTime(2010, 1, 1) && OutDate < new DateTime(2050, 1, 1)) + { + } + else + { + Ret = false; + OutDate = DateTime.MinValue; + } + } + } + + + } + catch (Exception) + { + Ret = false; + } + return Ret; + } + public static bool IsAdministrator() + { + var identity = WindowsIdentity.GetCurrent(); + var principal = new WindowsPrincipal(identity); + return principal.IsInRole(WindowsBuiltInRole.Administrator); + } + + public static Process GetaProcess(string processname) + { + try + { + if (Path.HasExtension(processname)) + processname = Path.GetFileNameWithoutExtension(processname); + Process[] aProc = Process.GetProcessesByName(processname); + if (aProc.Length > 0) + return aProc[0]; + else + return null; + } + catch (Exception) + { + return null; + } + } + + public static Process[] GetProcesses(string processname) + { + try + { + if (Path.HasExtension(processname)) + processname = Path.GetFileNameWithoutExtension(processname); + Process[] aProc = Process.GetProcessesByName(processname); + if (aProc.Length > 0) + return aProc; + else + return null; + } + catch (Exception) + { + return null; + } + } + + public enum ShowWindowEnum:int + { + /// + /// Hides the window and activates another window. + /// + SW_HIDE = 0, + // + /// Activates and displays a window. If the window is minimized or maximized, the system restores it to its original size and position. An application should specify this flag when displaying the window for the first time. + /// + SW_SHOWNORMAL = 1, + + /// + /// Activates the window and displays it as a minimized window. + /// + SW_SHOWMINIMIZED = 2, + + /// + /// Activates the window and displays it as a maximized window. + /// + SW_SHOWMAXIMIZED = 3, + + /// + /// Maximizes the specified window. + /// + SW_MAXIMIZE = 3, + + /// + /// Displays a window in its most recent size and position. This value is similar to , except the window is not activated. + /// + SW_SHOWNOACTIVATE = 4, + + /// + /// Activates the window and displays it in its current size and position. + /// + SW_SHOW = 5, + + /// + /// Minimizes the specified window and activates the next top-level window in the z-order. + /// + SW_MINIMIZE = 6, + + /// + /// Displays the window as a minimized window. This value is similar to , except the window is not activated. + /// + SW_SHOWMINNOACTIVE = 7, + + /// + /// Displays the window in its current size and position. This value is similar to , except the window is not activated. + /// + SW_SHOWNA = 8, + + /// + /// Activates and displays the window. If the window is minimized or maximized, the system restores it to its original size and position. An application should specify this flag when restoring a minimized window. + /// + SW_RESTORE = 9, + + /// + /// Items 10, 11 and 11 existed in the VB definition but not the c# definition - so I am assuming this was a mistake and have added them here. + /// Please forgive me if this is wrong! I don't think it should have any negative impact. + /// According to what I have read elsewhere: The SW_SHOWDEFAULT makes sure the window is restored prior to showing, then activating. + /// And the 11's try to coerce a window to minimized or maximized. + /// + SW_SHOWDEFAULT = 10, + SW_FORCEMINIMIZE = 11, + SW_MAX = 11 + } + [DllImport("user32.dll")] + private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + static extern bool SetForegroundWindow(IntPtr hWnd); + [System.Runtime.InteropServices.DllImport("User32.dll")] + private static extern bool IsIconic(IntPtr handle); + + [DllImport("User32.dll", SetLastError = true)] + static extern void SwitchToThisWindow(IntPtr hWnd, bool fAltTab); + [DllImport("user32.dll", EntryPoint = "FindWindow")] + private extern static IntPtr FindWindow(string lpClassName, string lpWindowName); + public static bool ShowProcessWindow(string processname, string WindowTitle, ShowWindowEnum WindowStyle) + { + bool ret = false; + + Process[] Procs = Global.GetProcesses(processname); + if (Procs.IsNotEmpty()) + { + foreach (var proc in Procs) + { + try + { + if (!proc.HasExited) + { + bool RefreshFound = false; + + if (proc.MainWindowHandle.IsNull()) + proc.Refresh(); + + IntPtr hwnd = proc.MainWindowHandle; + + if (hwnd.IsNull()) + { + //get the window a different way + hwnd = FindWindow(null, WindowTitle); + } + else + { + RefreshFound = true; + } + + if (hwnd.IsNotNull()) + { + bool IsIconicResult = IsIconic(hwnd); + + if (IsIconicResult) //check if the window is minimized. If it is not, then you don't want to restore it, you only need to activate it. + SwitchToThisWindow(hwnd, true); //SendMessageW(p.MainWindowHandle, WM_SYSCOMMAND, SC_RESTORE, 0) 'restore the window from it's minimized state + + //true or nonzero if the window was brought to the foreground, + //false or zero If the window was not + bool SetForegroundResult = SetForegroundWindow(hwnd); + //Return value + //If the window was previously visible, the return value is nonzero. + //If the window was previously hidden, the return value is zero. + bool ShowWindowResult = ShowWindow(hwnd, ((int)WindowStyle)); + ret = true; + Log($"Debug: Set '{processname}' ({proc.Id}, {hwnd.ToString()}) to '{WindowStyle.ToString()}' - RefreshFound = '{RefreshFound}', IsIconicResult = '{IsIconicResult}', SetForgroundWindow result = '{SetForegroundResult}', ShowWindowResult='{ShowWindowResult}'"); + + } + else + { + Log($"Debug: Could not get MainWindowHandle for process '{processname}, {WindowTitle}' ({proc.Id}). It may be running as a service without GUI?"); + } + + } + else + { + Log($"Debug: process not valid '{processname}'."); + } + + } + catch (Exception ex) + { + Log($"Trace: Error working with process: {ex.Msg()}"); + } + + } + } + else + { + Log($"Debug: Could not find running process? '{processname}'."); + } + + return ret; + } + public static bool KillProcesses(string ProcessPath) + { + List prc = GetProcessesByPath(ProcessPath); + return KillProcesses(prc); + } + + public static bool KillProcesses(List prc) + { + + int valid = 0; + int ccnt = 0; + if (prc != null && prc.Count > 0) + { + foreach (ClsProcess curprc in prc) + { + ccnt++; + if (ProcessValid(curprc)) + { + try + { + Log($"Debug: Killing {ccnt} of {prc.Count}: {curprc.FileName}"); + curprc.process.Kill(); + valid++; + } + catch (Exception ex) + { + + Log($"Error: Could not kill process {curprc.FileName}"); + } + } + else + { + Log($"Process no longer valid {curprc.FileName}"); + + } + } + if (prc.Count == valid) + return true; + else + return false; + } + return true; + } + + + + public static bool WaitForProcessToStart(Process prc, int TimeoutMS, string fullpath) + { + bool ret = false; + Stopwatch sw = Stopwatch.StartNew(); + try + { + if (prc != null && !prc.HasExited) + { + long LastMem = 0; + int cnt = 0; + //prc.WaitForInputIdle(TimeoutMS); //I think this only works with GUI app + prc.Refresh(); + while (sw.ElapsedMilliseconds <= TimeoutMS && !prc.HasExited && LastMem != prc.PrivateMemorySize64) + { + cnt++; + LastMem = prc.PrivateMemorySize64; + System.Threading.Thread.Sleep(AppSettings.Settings.loop_delay_ms); + prc.Refresh(); + } + sw.Stop(); + ret = prc != null && !prc.HasExited; + if (ret) + Log($"Debug: Waited {sw.ElapsedMilliseconds}ms (cnt={cnt}) for {fullpath} to initialize."); + else + Log($"Error: Process failed to start in {sw.ElapsedMilliseconds}ms (cnt={cnt}) for {fullpath} to initialize."); + + } + } + catch (Exception ex) + { + Log($"Error: {ex.Msg()}"); + } + return ret; + + } + public static bool WaitForProcessToClose(Process prc, int TimeoutMS, string fullpath) + { + bool ret = false; + Stopwatch sw = Stopwatch.StartNew(); + try + { + if (prc != null && !prc.HasExited) + { + int cnt = 0; + //prc.WaitForInputIdle(TimeoutMS); //I think this only works with GUI app + prc.Refresh(); + while (sw.ElapsedMilliseconds <= TimeoutMS && !prc.HasExited) + { + cnt++; + System.Threading.Thread.Sleep(AppSettings.Settings.loop_delay_ms); + prc.Refresh(); + } + sw.Stop(); + ret = prc == null || prc.HasExited; + int exitcode = 0; + if (prc.IsNotNull()) + exitcode = prc.ExitCode; + + if (ret) + Log($"Debug: Process closed after {sw.ElapsedMilliseconds}ms (cnt={cnt}) for {fullpath} to close with exit code '{exitcode}'"); + else + Log($"Debug: Process was still open after {sw.ElapsedMilliseconds}ms (cnt={cnt}) for {fullpath}"); + + } + else + { + ret = true; //exited or didnt exist + } + } + catch (Exception ex) + { + Log($"Error: {ex.Msg()}"); + } + return ret; + + } + + public static bool ProcessValid(List prc) + { + + int valid = 0; + if (prc != null && prc.Count > 0) + { + foreach (ClsProcess curprc in prc) + { + if (ProcessValid(curprc)) + valid++; + else + break; + } + if (prc.Count == valid) + return true; + } + return false; + } + public static bool ProcessValid(ClsProcess prc) + { + + if (prc != null && prc.process != null) + { + try + { + if (!prc.process.HasExited) + { + //if (!string.IsNullOrEmpty(prc.CommandLine) || !string.IsNullOrEmpty(prc.process.StartInfo.Arguments)) + //{ + return true; + //} + } + } + catch { } + } + return false; + } + + public static List GetProcessesByPath(string processname, bool ExcludeCurrentProcess = false) + { + List Ret = new List(); + try + { + string pname = Path.GetFileNameWithoutExtension(processname); + + Process[] aProc = Process.GetProcessesByName(pname); + + Process cprc = null; + if (ExcludeCurrentProcess) + cprc = Process.GetCurrentProcess(); + + ProcessDetail PD = null; + + if (aProc.Length > 0) + + { + foreach (Process curproc in aProc) + { + if (ExcludeCurrentProcess && cprc.Id == curproc.Id) + continue; + + //accessing 64 bit process from 32 bit app may not allow to get process properties, only name + //Stopwatch SW = Stopwatch.StartNew(); + ClsProcess CurPrc = new ClsProcess(); + + try + { + //if (IsAdministrator()) + //{ + // Process.EnterDebugMode(); + //} + PD = new ProcessDetail(curproc.Id); + CurPrc.FileName = PD.Win32ProcessImagePath; + //Todo: This is not working for some reason, even with admin rights + //if (IsAdministrator()) + //{ + // Ret.CommandLine = PD.CommandLine; //.Replace((char)34,""); + //} + } + catch + { + CurPrc = null; + } + + //asdf + //if (string.IsNullOrEmpty(Ret.CommandLine)) + //{ + // //Having trouble obtaining the command line? + // //Log($"Cannot get command line for '{curproc.ProcessName}', must be running as administrator."); + //} + + if (Ret != null && CurPrc != null && !string.IsNullOrEmpty(CurPrc.FileName) && string.Equals(CurPrc.FileName, processname, StringComparison.OrdinalIgnoreCase)) + { + CurPrc.process = curproc; + Ret.Add(CurPrc); + } + else + { + CurPrc = null; + } + } + + } + } + catch + { + Ret = new List(); + } + + return Ret; + } + + public static ClsProcess GetaProcessByPath(string processname) + { + ClsProcess Ret = null; + try + { + string pname = Path.GetFileNameWithoutExtension(processname); + + Process[] aProc = Process.GetProcessesByName(pname); + + ProcessDetail PD = null; + + if (aProc.Length > 0) + + { + foreach (Process curproc in aProc) + { + //accessing 64 bit process from 32 bit app may not allow to get process properties, only name + //Stopwatch SW = Stopwatch.StartNew(); + Ret = new ClsProcess(); + + try + { + //if (IsAdministrator()) + //{ + // Process.EnterDebugMode(); + //} + PD = new ProcessDetail(curproc.Id); + Ret.FileName = PD.Win32ProcessImagePath; + //Todo: This is not working for some reason, even with admin rights + //if (IsAdministrator()) + //{ + // Ret.CommandLine = PD.CommandLine; //.Replace((char)34,""); + //} + } + catch + { + Ret = null; + } + + //asdf + //if (string.IsNullOrEmpty(Ret.CommandLine)) + //{ + // //Having trouble obtaining the command line? + // //Log($"Cannot get command line for '{curproc.ProcessName}', must be running as administrator."); + //} + + if (Ret != null && !string.IsNullOrEmpty(Ret.FileName) && Ret.FileName.ToLower() == processname.ToLower()) + { + Ret.process = curproc; + break; + } + else + { + Ret = null; + } + } + + } + } + catch + { + Ret = null; + } + + return Ret; + } + + public static bool IsProcessRunning(string ProcessName) + { + bool Ret = false; + + try + { + Process Proc = GetaProcess(ProcessName); + + if (Proc == null) + Log($"Process is not running: '{ProcessName}'."); + else + { + Log($"Process IS running: '{ProcessName}'."); + Ret = true; + } + } + catch (Exception ex) + { + Log("Error: " + ex.Msg()); + } + + return Ret; + } + + + + public static string GetWMIPropertyFromProcess(Int32 PID, string PropName) + { + + // THIS IS SLOW AS FUCK + + // .net PROCESS object cannot seem to get the command line from processes that we did not start + // resort to using WMI + //https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/win32-process + // + //CommandLine, ExecutablePath, ProcessId + string Ret = ""; + try + { + Stopwatch SW = Stopwatch.StartNew(); + + //method 2 - 400ms faster: + + string wmiQuery = $"select ProcessId, CommandLine, ExecutablePath from Win32_Process where ProcessId='{Convert.ToUInt32(PID)}'"; + using (ManagementObjectSearcher search = new ManagementObjectSearcher(wmiQuery)) + { + + // By definition, the query returns at most 1 match, because the process + // is looked up by ID (which is unique by definition). + using (var matchEnum = search.Get().GetEnumerator()) + { + if (matchEnum.MoveNext()) // Move to the 1st item. + { + object obj = matchEnum.Current[PropName]; + + if (obj == null) + obj = ""; + + Ret = obj.ToString(); + + } + } + + //ManagementObjectCollection processList = search.Get(); + //foreach (ManagementObject process in processList) + //{ + // //Log("{0,6} - {1} - {2}", process["ProcessId"], process["Name"], process["CommandLine"]); + // object obj = process[PropName]; + + // if (obj == null) + // obj = ""; + + // Ret = obj.ToString(); + + // break; + + //} + } + + //Method 1 - 450 ms slowest + //ManagementClass mgmtClass = new ManagementClass("Win32_Process"); + //foreach (ManagementObject process in mgmtClass.GetInstances()) + //{ + // // Get pid + // UInt32 CurPID = (System.UInt32)process["ProcessId"]; + + // if (CurPID == Convert.ToUInt32(PID)) + // { + // object obj = process[PropName]; + + // if (obj == null) + // obj = ""; + + // Ret = obj.ToString(); + + // break; + // } + + //} + + SW.Stop(); + Log($"Got '{PropName}' for PID '{PID}' in '{SW.ElapsedMilliseconds}'ms: {Ret}"); + + } + catch (Exception ex) + { + + Log("Error: Could not get command line: " + ex.Msg()); + } + return Ret; + } + + //I had to make this class since win32 apps cant access 64 bit command line and module info + public class ClsProcess:IEquatable + { + public Process process = null; + public string FileName = ""; + public string CommandLine = ""; + public ClsProcess() + { + this.process = new Process(); + } + + public override bool Equals(object obj) + { + return Equals(obj as ClsProcess); + } + + public bool Equals(ClsProcess other) + { + return other != null && + string.Equals(FileName, other.FileName, StringComparison.OrdinalIgnoreCase) && + string.Equals(CommandLine, other.CommandLine, StringComparison.OrdinalIgnoreCase); + } + + public override string ToString() + { + return $"{Path.GetFileName(this.FileName)} {this.CommandLine}"; + } + + public static bool operator ==(ClsProcess left, ClsProcess right) + { + return EqualityComparer.Default.Equals(left, right); + } + + public static bool operator !=(ClsProcess left, ClsProcess right) + { + return !(left == right); + } + } + + + + + + public static string GetXValue(XElement XE, string Name, string AttributeName = "") + { + string Ret = ""; + try + { + if (XE.HasElements) + { + if (XE.Element(Name) != null) + { + if (string.IsNullOrEmpty(AttributeName)) + { + Ret = XE.Element(Name).Value; + } + else + { + if (XE.Element(Name).HasAttributes) + { + if (XE.Element(Name).Attribute(AttributeName) != null) + { + Ret = XE.Element(Name).Attribute(AttributeName).Value; + } + else + { + //M.DbgLog($"Warning: Attribute not found '{AttributeName}' for Element '{Name}'."); + } + } + else + { + //M.DbgLog($"Warning: Attribute not found '{AttributeName}' for Element '{Name}'."); + } + } + } + else + { + //M.DbgLog($"Warning: Elements not found '{Name}'."); + } + } + else + { + //M.Log($"Error: No elements found While getting '{Name}'."); + } + } + catch (Exception) + { + //M.Log($"Error: While getting '{Name}', got error '{ex.Message}'."); + } + return Ret; + } + + public static List GetFiles(string CurDirectory, string FileName = "*", System.IO.SearchOption SearchOptions = System.IO.SearchOption.AllDirectories, DateTime? MinLastWriteTime = null, DateTime? MaxLastWriteTime = null, int MaxFiles = 99999) + { + + List files = new List(); + try + { + //If Directory.Exists(CurDirectory) Then + List Folders = CurDirectory.SplitStr(";|"); + List Names = FileName.SplitStr(";|"); + bool HasDate = MinLastWriteTime.HasValue; + foreach (string fld in Folders) + { + string fldr = fld; + + //so we can be lazy and pass filenames... + if (Path.HasExtension(fldr)) + fldr = Path.GetDirectoryName(fldr); + + if (Directory.Exists(fldr)) + { + DirectoryInfo DirInfo = new DirectoryInfo(fldr); + foreach (string nam in Names) + { + //M.DbgLog($"Getting '{nam}' files from folder '{fldr}'..."); + try + { + foreach (FileInfo fi in DirInfo.EnumerateFiles(nam, SearchOptions)) + { + if (HasDate) + { + if (MaxLastWriteTime.HasValue) + { + if (fi.LastWriteTime >= MinLastWriteTime.Value && fi.LastWriteTime <= MaxLastWriteTime) + { + if (files.Count < MaxFiles) + files.Add(fi); + else + break; + } + + } + else + { + if (fi.LastWriteTime == MinLastWriteTime.Value) + { + if (files.Count < MaxFiles) + files.Add(fi); + else + break; + } + } + } + else + { + if (files.Count < MaxFiles) + files.Add(fi); + else + break; + } + } + } + catch (Exception ex) + { + Log("Error: While getting file list, received error: " + ex.Msg()); + } + } + + } + else + { + Log($"Debug: Directory doesn't exist: '{fldr}' - ({FileName})"); + } + } + //files.AddRange(IO.Directory.GetFiles(CurDirectory, nam, SearchOptions).Select(Function(p) New IO.FileInfo(p)).ToList) + //M.DbgLog("Found " & files.Count & " " & FileName & " files in " & CurDirectory) + //Else + //M.DbgLog("Error: Folder does not exist: " & CurDirectory) + //End If + + } + catch (Exception ex) + { + Log("Error: While getting file list, received error: " + ex.Msg()); + } + finally + { + } + + return files; + + } + + } + + internal static class Utility + { + public static string[] SplitArgs(string unsplitArgumentLine) + { + if (unsplitArgumentLine == null) + return new string[0]; + + int numberOfArgs; + IntPtr ptrToSplitArgs; + string[] splitArgs; + + ptrToSplitArgs = NativeMethods.CommandLineToArgvW(unsplitArgumentLine, out numberOfArgs); + + // CommandLineToArgvW returns NULL upon failure. + if (ptrToSplitArgs == IntPtr.Zero) + throw new ArgumentException("Unable to split argument.", new Win32Exception()); + + // Make sure the memory ptrToSplitArgs to is freed, even upon failure. + try + { + splitArgs = new string[numberOfArgs]; + + // ptrToSplitArgs is an array of pointers to null terminated Unicode strings. + // Copy each of these strings into our split argument array. + for (int i = 0; i < numberOfArgs; i++) + splitArgs[i] = Marshal.PtrToStringUni( + Marshal.ReadIntPtr(ptrToSplitArgs, i * IntPtr.Size)); + + return splitArgs; + } + finally + { + // Free memory obtained by CommandLineToArgW. + NativeMethods.LocalFree(ptrToSplitArgs); + } + } + + public static T ReadUnmanagedStructFromProcess(IntPtr processHandle, + IntPtr addressInProcess) + { + int bytesRead; + int bytesToRead = Marshal.SizeOf(typeof(T)); + IntPtr buffer = Marshal.AllocHGlobal(bytesToRead); + if (!NativeMethods.ReadProcessMemory(processHandle, addressInProcess, buffer, bytesToRead, + out bytesRead)) + throw new Win32Exception(); + T result = (T)Marshal.PtrToStructure(buffer, typeof(T)); + Marshal.FreeHGlobal(buffer); + return result; + } + + public static string ReadStringUniFromProcess(IntPtr processHandle, + IntPtr addressInProcess, + int NumChars) + { + int bytesRead; + IntPtr outBuffer = Marshal.AllocHGlobal(NumChars * 2); + + bool bresult = NativeMethods.ReadProcessMemory(processHandle, + addressInProcess, + outBuffer, + NumChars * 2, + out bytesRead); + if (!bresult) + throw new Win32Exception(); + + string result = Marshal.PtrToStringUni(outBuffer, bytesRead / 2); + Marshal.FreeHGlobal(outBuffer); + return result; + } + + public static int UnmanagedStructSize() + { + return Marshal.SizeOf(typeof(T)); + } + } + public static class LowLevelTypes + { + + #region Constants and Enums + // Represents the image format of a DLL or executable. + public enum ImageFormat + { + NATIVE, + MANAGED, + UNKNOWN + } + + // Flags used for opening a file handle (e.g. in a call to CreateFile), that determine the + // requested permission level. + [Flags] + public enum FileAccessFlags:uint + { + GENERIC_WRITE = 0x40000000, + GENERIC_READ = 0x80000000 + } + + // Value used for CreateFile to determine how to behave in the presence (or absence) of a + // file with the requested name. Used only for CreateFile. + public enum FileCreationDisposition:uint + { + CREATE_NEW = 1, + CREATE_ALWAYS = 2, + OPEN_EXISTING = 3, + OPEN_ALWAYS = 4, + TRUNCATE_EXISTING = 5 + } + + // Flags that determine what level of sharing this application requests on the target file. + // Used only for CreateFile. + [Flags] + public enum FileShareFlags:uint + { + EXCLUSIVE_ACCESS = 0x0, + SHARE_READ = 0x1, + SHARE_WRITE = 0x2, + SHARE_DELETE = 0x4 + } + + // Flags that control caching and other behavior of the underlying file object. Used only for + // CreateFile. + [Flags] + public enum FileFlagsAndAttributes:uint + { + NORMAL = 0x80, + OPEN_REPARSE_POINT = 0x200000, + SEQUENTIAL_SCAN = 0x8000000, + RANDOM_ACCESS = 0x10000000, + NO_BUFFERING = 0x20000000, + OVERLAPPED = 0x40000000 + } + + // The target architecture of a given executable image. The various values correspond to the + // magic numbers defined by the PE Executable Image File Format. + // http://www.microsoft.com/whdc/system/platform/firmware/PECOFF.mspx + public enum MachineType:ushort + { + UNKNOWN = 0x0, + X64 = 0x8664, + X86 = 0x14c, + IA64 = 0x200 + } + + // A flag indicating the format of the path string that Windows returns from a call to + // QueryFullProcessImageName(). + public enum ProcessQueryImageNameMode:uint + { + WIN32_FORMAT = 0, + NATIVE_SYSTEM_FORMAT = 1 + } + + // Flags indicating the level of permission requested when opening a handle to an external + // process. Used by OpenProcess(). + [Flags] + public enum ProcessAccessFlags:uint + { + NONE = 0x0, + ALL = 0x001F0FFF, + VM_OPERATION = 0x00000008, + VM_READ = 0x00000010, + QUERY_INFORMATION = 0x00000400, + QUERY_LIMITED_INFORMATION = 0x00001000 + } + + // Defines return value codes used by various Win32 System APIs. + public enum NTSTATUS:int + { + SUCCESS = 0, + } + + // Determines the amount of information requested (and hence the type of structure returned) + // by a call to NtQueryInformationProcess. + public enum PROCESSINFOCLASS:int + { + PROCESS_BASIC_INFORMATION = 0 + }; + + [Flags] + public enum SHGFI:uint + { + Icon = 0x000000100, + DisplayName = 0x000000200, + TypeName = 0x000000400, + Attributes = 0x000000800, + IconLocation = 0x000001000, + ExeType = 0x000002000, + SysIconIndex = 0x000004000, + LinkOverlay = 0x000008000, + Selected = 0x000010000, + Attr_Specified = 0x000020000, + LargeIcon = 0x000000000, + SmallIcon = 0x000000001, + OpenIcon = 0x000000002, + ShellIconSize = 0x000000004, + PIDL = 0x000000008, + UseFileAttributes = 0x000000010, + AddOverlays = 0x000000020, + OverlayIndex = 0x000000040, + } + #endregion + + #region Structures + // In general, for all structures below which contains a pointer (represented here by IntPtr), + // the pointers refer to memory in the address space of the process from which the original + // structure was read. While this seems obvious, it means we cannot provide an elegant + // interface to the various fields in the structure due to the de-reference requiring a + // handle to the target process. Instead, that functionality needs to be provided at a + // higher level. + // + // Additionally, since we usually explicitly define the fields that we're interested in along + // with their respective offsets, we frequently specify the exact size of the native structure. + + // Win32 UNICODE_STRING structure. + [StructLayout(LayoutKind.Sequential)] + public struct UNICODE_STRING + { + // The length in bytes of the string pointed to by buffer, not including the null-terminator. + private ushort length; + // The total allocated size in memory pointed to by buffer. + private ushort maximumLength; + // A pointer to the buffer containing the string data. + private IntPtr buffer; + + public ushort Length { get { return this.length; } } + public ushort MaximumLength { get { return this.maximumLength; } } + public IntPtr Buffer { get { return this.buffer; } } + } + + // Win32 RTL_USER_PROCESS_PARAMETERS structure. + [StructLayout(LayoutKind.Explicit, Size = 72)] + public struct RTL_USER_PROCESS_PARAMETERS + { + [FieldOffset(56)] + private UNICODE_STRING imagePathName; + [FieldOffset(64)] + private UNICODE_STRING commandLine; + + public UNICODE_STRING ImagePathName { get { return this.imagePathName; } } + public UNICODE_STRING CommandLine { get { return this.commandLine; } } + }; + + // Win32 PEB structure. Represents the process environment block of a process. + [StructLayout(LayoutKind.Explicit, Size = 472)] + public struct PEB + { + [FieldOffset(2), MarshalAs(UnmanagedType.U1)] + private bool isBeingDebugged; + [FieldOffset(12)] + private IntPtr ldr; + [FieldOffset(16)] + private IntPtr processParameters; + [FieldOffset(468)] + private uint sessionId; + + public bool IsBeingDebugged { get { return this.isBeingDebugged; } } + public IntPtr Ldr { get { return this.ldr; } } + public IntPtr ProcessParameters { get { return this.processParameters; } } + public uint SessionId { get { return this.sessionId; } } + }; + + // Win32 PROCESS_BASIC_INFORMATION. Contains a pointer to the PEB, and various other + // information about a process. + [StructLayout(LayoutKind.Explicit, Size = 24)] + public struct PROCESS_BASIC_INFORMATION + { + [FieldOffset(4)] + private IntPtr pebBaseAddress; + [FieldOffset(16)] + private UIntPtr uniqueProcessId; + + public IntPtr PebBaseAddress { get { return this.pebBaseAddress; } } + public UIntPtr UniqueProcessId { get { return this.uniqueProcessId; } } + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct SHFILEINFO + { + // C# doesn't support overriding the default constructor of value types, so we need to use + // a dummy constructor. + public SHFILEINFO(bool dummy) + { + this.hIcon = IntPtr.Zero; + this.iIcon = 0; + this.dwAttributes = 0; + this.szDisplayName = ""; + this.szTypeName = ""; + } + public IntPtr hIcon; + public int iIcon; + public uint dwAttributes; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] + public string szDisplayName; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)] + public string szTypeName; + }; + #endregion + } + public static class NativeMethods + { + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool ReadProcessMemory(IntPtr hProcess, + IntPtr lpBaseAddress, + IntPtr lpBuffer, + int dwSize, + out int lpNumberOfBytesRead); + + [DllImport("ntdll.dll", SetLastError = true)] + public static extern LowLevelTypes.NTSTATUS NtQueryInformationProcess( + IntPtr hProcess, + LowLevelTypes.PROCESSINFOCLASS pic, + ref LowLevelTypes.PROCESS_BASIC_INFORMATION pbi, + int cb, + out int pSize); + + [DllImport("shell32.dll", SetLastError = true)] + public static extern IntPtr CommandLineToArgvW( + [MarshalAs(UnmanagedType.LPWStr)] string lpCmdLine, + out int pNumArgs); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern IntPtr LocalFree(IntPtr hMem); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern IntPtr OpenProcess( + LowLevelTypes.ProcessAccessFlags dwDesiredAccess, + [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, + int dwProcessId); + + [DllImport("kernel32.dll", SetLastError = true, CallingConvention = CallingConvention.StdCall, + CharSet = CharSet.Unicode)] + public static extern uint QueryFullProcessImageName( + IntPtr hProcess, + [MarshalAs(UnmanagedType.U4)] LowLevelTypes.ProcessQueryImageNameMode flags, + [Out] StringBuilder lpImageName, ref int size); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool CloseHandle(IntPtr hObject); + + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern SafeFileHandle CreateFile(string lpFileName, + LowLevelTypes.FileAccessFlags dwDesiredAccess, + LowLevelTypes.FileShareFlags dwShareMode, + IntPtr lpSecurityAttributes, + LowLevelTypes.FileCreationDisposition dwDisp, + LowLevelTypes.FileFlagsAndAttributes dwFlags, + IntPtr hTemplateFile); + + [DllImport("shell32.dll", CharSet = CharSet.Unicode)] + public static extern IntPtr SHGetFileInfo(string pszPath, + uint dwFileAttributes, + ref LowLevelTypes.SHFILEINFO psfi, + uint cbFileInfo, + uint uFlags); + } + internal class ProcessDetail:IDisposable + { + + + public ProcessDetail(int pid) + { + // Initialize everything to null in case something fails. + this.processId = pid; + this.processHandleFlags = LowLevelTypes.ProcessAccessFlags.NONE; + this.cachedProcessBasicInfo = null; + this.machineTypeIsLoaded = false; + this.machineType = LowLevelTypes.MachineType.UNKNOWN; + this.cachedPeb = null; + this.cachedProcessParams = null; + this.cachedCommandLine = null; + this.processHandle = IntPtr.Zero; + + this.OpenAndCacheProcessHandle(); + } + + // Returns the machine type (x86, x64, etc) of this process. Uses lazy evaluation and caches + // the result. + public LowLevelTypes.MachineType MachineType + { + get + { + if (this.machineTypeIsLoaded) + return this.machineType; + if (!this.CanQueryProcessInformation) + return LowLevelTypes.MachineType.UNKNOWN; + + this.CacheMachineType(); + return this.machineType; + } + } + + public string NativeProcessImagePath + { + get + { + if (this.nativeProcessImagePath == null) + { + this.nativeProcessImagePath = this.QueryProcessImageName( + LowLevelTypes.ProcessQueryImageNameMode.NATIVE_SYSTEM_FORMAT); + } + return this.nativeProcessImagePath; + } + } + + public string Win32ProcessImagePath + { + get + { + if (this.win32ProcessImagePath == null) + { + this.win32ProcessImagePath = this.QueryProcessImageName( + LowLevelTypes.ProcessQueryImageNameMode.WIN32_FORMAT); + } + return this.win32ProcessImagePath; + } + } + + //public Icon SmallIcon + //{ + // get + // { + // LowLevelTypes.SHFILEINFO info = new LowLevelTypes.SHFILEINFO(true); + // LowLevelTypes.SHGFI flags = LowLevelTypes.SHGFI.Icon + // | LowLevelTypes.SHGFI.SmallIcon + // | LowLevelTypes.SHGFI.OpenIcon + // | LowLevelTypes.SHGFI.UseFileAttributes; + // int cbFileInfo = Marshal.SizeOf(info); + // NativeMethods.SHGetFileInfo(Win32ProcessImagePath, + // 256, + // ref info, + // (uint)cbFileInfo, + // (uint)flags); + // return Icon.FromHandle(info.hIcon); + // } + //} + + // Returns the command line that this process was launched with. Uses lazy evaluation and + // caches the result. Reads the command line from the PEB of the running process. + public string CommandLine + { + get + { + if (!this.CanReadPeb) + return ""; //throw new InvalidOperationException(); + this.CacheProcessInformation(); + this.CachePeb(); + this.CacheProcessParams(); + this.CacheCommandLine(); + return this.cachedCommandLine; + } + } + + // Determines if we have permission to read the process's PEB. + public bool CanReadPeb + { + get + { + LowLevelTypes.ProcessAccessFlags required_flags = + LowLevelTypes.ProcessAccessFlags.VM_READ + | LowLevelTypes.ProcessAccessFlags.QUERY_INFORMATION; + + // In order to read the PEB, we must have *both* of these flags. + if ((this.processHandleFlags & required_flags) != required_flags) + return false; + + // If we're on a 64-bit OS, in a 32-bit process, and the target process is not 32-bit, + // we can't read its PEB. + if (Environment.Is64BitOperatingSystem && !Environment.Is64BitProcess + && (this.MachineType != LowLevelTypes.MachineType.X86)) + return false; + + return true; + } + } + + // If we can't read the process's PEB, we may still be able to get other kinds of information + // from the process. This flag determines if we can get lesser information. + private bool CanQueryProcessInformation + { + get + { + LowLevelTypes.ProcessAccessFlags required_flags = + LowLevelTypes.ProcessAccessFlags.QUERY_LIMITED_INFORMATION + | LowLevelTypes.ProcessAccessFlags.QUERY_INFORMATION; + + // In order to query the process, we need *either* of these flags. + return (this.processHandleFlags & required_flags) != LowLevelTypes.ProcessAccessFlags.NONE; + } + } + + private string QueryProcessImageName(LowLevelTypes.ProcessQueryImageNameMode mode) + { + StringBuilder moduleBuffer = new StringBuilder(1024); + int size = moduleBuffer.Capacity; + NativeMethods.QueryFullProcessImageName( + this.processHandle, + mode, + moduleBuffer, + ref size); + if (mode == LowLevelTypes.ProcessQueryImageNameMode.NATIVE_SYSTEM_FORMAT) + moduleBuffer.Insert(0, "\\\\?\\GLOBALROOT"); + return moduleBuffer.ToString(); + } + + // Loads the top-level structure of the process's information block and caches it. + private void CacheProcessInformation() + { + System.Diagnostics.Debug.Assert(this.CanReadPeb); + + // Fetch the process info and set the fields. + LowLevelTypes.PROCESS_BASIC_INFORMATION temp = new LowLevelTypes.PROCESS_BASIC_INFORMATION(); + int size; + LowLevelTypes.NTSTATUS status = NativeMethods.NtQueryInformationProcess( + this.processHandle, + LowLevelTypes.PROCESSINFOCLASS.PROCESS_BASIC_INFORMATION, + ref temp, + Utility.UnmanagedStructSize(), + out size); + + if (status != LowLevelTypes.NTSTATUS.SUCCESS) + { + //-1073741820 = STATUS_INFO_LENGTH_MISMATCH - Probably a 64 bit process accessing 32 bit process memory related error + throw new Win32Exception(Convert.ToInt32(status)); + } + + this.cachedProcessBasicInfo = temp; + } + + // Follows a pointer from the PROCESS_BASIC_INFORMATION structure in the target process's + // address space to read the PEB. + private void CachePeb() + { + System.Diagnostics.Debug.Assert(this.CanReadPeb); + + if (this.cachedPeb == null) + { + this.cachedPeb = Utility.ReadUnmanagedStructFromProcess( + this.processHandle, + this.cachedProcessBasicInfo.Value.PebBaseAddress); + } + } + + // Follows a pointer from the PEB structure in the target process's address space to read the + // RTL_USER_PROCESS_PARAMETERS structure. + private void CacheProcessParams() + { + System.Diagnostics.Debug.Assert(this.CanReadPeb); + + if (this.cachedProcessParams == null) + { + this.cachedProcessParams = + Utility.ReadUnmanagedStructFromProcess( + this.processHandle, this.cachedPeb.Value.ProcessParameters); + } + } + + private void CacheCommandLine() + { + System.Diagnostics.Debug.Assert(this.CanReadPeb); + + if (this.cachedCommandLine == null) + { + this.cachedCommandLine = Utility.ReadStringUniFromProcess( + this.processHandle, + this.cachedProcessParams.Value.CommandLine.Buffer, + this.cachedProcessParams.Value.CommandLine.Length / 2); + } + } + + private void CacheMachineType() + { + System.Diagnostics.Debug.Assert(this.CanQueryProcessInformation); + + // If our extension is running in a 32-bit process (which it is), then attempts to access + // files in C:\windows\system (and a few other files) will redirect to C:\Windows\SysWOW64 + // and we will mistakenly think that the image file is a 32-bit image. The way around this + // is to use a native system format path, of the form: + // \\?\GLOBALROOT\Device\HarddiskVolume0\Windows\System\foo.dat + // NativeProcessImagePath gives us the full process image path in the desired format. + string path = this.NativeProcessImagePath; + + // Open the PE File as a binary file, and parse just enough information to determine the + // machine type. + //http://www.microsoft.com/whdc/system/platform/firmware/PECOFF.mspx + using (SafeFileHandle safeHandle = NativeMethods.CreateFile( + path, + LowLevelTypes.FileAccessFlags.GENERIC_READ, + LowLevelTypes.FileShareFlags.SHARE_READ, + IntPtr.Zero, + LowLevelTypes.FileCreationDisposition.OPEN_EXISTING, + LowLevelTypes.FileFlagsAndAttributes.NORMAL, + IntPtr.Zero)) + { + FileStream fs = new FileStream(safeHandle, FileAccess.Read); + using (BinaryReader br = new BinaryReader(fs)) + { + fs.Seek(0x3c, SeekOrigin.Begin); + Int32 peOffset = br.ReadInt32(); + fs.Seek(peOffset, SeekOrigin.Begin); + UInt32 peHead = br.ReadUInt32(); + if (peHead != 0x00004550) // "PE\0\0", little-endian + throw new Exception("Can't find PE header"); + this.machineType = (LowLevelTypes.MachineType)br.ReadUInt16(); + this.machineTypeIsLoaded = true; + } + } + } + + private void OpenAndCacheProcessHandle() + { + // Try to open a handle to the process with the highest level of privilege, but if we can't + // do that then fallback to requesting access with a lower privilege level. + this.processHandleFlags = LowLevelTypes.ProcessAccessFlags.QUERY_INFORMATION + | LowLevelTypes.ProcessAccessFlags.VM_READ; + this.processHandle = NativeMethods.OpenProcess(this.processHandleFlags, false, this.processId); + if (this.processHandle == IntPtr.Zero) + { + this.processHandleFlags = LowLevelTypes.ProcessAccessFlags.QUERY_LIMITED_INFORMATION; + this.processHandle = NativeMethods.OpenProcess(this.processHandleFlags, false, this.processId); + if (this.processHandle == IntPtr.Zero) + { + this.processHandleFlags = LowLevelTypes.ProcessAccessFlags.NONE; + throw new Win32Exception(); + } + } + } + + // An open handle to the process, along with the set of access flags that the handle was + // open with. + private int processId; + private IntPtr processHandle; + private LowLevelTypes.ProcessAccessFlags processHandleFlags; + private string nativeProcessImagePath; + private string win32ProcessImagePath; + + // The machine type is read by parsing the PE image file of the running process, so we cache + // its value since the operation expensive. + private bool machineTypeIsLoaded; + private LowLevelTypes.MachineType machineType; + + // The following fields exist ultimately so that we can access the command line. However, + // each field must be read separately through a pointer into another process's address + // space so the access is expensive, hence we cache the values. + private Nullable cachedProcessBasicInfo; + private Nullable cachedPeb; + private Nullable cachedProcessParams; + private string cachedCommandLine; + + ~ProcessDetail() + { + this.Dispose(); + } + + public void Dispose() + { + if (this.processHandle != IntPtr.Zero) + NativeMethods.CloseHandle(this.processHandle); + this.processHandle = IntPtr.Zero; + } + } + + + +} + diff --git a/src/UI/GlobalSuppressions.cs b/src/UI/GlobalSuppressions.cs new file mode 100644 index 00000000..be3f85ba --- /dev/null +++ b/src/UI/GlobalSuppressions.cs @@ -0,0 +1,9 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("AsyncUsage", "AsyncFixer03:Fire-and-forget async-void methods or delegates", Justification = "", Scope = "member", Target = "~M:AITool.Shell.UpdateLogAddedRemovedAsync(System.Boolean,System.Boolean)~System.Threading.Tasks.Task")] +[assembly: SuppressMessage("AsyncUsage", "AsyncFixer03:Fire-and-forget async-void methods or delegates", Justification = "", Scope = "member", Target = "~M:AITool.Shell.LoadHistoryAsync(System.Boolean,System.Boolean)~System.Threading.Tasks.Task")] diff --git a/src/UI/History.cs b/src/UI/History.cs new file mode 100644 index 00000000..9df5ce86 --- /dev/null +++ b/src/UI/History.cs @@ -0,0 +1,218 @@ +using System; +using System.Collections.Generic; + +using static AITool.AITOOL; + +namespace AITool +{ + public class History + { + //filename|date and time|camera|detections|positions of detections|success + //D:\BlueIrisStorage\AIInput\AIFOSCAMDRIVEWAY\AIFOSCAMDRIVEWAY.20200901_073513579.jpg|01.09.20, 07:35:15|AIFOSCAMDRIVEWAY|false alert||false + //D:\BlueIrisStorage\AIInput\AILOREXBACK\AILOREXBACK.20200901_073516231.jpg| + // 01.09.20, 07:35:17| + // AILOREXBACK| + // 1x not in confidence range; 6x irrelevant : fire hydrant (47%); dog (42%); umbrella (77%); chair (98%); chair (86%); chair (60%); chair (59%); | + // 1541,870,1690,1097;1316,1521,1583,1731;226,214,1231,637;451,1028,689,1321;989,769,1225,1145;882,616,1089,938;565,647,701,814;| + // false + + public DateTime Date { get; set; } = DateTime.MinValue; + public string Camera { get; set; } = ""; + public bool Success { get; set; } = false; + public string Detections { get; set; } = ""; + public bool IsPerson { get; set; } = false; + public bool IsVehicle { get; set; } = false; + public bool IsAnimal { get; set; } = false; + public bool WasMasked { get; set; } = false; + public bool WasSkipped { get; set; } = false; + public bool HasError { get; set; } = false; + public bool IsFace { get; set; } = false; + public bool IsKnownFace { get; set; } = false; + [SQLite.PrimaryKey, SQLite.Unique] //cannot have indexed here also - pk auto creates index I think + public string Filename { get; set; } = ""; + public string Positions { get; set; } = ""; + public string PredictionsJSON { get; set; } = ""; + public string AIServer { get; set; } = ""; + public long analysisDurationMS { get; set; } = 0; + public string state { get; set; } = ""; + + public List Predictions() + { + List ret = new List(); + if (!string.IsNullOrEmpty(this.PredictionsJSON)) + { + //I think we were storing predictions as json either due to something with sqlite db or for compatibility with original AITOOL + ret = Global.SetJSONString>(this.PredictionsJSON); + if (ret != null) + { + foreach (var pred in ret) + { + pred.UpdatePercent(); + } + } + else + { + ret = new List(); + } + } + return ret; + } + + + public History() + { + } + public History CreateFromCSV(string CSVLine) + { + //"filename|date and time|camera|detections|positions of detections|success" + // 0 1 2 3 4 5 + //d:\blueirisstorage\aiinput\aifoscamdriveway\aifoscamdriveway.20200919_195956172.jpg|19.09.20, 19:59:57|FOSCAMDRIVEWAY|false alert||False + //d:\blueirisstorage\aiinput\aisunroom\aisunroom.20200920_102321242.jpg|20.09.20, 10:23:21|SUNROOM|Person 65%|336,587,513,717;|True + //d:\blueirisstorage\aiinput\ailorexback\ailorexback.20200920_102343589.jpg|20.09.20, 10:23:44|LOREXBACK|2x irrelevant : Umbrella 72%; Dining table 43%|188,231,1174,583;471,765,881,988;|False + + + string[] sp = CSVLine.Split('|'); + + this.Camera = sp[2].Trim(); + this.Detections = sp[3]; + this.Filename = sp[0].Trim().ToLower(); + this.Positions = sp[4].Trim(); + + bool suc = false; + //20.09.20, 11:51:24 + + if (bool.TryParse(sp[5], out suc)) + { + this.Success = suc; + } + else + { + Log($"Error: Invalid bool in csv '{sp[5]}'"); + } + + //seems we are trying pretty hard here to get this non standard date back in a real DateTime... + DateTime date1; + + suc = DateTime.TryParseExact(sp[1], AppSettings.Settings.DateFormat, null, System.Globalization.DateTimeStyles.None, out date1); + if (suc) + { + this.Date = date1; + } + else + { + if (AppSettings.Settings.DateFormat != "dd.MM.yy, HH:mm:ss") + { + suc = DateTime.TryParseExact(sp[1], "dd.MM.yy, HH:mm:ss", null, System.Globalization.DateTimeStyles.None, out date1); + if (suc) + { + this.Date = date1; + } + } + + } + + if (!suc) + { + suc = DateTime.TryParse(sp[1], out date1); + if (suc) + { + this.Date = date1; + } + else + { + Log($"Error: Invalid date in csv '{sp[1]}'"); + } + + } + + + this.GetObjects(); + + return this; + + } + public History Create(string filename, DateTime date, string camera, string objects_and_confidence, string object_positions, bool Success, string PredictionsJSON, string AIServer, long analysisDurationMS, bool Trigger) + { + this.Filename = filename.Trim().ToLower(); + this.Date = date; + this.Camera = camera.Trim(); + this.Detections = objects_and_confidence.Trim(); + this.Positions = object_positions.Trim(); + this.PredictionsJSON = PredictionsJSON; + this.AIServer = AIServer; + this.analysisDurationMS = analysisDurationMS; + if (Trigger) + this.state = "on"; + else + this.state = "off"; + + this.Success = Success; //this.Detections.Contains("%") && !this.Detections.Contains(':'); + + this.GetObjects(); + + return this; + } + private void GetObjects() + { + string tmp = this.Detections.ToLower(); + + //person, bicycle, car, motorcycle, airplane, + //bus, train, truck, boat, traffic light, fire hydrant, stop_sign, + //parking meter, bench, bird, cat, dog, horse, sheep, cow, elephant, + //bear, zebra, giraffe, backpack, umbrella, handbag, tie, suitcase, + //frisbee, skis, snowboard, sports ball, kite, baseball bat, baseball glove, + //skateboard, surfboard, tennis racket, bottle, wine glass, cup, fork, + //knife, spoon, bowl, banana, apple, sandwich, orange, broccoli, carrot, + //hot dog, pizza, donot, cake, chair, couch, potted plant, bed, dining table, + //toilet, tv, laptop, mouse, remote, keyboard, cell phone, microwave, + //oven, toaster, sink, refrigerator, book, clock, vase, scissors, teddy bear, + //hair dryer, toothbrush. + + + this.IsPerson = tmp.Contains("person") || tmp.Contains("people"); + + this.IsFace = tmp.Contains("face"); + + //Ambulance, person, people, bear, fox, elephant, car, truck, pickup truck, SUV, van, bicycle, motorcycle, + //motorbike, bus, license plate, dog, horse, boat, train, zebra, giraffe, cow, pig, skunk, raccoon, sheep, rabbit, cat + + this.IsVehicle = tmp.Contains("car") || + tmp.Contains("truck") || + tmp.Contains("bus") || + tmp.Contains("bicycle") || + tmp.Contains("motorcycle") || + tmp.Contains("motorbike") || + tmp.Contains("boat") || + tmp.Contains("suv") || + tmp.Contains("van") || + tmp.Contains("bus") || + tmp.Contains("train") || + tmp.Contains("boat") || + tmp.Contains("airplane") || + tmp.Contains("ambulance"); + + this.IsAnimal = tmp.Contains("dog") || + tmp.Contains("cat") || + tmp.Contains("bird") || + tmp.Contains("bear") || + tmp.Contains("sheep") || + tmp.Contains("cow") || + tmp.Contains("horse") || + tmp.Contains("elephant") || + tmp.Contains("zebra") || + tmp.Contains("pig") || + tmp.Contains("skunk") || + tmp.Contains("raccoon") || + tmp.Contains("rabbit") || + tmp.Contains("fox") || + tmp.Contains("giraffe"); + + this.WasMasked = tmp.Contains("mask"); + + this.WasSkipped = tmp.Contains("skipped"); + + this.HasError = tmp.Contains("error"); + } + + } +} diff --git a/src/UI/InputForm.Designer.cs b/src/UI/InputForm.Designer.cs index e872e5e2..ddf78f3c 100644 --- a/src/UI/InputForm.Designer.cs +++ b/src/UI/InputForm.Designer.cs @@ -1,4 +1,4 @@ -namespace WindowsFormsApp2 +namespace AITool { partial class InputForm { @@ -30,72 +30,41 @@ private void InitializeComponent() { this.lbl_1 = new System.Windows.Forms.Label(); this.tb_1 = new System.Windows.Forms.TextBox(); - this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); - this.tableLayoutPanel2 = new System.Windows.Forms.TableLayoutPanel(); this.btn_2 = new System.Windows.Forms.Button(); this.btn_1 = new System.Windows.Forms.Button(); - this.tableLayoutPanel1.SuspendLayout(); - this.tableLayoutPanel2.SuspendLayout(); + this.cb_1 = new System.Windows.Forms.ComboBox(); this.SuspendLayout(); // // lbl_1 // - this.lbl_1.Anchor = System.Windows.Forms.AnchorStyles.None; - this.lbl_1.AutoSize = true; - this.lbl_1.Location = new System.Drawing.Point(170, 26); + this.lbl_1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.lbl_1.Location = new System.Drawing.Point(34, 20); + this.lbl_1.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0); this.lbl_1.Name = "lbl_1"; - this.lbl_1.Size = new System.Drawing.Size(35, 13); + this.lbl_1.Size = new System.Drawing.Size(328, 24); this.lbl_1.TabIndex = 0; this.lbl_1.Text = "label1"; // // tb_1 // - this.tb_1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); - this.tb_1.Location = new System.Drawing.Point(20, 87); - this.tb_1.Margin = new System.Windows.Forms.Padding(20, 3, 20, 3); + this.tb_1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tb_1.Location = new System.Drawing.Point(34, 61); + this.tb_1.Margin = new System.Windows.Forms.Padding(37, 6, 37, 6); this.tb_1.Name = "tb_1"; - this.tb_1.Size = new System.Drawing.Size(335, 20); + this.tb_1.Size = new System.Drawing.Size(315, 20); this.tb_1.TabIndex = 1; this.tb_1.KeyDown += new System.Windows.Forms.KeyEventHandler(this.tb_1_KeyDown); // - // tableLayoutPanel1 - // - this.tableLayoutPanel1.ColumnCount = 1; - this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.tableLayoutPanel1.Controls.Add(this.lbl_1, 0, 0); - this.tableLayoutPanel1.Controls.Add(this.tb_1, 0, 1); - this.tableLayoutPanel1.Controls.Add(this.tableLayoutPanel2, 0, 2); - this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill; - this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 0); - this.tableLayoutPanel1.Name = "tableLayoutPanel1"; - this.tableLayoutPanel1.RowCount = 3; - this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 30F)); - this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 30F)); - this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 40F)); - this.tableLayoutPanel1.Size = new System.Drawing.Size(375, 218); - this.tableLayoutPanel1.TabIndex = 3; - // - // tableLayoutPanel2 - // - this.tableLayoutPanel2.ColumnCount = 2; - this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); - this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); - this.tableLayoutPanel2.Controls.Add(this.btn_2, 0, 0); - this.tableLayoutPanel2.Controls.Add(this.btn_1, 0, 0); - this.tableLayoutPanel2.Dock = System.Windows.Forms.DockStyle.Fill; - this.tableLayoutPanel2.Location = new System.Drawing.Point(3, 133); - this.tableLayoutPanel2.Name = "tableLayoutPanel2"; - this.tableLayoutPanel2.RowCount = 1; - this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F)); - this.tableLayoutPanel2.Size = new System.Drawing.Size(369, 82); - this.tableLayoutPanel2.TabIndex = 2; - // // btn_2 // - this.btn_2.Anchor = System.Windows.Forms.AnchorStyles.None; - this.btn_2.Location = new System.Drawing.Point(239, 29); + this.btn_2.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.btn_2.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.btn_2.Location = new System.Drawing.Point(292, 89); + this.btn_2.Margin = new System.Windows.Forms.Padding(5, 6, 5, 6); this.btn_2.Name = "btn_2"; - this.btn_2.Size = new System.Drawing.Size(75, 23); + this.btn_2.Size = new System.Drawing.Size(70, 30); this.btn_2.TabIndex = 4; this.btn_2.Text = "Cancel"; this.btn_2.UseVisualStyleBackColor = true; @@ -103,36 +72,57 @@ private void InitializeComponent() // // btn_1 // - this.btn_1.Anchor = System.Windows.Forms.AnchorStyles.None; - this.btn_1.Location = new System.Drawing.Point(54, 29); + this.btn_1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.btn_1.Location = new System.Drawing.Point(212, 89); + this.btn_1.Margin = new System.Windows.Forms.Padding(5, 6, 5, 6); this.btn_1.Name = "btn_1"; - this.btn_1.Size = new System.Drawing.Size(75, 23); + this.btn_1.Size = new System.Drawing.Size(70, 30); this.btn_1.TabIndex = 3; this.btn_1.Text = "Ok"; this.btn_1.UseVisualStyleBackColor = true; this.btn_1.Click += new System.EventHandler(this.btn_1_Click); this.btn_1.KeyDown += new System.Windows.Forms.KeyEventHandler(this.btn_1_KeyDown); // + // cb_1 + // + this.cb_1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.cb_1.FormattingEnabled = true; + this.cb_1.Location = new System.Drawing.Point(34, 60); + this.cb_1.Margin = new System.Windows.Forms.Padding(4); + this.cb_1.Name = "cb_1"; + this.cb_1.Size = new System.Drawing.Size(328, 21); + this.cb_1.TabIndex = 5; + this.cb_1.SelectedIndexChanged += new System.EventHandler(this.cb_1_SelectedIndexChanged); + this.cb_1.KeyDown += new System.Windows.Forms.KeyEventHandler(this.cb_1_KeyDown); + // // InputForm // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(375, 218); + this.AcceptButton = this.btn_1; + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Inherit; + this.CancelButton = this.btn_2; + this.ClientSize = new System.Drawing.Size(398, 134); this.ControlBox = false; - this.Controls.Add(this.tableLayoutPanel1); - this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; + this.Controls.Add(this.cb_1); + this.Controls.Add(this.btn_2); + this.Controls.Add(this.btn_1); + this.Controls.Add(this.lbl_1); + this.Controls.Add(this.tb_1); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.KeyPreview = true; + this.Margin = new System.Windows.Forms.Padding(5, 6, 5, 6); this.MaximizeBox = false; this.MinimizeBox = false; this.Name = "InputForm"; + this.ShowIcon = false; this.ShowInTaskbar = false; this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; this.Text = "InputForm"; + this.Load += new System.EventHandler(this.InputForm_Load); this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.InputForm_KeyDown_1); - this.tableLayoutPanel1.ResumeLayout(false); - this.tableLayoutPanel1.PerformLayout(); - this.tableLayoutPanel2.ResumeLayout(false); this.ResumeLayout(false); + this.PerformLayout(); } @@ -140,9 +130,8 @@ private void InitializeComponent() private System.Windows.Forms.Label lbl_1; private System.Windows.Forms.TextBox tb_1; - private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; - private System.Windows.Forms.TableLayoutPanel tableLayoutPanel2; private System.Windows.Forms.Button btn_2; private System.Windows.Forms.Button btn_1; + private System.Windows.Forms.ComboBox cb_1; } } \ No newline at end of file diff --git a/src/UI/InputForm.cs b/src/UI/InputForm.cs index 84b77923..4a90805f 100644 --- a/src/UI/InputForm.cs +++ b/src/UI/InputForm.cs @@ -1,51 +1,52 @@ using System; using System.Collections.Generic; -using System.ComponentModel; -using System.Data; -using System.Drawing; using System.Linq; -using System.Text; -using System.Threading.Tasks; using System.Windows.Forms; -namespace WindowsFormsApp2 +namespace AITool { public partial class InputForm : Form { - public string text = ""; + public string text = ""; - public InputForm(string label, string title, bool show_textbox) + public InputForm(string label, string title, bool show_textbox = true, string text_OK = "Ok", string text_Cancel = "Cancel", string defaulttext = "", List cbitems = null) { - InitializeComponent(); - lbl_1.Text = label; - this.Text = title; - if (show_textbox) - { - tb_1.Show(); - } - else - { - tb_1.Hide(); - } - } + //updated to support defaults and combobox items - public InputForm(string label, string title, bool show_textbox, string text_OK, string text_Cancel) - { - InitializeComponent(); - lbl_1.Text = label; + this.InitializeComponent(); + this.lbl_1.Text = label; this.Text = title; + this.btn_1.Text = text_OK; + this.btn_2.Text = text_Cancel; + if (show_textbox) { - tb_1.Show(); + if (cbitems == null || cbitems.Count == 0) + { + this.tb_1.Show(); + this.tb_1.BringToFront(); + this.tb_1.Text = defaulttext; + this.tb_1.Enabled = true; + this.cb_1.Enabled = false; + } + else + { + this.cb_1.Show(); + this.cb_1.BringToFront(); + this.cb_1.Items.AddRange(cbitems.ToArray()); + this.cb_1.Text = defaulttext; + this.tb_1.Enabled = false; + this.cb_1.Enabled = true; + } } else { - tb_1.Hide(); + this.tb_1.Hide(); + this.cb_1.Hide(); } - btn_1.Text = text_OK; - btn_2.Text = text_Cancel; } + private void btn_2_Click(object sender, EventArgs e) { this.DialogResult = DialogResult.Cancel; @@ -54,11 +55,20 @@ private void btn_2_Click(object sender, EventArgs e) private void btn_1_Click(object sender, EventArgs e) { - text = tb_1.Text; + if (!string.IsNullOrWhiteSpace(this.tb_1.Text)) + { + this.text = this.tb_1.Text; + } + else if (!string.IsNullOrWhiteSpace(this.cb_1.Text)) + { + this.text = this.cb_1.Text; + } this.DialogResult = DialogResult.OK; this.Close(); } + + private void InputForm_KeyDown_1(object sender, KeyEventArgs e) { if (e.KeyCode == Keys.Escape) @@ -68,7 +78,14 @@ private void InputForm_KeyDown_1(object sender, KeyEventArgs e) } if (e.KeyCode == Keys.Enter) { - text = tb_1.Text; + if (!string.IsNullOrWhiteSpace(this.tb_1.Text)) + { + this.text = this.tb_1.Text; + } + else if (!string.IsNullOrWhiteSpace(this.cb_1.Text)) + { + this.text = this.cb_1.Text; + } this.DialogResult = DialogResult.OK; this.Close(); } @@ -76,32 +93,78 @@ private void InputForm_KeyDown_1(object sender, KeyEventArgs e) private void tb_1_KeyDown(object sender, KeyEventArgs e) { - if (e.KeyCode == Keys.Escape) - { - this.DialogResult = DialogResult.Cancel; - this.Close(); - } - if (e.KeyCode == Keys.Enter) - { - text = tb_1.Text; - this.DialogResult = DialogResult.OK; - this.Close(); - } + // if (e.KeyCode == Keys.Escape) + // { + // this.DialogResult = DialogResult.Cancel; + // this.Close(); + // } + // if (e.KeyCode == Keys.Enter) + // { + // if (tb_1.Visible) + // { + // text = tb_1.Text; + // } + // else if (cb_1.Visible) + // { + // text = cb_1.Text; + // } + // this.DialogResult = DialogResult.OK; + // this.Close(); + // } } private void btn_1_KeyDown(object sender, KeyEventArgs e) { - if (e.KeyCode == Keys.Escape) - { - this.DialogResult = DialogResult.Cancel; - this.Close(); - } - if (e.KeyCode == Keys.Enter) - { - text = tb_1.Text; - this.DialogResult = DialogResult.OK; - this.Close(); - } + //if (e.KeyCode == Keys.Escape) + //{ + // this.DialogResult = DialogResult.Cancel; + // this.Close(); + //} + //if (e.KeyCode == Keys.Enter) + //{ + // if (tb_1.Visible) + // { + // text = tb_1.Text; + // } + // else if (cb_1.Visible) + // { + // text = cb_1.Text; + // } + // this.DialogResult = DialogResult.OK; + // this.Close(); + //} + } + + private void InputForm_Load(object sender, EventArgs e) + { + + } + + private void cb_1_SelectedIndexChanged(object sender, EventArgs e) + { + + } + + private void cb_1_KeyDown(object sender, KeyEventArgs e) + { + //if (e.KeyCode == Keys.Escape) + //{ + // this.DialogResult = DialogResult.Cancel; + // this.Close(); + //} + //if (e.KeyCode == Keys.Enter) + //{ + // if (tb_1.Visible) + // { + // text = tb_1.Text; + // } + // else if (cb_1.Visible) + // { + // text = cb_1.Text; + // } + // this.DialogResult = DialogResult.OK; + // this.Close(); + //} } } } diff --git a/src/UI/Installer/AIToolSetup.2.6.95.exe b/src/UI/Installer/AIToolSetup.2.6.95.exe new file mode 100644 index 00000000..c959317d Binary files /dev/null and b/src/UI/Installer/AIToolSetup.2.6.95.exe differ diff --git a/src/UI/LogFileWriter.cs b/src/UI/LogFileWriter.cs new file mode 100644 index 00000000..fbbf6d85 --- /dev/null +++ b/src/UI/LogFileWriter.cs @@ -0,0 +1,839 @@ +using AITool; +using Microsoft.Win32.SafeHandles; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using System.Security.AccessControl; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +public class LogFileWriter : IDisposable +{ + //Private m_instance As LogWriter + private ConcurrentQueue logQueue; + private DateTime LastLogFlushedTimeUTC = DateTime.UtcNow; //UTC date is faster to obtain + private DateTime LastLogWrittenTime = DateTime.Now; + private DateTime LastLogRotateCheckTimeUTC = DateTime.MinValue; + private bool IsFlushing = false; + private bool FlushRightAway = false; + private long LogWriteCount = 0; + private bool RotateLogs = true; + //private bool m_ProcessAsCSV = false; + public int MaxLogFileAgeDays { get; set; } = 30; + public int MaxLogQueueAgeSecs { get; set; } = 10; + public long MaxLogQueueSize { get; set; } = 64; + + public string LogFile { get; set; } = ""; + public bool NoLog { get; set; } = false; + public long MaxLogSize { get; set; } = ((1024 * 2024) * 5); //5mb + /// + /// Private constructor to prevent instance creation + /// + public LogFileWriter(string InLogFile, bool RotateLogsSetting = true) + { + + if (InLogFile.ToLower() == "nolog") + { + this.NoLog = true; + return; + } + + + if (!string.IsNullOrWhiteSpace(InLogFile)) + { + this.LogFile = InLogFile; + } + + //if (!IsDirWritable(this.LogFile, true)) + //{ + // NoLog = true; + // Console.WriteLine("Error: Cannot write to folder: " + this.LogFile); + //} + + this.RotateLogs = RotateLogsSetting; + try + { + + this.logQueue = new ConcurrentQueue(); + + //Dim myCallback2 As New System.Threading.TimerCallback(AddressOf TimerTask) + //LogTimer = New System.Threading.Timer(myCallback2, Nothing, New TimeSpan(0, 0, 1), New TimeSpan(0, 0, 0, CInt(MaxLogAgeSecs))) + string Folder = Path.GetDirectoryName(this.LogFile); + + if (!Directory.Exists(Folder)) + { + Console.WriteLine("LogWriter: Creating folder " + Folder); + Directory.CreateDirectory(Folder); + } + + Task.Run(this.CheckLogEntriesToFlushLoop); + + //Console.WriteLine("LogWriter: New", , , False) + + } + catch (Exception ex) + { + this.NoLog = true; + Console.WriteLine("Error: " + ex.Message); + } + } + + private async Task CheckLogEntriesToFlushLoop() + { + try + { + Thread.CurrentThread.Priority = ThreadPriority.BelowNormal; + + Stopwatch sw = new Stopwatch(); + while (true) + { + sw.Reset(); + sw.Start(); + while (!(this.FlushRightAway || (sw.ElapsedMilliseconds / 1000.0) >= this.MaxLogQueueAgeSecs)) + { + await Task.Delay(50); //Thread.Sleep(250) + } + if (this.RotateLogs) //this only runs once a day + { + await Task.Run(this.CleanAndRotateLogsFolder); + } + await this.FlushLog(); + } + } + catch (Exception ex) + { + Console.WriteLine("Error: " + ex.Message); + } + Console.WriteLine("Out of CheckLogEntriesToFlushLoop()"); + } + + + + public void CleanAndRotateLogsFolder() + { + + if (this.LastLogRotateCheckTimeUTC.DayOfYear == DateTime.UtcNow.DayOfYear) + { + return; + } + + try + { + + //filename_2015-08-05_13_08_46_(info)_[info] + + string Folder = Path.GetDirectoryName(this.LogFile); + string Ext = Path.GetExtension(this.LogFile); + + string[] files = Directory.GetFiles(Folder, "*" + Ext); + int DelCnt = 0; + int RenCnt = 0; + foreach (string CurFile in files) + { + FileInfo fi = new FileInfo(CurFile); + DateTime FileNameDate = Global.GetTimeFromFileName(CurFile); + if (!FileNameDate.Equals(DateTime.MinValue) && (fi.LastWriteTimeUtc < DateTime.UtcNow.AddDays(-this.MaxLogFileAgeDays))) + { + DelCnt = DelCnt + 1; + fi.Delete(); + } + else if (FileNameDate.Equals(DateTime.MinValue) && (CurFile.ToLower() == this.LogFile.ToLower())) //this is the main log filename if it doesnt have a date in it. + { + + if ((this.MaxLogSize > 0 && fi.Length >= this.MaxLogSize) || (DateTime.UtcNow.DayOfYear != fi.LastWriteTimeUtc.DayOfYear)) + { + Console.WriteLine("LogWriter.New: Renaming log file because size or age: Size: " + fi.Length + ", Max: " + this.MaxLogSize + "..."); + string NewFileName = Path.GetFileNameWithoutExtension(CurFile); + NewFileName = NewFileName + "_" + fi.LastWriteTimeUtc.ToString("s").Replace(":", "_").Replace("T", "_"); //& "_{UTC}" + NewFileName = Folder + "\\" + NewFileName + Ext; + try + { + if (File.Exists(NewFileName)) + { + File.Delete(NewFileName); + } + fi.MoveTo(NewFileName); + RenCnt = RenCnt + 1; + } + catch (Exception ex) + { + Console.WriteLine("Error: " + ex.Message); + } + } + + } + } + + if (DelCnt > 0) + { + Console.WriteLine("Deleted " + DelCnt + " log files older than " + this.MaxLogFileAgeDays + " days old in " + Folder); + } + if (RenCnt > 0) + { + Console.WriteLine("Renamed " + RenCnt + " log files in " + Folder); + } + + } + catch (Exception ex) + { + Console.WriteLine("Error: " + ex.Message); + } + finally + { + this.LastLogRotateCheckTimeUTC = DateTime.UtcNow; + } + + + } + + #region IDisposable Support + private bool disposedValue; // To detect redundant calls + + // IDisposable + protected virtual async Task Dispose(bool disposing) + { + if (!this.disposedValue) + { + if (disposing) + { + // TODO: dispose managed state (managed objects). + //Console.WriteLine("LogWriter: Disposing...", , , False) + if (!this.NoLog) + { + await this.FlushLog(); + } + } + + // TODO: free unmanaged resources (unmanaged objects) and override Finalize() below. + // TODO: set large fields to null. + } + this.disposedValue = true; + } + + + // This code added by Visual Basic to correctly implement the disposable pattern. + public async void Dispose() + { + // Do not change this code. Put cleanup code in Dispose(disposing As Boolean) above. + await this.Dispose(true); + GC.SuppressFinalize(this); + } + #endregion + + /// + /// The single instance method that writes to the log file + /// + /// The message to write to the log + public void WriteToLog(string message, bool Flush = false) + { + + try + { + if (this.NoLog) + { + return; + } + ClsLogDetailItem DI = new ClsLogDetailItem(); + DI.Message = message; + this.WriteToLog(DI, Flush); + } + catch (Exception ex) + { + Console.WriteLine("Error: " + ex.Message); + } + finally + { + + } + } + + + //public void WriteAllLines(string[] DetailItms) + //{ + + // try + // { + // if (NoLog) + // { + // return; + // } + + // foreach (string lin in DetailItms) + // { + // if (lin != null) + // { + // // Create the entry and push to the Queue + // ClsLogDetailItem DetailItm = new ClsLogDetailItem(); + + // LogWriteCount = LogWriteCount + 1; + + // DetailItm.Idx = LogWriteCount; + // DetailItm.TimeUTC = DateTime.UtcNow; + // DetailItm.Message = lin; + // logQueue.Enqueue(DetailItm); + // if (LogWriteCount == long.MaxValue - 8) + // { + // LogWriteCount = 1; + // } + // } + // } + + // //// If we have reached the Queue Size then flush the Queue + // //if (Flush) + // //{ + // // //Await FlushLog() + // // FlushRightAway = true; + // //} + // } + // catch (Exception ex) + // { + // Console.WriteLine("Error: " + ex.Message); + // } + // finally + // { + + // } + //} + public void WriteToLog(ClsLogDetailItem DetailItm, bool Flush = false) + { + + try + { + if (this.NoLog) + { + return; + } + // Create the entry and push to the Queue + this.LogWriteCount = this.LogWriteCount + 1; + + DetailItm.Idx = this.LogWriteCount; + DetailItm.TimeUTC = DateTime.UtcNow; + this.logQueue.Enqueue(DetailItm); + if (this.LogWriteCount == long.MaxValue - 8) + { + this.LogWriteCount = 1; + } + // If we have reached the Queue Size then flush the Queue + if (this.LogWriteCount == 1 || Flush || this.logQueue.Count >= this.MaxLogQueueSize) //OrElse DoPeriodicFlush() Then + { + //Await FlushLog() + this.FlushRightAway = true; + } + } + catch (Exception ex) + { + Console.WriteLine("Error: " + ex.Message); + } + finally + { + + } + } + + /// + /// Flushes the Queue to the physical log file + /// + private async Task FlushLog() + { + + + try + { + if (this.IsFlushing) + { + Console.WriteLine("(AlreadyFlushing?)"); + + return; + } + this.IsFlushing = true; + long LastQCnt = this.logQueue.Count; + if (this.logQueue.Count > 0) + { + //Console.WriteLine("LogWriter:FlushLog: Flushing " & logQueue.Count & " items on thread " & Thread.CurrentThread.ManagedThreadId & ", Secs since last flush: " & (Now - LastFlushed).TotalSeconds & "...", , , False) + Stopwatch sw = Stopwatch.StartNew(); + using (IOFile FileWriter = new IOFile(this.LogFile)) + { + FileWriter.WriteQueue = this.logQueue; + await FileWriter.WriteFile(); + this.LastLogWrittenTime = FileWriter.LastLogWriteTime; + } + sw.Stop(); + if (sw.ElapsedMilliseconds >= 1000) + { + Console.WriteLine($"LogWriter:FlushLog: DONE Flushing in {sw.ElapsedMilliseconds}ms, LogQueue: {LastQCnt} items, File: {this.LogFile}"); + } + } + this.LastLogFlushedTimeUTC = DateTime.UtcNow; + } + catch (Exception ex) + { + Console.WriteLine("Error: " + ex.Msg()); + } + finally + { + + this.IsFlushing = false; + this.LastLogFlushedTimeUTC = DateTime.UtcNow; + + } + this.FlushRightAway = false; + + } + +} + +public class ClsLogDetailItem +{ + public DateTime TimeUTC { get; set; } = DateTime.MinValue; + public long Idx { get; set; } = 0; + public string MemberName { get; set; } = ""; + public string Message { get; set; } = ""; + public bool IsDbg { get; set; } = false; + public bool HasInfo { get; set; } = false; + public bool HasWarn { get; set; } = false; + public bool HasErr { get; set; } = false; + public double QueueWaitMS { get; set; } = 0; + +} + +public class IOFile : IDisposable +{ + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + private extern static SafeFileHandle CreateFile(string lpFileName, FileSystemRights dwDesiredAccess, FileShare dwShareMode, IntPtr securityAttrs, FileMode dwCreationDisposition, FileOptions dwFlagsAndAttributes, IntPtr hTemplateFile); + + private const int ERROR_SHARING_VIOLATION = 32; + private const int ERROR_LOCK_VIOLATION = 33; + + //Public Property MaxFileSize As Long = 0 + // Fields + private Encoding m_encoding = new UTF8Encoding(); //AsciiEncoding + private FileStream m_fileStream; + private SafeFileHandle m_fileHandle; + private bool m_WasLocked = false; + private string m_path = string.Empty; + private List m_strings; + //private bool m_ProcessAsCSV = false; + + public bool FileExists = false; + + public DateTime LastLogWriteTime = DateTime.MinValue; + public ConcurrentQueue WriteQueue { get; set; } + + private bool IsFileInUse(System.IO.FileShare fshare = FileShare.Read, System.Security.AccessControl.FileSystemRights fSysRights = FileSystemRights.Read, System.IO.FileAccess fAccess = FileAccess.Read, System.IO.FileMode fMode = FileMode.Open, System.IO.FileOptions fOptions = FileOptions.None) + { + bool inUse = false; + + + try + { + do + { + this.m_fileHandle = CreateFile(this.m_path, fSysRights, fshare, IntPtr.Zero, fMode, FileOptions.None, IntPtr.Zero); + + if (this.m_fileHandle.IsInvalid) + { + int LastErr = Marshal.GetLastWin32Error(); + //Console.WriteLine("IsFileInUse LastErr: " & LastErr & ", " & New Win32Exception(LastErr).Message, , , False) + inUse = true; + if (LastErr == ERROR_SHARING_VIOLATION || LastErr == ERROR_LOCK_VIOLATION) + { + if (!this.m_fileHandle.IsClosed) + { + this.m_fileHandle.Close(); + } + } + else + { + //Me.m_fileStream = New FileStream(m_fileHandle, fAccess) + } + } + else + { + this.m_fileStream = new FileStream(this.m_fileHandle, fAccess); + + } + break; + } while (true); + + } + catch (Exception ex) + { + Console.WriteLine("IsFileInUse Error: " + ex.Msg()); + inUse = true; + } + finally + { + + } + + return inUse; + } + // Methods + public IOFile(string LogPath) + { + + + try + { + this.m_path = LogPath; + //this.m_ProcessAsCSV = ProcessAsCSV; + this.CloseHandles(); + this.FileExists = File.Exists(this.m_path); + if (this.FileExists) + { + this.LastLogWriteTime = (new FileInfo(this.m_path)).LastWriteTime; + } + + } + catch (Exception ex) + { + Console.WriteLine("Error creating IOFile logger: " + ex.Message); + } + } + + public bool Delete() + { + try + { + if (this.FileExists) + { + File.Delete(this.m_path); + this.LastLogWriteTime = DateTime.MinValue; + this.FileExists = false; + } + + } + catch (Exception ex) + { + Console.WriteLine("Error deleting file: " + ex.Message); + } + //INSTANT C# NOTE: Inserted the following 'return' since all code paths must return a value in C#: + return false; + } + + #region IDisposable Support + private bool disposedValue; // To detect redundant calls + + // IDisposable + protected virtual void Dispose(bool disposing) + { + if (!this.disposedValue) + { + if (disposing) + { + // TODO: dispose managed state (managed objects). + //Console.WriteLine("IOFile: Disposing...", , , False) + this.CloseHandles(); + } + + // TODO: free unmanaged resources (unmanaged objects) and override Finalize() below. + // TODO: set large fields to null. + } + this.disposedValue = true; + } + + // TODO: override Finalize() only if Dispose(ByVal disposing As Boolean) above has code to free unmanaged resources. + //Protected Overrides Sub Finalize() + // ' Do not change this code. Put cleanup code in Dispose(ByVal disposing As Boolean) above. + // Dispose(False) + // MyBase.Finalize() + //End Sub + + // This code added by Visual Basic to correctly implement the disposable pattern. + public void Dispose() + { + // Do not change this code. Put cleanup code in Dispose(disposing As Boolean) above. + this.Dispose(true); + GC.SuppressFinalize(this); + } + #endregion + private void CloseHandles() + { + try + { + if (this.m_fileHandle != null && !this.m_fileHandle.IsClosed) + { + //Console.WriteLine("FileIO:CloseHandles: m_FileHandle was not closed, closing...", , , False) + this.m_fileHandle.Close(); + this.m_fileHandle.Dispose(); + } + } + catch (Exception ex) + { + Console.WriteLine("FileIO: Error disposing filehandle: " + ex.Message); + } + try + { + if (this.m_fileStream != null) + { + //Console.WriteLine("FileIO:CloseHandles: m_Filestream was not closed, closing...", , , False) + this.m_fileStream.Close(); + this.m_fileStream.Dispose(); + } + } + catch (Exception ex) + { + Console.WriteLine("FileIO: Error disposing filestream: " + ex.Message); + } + } + private async Task LockFile(System.IO.FileShare fshare = FileShare.Read, System.Security.AccessControl.FileSystemRights fSysRights = FileSystemRights.Read, System.IO.FileAccess fAccess = FileAccess.Read, System.IO.FileMode fMode = FileMode.Open, System.IO.FileOptions fOptions = FileOptions.None) + { + + + int num = 0; + bool flag = false; + Stopwatch SW = new Stopwatch(); + SW.Start(); + while (!flag && (num < 2000) && (SW.ElapsedMilliseconds < 30000)) + { + //Try + if (!this.IsFileInUse(fshare, fSysRights, fAccess, fMode, fOptions)) + { + if (fMode == FileMode.OpenOrCreate || fAccess == FileAccess.ReadWrite || fAccess == FileAccess.Write) + { + try + { + this.m_fileStream.Lock(this.m_fileStream.Length, 0xFFFF); + this.m_WasLocked = true; + this.FileExists = true; + } + catch (Exception ex) + { + Console.WriteLine(" LockFile Error: " + ex.Message); + } + } + } + else + { + num += 1; + await Task.Delay(10); //'Thread.Sleep(10) + continue; + } + //Catch exception1 As Exception + // num += 1 + // Thread.Sleep(10) + // Continue Do + //End Try + flag = true; + } + SW.Stop(); + if (num > 0) + { + Console.WriteLine(" Lockfile lock time: " + SW.ElapsedMilliseconds + "ms, errcount=" + num); + } + + return flag; + } + + //public virtual async Task ReadFile() + //{ + // long position = 0; + // try + // { + // return await this.ReadFile(position); + // } + // catch (Exception exception1) + // { + // return false; + // } + //} + + //public virtual async Task ReadFile(long position) + //{ + // this.m_strings = new List(); + // if (!(await FileExistsAsync(this.m_path))) + // { + // return false; + // } + // FileStream stream = null; + // try + // { + // stream = new FileStream(this.m_path, FileMode.Open, FileAccess.Read, (FileShare.Delete | FileShare.ReadWrite)); + // } + // catch (Exception obj1) + // { + // return false; + // } + // StreamReader reader = null; + // try + // { + // stream.Seek(position, SeekOrigin.Begin); + // if (position == 0) + // { + // reader = new StreamReader(stream, true); + // } + // else + // { + // reader = new StreamReader(stream, this.m_encoding); + // } + // } + // catch (Exception obj2) + // { + // stream.Close(); + // return false; + // } + // bool flag = false; + // string item = string.Empty; + // try + // { + // do + // { + // item = await reader.ReadLineAsync(); + // if (item != null) + // { + // this.m_strings.Add(item); + // } + // else + // { + // break; + // } + // } while (true); + // if (position == 0) + // { + // this.m_encoding = reader.CurrentEncoding; + // } + // position = stream.Position; + // flag = true; + // } + // catch (Exception exception1) + // { + // } + // reader.Close(); + // stream.Close(); + // return flag; + //} + + public async Task WriteFile() + { + + + bool Ret = false; + Stopwatch SW = new Stopwatch(); + SW.Start(); + try + { + bool Locked = await this.LockFile(FileShare.Read, FileSystemRights.CreateFiles & FileSystemRights.Write, FileAccess.Write, FileMode.OpenOrCreate); + if (Locked) + { + StreamWriter writer = null; + bool Err = false; + try + { + writer = new StreamWriter(this.m_fileStream, this.m_encoding); //With {.AutoFlush = True} + } + catch (Exception obj2) + { + Console.WriteLine("Error creating streamwriter: " + obj2.Message); + if (writer != null) + { + writer.Close(); + } + this.m_fileStream.Close(); + Err = true; + } + if (Err == true) + { + return Ret; + } + long position = 0; + try + { + position = this.m_fileStream.Length; + this.m_fileStream.Seek(0, SeekOrigin.End); + ClsLogDetailItem OutQueueItem = new ClsLogDetailItem(); + + //if (position == 0) + //{ + // //Convert the class to tab delimited format - HEADER: + // string OutCsv = null; + // if (this.m_ProcessAsCSV) + // { + // OutCsv = ClsToCSV(OutQueueItem, true); + // //write header + // await writer.WriteLineAsync(OutCsv); + // } + // else + // { + // if (OutQueueItem.Message.Length > 0) //for some reason we are getting an empty line? + // { + // OutCsv = OutQueueItem.Message; + // //write header + // await writer.WriteLineAsync(OutCsv); + // } + // } + //} + + while (this.WriteQueue.TryDequeue(out OutQueueItem)) + { + //update time in queue + OutQueueItem.QueueWaitMS = (DateTime.UtcNow - OutQueueItem.TimeUTC).TotalMilliseconds; + //Convert the class to tab delimited format: + string OutCsv = null; + //if (this.m_ProcessAsCSV) + //{ + // OutCsv = ClsToCSV(OutQueueItem, false); + //} + //else + //{ + OutCsv = OutQueueItem.Message; + //} + await writer.WriteLineAsync(OutCsv); + } + await writer.FlushAsync(); + Ret = true; + } + catch (Exception exception1) + { + Console.WriteLine("Error writing to streamwriter: " + exception1.Message); + } + try + { + if (position > 0 && this.m_WasLocked) + { + this.m_fileStream.Unlock(position, 0xFFFF); + } + } + catch (IOException exception) + { + Ret = false; + Console.WriteLine("Error unlocking file: " + exception.Message); //already unlocked + } + + writer.Close(); + + } + else + { + Ret = false; + } + + } + catch (Exception ex) + { + Console.WriteLine("Error: " + ex.Message); + } + finally + { + + if (Ret) + { + this.LastLogWriteTime = DateTime.Now; + } + } + + return Ret; + } + + // Properties + public List Strings => this.m_strings; + + +} + + + + diff --git a/src/UI/Logo-Error-old.ico b/src/UI/Logo-Error-old.ico new file mode 100644 index 00000000..32370c9f Binary files /dev/null and b/src/UI/Logo-Error-old.ico differ diff --git a/src/UI/Logo-Error.ico.old b/src/UI/Logo-Error.ico.old new file mode 100644 index 00000000..32370c9f Binary files /dev/null and b/src/UI/Logo-Error.ico.old differ diff --git a/src/UI/Logo_old.ico b/src/UI/Logo_old.ico new file mode 100644 index 00000000..5e3da641 Binary files /dev/null and b/src/UI/Logo_old.ico differ diff --git a/src/UI/MQTTClient.cs b/src/UI/MQTTClient.cs new file mode 100644 index 00000000..d61f1d30 --- /dev/null +++ b/src/UI/MQTTClient.cs @@ -0,0 +1,411 @@ +using MQTTnet; +using MQTTnet.Client; +//using MQTTnet.Client.Connecting; +//using MQTTnet.Client.Options; +//using MQTTnet.Client.Publishing; +//using MQTTnet.Client.Subscribing; +using MQTTnet.Protocol; + +using System; +using System.Diagnostics; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +using static AITool.AITOOL; + +namespace AITool +{ + public class MQTTClient:IDisposable + { + public bool IsSubscribed = false; + public bool IsConnected = false; + + int portint = 0; + string server = ""; + string port = ""; + string LastTopic = ""; + string LastPayload = ""; + bool LastRetain = false; + + MqttFactory factory = null; + IMqttClient mqttClient = null; + MqttClientOptions options = null; + MqttClientConnectResult cres = null; + + public async void Dispose() + { + + if (mqttClient != null) + { + + if (mqttClient.IsConnected) + { + Log($"Debug: MQTT: Disconnecting from server."); + try + { + await mqttClient.DisconnectAsync(); + } + catch (Exception ex) + { + //dont throw ERROR in the log if fail to disconnect + Log($"Debug: MQTT: Could not disconnect from server, got: {ex.Msg()}"); + } + } + else + { + Log($"Debug: MQTT: Already disconnected from server, no need to disconnect."); + } + + + IsConnected = false; + + mqttClient.Dispose(); + + } + + + + } + + public MQTTClient() + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + try + { + this.factory = new MqttFactory(); + this.mqttClient = factory.CreateMqttClient(); + + } + catch (Exception ex) + { + Log($"Error: {ex.Msg()}"); + } + } + + private string LastServerPortUser = ""; + public async Task Connect() + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + bool ret = false; + try + { + if (this.mqttClient.IsConnected) + await this.mqttClient.DisconnectAsync(); + + this.server = AppSettings.Settings.mqtt_serverandport.GetWord("", ":"); + this.port = AppSettings.Settings.mqtt_serverandport.GetWord(":", "/"); + this.portint = 0; + + if (!string.IsNullOrEmpty(this.port)) + this.portint = Convert.ToInt32(this.port); + if (this.portint == 0 && AppSettings.Settings.mqtt_UseTLS) + { + this.portint = 8883; + } + else if (this.portint == 0) + { + this.portint = 1883; + } + + bool IsWebSocket = (AppSettings.Settings.mqtt_serverandport.IndexOf("ws://", StringComparison.OrdinalIgnoreCase) >= 0 || + AppSettings.Settings.mqtt_serverandport.IndexOf("/mqtt", StringComparison.OrdinalIgnoreCase) >= 0 || + AppSettings.Settings.mqtt_serverandport.IndexOf("wss://", StringComparison.OrdinalIgnoreCase) >= 0); + + bool UseTLS = (AppSettings.Settings.mqtt_UseTLS || portint == 8883 || AppSettings.Settings.mqtt_serverandport.IndexOf("wss://", StringComparison.OrdinalIgnoreCase) >= 0); + + //bool UseCreds = (!string.IsNullOrWhiteSpace(AppSettings.Settings.mqtt_username)); + + + + //===================================================================== + //Seems like there should be a better way here with this Options builder... + //I dont see an obvious way to directly modify options without the builder + //and I cant seem to put IF statements around each part of the option builder + //parameters. + //===================================================================== + + var lw = new MqttApplicationMessage() + { + Topic = AppSettings.Settings.mqtt_LastWillTopic, + Payload = Encoding.UTF8.GetBytes(AppSettings.Settings.mqtt_LastWillPayload), + QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce, + Retain = true + }; + + if (UseTLS) + { + if (IsWebSocket) + { + + options = new MqttClientOptionsBuilder() + .WithClientId(AppSettings.Settings.mqtt_clientid) + .WithWebSocketServer(AppSettings.Settings.mqtt_serverandport) + .WithCredentials(AppSettings.Settings.mqtt_username, AppSettings.Settings.mqtt_password) + .WithTls() + .WithWillTopic(AppSettings.Settings.mqtt_LastWillTopic) + .WithWillPayload(Encoding.UTF8.GetBytes(AppSettings.Settings.mqtt_LastWillPayload)) + .WithWillRetain(true) + .WithWillQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce) + .WithCleanSession() + .Build(); + + } + else + { + options = new MqttClientOptionsBuilder() + .WithClientId(AppSettings.Settings.mqtt_clientid) + .WithTcpServer(server, portint) + .WithCredentials(AppSettings.Settings.mqtt_username, AppSettings.Settings.mqtt_password) + .WithTls() + .WithWillTopic(AppSettings.Settings.mqtt_LastWillTopic) + .WithWillPayload(Encoding.UTF8.GetBytes(AppSettings.Settings.mqtt_LastWillPayload)) + .WithWillRetain(true) + .WithWillQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce) + .WithCleanSession() + .Build(); + } + + + } + else + { + if (IsWebSocket) + { + options = new MqttClientOptionsBuilder() + .WithClientId(AppSettings.Settings.mqtt_clientid) + .WithWebSocketServer(AppSettings.Settings.mqtt_serverandport) + .WithCredentials(AppSettings.Settings.mqtt_username, AppSettings.Settings.mqtt_password) + .WithWillTopic(AppSettings.Settings.mqtt_LastWillTopic) + .WithWillPayload(Encoding.UTF8.GetBytes(AppSettings.Settings.mqtt_LastWillPayload)) + .WithWillRetain(true) + .WithWillQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce) + .WithCleanSession() + .Build(); + } + else + { + options = new MqttClientOptionsBuilder() + .WithClientId(AppSettings.Settings.mqtt_clientid) + .WithTcpServer(server, portint) + .WithCredentials(AppSettings.Settings.mqtt_username, AppSettings.Settings.mqtt_password) + .WithWillTopic(AppSettings.Settings.mqtt_LastWillTopic) + .WithWillPayload(Encoding.UTF8.GetBytes(AppSettings.Settings.mqtt_LastWillPayload)) + .WithWillRetain(true) + .WithWillQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce) + .WithCleanSession() + .Build(); + + } + + + + } + + //mqttClient.UseDisconnectedHandler(async e => + + mqttClient.DisconnectedAsync += async e => + { + IsConnected = false; + string excp = ""; + if (e.Exception != null) + { + excp = e.Exception.Message; + } + Log($"Debug: MQTT: ### DISCONNECTED FROM SERVER ### - Reason: {e.Reason}, ClientWasDisconnected: {e.ClientWasConnected}, {excp}"); + + //reconnect here if needed? + }; + + + //mqttClient.UseApplicationMessageReceivedHandler(async e => + mqttClient.ApplicationMessageReceivedAsync += async e => + { + Log($"Debug: MQTT: ### RECEIVED APPLICATION MESSAGE ###"); + Log($"Debug: MQTT: + Topic = {e.ApplicationMessage.Topic}"); + Log($"Debug: MQTT: + Payload = {Encoding.UTF8.GetString(e.ApplicationMessage.Payload).Truncate()}"); + Log($"Debug: MQTT: + QoS = {e.ApplicationMessage.QualityOfServiceLevel}"); + Log($"Debug: MQTT: + Retain = {e.ApplicationMessage.Retain}"); + Log(""); + + }; + + + //mqttClient.UseConnectedHandler(async e => + mqttClient.ConnectedAsync += async e => + { + IsConnected = true; + Log($"Debug: MQTT: ### CONNECTED WITH SERVER '{AppSettings.Settings.mqtt_serverandport}' ### - Result: {e.ConnectResult.ResultCode}, '{e.ConnectResult.ReasonString}'"); + + + MqttApplicationMessage ma = new MqttApplicationMessageBuilder() + .WithTopic(AppSettings.Settings.mqtt_LastWillTopic) + .WithPayload(AppSettings.Settings.mqtt_OnlinePayload) + .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce) + .WithRetainFlag(true) + .Build(); + + Log($"Debug: MQTT: Sending '{AppSettings.Settings.mqtt_OnlinePayload}' message..."); + MqttClientPublishResult res = await mqttClient.PublishAsync(ma, CancellationToken.None); + + //if (!string.IsNullOrWhiteSpace(this.LastTopic)) + //{ + // // Subscribe to the topic + // MqttClientSubscribeResult res = await mqttClient.SubscribeAsync(this.LastTopic, MQTTnet.Protocol.MqttQualityOfServiceLevel.AtLeastOnce); + + // IsSubscribed = true; + + // Log($"Debug: MQTT: ### SUBSCRIBED to topic '{this.LastTopic}'"); + //} + }; + + Log($"Debug: MQTT: Connecting to server '{this.server}:{this.portint}' with ClientID '{AppSettings.Settings.mqtt_clientid}', Username '{AppSettings.Settings.mqtt_username}', Password '{AppSettings.Settings.mqtt_username.ReplaceChars('*')}'..."); + + + cres = await mqttClient.ConnectAsync(options, CancellationToken.None); + + } + catch (Exception ex) + { + + Log($"Error: {ex.Msg()}"); + } + return ret; + } + + public async Task PublishAsync(string topic, string payload, bool retain, ClsImageQueueItem CurImg) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + MqttClientPublishResult res = null; + Stopwatch sw = Stopwatch.StartNew(); + + this.LastRetain = retain; + this.LastPayload = payload; + this.LastTopic = topic; + if (string.IsNullOrWhiteSpace(topic)) + { + this.LastTopic = Guid.NewGuid().ToString(); + } + + if (!this.IsConnected || this.LastServerPortUser != AppSettings.Settings.mqtt_serverandport + AppSettings.Settings.mqtt_password + AppSettings.Settings.mqtt_username + AppSettings.Settings.mqtt_clientid) + { + await this.Connect(); + this.LastServerPortUser = AppSettings.Settings.mqtt_serverandport + AppSettings.Settings.mqtt_password + AppSettings.Settings.mqtt_username + AppSettings.Settings.mqtt_clientid; + } + + if (!this.IsConnected) + return res; + + await Task.Run(async () => + { + + + try + { + + + Log($"Debug: MQTT: Sending topic '{this.LastTopic}' with payload '{this.LastPayload}' to server '{this.server}:{this.portint}'..."); + + if (cres != null && mqttClient.IsConnected && cres.ResultCode == MqttClientConnectResultCode.Success) + { + + IsConnected = true; + + MqttApplicationMessage ma; + + if (CurImg != null) + { + + ma = new MqttApplicationMessageBuilder() + .WithTopic(this.LastTopic) + .WithPayload(CurImg.ToMemStream()) + .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce) + .WithRetainFlag(this.LastRetain) + .Build(); + + res = await mqttClient.PublishAsync(ma, CancellationToken.None); + + + if (res.ReasonCode == MqttClientPublishReasonCode.Success) + { + Log($"Debug: MQTT: ...Sent image in {sw.ElapsedMilliseconds}ms, Reason: '{res.ReasonCode}' ({Convert.ToInt32(res.ReasonCode)} - '{res.ReasonString}')"); + } + else + { + Log($"Error: MQTT: sending image: ({sw.ElapsedMilliseconds}ms) Reason: '{res.ReasonCode}' ({Convert.ToInt32(res.ReasonCode)} - '{res.ReasonString}')"); + } + + } + else + { + ma = new MqttApplicationMessageBuilder() + .WithTopic(this.LastTopic) + .WithPayload(this.LastPayload) + .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce) + .WithRetainFlag(this.LastRetain) + .Build(); + + res = await mqttClient.PublishAsync(ma, CancellationToken.None); + + //Success = 0, + // NoMatchingSubscribers = 0x10, + // UnspecifiedError = 0x80, + // ImplementationSpecificError = 0x83, + // NotAuthorized = 0x87, + // TopicNameInvalid = 0x90, + // PacketIdentifierInUse = 0x91, + // QuotaExceeded = 0x97, + // PayloadFormatInvalid = 0x99 + + if (res.ReasonCode == MqttClientPublishReasonCode.Success) + { + Log($"Debug: MQTT: ...Sent in {sw.ElapsedMilliseconds}ms, Reason: '{res.ReasonCode}' ({Convert.ToInt32(res.ReasonCode)} - '{res.ReasonString}')"); + } + else + { + Log($"Error: MQTT: sending: ({sw.ElapsedMilliseconds}ms) Reason: '{res.ReasonCode}' ({Convert.ToInt32(res.ReasonCode)} - '{res.ReasonString}')"); + } + + } + + } + else if (cres != null) + { + IsConnected = false; + Log($"Error: MQTT: connecting: ({sw.ElapsedMilliseconds}ms) Result: '{cres.ResultCode}' - '{cres.ReasonString}'"); + } + else + { + IsConnected = false; + Log($"Error: MQTT: Error connecting: ({sw.ElapsedMilliseconds}ms) cres=null"); + } + + + + + + } + catch (Exception ex) + { + + Log($"Error: MQTT: Unexpected Problem: Topic '{this.LastTopic}' Payload '{this.LastPayload}': " + ex.Msg()); + } + finally + { + + } + + }); + + + return res; + + + } + + } +} diff --git a/src/UI/MaskManager.cs b/src/UI/MaskManager.cs new file mode 100644 index 00000000..8bd88a7d --- /dev/null +++ b/src/UI/MaskManager.cs @@ -0,0 +1,562 @@ +using Newtonsoft.Json; + +using System; +using System.Collections.Generic; +using System.Timers; + +using static AITool.AITOOL; + +namespace AITool +{ + public class MaskManager + { + public enum RemoveEvent + { + Timer, + Detection + } + + private bool _maskingEnabled = true; + public bool MaskingEnabled + { + get => this._maskingEnabled; + set + { + this._maskingEnabled = value; + StartStopMaskTimer(); + + } + } + + public int MaskRemoveMins { get; set; } = 5; //how many minutes to keep masked objects that are not visible + public int MaxMaskUnusedDays { get; set; } = 60; //If a static mask has not been used in this many days, remove it. + public int HistorySaveMins { get; set; } = 5; //how long to store detected objects in history before purging list + public int HistoryThresholdCount { get; set; } = 2; //number of times object is seen in same position before moving it to the masked_positions list + public double PercentMatch { get; set; } = 85; //minimum percentage match to be considered a match + public int MaskRemoveThreshold { get; set; } = 2; //number of times object is NOT seen before being removed by the cleanup timer + public string Camera { get; set; } = ""; + public List LastPositionsHistory { get; set; } //list of last detected object positions during defined time period - history_save_mins + public List MaskedPositions { get; set; } //stores dynamic masked object list (created in default constructor) + + private DateTime _lastDetectionDate; + public DateTime LastDetectionDate + { + get => this._lastDetectionDate; + set + { + this._lastDetectionDate = value; + this.CleanUpExpiredMasks(RemoveEvent.Detection); + } + } + + public string Objects { get; set; } = ""; + public ClsRelevantObjectManager MaskTriggeringObjects { get; set; } = null; + + public ObjectScale ScaleConfig { get; set; } + + private object _maskLockObject = new object(); + private Timer _cleanMaskTimer = new Timer(); + private ElapsedEventHandler ev; + private bool timerinfodisplayed = false; + + //I think JsonConstructor may not be needed, but adding anyway -Vorlon + [JsonConstructor] + public MaskManager() + { + this.LastPositionsHistory = new List(); + this.MaskedPositions = new List(); + this.ScaleConfig = new ObjectScale(); + + this._cleanMaskTimer.Interval = 60000; // 1min = 60,000ms + StartStopMaskTimer(); + } + + private void StartStopMaskTimer() + { + if (this._maskingEnabled && !this._cleanMaskTimer.Enabled) + { + //register event handler to run clean history every minute + ev = new System.Timers.ElapsedEventHandler(this.cleanMaskEvent); + this._cleanMaskTimer.Elapsed += ev; + + this._cleanMaskTimer.Start(); + Log($"Debug: Mask timer started for camera '{this.Camera}' at interval {this._cleanMaskTimer.Interval}."); + } + else if (!this._maskingEnabled && this._cleanMaskTimer.Enabled) + { + //deregister event handler to run clean history every minute + this._cleanMaskTimer.Elapsed -= ev; + this._cleanMaskTimer.Stop(); + Log($"Debug: Mask timer stopped for camera '{this.Camera}'"); + } + else if (!timerinfodisplayed) + { + timerinfodisplayed = true; //we are doing this because on first initialization the camera name is not available. + Log($"Debug: Mask timer updated for '{this.Camera}'. Started={this._cleanMaskTimer.Enabled}, at interval {this._cleanMaskTimer.Interval}."); + } + } + + public void Update(Camera cam) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + lock (this._maskLockObject) + { + this.Camera = cam.Name; + + //This will run on save/load settings + if (this.PercentMatch < 1) + { + this.PercentMatch = this.PercentMatch * 100; + } + + if (cam.maskManager.ScaleConfig == null) + cam.maskManager.ScaleConfig = new ObjectScale(); + + if (cam.maskManager.MaskTriggeringObjects == null || !cam.maskManager.Objects.IsEmpty()) + { + cam.maskManager.MaskTriggeringObjects = new ClsRelevantObjectManager(cam.maskManager.Objects, "DynamicMask", cam); + cam.maskManager.Objects = ""; + } + + + foreach (ObjectPosition op in this.LastPositionsHistory) + { + //Update PercentMatch since it could have been changed since mask created + op.PercentMatch = this.PercentMatch; + + //update the camera name since it could have been renamed since the mask was created + op.CameraName = cam.Name; + + //update last seen date if hasnt been set + if (op.LastSeenDate == DateTime.MinValue) + { + op.LastSeenDate = op.CreateDate; + } + //if null image from older build, force to have the last image from the camera: + if (op.ImagePath == null || string.IsNullOrEmpty(op.ImagePath)) + { + //first, set to empty string rather than null to fix bug I saw + op.ImagePath = ""; + //next, fill with most recent image with detections + if (!string.IsNullOrEmpty(cam.last_image_file_with_detections)) + { + op.ImagePath = cam.last_image_file_with_detections; + } + else if (!string.IsNullOrEmpty(cam.last_image_file)) + { + op.ImagePath = cam.last_image_file; + } + } + } + + foreach (ObjectPosition op in this.MaskedPositions) + { + //Update PercentMatch since it could have been changed since mask created + op.PercentMatch = this.PercentMatch; + + //update the camera name since it could have been renamed since the mask was created + op.CameraName = cam.Name; + + //update last seen date if hasnt been set + if (op.LastSeenDate == DateTime.MinValue) + { + op.LastSeenDate = op.CreateDate; + } + //if null image from older build, force to have the last image from the camera: + if (op.ImagePath == null || string.IsNullOrEmpty(op.ImagePath)) + { + //first, set to empty string rather than null to fix bug I saw + op.ImagePath = ""; + + //next, fill with most recent image with detections + if (!string.IsNullOrEmpty(cam.last_image_file_with_detections)) + { + op.ImagePath = cam.last_image_file_with_detections; + } + else if (!string.IsNullOrEmpty(cam.last_image_file)) + { + op.ImagePath = cam.last_image_file; + } + } + } + + StartStopMaskTimer(); + } + } + + public void RemoveActiveMask(ObjectPosition op) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + lock (this._maskLockObject) + { + try + { + int fndcnt = 0; + //so the objectposition equals function doesnt kick in, remove it the old way + for (int i = this.MaskedPositions.Count - 1; i >= 0; i--) + { + if (this.MaskedPositions[i].GetHashCode() == op.GetHashCode() && this.MaskedPositions[i].CreateDate == op.CreateDate) + { + fndcnt++; + Log("Debug: removed active mask: " + this.MaskedPositions[i].ToString()); + this.MaskedPositions.RemoveAt(i); + } + } + Log($"Debug: Removed {fndcnt} active masks. {this.MaskedPositions.Count} left."); + } + catch (Exception ex) + { + Log("Error: " + ex.Msg()); + } + } + } + public void RemoveHistoryMask(ObjectPosition op) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + lock (this._maskLockObject) + { + try + { + int fndcnt = 0; + //so the objectposition equals function doesnt kick in, remove it the old way + for (int i = this.LastPositionsHistory.Count - 1; i >= 0; i--) + { + if (this.LastPositionsHistory[i].GetHashCode() == op.GetHashCode() && this.LastPositionsHistory[i].CreateDate == op.CreateDate) + { + fndcnt++; + Log("Debug: removed history mask: " + this.LastPositionsHistory[i].ToString()); + this.LastPositionsHistory.RemoveAt(i); + } + } + Log($"Debug: Removed {fndcnt} history masks. {this.LastPositionsHistory.Count} left."); + } + catch (Exception ex) + { + Log("Error: " + ex.Msg()); + } + } + } + + + public MaskResultInfo CreateDynamicMask(ObjectPosition currentObject, bool forceStatic = false, bool forceDynamic = false) + { + + + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + MaskResultInfo returnInfo = new MaskResultInfo(); + + try + { + lock (this._maskLockObject) //moved this up, trying to figure out why IsMasked isnt returning correctly + { + + //if (!Global.IsInList(currentObject.Label, this.Objects, TrueIfEmpty: true)) + if (this.MaskTriggeringObjects.IsRelevant(currentObject.Label, out bool IgnoreImageMask, out bool IgnoreDynamicMask) != ResultType.Relevant) + { + //Log($"Debug: Skipping mask creation because '{currentObject.Label}' is not one of the configured objects: '{this.Objects}'", "", currentObject.CameraName, currentObject.ImagePath); + returnInfo.SetResults(MaskType.Unknown, MaskResult.Unwanted); + return returnInfo; + } + + if (IgnoreDynamicMask) + { + returnInfo.SetResults(MaskType.Unknown, MaskResult.ObjectIgnoredMask); + return returnInfo; + } + + Log("Trace: *** Starting new object mask processing ***", "", currentObject.CameraName, currentObject.ImagePath); + Log($"Trace: Current object detected: {currentObject.ToString()}", "", currentObject.CameraName, currentObject.ImagePath); + + currentObject.ScaleConfig = this.ScaleConfig; + currentObject.PercentMatch = this.PercentMatch; + + if (forceStatic || forceDynamic) + { + return this.forceMaskCreation(forceStatic, forceDynamic, currentObject); + } + + int historyIndex = this.LastPositionsHistory.IndexOf(currentObject); + + if (historyIndex > -1) + { + ObjectPosition foundObject = this.LastPositionsHistory[historyIndex]; + + foundObject.LastSeenDate = DateTime.Now; + + //Update last image that has same detection, and camera name found for existing mask + foundObject.ImagePath = currentObject.ImagePath; + foundObject.CameraName = currentObject.CameraName; + + Log($"Trace: Found '{currentObject.Label}' (Key={currentObject.Key}) in last_positions_history: {foundObject.ToString()}", "", currentObject.CameraName, currentObject.ImagePath); + + if (foundObject.Counter < this.HistoryThresholdCount) + { + foundObject.Counter++; + returnInfo.SetResults(MaskType.History, MaskResult.ThresholdNotMet, foundObject); + } + else + { + Log($"Debug: History Threshold reached Moving {foundObject.ToString()} to masked object list", "", currentObject.CameraName, currentObject.ImagePath); + + this.LastPositionsHistory.RemoveAt(historyIndex); + foundObject.CreateDate = DateTime.Now; //reset create date as history object is converted to a mask + foundObject.Counter = this.MaskRemoveThreshold; //sets the number of detections not visible before being eligible to remove by timer + foundObject.LastPercentMatch = 0; + this.MaskedPositions.Add(foundObject); + returnInfo.SetResults(MaskType.Dynamic, MaskResult.NewDynamicCreated, foundObject); + } + return returnInfo; + } + + int maskIndex = this.MaskedPositions.IndexOf(currentObject); + + if (maskIndex > -1) + { + ObjectPosition maskedObject = this.MaskedPositions[maskIndex]; + + maskedObject.LastSeenDate = DateTime.Now; + + //increase threashold counter when object is visible + if ((maskedObject.Counter < this.MaskRemoveThreshold) || this.MaskRemoveThreshold == 0) + { + maskedObject.Counter++; + } + + //Update last image that has same detection, and camera name found for existing mask + maskedObject.ImagePath = currentObject.ImagePath; + maskedObject.CameraName = currentObject.CameraName; + + Log($"Debug: Found '{currentObject.Label}' (Key={currentObject.Key}) in masked_positions {maskedObject.ToString()}", "", currentObject.CameraName, currentObject.ImagePath); + + MaskType type = maskedObject.IsStatic ? MaskType.Static : MaskType.Dynamic; + returnInfo.SetResults(type, MaskResult.Found, maskedObject); + } + else + { + Log($"Trace: + New object found: {currentObject.ToString()}. Adding to last_positions_history.", "", currentObject.CameraName, currentObject.ImagePath); + this.LastPositionsHistory.Add(currentObject); + returnInfo.SetResults(MaskType.History, MaskResult.New, currentObject); + } + } + } + catch (Exception ex) + { + Log($"Error: {ex.Msg()}"); + returnInfo.Result = MaskResult.Error; + } + + return returnInfo; + } + + /*======================================================================= + * This is so we can add a static mask from History > Prediction Details + * and perhaps use it for adding new static on mask details screen right + * click since we are threadsafe in here + ========================================================================*/ + private MaskResultInfo forceMaskCreation(bool forceStatic, bool forceDynamic, ObjectPosition currentObject) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + MaskResultInfo returnInfo = new MaskResultInfo(); + + int idx = this.MaskedPositions.IndexOf(currentObject); + + if (idx > -1) + { + ObjectPosition maskedObject = this.MaskedPositions[idx]; + + //Update last image that has same detection, and camera name found for existing mask + maskedObject.ImagePath = currentObject.ImagePath; + maskedObject.CameraName = currentObject.CameraName; + + //always remove from history if found in masked positions + if (this.LastPositionsHistory.Contains(currentObject)) + this.LastPositionsHistory.Remove(currentObject); + + if (forceStatic && maskedObject.IsStatic) + { + Log("Did not add new static mask because it was already found in masked_positions " + maskedObject.ToString() + " for camera " + currentObject.CameraName, "", currentObject.CameraName, currentObject.ImagePath); + } + else if (forceStatic && !maskedObject.IsStatic) + { + maskedObject.IsStatic = true; + Log("Forced conversion of existing Dynamic mask to Static " + maskedObject.ToString() + " for camera " + currentObject.CameraName, "", currentObject.CameraName, currentObject.ImagePath); + } + else if (forceDynamic && maskedObject.IsStatic) + { + Log("Did not add new Dynamic mask because it was already Static " + maskedObject.ToString() + " for camera " + currentObject.CameraName, "", currentObject.CameraName, currentObject.ImagePath); + } + else if (forceDynamic && !maskedObject.IsStatic) + { + Log("Did not add new Dynamic mask because it was already Static " + maskedObject.ToString() + " for camera " + currentObject.CameraName, "", currentObject.CameraName, currentObject.ImagePath); + } + returnInfo.SetResults(MaskType.Static, MaskResult.Found, maskedObject); + } + else if (forceStatic) + { + Log("+ Forced addition of new Static mask: " + currentObject.ToString() + ". Adding to masked_positions for camera: " + currentObject.CameraName, "", currentObject.CameraName, currentObject.ImagePath); + //check to see if it is in the history list and remove: + if (this.LastPositionsHistory.Contains(currentObject)) + this.LastPositionsHistory.Remove(currentObject); + currentObject.CreateDate = DateTime.Now; //reset create date as history object is converted to a mask + currentObject.IsStatic = true; + this.MaskedPositions.Add(currentObject); + returnInfo.SetResults(MaskType.Static, MaskResult.New, currentObject); + } + else if (forceDynamic) + { + Log("Debug: + Forced addition of new Dynamic mask (and removed from history): " + currentObject.ToString() + ". Adding to masked_positions for camera: " + currentObject.CameraName, "", currentObject.CameraName); + //check to see if it is in the history list and remove: + if (this.LastPositionsHistory.Contains(currentObject)) + this.LastPositionsHistory.Remove(currentObject); + + currentObject.CreateDate = DateTime.Now; //reset create date as history object is converted to a mask + currentObject.Counter = this.MaskRemoveThreshold; //sets the number of detections not visiable before being eligable to remove by timer + currentObject.IsStatic = false; + this.MaskedPositions.Add(currentObject); + returnInfo.SetResults(MaskType.Static, MaskResult.New, currentObject); + } + + return returnInfo; + } + + //Remove objects from history if they have not been detected in defined time (history_save_mins) and found counter < history_threshold_count + private void CleanUpExpiredHistory() + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + lock (this._maskLockObject) + { + try + { + if (this.LastPositionsHistory != null && this.LastPositionsHistory.Count > 0) + { + //scan backward through the list and remove by index. Not as easy to read but the faster for removals + for (int x = this.LastPositionsHistory.Count - 1; x >= 0; x--) + { + ObjectPosition historyObject = this.LastPositionsHistory[x]; + TimeSpan ts = DateTime.Now - historyObject.CreateDate; + double minutes = ts.TotalMinutes; + + //Log("\t" + historyObject.ToString() + " existed for: " + ts.Minutes + " minutes"); + if (minutes >= this.HistorySaveMins) + { + Log($"Debug: Removing expired history: {historyObject.ToString()} which existed for {minutes.Round()} minutes. (max={this.HistorySaveMins})", "", historyObject.CameraName); + this.LastPositionsHistory.RemoveAt(x); + } + } + } + else if (this.LastPositionsHistory == null) + { + Log("Error: historyList is null?"); + } + } + catch (Exception ex) + { + Log("Error: " + ex.Msg()); + } + } + } + + public void CleanUpExpiredMasks(RemoveEvent trigger) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + lock (this._maskLockObject) + { + try + { + if (this.MaskedPositions != null && this.MaskedPositions.Count > 0) + { + //if using timer, use current time since they may not have been a recent detection date set - Otherwise items would never expire unless there was a recent detection + DateTime CurTime; + if (trigger == RemoveEvent.Timer) + CurTime = DateTime.Now; + else + CurTime = this.LastDetectionDate; + + //scan backward through the list and remove by index. Not as easy to read as find by object but the faster for removals + for (int x = this.MaskedPositions.Count - 1; x >= 0; x--) + { + ObjectPosition maskedObject = this.MaskedPositions[x]; + TimeSpan ts = CurTime - maskedObject.LastSeenDate; + double minutes = ts.TotalMinutes; + double days = ts.TotalDays; + + switch (trigger) + { + case RemoveEvent.Timer: + if (minutes >= this.MaskRemoveMins && (!maskedObject.IsStatic && (maskedObject.Counter == 0 || this.MaskRemoveThreshold == 0))) + { + Log($"Debug: Removing expired (after {minutes.Round()} mins), Counter={maskedObject.Counter}, MaskRemoveThreshold={this.MaskRemoveThreshold}, MaskSaveMins={this.MaskRemoveMins}) masked object by timer thread: " + maskedObject.ToString(), "", maskedObject.CameraName); + this.MaskedPositions.RemoveAt(x); + } + else if (days >= this.MaxMaskUnusedDays) + { + Log($"Debug: Removing unused (after {days.Round()} days), Counter={maskedObject.Counter}, MaxMaskUnusedDays={this.MaxMaskUnusedDays}) masked object by timer thread: " + maskedObject.ToString(), "", maskedObject.CameraName); + this.MaskedPositions.RemoveAt(x); + } + else + { + //Log($"Trace: Not removing mask yet. Static={maskedObject.IsStatic}, Minutes={minutes.Round()}, Days={days.Round()}, Counter={maskedObject.Counter}, MaskRemoveThreshold={this.MaskRemoveThreshold}, MaskSaveMins={this.MaskRemoveMins}) masked object by timer thread: " + maskedObject.ToString(), "", maskedObject.CameraName); + } + break; + case RemoveEvent.Detection: + if (minutes > 1 && !maskedObject.IsStatic) //if not visible and not marked as a static mask + { + if ((maskedObject.Counter == 0 || this.MaskRemoveThreshold == 0) && minutes >= this.MaskRemoveMins) + { + Log($"Debug: Removing expired ({minutes.Round()} mins, Counter={maskedObject.Counter}, MaskRemoveThreshold={this.MaskRemoveThreshold}, MaskSaveMins={this.MaskRemoveMins}) masked object after detection: " + maskedObject.ToString(), "", maskedObject.CameraName); + this.MaskedPositions.RemoveAt(x); + } + else if (maskedObject.Counter > 0) + { + maskedObject.Counter--; + } + } + break; + } + } + } + else if (this.MaskedPositions == null) + { + Log("Error: Maskedlist is null?"); + } + } + catch (Exception ex) + { + Log("Error: " + ex.Msg()); + } + } + } + + private void cleanMaskEvent(object sender, EventArgs e) + { + this.CleanUpExpiredHistory(); + this.CleanUpExpiredMasks(RemoveEvent.Timer); + } + } + + public class ObjectScale + { + //empty default constructor required by Newtonsoft deserialization for new objects + public ObjectScale() { } + + public bool IsScaledObject { get; set; } = false; + public int SmallObjectMaxPercent { get; set; } = 2; + public int SmallObjectMatchPercent { get; set; } = 78; + public int MediumObjectMinPercent { get; set; } = 2; + public int MediumObjectMaxPercent { get; set; } = 5; + public int MediumObjectMatchPercent { get; set; } = 82; + + public override string ToString() + { + return $"Scaled={this.IsScaledObject}, SOMX%={this.SmallObjectMaxPercent}, SOMT%={this.SmallObjectMatchPercent}, MOMN%={this.MediumObjectMinPercent}, MOMX%={this.MediumObjectMaxPercent}, MOMT%={this.MediumObjectMatchPercent}"; + } + } + +} diff --git a/src/UI/MaskResultInfo.cs b/src/UI/MaskResultInfo.cs new file mode 100644 index 00000000..918b6e6f --- /dev/null +++ b/src/UI/MaskResultInfo.cs @@ -0,0 +1,67 @@ +namespace AITool +{ + public enum MaskType + { + History, + Dynamic, + Static, + Image, + None, + Unknown + } + public enum MaskResult + { + New, + ThresholdNotMet, + NewDynamicCreated, + Found, + NotFound, + MajorityOutsideMask, + CompletlyOutsideMask, + MajorityInsideMask, + CompletlyInsideMask, + NoMaskImageFile, + Unwanted, + Error, + SkippedDynamicMaskCheck, + NotEnabled, + Unknown, + ObjectIgnoredMask + } + public class MaskResultInfo + { + public bool IsMasked = false; + public MaskType MaskType = MaskType.Unknown; + public MaskResult Result = MaskResult.Unknown; + public int ImagePointsOutsideMask = 0; + public int DynamicThresholdCount = 0; + public double PercentMatch = 0; + + public void SetResults(MaskType type, MaskResult result) + { + switch (type) + { + case MaskType.Dynamic: + case MaskType.Static: + this.IsMasked = true; + break; + case MaskType.Unknown: + case MaskType.History: + this.IsMasked = false; + break; + } + + this.MaskType = type; + this.Result = result; + } + + public void SetResults(MaskType type, MaskResult result, ObjectPosition op) + { + this.DynamicThresholdCount = op.Counter; + this.PercentMatch = op.LastPercentMatch; + + this.SetResults(type, result); + } + } + +} diff --git a/src/UI/MessageManager.cs b/src/UI/MessageManager.cs new file mode 100644 index 00000000..4b1d8ecf --- /dev/null +++ b/src/UI/MessageManager.cs @@ -0,0 +1,85 @@ +using System; +using System.Runtime.CompilerServices; + +namespace AITool +{ + class MessageManager + { + + public IProgress progress { get; set; } = null; + + public MessageManager() + { + + } + + public void SendMessage(MessageType MT, string Descript = "", object Payload = null, [CallerMemberName] string memberName = null) + { + if (this.progress == null) + return; + + ClsMessage msg = new ClsMessage(MT, Descript, Payload, memberName); + + this.progress.Report(msg); + + } + + public void DeleteHistoryItem(string filename, [CallerMemberName] string memberName = null) + { + if (this.progress == null) + return; + + ClsMessage msg = new ClsMessage(MessageType.DeleteHistoryItem, filename, null, memberName); + + this.progress.Report(msg); + + } + + public void CreateHistoryItem(History hist, [CallerMemberName] string memberName = null) + { + if (this.progress == null) + return; + + ClsMessage msg = new ClsMessage(MessageType.CreateHistoryItem, "", hist, memberName); + + this.progress.Report(msg); + + } + + public void UpdateLabel(string Message, string LabelControlName, [CallerMemberName] string memberName = null) + { + if (this.progress == null) + return; + + ClsMessage msg = new ClsMessage(MessageType.UpdateLabel, Message, LabelControlName, memberName); + + this.progress.Report(msg); + + } + + public void Log(string Message, [CallerMemberName] string memberName = null) + { + if (this.progress == null) + return; + + ClsMessage msg = new ClsMessage(MessageType.LogEntry, "", null, memberName); + + //this is for logging in non-gui classes. Reports back to real logger + //progress needs to be subscribed to in main gui + string mn = ""; + if (memberName != null && !string.IsNullOrEmpty(memberName)) + { + mn = $"{memberName}>> "; + } + msg.Description = $"{mn}{Message}"; + + Global.SaveRegSetting("LastLogEntry", msg.Description); + Global.SaveRegSetting("LastShutdownState", $"checkpoint: Global.Log: {DateTime.Now}"); + + + this.progress.Report(msg); + + } + + } +} diff --git a/src/UI/MovingCalcs.cs b/src/UI/MovingCalcs.cs new file mode 100644 index 00000000..4946dc9d --- /dev/null +++ b/src/UI/MovingCalcs.cs @@ -0,0 +1,194 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; + +using Newtonsoft.Json; + + +namespace AITool +{ + public class MovingCalcs + { + + [JsonIgnore] + public ConcurrentQueue samples = new ConcurrentQueue(); + public int windowSize = 250; + public int lastDayOfYear = 0; + public int lastMonth = 0; + [JsonIgnore] + public Decimal sampleAccumulator { get; set; } = 0; + [JsonProperty("Average")] + public Decimal Avg { get; set; } = 0; + [JsonIgnore] + public string AvgS { get { return this.Avg.ToString("#####0"); } } + public Decimal Min { get; set; } = 0; + [JsonIgnore] + public string MinS { get { return this.Min.ToString("#####0"); } } + public Decimal Max { get; set; } = 0; + [JsonIgnore] + public string MaxS { get { return this.Max.ToString("#####0"); } } + [JsonIgnore] + public ThreadSafe.Integer Count { get; set; } = 0; + public ThreadSafe.Integer CountToday { get; set; } = 0; + public ThreadSafe.Integer CountMonth { get; set; } = 0; + public Decimal Current { get; set; } = 0; + [JsonIgnore] + public ThreadSafe.DateTime TimeInitialized { get; set; } = DateTime.Now; + public ThreadSafe.DateTime LastMaxTime { get; set; } = DateTime.MinValue; + public string ItemName { get; set; } = "Items"; + public bool IsTime { get; set; } = false; + public string ToStringFormat { get; set; } = "#####0"; + [JsonConstructor] + public MovingCalcs() { this.UpdateDate(true); } + public MovingCalcs(int windowSize, string itemName, bool IsTime, string ToStringFormat = "#####0") + { + this.ToStringFormat = ToStringFormat; + this.windowSize = windowSize; + this.ItemName = ItemName; + this.IsTime = IsTime; + } + + public void Clear() + { + this.lastDayOfYear = 0; + this.lastMonth = 0; + this.samples.Clear(); + this.Min = 0; + this.Max = 0; + this.Avg = 0; + this.Count = 0; + this.CountMonth = 0; + this.CountToday = 0; + this.Current = 0; + this.TimeInitialized = DateTime.Now; + this.sampleAccumulator = 0; + this.LastMaxTime = DateTime.MinValue; + + } + public double ItemsPerMinute() + { + + this.UpdateDate(false); + + if (this.CountToday == 0) + return 0; + + return this.CountToday / (DateTime.Now - TimeInitialized).TotalMinutes; + } + public double ItemsPerSecond() + { + this.UpdateDate(false); + + if (this.CountToday == 0) + return 0; + + return this.CountToday / (DateTime.Now - TimeInitialized).TotalSeconds; + } + + + public void AddToCalc(double newSample) + { + this.AddToCalc(Convert.ToDecimal(newSample)); + } + public void AddToCalc(int newSample) + { + this.AddToCalc(Convert.ToDecimal(newSample)); + } + public void AddToCalc(long newSample) + { + this.AddToCalc(Convert.ToDecimal(newSample)); + } + public void UpdateDate(bool init) + { + if (DateTime.Now.DayOfYear != this.lastDayOfYear) + { + if (init) + this.CountToday = 0; + else + this.CountToday = 1; + + this.lastDayOfYear = DateTime.Now.DayOfYear; + + if (DateTime.Now.Month != this.lastMonth) + { + if (init) + this.CountMonth = 0; + else + this.CountMonth = 1; + + this.lastMonth = DateTime.Now.Month; + } + } + } + public void AddToCalc(Decimal newSample) + { + + lock (this.samples) + { + if (newSample > 0) + { + this.Current = newSample; + + this.Count++; + this.CountToday++; + this.CountMonth++; + + this.UpdateDate(false); + + this.sampleAccumulator += newSample; + this.samples.Enqueue(newSample); + + if (this.samples.Count > this.windowSize) + { + //this.sampleAccumulator -= this.samples.Dequeue(); + if (samples.TryDequeue(out decimal dequeuedSample)) + { + sampleAccumulator -= dequeuedSample; + } + } + + + if (this.sampleAccumulator > 0) //divide by 0? + this.Avg = this.sampleAccumulator / this.samples.Count; + + if (this.Min == 0) + { + this.Min = newSample; + } + else + { + this.Min = Math.Min(newSample, this.Min); + } + //this.Max = Math.Max(newSample, this.Max); + if (newSample > this.Max) + { + this.Max = newSample; + this.LastMaxTime = DateTime.Now; + } + + } + + } + + } + + public override string ToString() + { + string ms = ""; + if (this.IsTime) + ms = "ms"; + + return $"{this.Count} {this.ItemName} | {this.CountToday} today | {this.CountMonth} Month | {this.ItemsPerMinute().ToString("#####0")}/MIN (Min={this.Min.ToString(this.ToStringFormat)}{ms},Max={this.Max.ToString(this.ToStringFormat)}{ms},Avg={this.Avg.ToString(this.ToStringFormat)}{ms},Last={this.Current.ToString(this.ToStringFormat)}{ms})"; + } + public string ToStringShort() + { + string ms = ""; + if (this.IsTime) + ms = "ms"; + + return $"Cnt={this.Count},Min={this.Min.ToString(this.ToStringFormat)}{ms},Max={this.Max.ToString(this.ToStringFormat)}{ms},Avg={this.Avg.ToString(this.ToStringFormat)}{ms},Last={this.Current.ToString(this.ToStringFormat)}{ms}"; + } + } + +} + diff --git a/src/UI/NPushOver/Converters/BoolConverter.cs b/src/UI/NPushOver/Converters/BoolConverter.cs new file mode 100644 index 00000000..e8015fd6 --- /dev/null +++ b/src/UI/NPushOver/Converters/BoolConverter.cs @@ -0,0 +1,100 @@ +using Newtonsoft.Json; +using System; + +namespace NPushover.Converters +{ + /// + /// Handles explicitly the conversion of integers to booleans and vice versa. + /// + /// + /// The values 0 or null are interpreted as false, anything other than those are interpreted as true. + /// + internal class BoolConverter : JsonConverter + { + /// + /// Writes the JSON representation of the object. + /// + /// The to write to. + /// The value. + /// The calling serializer. + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + writer.WriteValue(((bool)value) ? 1 : 0); + } + + /// + /// Reads the JSON representation of the object. + /// + /// The to read from. + /// Type of the object. + /// The existing value of object being read. + /// The calling serializer. + /// The object value. + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (reader.Value == null) + return false; + if ((reader.TokenType != JsonToken.Integer) && (reader.TokenType != JsonToken.String)) + throw new JsonSerializationException(String.Format("Unexpected token parsing bool. Expected String/Integer, got {0}.", reader.TokenType)); + return int.Parse(reader.Value.ToString()) != 0; + } + + /// + /// Determines whether this instance can convert the specified object type. + /// + /// Type of the object. + /// True if this instance can convert the specified object type; otherwise, false. + public override bool CanConvert(Type objectType) + { + return objectType == typeof(bool); + } + } + + /// + /// Handles explicitly the conversion of integers to nullable booleans and vice versa. + /// + /// + /// The value 0 is interpreted as false, null is interpreted as null; anything other than this is interpreted as true. + /// + internal class NullableBoolConverter : BoolConverter + { + /// + /// Writes the JSON representation of the object. + /// + /// The to write to. + /// The value. + /// The calling serializer. + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + if (value == null) + writer.WriteNull(); + else + base.WriteJson(writer, value, serializer); + } + + /// + /// Reads the JSON representation of the object. + /// + /// The to read from. + /// Type of the object. + /// The existing value of object being read. + /// The calling serializer. + /// The object value. + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (reader.Value == null) + return null; + return base.ReadJson(reader, objectType, existingValue, serializer); + } + + /// + /// Determines whether this instance can convert the specified object type. + /// + /// Type of the object. + /// True if this instance can convert the specified object type; otherwise, false. + public override bool CanConvert(Type objectType) + { + return objectType == typeof(bool?); + } + } +} diff --git a/src/UI/NPushOver/Converters/UnixDateTimeConverter.cs b/src/UI/NPushOver/Converters/UnixDateTimeConverter.cs new file mode 100644 index 00000000..f2d47ae6 --- /dev/null +++ b/src/UI/NPushOver/Converters/UnixDateTimeConverter.cs @@ -0,0 +1,110 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using System; + +namespace NPushover.Converters +{ + /// + /// Handles explicitly the conversion of UNIX (Epoch) time to DateTime and vice versa. + /// + /// + /// Dates are specified in seconds from UNIX epoch. This converter helps to convert these values to DateTime + /// objects with the Kind property explicitly set to Utc. + /// + internal class UnixDateTimeConverter : DateTimeConverterBase + { + private static DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + /// + /// Writes the JSON representation of the object. + /// + /// The to write to. + /// The value. + /// The calling serializer. + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var date = (DateTime)value; + if (date.Kind == DateTimeKind.Utc) + writer.WriteValue((long)date.Subtract(epoch).TotalSeconds); + else + writer.WriteValue((long)TimeZoneInfo.ConvertTimeToUtc(date).Subtract(epoch).TotalSeconds); + } + + /// + /// Reads the JSON representation of the object. + /// + /// The to read from. + /// Type of the object. + /// The existing value of object being read. + /// The calling serializer. + /// The object value. + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType != JsonToken.Integer) + throw new JsonSerializationException(String.Format("Unexpected token parsing date. Expected Integer, got {0}.", reader.TokenType)); + + return epoch.AddSeconds((long)reader.Value); + } + + /// + /// Determines whether this instance can convert the specified object type. + /// + /// Type of the object. + /// True if this instance can convert the specified object type; otherwise, false. + public override bool CanConvert(Type objectType) + { + return objectType == typeof(DateTime); + } + } + + /// + /// Handles explicitly the conversion of UNIX (Epoch) time to nullable DateTime and vice versa. + /// + /// + /// Dates are specified in seconds from UNIX epoch. This converter helps to convert these values to DateTime + /// objects with the Kind property explicitly set to Utc. + /// + internal class NullableUnixDateTimeConverter : UnixDateTimeConverter + { + /// + /// Writes the JSON representation of the object. + /// + /// The to write to. + /// The value. + /// The calling serializer. + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + if (value == null) + writer.WriteNull(); + else + base.WriteJson(writer, value, serializer); + } + + /// + /// Reads the JSON representation of the object. + /// + /// The to read from. + /// Type of the object. + /// The existing value of object being read. + /// The calling serializer. + /// The object value. + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (reader.Value == null) + return null; + if ((reader.TokenType == JsonToken.Integer) && ((long)reader.Value == 0)) + return null; + return base.ReadJson(reader, objectType, existingValue, serializer); + } + + /// + /// Determines whether this instance can convert the specified object type. + /// + /// Type of the object. + /// True if this instance can convert the specified object type; otherwise, false. + public override bool CanConvert(Type objectType) + { + return objectType == typeof(DateTime?); + } + } +} diff --git a/src/UI/NPushOver/Exceptions/BadRequestException.cs b/src/UI/NPushOver/Exceptions/BadRequestException.cs new file mode 100644 index 00000000..ce9ba8c2 --- /dev/null +++ b/src/UI/NPushOver/Exceptions/BadRequestException.cs @@ -0,0 +1,41 @@ +using NPushover.ResponseObjects; +using System; + +namespace NPushover.Exceptions +{ + /// + /// Represents an error that occurs when pushover returned an HTTP status 400: bad request. + /// + /// + /// Exceptions of this type are typically caused by sending incorrect values or not sending all required values + /// to the Pushover service. + /// + public class BadRequestException : ResponseException + { + /// + /// Initializes a new instance of the class with an empty (null) + /// . + /// + public BadRequestException() + : this(null) { } + + /// + /// Initializes a new instance of the class with a reference to the + /// a that is the cause of this exception. + /// + /// The that is the cause for the exception. + public BadRequestException(PushoverResponse response) + : this(response, null) { } + + /// + /// Initializes a new instance of the class with a reference to the + /// and a reference to the inner exception that are the cause of this exception. + /// + /// The that is the cause for the exception. + /// + /// The exception that is the cause of the current exception, or a null reference if no inner exception. + /// + public BadRequestException(PushoverResponse response, Exception innerException) + : base("Bad request", response, innerException) { } + } +} diff --git a/src/UI/NPushOver/Exceptions/InvalidKeyException.cs b/src/UI/NPushOver/Exceptions/InvalidKeyException.cs new file mode 100644 index 00000000..e44ab02a --- /dev/null +++ b/src/UI/NPushOver/Exceptions/InvalidKeyException.cs @@ -0,0 +1,25 @@ +namespace NPushover.Exceptions +{ + /// + /// Represents an error that occurs when a key, id or token is determined to be invalid based on it's format. + /// + public class InvalidKeyException : PushoverException + { + /// + /// Gets the key, id or token that caused the current exception. + /// + public string Key { get; private set; } + + /// + /// Initializes a new instance of the class with the name of the parameter and + /// a reference to the key, id or token that is the cause of this exception. + /// + /// The name of the parameter that caused the current exception. + /// The key, id or token that is the cause of this exception. + public InvalidKeyException(string paramName, string key) + : base(string.Format("Invalid argument: {0}", paramName)) + { + this.Key = key; + } + } +} diff --git a/src/UI/NPushOver/Exceptions/PushOverException.cs b/src/UI/NPushOver/Exceptions/PushOverException.cs new file mode 100644 index 00000000..dc8defc6 --- /dev/null +++ b/src/UI/NPushOver/Exceptions/PushOverException.cs @@ -0,0 +1,28 @@ +using System; + +namespace NPushover.Exceptions +{ + /// + /// Provides a baseclass for all exceptions thrown by NPushover. + /// + public class PushoverException : Exception + { + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// The error message that explains the reason for the exception. + public PushoverException(string message) + : this(message, null) { } + + /// + /// Initializes a new instance of the class with a specified error message and + /// a reference to the inner exception that is the cause of this exception. + /// + /// The error message that explains the reason for the exception. + /// + /// The exception that is the cause of the current exception, or a null reference if no inner exception. + /// + public PushoverException(string message, Exception innerException) + : base(message, innerException) { } + } +} diff --git a/src/UI/NPushOver/Exceptions/RateLimitExceededException.cs b/src/UI/NPushOver/Exceptions/RateLimitExceededException.cs new file mode 100644 index 00000000..9d011323 --- /dev/null +++ b/src/UI/NPushOver/Exceptions/RateLimitExceededException.cs @@ -0,0 +1,42 @@ +using NPushover.ResponseObjects; +using System; + +namespace NPushover.Exceptions +{ + /// + /// Represents an error that occurs when the Pushover service returns a "rate limit exceeded" response. + /// + /// + /// Exceptions of this type are caused by the Pushover service. + /// + public class RateLimitExceededException : ResponseException + { + /// + /// Returns reported by the Pushover service containing information on why a request + /// was ratelimited and when the ratelimit is reset. + /// + public RateLimitInfo RateLimitInfo { get; set; } + + /// + /// Initializes a new instance of the class with a reference to the + /// a that is the cause of this exception. + /// + /// The that is the cause for the exception. + public RateLimitExceededException(PushoverResponse response) + : this(response, null) { } + + /// + /// Initializes a new instance of the class with a reference to the + /// and a reference to the inner exception that are the cause of this exception. + /// + /// The that is the cause for the exception. + /// + /// The exception that is the cause of the current exception, or a null reference if no inner exception. + /// + public RateLimitExceededException(PushoverResponse response, Exception innerException) + : base("Rate limit exceeded", response, innerException) + { + this.RateLimitInfo = response.RateLimitInfo; + } + } +} diff --git a/src/UI/NPushOver/Exceptions/ResponseException.cs b/src/UI/NPushOver/Exceptions/ResponseException.cs new file mode 100644 index 00000000..8ea68bfb --- /dev/null +++ b/src/UI/NPushOver/Exceptions/ResponseException.cs @@ -0,0 +1,48 @@ +using NPushover.ResponseObjects; +using System; + +namespace NPushover.Exceptions +{ + /// + /// Provides a baseclass for all exceptions encountered in Pushover responses to requests. + /// + public class ResponseException : PushoverException + { + /// + /// Gets the that caused the exception. + /// + public PushoverResponse Response { get; private set; } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// The error message that explains the reason for the exception. + public ResponseException(string message) + : base(message) { } + + /// + /// Initializes a new instance of the class with a specified error message and + /// a reference to the that is the cause of this exception. + /// + /// The error message that explains the reason for the exception. + /// The that is the cause for the exception. + public ResponseException(string message, PushoverResponse response) + : this(message, response, null) { } + + /// + /// Initializes a new instance of the class with a specified error message, + /// a reference to the and a reference to the inner exception that are the cause + /// of this exception. + /// + /// The error message that explains the reason for the exception. + /// The that is the cause for the exception. + /// + /// The exception that is the cause of the current exception, or a null reference if no inner exception. + /// + public ResponseException(string message, PushoverResponse response, Exception innerException) + : base(message, innerException) + { + this.Response = response; + } + } +} diff --git a/src/UI/NPushOver/NameValueCollectionExtensions.cs b/src/UI/NPushOver/NameValueCollectionExtensions.cs new file mode 100644 index 00000000..b5414315 --- /dev/null +++ b/src/UI/NPushOver/NameValueCollectionExtensions.cs @@ -0,0 +1,95 @@ +using NPushover.RequestObjects; +using System; +using System.Collections.Specialized; +using System.Net.Http; + +namespace NPushover +{ + internal static class MultipartFormDataContentExtensions + { + public static void AddConditional(this MultipartFormDataContent collection, string key, string value) + { + if (!string.IsNullOrEmpty(value)) + collection.Add(new StringContent(value), $"\"{key}\""); + } + + + public static void AddConditional(this MultipartFormDataContent collection, string key, string[] value, string separator = ",") + { + if (value != null && value.Length > 0) + collection.Add(new StringContent(string.Join(separator, value)), $"\"{key}\""); + } + + public static void AddConditional(this MultipartFormDataContent collection, string key, bool value) + { + if (value) + collection.Add(new StringContent("1"), $"\"{key}\""); + } + + public static void AddConditional(this MultipartFormDataContent collection, string key, OS value) + { + if (value != OS.Any) + collection.Add(new StringContent(value.ToString().ToLowerInvariant()), $"\"{key}\""); + } + + public static void Add(this MultipartFormDataContent collection, string key, Uri value) + { + if (value != null) + collection.Add(new StringContent(value.AbsoluteUri), $"\"{key}\""); + } + + public static void Add(this MultipartFormDataContent collection, string key, TimeSpan value) + { + if (value != null) + collection.Add(new StringContent(((int)value.TotalSeconds).ToString()), $"\"{key}\""); + } + + public static void Add(this MultipartFormDataContent collection, string key, int value) + { + collection.Add(new StringContent(value.ToString()), $"\"{key}\""); + } + } + internal static class NameValueCollectionExtensions + { + public static void AddConditional(this NameValueCollection collection, string key, string value) + { + if (!string.IsNullOrEmpty(value)) + collection.Add(key, value); + } + + public static void AddConditional(this NameValueCollection collection, string key, string[] value, string separator = ",") + { + if (value != null && value.Length > 0) + collection.Add(key, string.Join(separator, value)); + } + + public static void AddConditional(this NameValueCollection collection, string key, bool value) + { + if (value) + collection.Add(key, 1); + } + + public static void AddConditional(this NameValueCollection collection, string key, OS value) + { + if (value != OS.Any) + collection.Add(key, value.ToString().ToLowerInvariant()); + } + + public static void Add(this NameValueCollection collection, string key, Uri value) + { + if (value != null) + collection.Add(key, value.AbsoluteUri); + } + + public static void Add(this NameValueCollection collection, string key, TimeSpan value) + { + if (value != null) + collection.Add(key, (int)value.TotalSeconds); + } + + public static void Add(this NameValueCollection collection, string key, int value) + { + collection.Add(key, value.ToString()); + } + } +} diff --git a/src/UI/NPushOver/PushOver.cs b/src/UI/NPushOver/PushOver.cs new file mode 100644 index 00000000..416a347f --- /dev/null +++ b/src/UI/NPushOver/PushOver.cs @@ -0,0 +1,962 @@ +using AITool; + +using Newtonsoft.Json; + +using NPushover.Exceptions; +using NPushover.RequestObjects; +using NPushover.ResponseObjects; +using NPushover.Validators; + +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace NPushover +{ + // NOTE: This library is not written or supported by Superblock (the creators of Pushover). + + // TODO: Implement OpenClient API realtime notifications? https://pushover.net/api/client + + /// + /// Provides asynchronous methods to interact with the Pushover service. + /// + public class Pushover + { + private static readonly string HOMEURL = "https://github.com/RobThree/NPushOver"; + private static readonly AssemblyName ASSEMBLYNAME = typeof(Pushover).Assembly.GetName(); + private static readonly DateTime EPOCH = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + private static readonly string USERAGENT = string.Format("{0} v{1} ({2})", ASSEMBLYNAME.Name, ASSEMBLYNAME.Version.ToString(2), HOMEURL); + + private HttpClient httpClient = null; + + #region Public 'consts' + /// + /// The default used by Pushover. + /// + public static readonly Uri DEFAULTBASEURI = new Uri("https://api.pushover.net/"); + + /// + /// The default used by Pushover. + /// + public static readonly Encoding DEFAULTENCODING = Encoding.UTF8; + #endregion + + #region Public properties + /// + /// Gets the encoding used for exchanging messages with Pushover. + /// + public Encoding Encoding { get; private set; } + + /// + /// Gets the base URL used by Pushover. + /// + public Uri BaseUri { get; private set; } + + /// + /// Gets the application token used to identify to Pushover. + /// + public string ApplicationKey { get; private set; } + + /// + /// Gets/sets the server used by Pushover. + /// + public IWebProxy Proxy { get; set; } + + /// + /// Gets/sets the used to validate messages before sending. + /// + public IValidator MessageValidator { get; set; } + + /// + /// Gets/sets the used to validate the Application Key. + /// + public IValidator AppKeyValidator { get; set; } + + /// + /// Gets/sets the used to validate user or group keys. + /// + public IValidator UserOrGroupKeyValidator { get; set; } + + /// + /// Gets/sets the used to validate devicenames. + /// + public IValidator DeviceNameValidator { get; set; } + + /// + /// Gets/sets the used to receipts. + /// + public IValidator ReceiptValidator { get; set; } + + /// + /// Gets/sets the used to validate e-mail addresses. + /// + public IValidator EmailValidator { get; set; } + #endregion + + #region Constructors + /// + /// Initializes a new instance of with the specified applicationkey, the + /// and . + /// + /// The application key (or token). + /// Pushover API documentation + /// Thrown when applicationKey is null. + /// Thrown when an invalid applicationKey is specified. + public Pushover(string applicationKey) + : this(applicationKey, DEFAULTBASEURI) { } + + /// + /// Initializes a new instance of with the specified applicationkey and base URI and + /// . + /// + /// The application key (or token). + /// The base to use. Note that this does not include the API version (e.g. 1). + /// Pushover API documentation + /// Thrown when applicationKey or baseUri are null. + /// Thrown when an invalid applicationKey is specified. + public Pushover(string applicationKey, Uri baseUri) + : this(applicationKey, baseUri, DEFAULTENCODING) { } + + /// + /// Initializes a new instance of with the specified applicationkey, base URI and . + /// + /// The application key (or token). + /// The base to use. Note that this does not include the API version (e.g. 1). + /// The to use for exchaning data with Pushover. + /// Pushover API documentation + /// Thrown when applicationKey, baseUri or Encoding are null. + /// Thrown when an invalid applicationKey is specified. + public Pushover(string applicationKey, Uri baseUri, Encoding encoding) + : this(applicationKey, baseUri, encoding, new ApplicationKeyValidator()) + { + + } + + /// + /// Initializes a new instance of with the specified applicationkey, base URI and . + /// + /// The application key (or token). + /// The base to use. Note that this does not include the API version (e.g. 1). + /// The to use for exchaning data with Pushover. + /// The to use to validate the application key. + /// Pushover API documentation + /// Thrown when applicationKey, baseUri, Encoding or applicationKeyValidator are null. + /// Thrown when an invalid applicationKey is specified. + public Pushover(string applicationKey, Uri baseUri, Encoding encoding, IValidator applicationKeyValidator) + { + if (baseUri == null) + throw new ArgumentNullException("baseUri"); + if (encoding == null) + throw new ArgumentNullException("encoding"); + if (applicationKeyValidator == null) + throw new ArgumentNullException("applicationKeyValidator"); + + applicationKeyValidator.Validate("applicationKey", applicationKey); + + this.AppKeyValidator = applicationKeyValidator; + this.ApplicationKey = applicationKey; + this.BaseUri = baseUri; + this.Encoding = encoding; + } + #endregion + + #region Public methods + /// + /// Sends, asynchronously, the specified using Pushover to the specified user or group. + /// + /// The to send. + /// The user or group id to send the message to. + /// Returns the returned by the server. + /// Pushover API documentation + /// Thrown when message or user/group arguments are null. + /// Thrown when an invalid user/group is specified. + public async Task SendPushoverMessageAsync(Message message, string userOrGroup, ClsImageQueueItem CurImg) + { + return await this.SendPushoverMessageAsync(message, userOrGroup, (string[])null, CurImg).ConfigureAwait(false); + } + + /// + /// Sends, asynchronously, the specified using Pushover to the specified device of the + /// specified user or group. + /// + /// The to send. + /// The user or group id to send the message to. + /// The devicename to send the message to. + /// Returns the returned by the server. + /// Pushover API documentation + /// Thrown when message or user/group arguments are null. + /// Thrown when an invalid user/group is specified. + public async Task SendPushoverMessageAsync(Message message, string userOrGroup, string deviceName, ClsImageQueueItem CurImg) + { + List devices = deviceName.SplitStr(";"); + + return await this.SendPushoverMessageAsync(message, userOrGroup, devices.ToArray(), CurImg).ConfigureAwait(false); + } + + /// + /// Sends, asynchronously, the specified using Pushover to the specified device(s) of the + /// specified user or group. + /// + /// The to send. + /// The user or group id to send the message to. + /// The devicenames to send the message to. + /// Returns the returned by the server. + /// Pushover API documentation + /// Thrown when message or user/group arguments are null. + /// Thrown when an invalid user/group is specified. + public async Task SendPushoverMessageAsync(Message message, string userOrGroup, string[] deviceNames, ClsImageQueueItem CurImg) + { + + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + PushoverUserResponse ret = new PushoverUserResponse(); + + try + { + (this.MessageValidator ?? new DefaultMessageValidator()).Validate("message", message); + (this.UserOrGroupKeyValidator ?? new UserOrGroupKeyValidator()).Validate("userOrGroup", userOrGroup); + if (deviceNames != null && deviceNames.Length > 0) + { + foreach (var device in deviceNames) + { + if (!string.IsNullOrWhiteSpace(device)) + (this.DeviceNameValidator ?? new DeviceNameValidator()).Validate("device", device); + } + } + + MultipartFormDataContent parameters = new MultipartFormDataContent(); + + parameters.AddConditional("token", this.ApplicationKey); + parameters.AddConditional("user", userOrGroup); + parameters.AddConditional("message", message.Body); + parameters.Add("priority", (int)message.Priority); + parameters.AddConditional("device", deviceNames); + parameters.AddConditional("title", message.Title); + parameters.AddConditional("sound", message.Sound); + parameters.AddConditional("html", message.IsHtmlBody); + if (message.SupplementaryUrl != null) + { + parameters.Add("url", message.SupplementaryUrl.Uri); + parameters.AddConditional("url_title", message.SupplementaryUrl.Title); + } + + if (message.Priority == Priority.Emergency) + { + parameters.Add("retry", message.RetryOptions.RetryEvery); + parameters.Add("expire", message.RetryOptions.RetryPeriod); + parameters.Add("callback", message.RetryOptions.CallBackUrl); + } + if (message.Timestamp != null) + parameters.Add("timestamp", (int)(TimeZoneInfo.ConvertTimeToUtc(message.Timestamp.Value).Subtract(EPOCH).TotalSeconds)); + + if (CurImg != null && CurImg.IsValid()) + { + StreamContent imageParameter = new StreamContent(CurImg.ToMemStream()); + imageParameter.Headers.ContentType = MediaTypeHeaderValue.Parse("image/jpeg"); + parameters.Add(imageParameter, "attachment", Path.GetFileName(CurImg.image_path)); + } + + ret = await this.PushoverPost(GetV1APIUriFromBase("messages.json"), parameters); + + } + catch (Exception ex) + { + + AITOOL.Log($"Error: {ex.Msg()}"); + ret.Errors = new string[] { ex.Msg() }; + } + + return ret; + + } + + + private async Task PushoverPost(Uri uri, MultipartFormDataContent parameters) + { + + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + PushoverUserResponse ret = new PushoverUserResponse(); + + if (this.httpClient == null) + { + this.httpClient = new HttpClient(); + this.httpClient.Timeout = TimeSpan.FromSeconds(AppSettings.Settings.HTTPClientRemoteTimeoutSeconds); + } + + // Remove content type that is not in the docs + //foreach (HttpContent param in parameters) + // param.Headers.ContentType = null; + + try + { + HttpResponseMessage response = null; + string json = ""; + + response = await httpClient.PostAsync(uri, parameters); + + json = await response.Content.ReadAsStringAsync(); + json = json.CleanString(); + + if (response.IsSuccessStatusCode && !string.IsNullOrWhiteSpace(json)) + { + //var json = this.Encoding.GetString(await wc.UploadValuesTaskAsync(uri, parameters).ConfigureAwait(false)); + ret = await ParseResponse(json, response.Headers); + } + else + { + if (response.StatusCode.ToString().Contains("TooLarge")) + { + ret.Errors = new string[] { $"StatusCode='{response.StatusCode}', Reason='{response.ReasonPhrase}' (Max pushover attachment size is 2.5MB), ResponseText='{json}'" }; + } + else + { + ret.Errors = new string[] { $"StatusCode='{response.StatusCode}', Reason='{response.ReasonPhrase}', ResponseText='{json}'" }; + } + } + } + catch (Exception ex) + { + AITOOL.Log($"Error: {ex.Msg()}"); + ret.Errors = new string[] { ex.Msg() }; + } + + return ret; + + } + + + /// + /// Retrieves, asynchronously, a list of available sounds. + /// + /// Pushover API documentation + /// Returns a . + public async Task ListSoundsAsync() + { + return await this.Get(GetV1APIUriFromBase("sounds.json?token={0}", this.ApplicationKey)).ConfigureAwait(false); + } + + /// + /// Retrieves, asynchronously, information about a receipt. + /// + /// The receipt id to retrieve the information for. + /// Returns a . + /// Pushover API documentation + /// + /// + /// Thrown when receipt is null. + /// Thrown when an invalid receipt id is specified. + public async Task GetReceiptAsync(string receipt) + { + (this.ReceiptValidator ?? new ReceiptValidator()).Validate("receipt", receipt); + return await this.Get(GetV1APIUriFromBase("receipts/{0}.json?token={1}", receipt, this.ApplicationKey)).ConfigureAwait(false); + } + + /// + /// Cancels, asynchronously, a receipt. + /// + /// The receipt id to cancel. + /// Returns a . + /// Pushover API documentation + /// Thrown when receipt is null. + /// Thrown when an invalid receipt id is specified. + public async Task CancelReceiptAsync(string receipt) + { + (this.ReceiptValidator ?? new ReceiptValidator()).Validate("receipt", receipt); + var parameters = new NameValueCollection { + { "token", this.ApplicationKey } + }; + return await this.Post(GetV1APIUriFromBase("receipts/{0}/cancel.json", receipt), parameters).ConfigureAwait(false); + } + + /// + /// Validates, asynchronously, a specified user or group with the Pushover service. + /// + /// The user or group id to validate. + /// Returns a . + /// + /// Currently, this method throws when the user/group is not known by the Pushover service; this is likely to + /// change in the future. + /// + /// Pushover API documentation + /// Thrown when user/group id is null. + /// Thrown when user/group id is invalid. + public async Task ValidateUserOrGroupAsync(string userOrGroup) + { + return await ValidateUserOrGroupAsync(userOrGroup, null).ConfigureAwait(false); + } + + /// + /// Validates, asynchronously, a specified device for a user or group with the Pushover service. + /// + /// The user or group id to validate the device for. + /// The devicename to validate. + /// Returns a . + /// + /// Currently, this method throws when the user/group and/or devicename is not known by the Pushover service; + /// this is likely to change in the future. + /// + /// Pushover API documentation + /// Thrown when user/group id or the devicename is null. + /// Thrown when user/group id or the devicename is invalid. + public async Task ValidateUserOrGroupAsync(string userOrGroup, string deviceName) + { + (this.UserOrGroupKeyValidator ?? new UserOrGroupKeyValidator()).Validate("userOrGroup", userOrGroup); + if (deviceName != null) + (this.DeviceNameValidator ?? new DeviceNameValidator()).Validate("device", deviceName); + + var parameters = new NameValueCollection { + { "token", this.ApplicationKey }, + { "user", userOrGroup } + }; + return await this.Post(GetV1APIUriFromBase("users/validate.json"), parameters).ConfigureAwait(false); + } + + /// + /// Migrates, asynchronously, a specific subscription to a user/group. + /// + /// Subscription code to migrate. + /// User code to migrate the subscription to. + /// Returns a . + /// Applications that formerly collected Pushover user keys are encouraged to migrate to subscription keys. + /// Pushover API documentation + /// Thrown when subscription or user is null. + /// Thrown when user or devicename are invalid. + public async Task MigrateSubscriptionAsync(string subscription, string user) + { + return await MigrateSubscriptionAsync(subscription, user, null).ConfigureAwait(false); + } + + /// + /// Migrates, asynchronously, a specific subscription to a user/group and limits it to a specified device. + /// + /// Subscription code to migrate. + /// User code to migrate the subscription to. + /// The device name that the subscription should be limited to. + /// Returns a . + /// Applications that formerly collected Pushover user keys are encouraged to migrate to subscription keys. + /// Pushover API documentation + /// Thrown when subscription or user is null. + /// Thrown when user or devicename are invalid. + public async Task MigrateSubscriptionAsync(string subscription, string user, string device) + { + return await MigrateSubscriptionAsync(subscription, user, null, null).ConfigureAwait(false); + } + + /// + /// Migrates, asynchronously, a specific subscription to a user/group and limits it to a specified device, setting the user's preferred default sound. + /// + /// Subscription code to migrate. + /// User code to migrate the subscription to. + /// The device name that the subscription should be limited to. + /// The user's preferred default sound. + /// Returns a . + /// Applications that formerly collected Pushover user keys are encouraged to migrate to subscription keys. + /// Pushover API documentation + /// Thrown when subscription or user is null. + /// Thrown when user or devicename are invalid. + public async Task MigrateSubscriptionAsync(string subscription, string user, string device, string sound) + { + if (string.IsNullOrEmpty(subscription)) + throw new ArgumentNullException("subscription"); + (this.UserOrGroupKeyValidator ?? new UserOrGroupKeyValidator()).Validate("user", user); + if (device != null) + (this.DeviceNameValidator ?? new DeviceNameValidator()).Validate("device", device); + + var parameters = new NameValueCollection { + { "token", this.ApplicationKey }, + { "subscription", subscription }, + { "user", user }, + }; + parameters.AddConditional("device_name", device); + parameters.AddConditional("sound", sound); + + return await this.Post(GetV1APIUriFromBase("subscriptions/migrate.json"), parameters).ConfigureAwait(false); + } + + /// + /// Assigns, asynchronously, a license to a specific user or e-mail address. + /// + /// The user id (required unless e-mail is specified). + /// The user's e-mail address (required unless user is specified). + /// Returns a . + /// Pushover API documentation + /// When user and email are both null. + /// Invalid specified. + public async Task AssignLicenseAsync(string user, string email) + { + return await AssignLicenseAsync(user, email, OS.Any).ConfigureAwait(false); + } + + /// + /// Assigns, asynchronously, a license to a specific user or e-mail address and specified . + /// + /// The user id (required unless e-mail is specified). + /// The user's e-mail address (required unless user is specified). + /// The to assign the license to. + /// Returns a . + /// Pushover API documentation + /// When user and email are both null. + /// Invalid specified. + public async Task AssignLicenseAsync(string user, string email, OS os) + { + if (user != null) + (this.UserOrGroupKeyValidator ?? new UserOrGroupKeyValidator()).Validate("user", user); + if (email != null) + (this.EmailValidator ?? new EMailValidator()).Validate("email", email); + + if (user == null && email == null) + throw new InvalidOperationException("User or Email required"); + + if (!Enum.IsDefined(typeof(OS), os)) + throw new ArgumentOutOfRangeException("os"); + + var parameters = new NameValueCollection { + { "token", this.ApplicationKey }, + }; + parameters.AddConditional("user", user); + parameters.AddConditional("email", email); + parameters.AddConditional("os", os); + + return await this.Post(GetV1APIUriFromBase("licenses/assign.json"), parameters).ConfigureAwait(false); + } + + /// + /// Retrieves, asynchronously, the user's Pushover key and secret. + /// + /// The user's e-mail address. + /// The user's password. + /// Returns a . + /// Pushover API documentation + /// Thrown when e-mail or password is null. + /// Thrown when e-mail is invalid. + public async Task LoginAsync(string email, string password) + { + (this.EmailValidator ?? new EMailValidator()).Validate("email", email); + if (string.IsNullOrEmpty(password)) + throw new ArgumentNullException("password"); + + var parameters = new NameValueCollection { + { "email", email }, + { "password", password }, + }; + + return await this.Post(GetV1APIUriFromBase("users/login.json"), parameters).ConfigureAwait(false); + } + + /// + /// Registers, asynchronously, a device. + /// + /// The user's secret. + /// The short name of the device. + /// Returns a . + /// Pushover API documentation + /// Thrown when secret or devicename are null. + /// Thrown when devicename is invalid. + public async Task RegisterDeviceAsync(string secret, string deviceName) + { + if (string.IsNullOrEmpty(secret)) + throw new ArgumentNullException("secret"); + (this.DeviceNameValidator ?? new DeviceNameValidator()).Validate("deviceName", deviceName); + + var parameters = new NameValueCollection { + { "secret", secret }, + { "name", deviceName }, + { "os", "O" }, //This is, currently, the only supported value ("Open Client") + }; + + //TODO: BUG? Report? See https://pushover.net/api/client#register and https://www.reddit.com/r/pushover/comments/3abuy0/api_inconsistencies/ + //When an existing devicename is used, a 400 bad request is returned + //However, the JSON returned is {"errors":{"name":["has already been taken"]},"status":0,"request":"b3e85163d0bd8fc84565839ffc33bb42"} + //Where "errors" normally is an array, it is now an object... this is not (very) consistent with the rest of the responses + //The call, currently, throws a BadRequestException, as it should, however the errorresponse fails to parse because of this inconsistency... + return await this.Post(GetV1APIUriFromBase("devices.json"), parameters).ConfigureAwait(false); + } + + /// + /// Retrieves, asynchronously, all existing messages waiting for the device. + /// + /// The user's secret. + /// Device id for which to download the messages. + /// Returns a . + /// Pushover API documentation + /// Thrown when secret or device id is null. + public async Task ListMessagesAsync(string secret, string deviceId) + { + if (string.IsNullOrEmpty(secret)) + throw new ArgumentNullException("secret"); + if (string.IsNullOrEmpty(deviceId)) + throw new ArgumentNullException("deviceId"); + + return await this.Get(GetV1APIUriFromBase("messages.json?secret={0}&device_id={1}", secret, deviceId)).ConfigureAwait(false); + } + + /// + /// Deletes, asynchronously, all message up to, and including, the specified message id. + /// + /// The user's secret. + /// Device id for which to delete the messages. + /// Message id of message up to, and including, to delete. + /// Returns a . + /// Pushover API documentation + /// Thrown when secret or device id is null. + /// Thrown when message id is invalid. + public async Task DeleteMessagesAsync(string secret, string deviceId, int upToAndIncludingMessageId) + { + if (string.IsNullOrEmpty(secret)) + throw new ArgumentNullException("secret"); + if (string.IsNullOrEmpty(deviceId)) + throw new ArgumentNullException("deviceId"); + if (upToAndIncludingMessageId < 0) + throw new ArgumentOutOfRangeException("upToAndIncludingMessageId"); + + var parameters = new NameValueCollection { + { "secret", secret }, + }; + parameters.Add("message", upToAndIncludingMessageId); + + return await this.Post(GetV1APIUriFromBase("devices/{0}/update_highest_message.json", deviceId), parameters).ConfigureAwait(false); + } + + /// + /// Acknowledges, asynchronously, an emergency-priority message. + /// + /// The user's secret. + /// The receipt of the message to acknowledge. + /// Returns a . + /// Pushover API documentation + /// + /// + /// Thrown when secret or receipt is null. + /// Thrown when receipt is invalid. + public async Task AcknowledgeMessageAsync(string secret, string receipt) + { + if (string.IsNullOrEmpty(secret)) + throw new ArgumentNullException("secret"); + (this.ReceiptValidator ?? new ReceiptValidator()).Validate("receipt", receipt); + + var parameters = new NameValueCollection { + { "secret", secret } + }; + return await this.Post(GetV1APIUriFromBase("receipts/{0}/acknowledge.json", receipt), parameters).ConfigureAwait(false); + } + + /// + /// Downloads, asynchronously, a specified icon from the Pushover service. + /// + /// Name of the icon to download. + /// Returns a . + /// Pushover API documentation + /// Thrown when iconname is null. + public async Task DownloadIconAsync(string iconName) + { + if (string.IsNullOrEmpty(iconName)) + throw new ArgumentNullException("iconName"); + return await this.DownloadFileAsync(GetIconUriFromBase("{0}.png", iconName)).ConfigureAwait(false); + } + + /// + /// Downloads, asynchronously, a specified sound from the Pushover service in Mp3 format. + /// + /// Name of the sound to download. + /// Returns a . + /// Pushover API documentation + /// Thrown when soundname is null. + public async Task DownloadSoundAsync(string soundName) + { + return await DownloadSoundAsync(soundName, AudioFormat.Mp3).ConfigureAwait(false); + } + + /// + /// Downloads, asynchronously, a specified sound from the Pushover service in the specified format. + /// + /// Name of the sound to download. + /// to download. + /// Returns a . + /// Pushover API documentation + /// Thrown when soundname is null. + /// Thrown when audioFormat is invalid. + public async Task DownloadSoundAsync(string soundName, AudioFormat audioFormat) + { + if (string.IsNullOrEmpty(soundName)) + throw new ArgumentNullException("soundName"); + if (!Enum.IsDefined(typeof(AudioFormat), audioFormat)) + throw new ArgumentOutOfRangeException("audioFormat"); + + return await this.DownloadFileAsync(GetSoundsUriFromBase("{0}.{1}", soundName, audioFormat.ToString().ToLowerInvariant())).ConfigureAwait(false); + } + #endregion + + #region Private methods + /// + /// Downloads, asynchronously, a specified file. + /// + /// of file to download. + /// Returns a . + private async Task DownloadFileAsync(Uri uri) + { + using (var wc = this.GetWebClient()) + return await wc.OpenReadTaskAsync(uri).ConfigureAwait(false); + } + + /// + /// Returns a relative based on the . + /// + /// Relative path from the . + /// An object array that contains zero or more objects to format. + /// Returns a relative based on the . + private Uri GetUriFromBase(string relative, params object[] args) + { + return new Uri(this.BaseUri, this.FormatUri(relative, args)); + } + + /// + /// Returns the V1 api based on the . + /// + /// Relative path from the V1 API . + /// An object array that contains zero or more objects to format. + /// Returns the V1 api based on the . + private Uri GetV1APIUriFromBase(string relative, params object[] args) + { + return new Uri(GetUriFromBase("1/"), this.FormatUri(relative, args)); + } + + /// + /// Returns an icon based on the . + /// + /// Relative path for the icon. + /// An object array that contains zero or more objects to format. + /// Returns an icon based on the . + private Uri GetIconUriFromBase(string relative, params object[] args) + { + return new Uri(GetUriFromBase("icons/"), this.FormatUri(relative, args)); + } + + /// + /// Returns a sound based on the . + /// + /// Relative path for the sound. + /// An object array that contains zero or more objects to format. + /// Returns a sound based on the . + private Uri GetSoundsUriFromBase(string relative, params object[] args) + { + return new Uri(GetUriFromBase("sounds/"), this.FormatUri(relative, args)); + } + + /// + /// Acts like a regular string.Format. However, it escapes each argument for use in a . + /// + /// The composite (partial) uri(format); e.g. /foo/{0}/bar?baz={1}. + /// An array that contains zero or more objects to format. + /// Returns the escaped . + private string FormatUri(string uri, params object[] args) + { + return string.Format(uri, args.Select(a => Uri.EscapeDataString(a.ToString())).ToArray()); + } + + + + /// + /// Executes a POST to the given passing the specified parameters. + /// + /// The type of response to expext. + /// The to post to. + /// The values to post. + /// Returns the parsed response. + private async Task Post(Uri uri, NameValueCollection parameters) + where T : PushoverResponse + { + using (var wc = this.GetWebClient()) + { + return await ExecuteWebRequest(async () => + { + var json = this.Encoding.GetString(await wc.UploadValuesTaskAsync(uri, parameters).ConfigureAwait(false)); + return await ParseResponse(json, wc.ResponseHeaders).ConfigureAwait(false); + }).ConfigureAwait(false); + } + } + + /// + /// Executes a GET to the given . + /// + /// The type of response to expext. + /// The to GET. + /// Returns the parsed response. + private async Task Get(Uri uri) + where T : PushoverResponse + { + using (var wc = this.GetWebClient()) + { + return await ExecuteWebRequest(async () => + { + var json = await wc.DownloadStringTaskAsync(uri).ConfigureAwait(false); + return await ParseResponse(json, wc.ResponseHeaders).ConfigureAwait(false); + }).ConfigureAwait(false); + } + } + + /// + /// Executes a specified function asynchronously assuming it returns a and + /// handles any web exceptions and other problems as best as possible (or re-throws). + /// + private async Task ExecuteWebRequest(Func> func) + where T : PushoverResponse + { + try + { + return await func.Invoke().ConfigureAwait(false); + } + catch (WebException wex) + { + var response = wex.Response as HttpWebResponse; + if (response != null) + { + //Try parse any json response... IF any + var errorresponse = ParseErrorResponse(response); + if (errorresponse != null) + { + switch ((int)response.StatusCode) + { + case 400: //Bad request + throw new BadRequestException(errorresponse, wex); + case 429: //Rate limited + throw new RateLimitExceededException(errorresponse); + } + } + else + { + throw new ResponseException(string.Format("Unable to parse error response. Status was: {0} {1}", response.StatusCode, response.StatusDescription)); + } + } + throw; + } + catch (PushoverException) + { + throw; + } + catch (Exception ex) + { + throw new PushoverException("Error retrieving response", ex); + } + } + + /// + /// When Pushover returns an error it is returned in a (sort of...) structured format; this method tries to + /// parse the result and extract possible information from it. + /// + private PushoverUserResponse ParseErrorResponse(HttpWebResponse response) + { + PushoverUserResponse errorresponse = null; + try + { + using (var s = response.GetResponseStream()) + using (var r = new StreamReader(s, this.Encoding)) + { + var json = r.ReadToEnd(); + errorresponse = JsonConvert.DeserializeObject(json); + } + + errorresponse.RateLimitInfo = ParseRateLimitInfo(response.Headers); + } + catch + { + //NOP + } + return errorresponse; + } + + private static RateLimitInfo ParseRateLimitInfo(HttpResponseHeaders headers) + { + int limit, remaining, reset; + + if (int.TryParse(headers.GetValues("X-Limit-App-Limit").FirstOrDefault(), out limit) + && int.TryParse(headers.GetValues("X-Limit-App-Remaining").FirstOrDefault(), out remaining) + && int.TryParse(headers.GetValues("X-Limit-App-Reset").FirstOrDefault(), out reset)) + { + return new RateLimitInfo(limit, remaining, EPOCH.AddSeconds(reset)); + } + return null; + } + + /// + /// Parses, possible, rate-limiting information from a Pushover response if any. + /// + private static RateLimitInfo ParseRateLimitInfo(WebHeaderCollection headers) + { + int limit, remaining, reset; + + if (int.TryParse(headers["X-Limit-App-Limit"], out limit) + && int.TryParse(headers["X-Limit-App-Remaining"], out remaining) + && int.TryParse(headers["X-Limit-App-Reset"], out reset)) + { + return new RateLimitInfo(limit, remaining, EPOCH.AddSeconds(reset)); + } + return null; + } + + + private static async Task ParseResponse(string json, HttpResponseHeaders headers) + where T : PushoverResponse + { + T result; + try + { + result = await Task.Factory.StartNew(() => JsonConvert.DeserializeObject(json)).ConfigureAwait(false); + } + catch (Exception ex) + { + throw new PushoverException("Error parsing response", ex); + } + + result.RateLimitInfo = ParseRateLimitInfo(headers); + if (!result.IsOk) + throw new ResponseException("API returned one or more errors", result); + + return result; + } + + /// + /// Parses a Pushover JSON response asynchronously. + /// + private static async Task ParseResponse(string json, WebHeaderCollection headers) + where T : PushoverResponse + { + T result; + try + { + result = await Task.Factory.StartNew(() => JsonConvert.DeserializeObject(json)).ConfigureAwait(false); + } + catch (Exception ex) + { + throw new PushoverException("Error parsing response", ex); + } + + result.RateLimitInfo = ParseRateLimitInfo(headers); + if (!result.IsOk) + throw new ResponseException("API returned one or more errors", result); + + return result; + } + + /// + /// Creates and returns a to be used when communicating with Pushover's service. + /// + private WebClient GetWebClient() + { + var wc = new WebClient(); + wc.Proxy = this.Proxy; + wc.Headers.Add(HttpRequestHeader.UserAgent, USERAGENT); + wc.Encoding = this.Encoding; + return wc; + } + #endregion + } +} diff --git a/src/UI/NPushOver/RequestObjects/Enums.cs b/src/UI/NPushOver/RequestObjects/Enums.cs new file mode 100644 index 00000000..cd87cbf3 --- /dev/null +++ b/src/UI/NPushOver/RequestObjects/Enums.cs @@ -0,0 +1,153 @@ +namespace NPushover.RequestObjects +{ + /// + /// Defines the available priorities for s. + /// + public enum Priority + { + /// + /// When the priority parameter is specified with this value, messages will be considered lowest priority and + /// will not generate any notification. On iOS, the application badge number will be increased. + /// + Lowest = -2, + + /// + /// Messages this priority will be considered low priority and will not generate any sound or vibration, but + /// will still generate a popup/scrolling notification depending on the client operating system. Messages + /// delivered during a user's quiet hours are sent as though they had a priority of . + /// + Low = -1, + + /// + /// Messages sent with this priority will have the default priority. These messages trigger sound, vibration, + /// and display an alert according to the user's device settings. On iOS, the message will display at the top + /// of the screen or as a modal dialog, as well as in the notification center. On Android, the message will + /// scroll at the top of the screen and appear in the notification center. If a user has quiet hours set and + /// the message is received during those times, your message will be delivered as though it had a priority of + /// + Normal = 0, + + /// + /// Messages sent with this priority are high priority messages that bypass a user's quiet hours. These + /// messages will always play a sound and vibrate (if the user's device is configured to) regardless of the + /// delivery time. High-priority should only be used when necessary and appropriate. High-priority messages are + /// highlighted in red in the device clients. + /// + High = 1, + + /// + /// Emergency-priority notifications are similar to high-priority notifications, but they are repeated until + /// the notification is acknowledged by the user. These are designed for dispatching and on-call situations + /// where it is critical that a notification be repeatedly shown to the user (or all users of the group that + /// the message was sent to) until it is acknowledged. Applications sending emergency notifications are issued + /// a receipt that can be used to get the status of a notification and find out whether it was acknowledged, or + /// automatically receive a callback when the user has acknowledged the notification. To send an + /// emergency-priority notification, the priority parameter must be set to this value and the + /// must be supplied. + /// + Emergency = 2 + } + + /// + /// Defines the available sounds (however, more sounds may become available; see ). + /// + public enum Sounds + { + /// Pushover (default) + Pushover, + + /// Bike + Bike, + + /// Bugle + Bugle, + + /// Cash register + Cashregister, + + /// Classical + Classical, + + /// Cosmic + Cosmic, + + /// Falling + Falling, + + /// Gamelan + Gamelan, + + /// Incoming + Incoming, + + /// Intermission + Intermission, + + /// Magic + Magic, + + /// Mechanical + Mechanical, + + /// Piano bar + Pianobar, + + /// Siren + Siren, + + /// Space alarm + Spacealarm, + + /// Tugboat + Tugboat, + + /// Alien alarm (long) + Alien, + + /// Climb (long) + Climb, + + /// Persistent (long) + Persistent, + + /// Pushover echo (long) + Echo, + + /// Up down (long) + Updown, + + /// None (silent) + None + } + + /// + /// Defines the available Operating System choices for licensing (see + /// ). + /// + public enum OS + { + /// Assign the license to the first operating system the user registers with + Any, + + /// Android + Android, + + /// iOS + iOS, + + /// Desktop + Desktop + } + + /// + /// Defines the available audio formats for retrieval (see ). + /// + public enum AudioFormat + { + /// Mp3 + Mp3, + + /// Wave + Wav + } +} diff --git a/src/UI/NPushOver/RequestObjects/Message.cs b/src/UI/NPushOver/RequestObjects/Message.cs new file mode 100644 index 00000000..00d52384 --- /dev/null +++ b/src/UI/NPushOver/RequestObjects/Message.cs @@ -0,0 +1,236 @@ +using System; + +namespace NPushover.RequestObjects +{ + /// + /// Represents an Pushover message that can be sent using the class. + /// + public class Message + { + /// + /// Initializes a new instance of the class with default and + /// . + /// + public Message() + : this(Priority.Normal, null, null, false, Sounds.Pushover) { } + + /// + /// Initializes a new instance of the class with default and with + /// the specified . + /// + /// The of the . + public Message(Sounds sound) + : this(Priority.Normal, null, sound) { } + + /// + /// Initializes a new instance of the class with default and + /// and with the specified . + /// + /// The . + public Message(string body) + : this(Priority.Normal, body) { } + + /// + /// Initializes a new instance of the class with default and with the + /// specified and . + /// + /// The . + /// The . + public Message(Priority priority, string body) + : this(priority, body, Sounds.Pushover) { } + + /// + /// Initializes a new instance of the class with the specified , + /// and . + /// + /// The . + /// The . + /// The of the . + public Message(Priority priority, string body, Sounds sound) + : this(priority, body, false, sound) { } + + /// + /// Initializes a new instance of the class with the specified , + /// and . + /// + /// The . + /// The . + /// When the contains HTML, set to true. False otherwise. See . + /// The of the . + public Message(Priority priority, string body, bool isHtmlBody, Sounds sound) + : this(priority, null, body, isHtmlBody, sound) { } + + /// + /// Initializes a new instance of the class with the specified , + /// , and . + /// + /// The . + /// The for the . + /// The . + /// When the contains HTML, set to true. False otherwise. See . + /// The of the . + public Message(Priority priority, string title, string body, bool isHtmlBody, Sounds sound) + { + this.Priority = priority; + this.Title = title; + this.Body = body; + this.IsHtmlBody = IsHtmlBody; + this.SetSound(sound); + } + + /// + /// Gets/sets the of the . + /// + /// + /// When you send a with priority , + /// Pushover will respond with a containing a + /// that can be used to get information + /// about whether the has been acknowledged. + /// + /// + /// + public Priority Priority { get; set; } + + /// + /// Gets/sets the (optional) title of the . + /// + public string Title { get; set; } + + /// + /// Gets/sets the body of the ; may contain some HTML. See . + /// + /// Pushover API documentation + public string Body { get; set; } + + /// + /// Gets/sets the of the . + /// + /// Pushover API documentation + /// + public SupplementaryURL SupplementaryUrl { get; set; } + + /// + /// Gets/set the time of the . + /// + /// Make sure Timestamp is specified in UTC; if not it will be assumed local and converted to UTC. + /// Pushover API documentation + public DateTime? Timestamp { get; set; } + + /// + /// Gets/sets the notification sound for the . + /// + /// A list of available sounds can be retrieved with . + /// + /// + /// Pushover API documentation + public string Sound { get; set; } + + /// + /// Get/sets whether the is to be interpreted as HTML. + /// + /// Pushover API documentation + /// + /// HTML tags currently supported by Pushover: + ///
    + ///
  • <b>word</b> - display word in bold
  • + ///
  • <i>word</i> - display word in italics
  • + ///
  • <u>word</u> - display word underlined
  • + ///
  • <font color="blue">word</font> - display word in blue text (most colors and hex codes permitted)
  • + ///
  • <a href="http://example.com/">word</a> - display word as a tappable link to http://example.com/
  • + ///
+ ///
+ public bool IsHtmlBody { get; set; } + + /// + /// Gets/set the for the . + /// + /// Pushover API documentation + public RetryOptions RetryOptions { get; set; } + + + /// + /// Creates a with the specified and default (Normal). + /// + /// The of the . + /// A with the specified . + public static Message Create(Sounds sound) + { + return new Message(sound); + } + + /// + /// Creates a with the specified and default (Normal) and sound. + /// + /// The . + /// A with the specified and default (Normal) and sound. + public static Message Create(string body) + { + return new Message(body); + } + + /// + /// Creates a with the specified and and default sound. + /// + /// The . + /// The . + /// A with the specified and and default sound. + public static Message Create(Priority priority, string body) + { + return new Message(priority, body); + } + + /// + /// Creates a with the specified , and . + /// + /// The . + /// The . + /// The of the . + /// A with the specified , and . + public static Message Create(Priority priority, string body, Sounds sound) + { + return new Message(priority, body, sound); + } + + /// + /// Creates a with the specified , and . + /// + /// The . + /// The . + /// When the contains HTML, set to true. False otherwise. See . + /// The of the . + /// A with the specified , and . + public static Message Create(Priority priority, string body, bool isHtmlBody, Sounds sound) + { + return new Message(priority, body, isHtmlBody, sound); + } + + /// + /// Creates a with the specified , , + /// and . + /// + /// The . + /// The for the . + /// The . + /// When the contains HTML, set to true. False otherwise. See . + /// The of the . + /// + /// A with the specified , , + /// and . + /// + public static Message Create(Priority priority, string title, string body, bool isHtmlBody, Sounds sound) + { + return new Message(priority, title, body, isHtmlBody, sound); + } + + /// + /// Sets the property to any of the available values. + /// + /// The values to set this messages' property to. + /// + /// Pushover API documentation + public void SetSound(Sounds sound) + { + this.Sound = sound.ToString().ToLowerInvariant(); + } + } +} diff --git a/src/UI/NPushOver/RequestObjects/RetryOptions.cs b/src/UI/NPushOver/RequestObjects/RetryOptions.cs new file mode 100644 index 00000000..ad227f26 --- /dev/null +++ b/src/UI/NPushOver/RequestObjects/RetryOptions.cs @@ -0,0 +1,38 @@ +using System; + +namespace NPushover.RequestObjects +{ + /// + /// Represents options for retrying delivery. + /// + /// + /// These options can only be used/specified for messages. + /// + /// + /// + public class RetryOptions + { + /// + /// Gets/sets how often the Pushover servers will send the same to the user. In a + /// situation where a user might be in a noisy environment or sleeping, retrying the notification (with sound + /// and vibration) will help get his or her attention. The minimum value is 30 seconds between retries. + /// + /// + public TimeSpan RetryEvery { get; set; } + + /// + /// Gets/set how long the notification will continue to be retried with the specified + /// interval. If the notification has not been acknowledged in this time, it will be marked as expired + /// and will stop being sent to the user. Note that the notification is still shown to the user after it is + /// expired, but it will not prompt the user for acknowledgement. This maximum value allowed is 24 hours. + /// + /// + public TimeSpan RetryPeriod { get; set; } + + /// + /// Gets/sets the (optional) callback parameter may be supplied with a publicly-accessible URL that the Pushover + /// servers will send a request to when the user has acknowledged the message. + /// + public Uri CallBackUrl { get; set; } + } +} diff --git a/src/UI/NPushOver/RequestObjects/SupplementaryURL.cs b/src/UI/NPushOver/RequestObjects/SupplementaryURL.cs new file mode 100644 index 00000000..d01920c8 --- /dev/null +++ b/src/UI/NPushOver/RequestObjects/SupplementaryURL.cs @@ -0,0 +1,57 @@ +using System; + +namespace NPushover.RequestObjects +{ + /// + /// Represents a supplementary (to a ) URL. + /// + /// + /// + /// It may be desirable to include a supplementary URL that is not included in the + /// , but available for the user to click on. This will + /// be passed directly to the device client, with a URL title of the supplied (defaulting to + /// the itself if no title given). + /// + /// + /// Supplementary URLs can be useful for presenting long URLs in a notification as well as interacting with 3rd + /// party applications. For example, if a Pushover application were sending Twitter messages to a user, a + /// may be sent that includes the actual link to the message that would open in + /// the user's browser (e.g., http://twitter.com/user/status/12345) or a URL that will perform some action in + /// another application installed on the device (e.g., twitter://status?id=12345). The message displayed in the + /// Pushover client would be the actual contents of the Twitter message (with any URLs originally contained in + /// it automatically turned into links), but the supplementary link will be shown underneath it as an option + /// available to the user when the message is highlighted. + /// + /// + /// While there are some standard URL schemes like tel: and sms: that will be handled by iOS and Android the + /// same way, others like the twitter:// scheme used above are highly specific to the platform and other + /// applications installed on the device. A list of common URL schemes supported by applications on iOS can be + /// found at handleopenurl.com, and a list handled natively by Android can be found on developer.android.com. + /// Since Pushover users may be on different platforms and have different 3rd party applications installed, it + /// is not recommended to use app-specific URL schemes as supplementary URLs in public plugins, websites, and + /// apps. + /// + /// + /// Due to limitations of the iOS push notification service, supplementary URLs are not able to be shown with + /// push notifications. Notifications in the Notification Center will only show the title and message. The user + /// must tap on the notification or otherwise open the Pushover client, which will perform a sync with the + /// Pushover servers, in order to download the attached supplementary URL. Since these URLs are supplementary, + /// they should not be used as the primary content of your notification. If your notification is just a URL, + /// include it in the message body instead. + /// + /// + public class SupplementaryURL + { + /// + /// Gets/sets the of the . + /// + /// + public Uri Uri { get; set; } + + /// + /// Gets/sets the title (or text) to be displayed for the URL. + /// + /// + public string Title { get; set; } + } +} diff --git a/src/UI/NPushOver/ResponseObjects/AssignLicenseResponse.cs b/src/UI/NPushOver/ResponseObjects/AssignLicenseResponse.cs new file mode 100644 index 00000000..7453d25a --- /dev/null +++ b/src/UI/NPushOver/ResponseObjects/AssignLicenseResponse.cs @@ -0,0 +1,18 @@ +using Newtonsoft.Json; + +namespace NPushover.ResponseObjects +{ + /// + /// Represents a response for a call. + /// + /// Pushover API documentation + public class AssignLicenseResponse : PushoverUserResponse + { + /// + /// Gets the number of credits available for the application. + /// + /// Pushover API documentation + [JsonProperty("credits")] + public int Credits { get; set; } + } +} diff --git a/src/UI/NPushOver/ResponseObjects/ListMessagesResponse.cs b/src/UI/NPushOver/ResponseObjects/ListMessagesResponse.cs new file mode 100644 index 00000000..37750245 --- /dev/null +++ b/src/UI/NPushOver/ResponseObjects/ListMessagesResponse.cs @@ -0,0 +1,28 @@ +using Newtonsoft.Json; + +namespace NPushover.ResponseObjects +{ + /// + /// Represents a response for a call. + /// + /// Pushover API documentation + public class ListMessagesResponse : PushoverResponse + { + /// + /// Gets the messages retrieved from the Pushover services. + /// + /// Pushover API documentation + [JsonProperty("messages")] + public StoredMessage[] Messages { get; set; } + + /// + /// Gets information about the current user; see . + /// + /// + /// This is an undocumented property. + /// + /// Pushover API documentation + [JsonProperty("user")] + public ListMessagesUser User { get; set; } + } +} diff --git a/src/UI/NPushOver/ResponseObjects/ListMessagesUser.cs b/src/UI/NPushOver/ResponseObjects/ListMessagesUser.cs new file mode 100644 index 00000000..bf41d91d --- /dev/null +++ b/src/UI/NPushOver/ResponseObjects/ListMessagesUser.cs @@ -0,0 +1,38 @@ +using Newtonsoft.Json; + +namespace NPushover.ResponseObjects +{ + /// + /// Represents information about a user which is part of the response from a from + /// a call. + /// + /// + /// This part of the response is undocumented by Pushover. + /// + public class ListMessagesUser + { + /// + /// Indicates if the user is, currently(?), in quiet hours. + /// + [JsonProperty("quiet_hours")] + public bool QuietHours { get; set; } + + /// + /// Indicates if the user has a license for Android devices. + /// + [JsonProperty("is_android_licensed")] + public bool IsAndroidLicensed { get; set; } + + /// + /// Indicates if the user has a license for iOS devices. + /// + [JsonProperty("is_ios_licensed")] + public bool IsIosLicensed { get; set; } + + /// + /// Indicates if the user has a license for desktop application. + /// + [JsonProperty("is_desktop_licensed")] + public bool IsDesktopLicensed { get; set; } + } +} diff --git a/src/UI/NPushOver/ResponseObjects/LoginResponse.cs b/src/UI/NPushOver/ResponseObjects/LoginResponse.cs new file mode 100644 index 00000000..99d174b9 --- /dev/null +++ b/src/UI/NPushOver/ResponseObjects/LoginResponse.cs @@ -0,0 +1,25 @@ +using Newtonsoft.Json; + +namespace NPushover.ResponseObjects +{ + /// + /// Represents a response for a call. + /// + /// Pushover API documentation + public class LoginResponse : PushoverUserResponse + { + /// + /// Gets the user key. + /// + /// Pushover API documentation + [JsonProperty("id")] + public string Id { get; set; } + + /// + /// Gets the user's secret. + /// + /// Pushover API documentation + [JsonProperty("secret")] + public string Secret { get; set; } + } +} diff --git a/src/UI/NPushOver/ResponseObjects/MigrateSubscriptionResponse.cs b/src/UI/NPushOver/ResponseObjects/MigrateSubscriptionResponse.cs new file mode 100644 index 00000000..644c4589 --- /dev/null +++ b/src/UI/NPushOver/ResponseObjects/MigrateSubscriptionResponse.cs @@ -0,0 +1,22 @@ +using Newtonsoft.Json; + +namespace NPushover.ResponseObjects +{ + /// + /// Represents a response for a call. + /// + /// + /// Applications that formerly collected Pushover user keys are encouraged to migrate to subscription keys. + /// + /// Pushover API documentation + public class MigrateSubscriptionResponse : PushoverUserResponse + { + /// + /// Gets the key to save in place of the user's original key (of wich the latter can be discarded). + /// + /// + /// Pushover API documentation + [JsonProperty("subscribed_user_key")] + public string SubscribedUserKey { get; set; } + } +} diff --git a/src/UI/NPushOver/ResponseObjects/PushoverResponse.cs b/src/UI/NPushOver/ResponseObjects/PushoverResponse.cs new file mode 100644 index 00000000..def81113 --- /dev/null +++ b/src/UI/NPushOver/ResponseObjects/PushoverResponse.cs @@ -0,0 +1,95 @@ +using Newtonsoft.Json; +namespace NPushover.ResponseObjects +{ + /// + /// Provides a base class for responses received from calls to the Pushover services. + /// + /// Pushover API documentation + public class PushoverResponse + { + /// + /// Gets the status of the response; a value of 0 indicates a problem, 1 indicates OK. + /// + /// Pushover API documentation + [JsonProperty("status")] + public int Status { get; set; } + + /// + /// Gets the unique id for the request. + /// + /// Pushover API documentation + [JsonProperty("request")] + public string Request { get; set; } + + /// + /// Gets a receipt code (if any). Null otherwise. + /// + /// + /// Only s that are sent with a + /// a + /// receipt code will be returned. + /// + /// Pushover API documentation + /// Pushover API documentation + [JsonProperty("receipt")] + public string Receipt { get; set; } + + /// + /// Gets any errors returned by the Pushover services. Null otherwise. + /// + /// Pushover API documentation + [JsonProperty("errors")] + public string[] Errors { get; set; } + + /// + /// Gets information about ratelimiting returned by the Pushover services, if any. Null otherwise. + /// + /// + /// Usually only a response to a contains this + /// information. + /// + /// Pushover API documentation + /// Pushover API documentation + [JsonIgnore] + public RateLimitInfo RateLimitInfo { get; set; } + + /// + /// Indicates if the contains any errors. + /// + public bool HasErrors + { + get { return this.Errors != null && this.Errors.Length > 0; } + } + + /// + /// Indicates if the has a code that represents an OK result. + /// + public bool IsOkStatus + { + get { return this.Status == 1; } + } + + /// + /// Indicates if the is OK: no and a + /// that represents an OK value. + /// + public bool IsOk + { + get { return this.IsOkStatus && !this.HasErrors; } + } + } + + /// + /// Represents a response from the Pushover services that may also contain user information. + /// + /// Pushover API documentation + public class PushoverUserResponse : PushoverResponse + { + /// + /// Gets the user information, if any. Null otherwise. + /// + /// Pushover API documentation + [JsonProperty("user")] + public string User { get; set; } + } +} diff --git a/src/UI/NPushOver/ResponseObjects/RateLimitInfo.cs b/src/UI/NPushOver/ResponseObjects/RateLimitInfo.cs new file mode 100644 index 00000000..941f174e --- /dev/null +++ b/src/UI/NPushOver/ResponseObjects/RateLimitInfo.cs @@ -0,0 +1,40 @@ +using System; + +namespace NPushover.ResponseObjects +{ + /// + /// Represents rate limiting info returned by the Pushover services (if any). + /// + /// + /// Pushover API documentation + public class RateLimitInfo + { + /// + /// Gets the monthly message limit (plus any additional purchased capacity). + /// + /// Pushover API documentation + public int Limit { get; private set; } + + /// + /// Gets the remaining monthly message limit (plus any additional purchased capacity). + /// + /// Pushover API documentation + public int Remaining { get; private set; } + + /// + /// Gets the date when the count for the rate limit will be rest. + /// + /// Pushover API documentation + public DateTime Reset { get; private set; } + + /// + /// Initializes a new object. + /// + internal RateLimitInfo(int limit, int remaining, DateTime reset) + { + this.Limit = limit; + this.Remaining = remaining; + this.Reset = reset; + } + } +} diff --git a/src/UI/NPushOver/ResponseObjects/ReceiptResponse.cs b/src/UI/NPushOver/ResponseObjects/ReceiptResponse.cs new file mode 100644 index 00000000..23e980a6 --- /dev/null +++ b/src/UI/NPushOver/ResponseObjects/ReceiptResponse.cs @@ -0,0 +1,76 @@ +using Newtonsoft.Json; +using NPushover.Converters; +using System; + +namespace NPushover.ResponseObjects +{ + /// + /// Represents a response for a call. + /// + /// Pushover API documentation + public class ReceiptResponse : PushoverUserResponse + { + /// + /// Gets whether the user has acknowledged the notification. + /// + /// Pushover API documentation + [JsonProperty("acknowledged")] + [JsonConverter(typeof(BoolConverter))] + public bool Acknowledged { get; set; } + + /// + /// Gets the date of when the user has acknowledged the notification, if any. + /// + /// Pushover API documentation + [JsonProperty("acknowledged_at")] + [JsonConverter(typeof(NullableUnixDateTimeConverter))] + public DateTime? AcknowledgedAt { get; set; } + + /// + /// Gets the user key of the user that first acknowledged the notification. + /// + /// Pushover API documentation + [JsonProperty("acknowledged_by")] + public string AcknowledgedBy { get; set; } + + /// + /// Gets when the notification was last retried / delivered, if at all. + /// + /// Pushover API documentation + [JsonProperty("last_delivered_at")] + [JsonConverter(typeof(NullableUnixDateTimeConverter))] + public DateTime? LastDeliveredAt { get; set; } + + /// + /// Gets whether the expiration date has passed. + /// + /// Pushover API documentation + [JsonProperty("expired")] + [JsonConverter(typeof(BoolConverter))] + public bool Expired { get; set; } + + /// + /// Gets when the notification will stop being retried. + /// + /// Pushover API documentation + [JsonProperty("expires_at")] + [JsonConverter(typeof(UnixDateTimeConverter))] + public DateTime ExpiresAt { get; set; } + + /// + /// Gets whether the Pushover services have called back the callback URL (if any). + /// + /// Pushover API documentation + [JsonProperty("called_back")] + [JsonConverter(typeof(BoolConverter))] + public bool CalledBack { get; set; } + + /// + /// Gets when the Pushover services have called back the callback URL (if any, if at all). + /// + /// Pushover API documentation + [JsonProperty("called_back_at")] + [JsonConverter(typeof(NullableUnixDateTimeConverter))] + public DateTime? CalledBackAt { get; set; } + } +} diff --git a/src/UI/NPushOver/ResponseObjects/RegisterDeviceResponse.cs b/src/UI/NPushOver/ResponseObjects/RegisterDeviceResponse.cs new file mode 100644 index 00000000..efbd47e1 --- /dev/null +++ b/src/UI/NPushOver/ResponseObjects/RegisterDeviceResponse.cs @@ -0,0 +1,21 @@ +using Newtonsoft.Json; + +namespace NPushover.ResponseObjects +{ + /// + /// Represents a response for a call. + /// + /// Pushover API documentation + public class RegisterDeviceResponse : PushoverUserResponse + { + /// + /// Gets the device's unique id. + /// + /// + /// Store this value in a secure location. + /// + /// Pushover API documentation + [JsonProperty("id")] + public string Id { get; set; } + } +} diff --git a/src/UI/NPushOver/ResponseObjects/SoundsResponse.cs b/src/UI/NPushOver/ResponseObjects/SoundsResponse.cs new file mode 100644 index 00000000..b8a41c49 --- /dev/null +++ b/src/UI/NPushOver/ResponseObjects/SoundsResponse.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace NPushover.ResponseObjects +{ + /// + /// Represents a response for a call. + /// + /// Pushover API documentation + public class SoundsResponse : PushoverUserResponse + { + /// + /// Gets available sounds from the Pushover services (name / description). + /// + /// Pushover API documentation + [JsonProperty("sounds")] + public IDictionary Sounds { get; set; } + } +} diff --git a/src/UI/NPushOver/ResponseObjects/StoredMessage.cs b/src/UI/NPushOver/ResponseObjects/StoredMessage.cs new file mode 100644 index 00000000..8ec6158a --- /dev/null +++ b/src/UI/NPushOver/ResponseObjects/StoredMessage.cs @@ -0,0 +1,142 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using NPushover.Converters; +using NPushover.RequestObjects; +using System; + +namespace NPushover.ResponseObjects +{ + /// + /// Represents information about a message for a + /// call. + /// + /// Pushover API documentation + public class StoredMessage + { + /// + /// Gets the unique id of the , relative to the device. + /// + /// Pushover API documentation + [JsonProperty("id")] + public int Id { get; set; } + + /// + /// Gets the unique id of the relative to all devices on the same user's account. + /// + /// + /// When a is received by Pushover and sent to all devices on a user's account, each + /// is given the same value. This is primarily used for cross-device + /// notification dismissal sync. + /// + /// Pushover API documentation + [JsonProperty("umid")] + public int UMId { get; set; } + + /// + /// Gets the title of the , if present. If not present, the name of the application (app) + /// should be displayed. + /// + /// Pushover API documentation + [JsonProperty("title")] + public string Title { get; set; } + + /// + /// Gets the body of the . + /// + /// Pushover API documentation + [JsonProperty("message")] + public string Body { get; set; } + + /// + /// Gets the name of the application that sent the . This may not be unique. + /// + /// Pushover API documentation + [JsonProperty("app")] + public string Application { get; set; } + + /// + /// Gets the unique id of the application that sent the . + /// + /// Pushover API documentation + [JsonProperty("aid")] + public int ApplicationId { get; set; } + + /// + /// Gets the icon filename of the application that sent the . + /// + /// + /// The image data can be fetched using . When an + /// application changes its icon, this value will change. + /// + /// + /// Pushover API documentation + [JsonProperty("icon")] + public string Icon { get; set; } + + /// + /// Gets the date and time the was received by the Pushover services, unless the sender + /// overrode the timestamp when sending it. + /// + /// Pushover API documentation + [JsonProperty("date")] + [JsonConverter(typeof(Newtonsoft.Json.Converters.UnixDateTimeConverter))] + public DateTime Timestamp { get; set; } + + /// + /// Gets the of the . + /// + /// Pushover API documentation + [JsonProperty("priority")] + [JsonConverter(typeof(StringEnumConverter))] + public Priority Priority { get; set; } + + /// + /// Gets the , if specified, from a . + /// + /// + /// This resource should be downloaded and cached. + /// + /// + /// Pushover API documentation + [JsonProperty("sound")] + public string Sound { get; set; } + + /// + /// Gets the 's url for the , if any. + /// + /// Pushover API documentation + [JsonProperty("url")] + public string Url { get; set; } + + /// + /// Gets the 's title for the , if any. + /// + /// Pushover API documentation + [JsonProperty("url_title")] + public string UrlTitle { get; set; } + + /// + /// Gets whether the was acknowledged. Used for messages. + /// + /// Pushover API documentation + [JsonProperty("acked")] + [JsonConverter(typeof(BoolConverter))] + public bool Acknowledged { get; set; } + + /// + /// Gets the 's receipt code, if any. Used for messages. + /// + /// Pushover API documentation + [JsonProperty("receipt")] + public string Receipt { get; set; } + + /// + /// Gets whether the contains HTML. + /// + /// + /// Pushover API documentation + [JsonProperty("html")] + [JsonConverter(typeof(BoolConverter))] + public bool IsHtmlBody { get; set; } + } +} diff --git a/src/UI/NPushOver/ResponseObjects/ValidateUserOrGroupResponse.cs b/src/UI/NPushOver/ResponseObjects/ValidateUserOrGroupResponse.cs new file mode 100644 index 00000000..049f1859 --- /dev/null +++ b/src/UI/NPushOver/ResponseObjects/ValidateUserOrGroupResponse.cs @@ -0,0 +1,18 @@ +using Newtonsoft.Json; + +namespace NPushover.ResponseObjects +{ + /// + /// Represents a response for a call. + /// + /// Pushover API documentation + public class ValidateUserOrGroupResponse : PushoverUserResponse + { + /// + /// Gets the names of the user's active devices. + /// + /// Pushover API documentation + [JsonProperty("devices")] + public string[] Devices { get; set; } + } +} diff --git a/src/UI/NPushOver/Validators/DefaultMessageValidator.cs b/src/UI/NPushOver/Validators/DefaultMessageValidator.cs new file mode 100644 index 00000000..d1d87066 --- /dev/null +++ b/src/UI/NPushOver/Validators/DefaultMessageValidator.cs @@ -0,0 +1,78 @@ +using AITool; + +using NPushover.RequestObjects; + +using System; + +namespace NPushover.Validators +{ + /// + /// Validates s. + /// + public class DefaultMessageValidator : IValidator + { + private const int MAXBODYLENGTH = 1024; + private const int MAXTITLELENGTH = 250; + private const int MAXSUPURLTITLELENGTH = 100; + private static readonly TimeSpan MINRETRYEVERY = TimeSpan.FromSeconds(30); + private static readonly TimeSpan MAXRETRYPERIOD = TimeSpan.FromHours(24); + + /// + /// Validates the specified and throws whenever the is deemed invalid. + /// + /// The name of the parameter being validated. + /// The message to validate. + /// Pushover API documentation + /// + /// Thrown when message is null or any of the message's non-nullable properties is null. + /// + /// + /// Thrown when any of the message'properties contains an invalid (too long or otherwise invalid) value. + /// + public void Validate(string paramName, Message message) + { + if (message == null) + throw new ArgumentNullException("message"); + + if (message.Body == null) + throw new ArgumentNullException("body"); + if (message.Body.Length > MAXBODYLENGTH) + throw new ArgumentOutOfRangeException("body"); + + if ((message.Title ?? string.Empty).Length > MAXTITLELENGTH) + throw new ArgumentOutOfRangeException("title"); + + if (!Enum.IsDefined(typeof(Priority), message.Priority)) + throw new ArgumentOutOfRangeException("priority"); + + if (message.Priority == Priority.Emergency) + { + if (message.RetryOptions == null) + throw new ArgumentNullException("retryOptions"); + + if (message.RetryOptions.RetryEvery < MINRETRYEVERY) + throw new ArgumentOutOfRangeException("retryOptions.retryEvery", $"RetryEvery less than {MINRETRYEVERY.FormatTS(true)}: {message.RetryOptions.RetryEvery.FormatTS(true)}"); + if (message.RetryOptions.RetryEvery > MAXRETRYPERIOD) + throw new ArgumentOutOfRangeException("retryOptions.retryEvery", $"RetryEvery is over {MAXRETRYPERIOD.FormatTS(true)}: {message.RetryOptions.RetryEvery.FormatTS(true)}"); + if (message.RetryOptions.RetryPeriod > MAXRETRYPERIOD) + throw new ArgumentOutOfRangeException("retryOptions.retryPeriod", $"RetryPeriod is over {MAXRETRYPERIOD.FormatTS(true)}: {message.RetryOptions.RetryPeriod.FormatTS(true)}"); + if (message.RetryOptions.RetryPeriod < TimeSpan.Zero) + throw new ArgumentOutOfRangeException("retryOptions.retryPeriod", $"RetryPeriod is less than 0?"); + } + else + { + if (message.RetryOptions != null) + throw new ArgumentException("retryOptions"); + } + + if (message.SupplementaryUrl != null) + { + if (message.SupplementaryUrl.Uri == null) + throw new ArgumentNullException("supplementaryUrl.uri"); + + if ((message.SupplementaryUrl.Title ?? string.Empty).Length > MAXSUPURLTITLELENGTH) + throw new ArgumentOutOfRangeException("supplementaryUrl.title"); + } + } + } +} diff --git a/src/UI/NPushOver/Validators/EMailValidator.cs b/src/UI/NPushOver/Validators/EMailValidator.cs new file mode 100644 index 00000000..8d28e5a4 --- /dev/null +++ b/src/UI/NPushOver/Validators/EMailValidator.cs @@ -0,0 +1,32 @@ +using System; +using System.Linq; + +namespace NPushover.Validators +{ + /// + /// Provides VERY simple e-mail address validation (all that's required to validate is the string to contain an + /// '@', the rest is up to Pushover's servers). + /// + public class EMailValidator : IValidator + { + /// + /// Validates an e-mail address and throws whenever the e-mail address is deemed invalid. + /// + /// The name of the parameter being verified. + /// The e-mail address to validate. + /// + /// Provides VERY simple e-mail address validation (all that's required to validate is the string to contain an + /// '@', the rest is up to Pushover's servers). + /// + /// Thrown when email is null. + /// Thrown when email is invalid. + public void Validate(string paramName, string email) + { + if (email == null) + throw new ArgumentNullException(paramName); + + if (!email.Contains('@')) + throw new ArgumentException("Invalid email address", paramName); + } + } +} diff --git a/src/UI/NPushOver/Validators/IValidator.cs b/src/UI/NPushOver/Validators/IValidator.cs new file mode 100644 index 00000000..868a8891 --- /dev/null +++ b/src/UI/NPushOver/Validators/IValidator.cs @@ -0,0 +1,16 @@ +namespace NPushover.Validators +{ + /// + /// Provides the base validator interface for NPushover validators. + /// + /// The type the validator operates on. + public interface IValidator + { + /// + /// Validates the given object and throws any exceptions when the object is not valid. + /// + /// The name of the parameter being validated. + /// The object to validate. + void Validate(string paramName, T obj); + } +} diff --git a/src/UI/NPushOver/Validators/KeyValidators.cs b/src/UI/NPushOver/Validators/KeyValidators.cs new file mode 100644 index 00000000..865e4372 --- /dev/null +++ b/src/UI/NPushOver/Validators/KeyValidators.cs @@ -0,0 +1,105 @@ +using NPushover.Exceptions; +using System; +using System.Text.RegularExpressions; + +namespace NPushover.Validators +{ + /// + /// Provides a baseclass for validators based on a regular expression to validate values. + /// + public abstract class RegexValidator : IValidator + { + /// + /// Gets the Regex for the validator. + /// + protected Regex ValidationRegex { get; private set; } + + /// + /// Default options for most objects. + /// + protected const RegexOptions DefaultRegexOptions = RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.Singleline; + + /// + /// Initializes a new validator with the specified . + /// + /// The for the validator. + /// Thrown when regex is null. + public RegexValidator(Regex regex) + { + if (regex == null) + throw new ArgumentNullException("regex"); + + this.ValidationRegex = regex; + } + + /// + /// Executes the validator's regex and throws whenever the value doesn't match the validator's regex. + /// + /// The name of the parameter being validated. + /// The value to validate/match. + /// + /// This method uses the 's method. + /// + /// Thrown when value is null. + /// Thrown when value does not match the validator's . + public void Validate(string paramName, string value) + { + if (value == null) + throw new ArgumentNullException(paramName); + if (!this.ValidationRegex.IsMatch(value)) + throw new InvalidKeyException(paramName, value); + } + } + + /// + /// Validates application keys. + /// + /// Pushover API documentation + public class ApplicationKeyValidator : RegexValidator + { + /// + /// Initializes a new instance of an . + /// + public ApplicationKeyValidator() + : base(new Regex("^[A-Za-z0-9]{30}$", DefaultRegexOptions)) { } + } + + /// + /// Validates user and/or group id's. + /// + /// Pushover API documentation + public class UserOrGroupKeyValidator : RegexValidator + { + /// + /// Initializes a new instance of an . + /// + public UserOrGroupKeyValidator() + : base(new Regex("^[A-Za-z0-9]{30}$", DefaultRegexOptions)) { } + } + + /// + /// Validates devicenames. + /// + /// Pushover API documentation + public class DeviceNameValidator : RegexValidator + { + /// + /// Initializes a new instance of an . + /// + public DeviceNameValidator() + : base(new Regex("^[A-Za-z0-9_-]{1,25}$", DefaultRegexOptions)) { } + } + + /// + /// Validates receipts. + /// + /// Pushover API documentation + public class ReceiptValidator : RegexValidator + { + /// + /// Initializes a new instance of an . + /// + public ReceiptValidator() + : base(new Regex("^[A-Za-z0-9]{30}$", DefaultRegexOptions)) { } + } +} diff --git a/src/UI/NamedPipeWrapper/NamedPipeClient.cs b/src/UI/NamedPipeWrapper/NamedPipeClient.cs new file mode 100644 index 00000000..3cdb0e6d --- /dev/null +++ b/src/UI/NamedPipeWrapper/NamedPipeClient.cs @@ -0,0 +1,228 @@ +using NamedPipeWrapper.IO; +using NamedPipeWrapper.Threading; +using System; +using System.IO.Pipes; +using System.Threading; + +namespace NamedPipeWrapper +{ + /// + /// Wraps a . + /// + /// Reference type to read from and write to the named pipe + public class NamedPipeClient : NamedPipeClient where TReadWrite : class + { + /// + /// Constructs a new NamedPipeClient to connect to the specified by . + /// + /// Name of the server's pipe + /// server name default is local. + public NamedPipeClient(string pipeName, string serverName = ".") : base(pipeName, serverName) + { + } + } + + /// + /// Wraps a . + /// + /// Reference type to read from the named pipe + /// Reference type to write to the named pipe + public class NamedPipeClient + where TRead : class + where TWrite : class + { + /// + /// Gets or sets whether the client should attempt to reconnect when the pipe breaks + /// due to an error or the other end terminating the connection. + /// Default value is true. + /// + public bool AutoReconnect { get; set; } + + /// + /// Invoked whenever a message is received from the server. + /// + public event ConnectionMessageEventHandler ServerMessage; + + /// + /// Invoked when the client disconnects from the server (e.g., the pipe is closed or broken). + /// + public event ConnectionEventHandler Disconnected; + + /// + /// Invoked whenever an exception is thrown during a read or write operation on the named pipe. + /// + public event PipeExceptionEventHandler Error; + + private readonly string _pipeName; + private NamedPipeConnection _connection; + + private readonly AutoResetEvent _connected = new AutoResetEvent(false); + private readonly AutoResetEvent _disconnected = new AutoResetEvent(false); + + private volatile bool _closedExplicitly; + /// + /// the server name, which client will connect to. + /// + private string _serverName { get; set; } + + /// + /// Constructs a new NamedPipeClient to connect to the specified by . + /// + /// Name of the server's pipe + /// the Name of the server, default is local machine + public NamedPipeClient(string pipeName, string serverName) + { + this._pipeName = pipeName; + this._serverName = serverName; + this.AutoReconnect = true; + } + + /// + /// Connects to the named pipe server asynchronously. + /// This method returns immediately, possibly before the connection has been established. + /// + public void Start() + { + this._closedExplicitly = false; + var worker = new Worker(); + worker.Error += this.OnError; + worker.DoWork(this.ListenSync); + } + + /// + /// Sends a message to the server over a named pipe. + /// + /// Message to send to the server. + public void PushMessage(TWrite message) + { + if (this._connection != null) + this._connection.PushMessage(message); + } + + /// + /// Closes the named pipe. + /// + public void Stop() + { + this._closedExplicitly = true; + if (this._connection != null) + this._connection.Close(); + } + + #region Wait for connection/disconnection + + public void WaitForConnection() + { + this._connected.WaitOne(); + } + + public void WaitForConnection(int millisecondsTimeout) + { + this._connected.WaitOne(millisecondsTimeout); + } + + public void WaitForConnection(TimeSpan timeout) + { + this._connected.WaitOne(timeout); + } + + public void WaitForDisconnection() + { + this._disconnected.WaitOne(); + } + + public void WaitForDisconnection(int millisecondsTimeout) + { + this._disconnected.WaitOne(millisecondsTimeout); + } + + public void WaitForDisconnection(TimeSpan timeout) + { + this._disconnected.WaitOne(timeout); + } + + #endregion + + #region Private methods + + private void ListenSync() + { + // Get the name of the data pipe that should be used from now on by this NamedPipeClient + var handshake = PipeClientFactory.Connect(this._pipeName, this._serverName); + var dataPipeName = handshake.ReadObject(); + handshake.Close(); + + // Connect to the actual data pipe + var dataPipe = PipeClientFactory.CreateAndConnectPipe(dataPipeName, this._serverName); + + // Create a Connection object for the data pipe + this._connection = ConnectionFactory.CreateConnection(dataPipe); + this._connection.Disconnected += this.OnDisconnected; + this._connection.ReceiveMessage += this.OnReceiveMessage; + this._connection.Error += this.ConnectionOnError; + this._connection.Open(); + + this._connected.Set(); + } + + private void OnDisconnected(NamedPipeConnection connection) + { + if (Disconnected != null) + Disconnected(connection); + + this._disconnected.Set(); + + // Reconnect + if (this.AutoReconnect && !this._closedExplicitly) + this.Start(); + } + + private void OnReceiveMessage(NamedPipeConnection connection, TRead message) + { + if (ServerMessage != null) + ServerMessage(connection, message); + } + + /// + /// Invoked on the UI thread. + /// + private void ConnectionOnError(NamedPipeConnection connection, Exception exception) + { + this.OnError(exception); + } + + /// + /// Invoked on the UI thread. + /// + /// + private void OnError(Exception exception) + { + if (Error != null) + Error(exception); + } + + #endregion + } + + static class PipeClientFactory + { + public static PipeStreamWrapper Connect(string pipeName, string serverName) + where TRead : class + where TWrite : class + { + return new PipeStreamWrapper(CreateAndConnectPipe(pipeName, serverName)); + } + + public static NamedPipeClientStream CreateAndConnectPipe(string pipeName, string serverName) + { + var pipe = CreatePipe(pipeName, serverName); + pipe.Connect(); + return pipe; + } + + private static NamedPipeClientStream CreatePipe(string pipeName, string serverName) + { + return new NamedPipeClientStream(serverName, pipeName, PipeDirection.InOut, PipeOptions.Asynchronous | PipeOptions.WriteThrough); + } + } +} diff --git a/src/UI/NamedPipeWrapper/NamedPipeConnection.cs b/src/UI/NamedPipeWrapper/NamedPipeConnection.cs new file mode 100644 index 00000000..da098399 --- /dev/null +++ b/src/UI/NamedPipeWrapper/NamedPipeConnection.cs @@ -0,0 +1,237 @@ +using NamedPipeWrapper.IO; +using NamedPipeWrapper.Threading; +using System; +using System.Collections.Concurrent; +using System.IO.Pipes; +using System.Runtime.Serialization; +using System.Threading; + +namespace NamedPipeWrapper +{ + /// + /// Represents a connection between a named pipe client and server. + /// + /// Reference type to read from the named pipe + /// Reference type to write to the named pipe + public class NamedPipeConnection + where TRead : class + where TWrite : class + { + /// + /// Gets the connection's unique identifier. + /// + public readonly int Id; + + /// + /// Gets the connection's name. + /// + public string Name { get; } + + /// + /// Gets a value indicating whether the pipe is connected or not. + /// + public bool IsConnected { get { return this._streamWrapper.IsConnected; } } + + /// + /// Invoked when the named pipe connection terminates. + /// + public event ConnectionEventHandler Disconnected; + + /// + /// Invoked whenever a message is received from the other end of the pipe. + /// + public event ConnectionMessageEventHandler ReceiveMessage; + + /// + /// Invoked when an exception is thrown during any read/write operation over the named pipe. + /// + public event ConnectionExceptionEventHandler Error; + + private readonly PipeStreamWrapper _streamWrapper; + + private readonly AutoResetEvent _writeSignal = new AutoResetEvent(false); + /// + /// To support Multithread, we should use BlockingCollection. + /// + private readonly BlockingCollection _writeQueue = new BlockingCollection(); + + private bool _notifiedSucceeded; + + internal NamedPipeConnection(int id, string name, PipeStream serverStream) + { + this.Id = id; + this.Name = name; + this._streamWrapper = new PipeStreamWrapper(serverStream); + } + + /// + /// Begins reading from and writing to the named pipe on a background thread. + /// This method returns immediately. + /// + public void Open() + { + var readWorker = new Worker(); + readWorker.Succeeded += this.OnSucceeded; + readWorker.Error += this.OnError; + readWorker.DoWork(this.ReadPipe); + + var writeWorker = new Worker(); + writeWorker.Succeeded += this.OnSucceeded; + writeWorker.Error += this.OnError; + writeWorker.DoWork(this.WritePipe); + } + + /// + /// Adds the specified to the write queue. + /// The message will be written to the named pipe by the background thread + /// at the next available opportunity. + /// + /// + public void PushMessage(TWrite message) + { + this._writeQueue.Add(message); + this._writeSignal.Set(); + } + + /// + /// Closes the named pipe connection and underlying PipeStream. + /// + public void Close() + { + this.CloseImpl(); + } + + /// + /// Invoked on the background thread. + /// + private void CloseImpl() + { + this._streamWrapper.Close(); + this._writeSignal.Set(); + } + + /// + /// Invoked on the UI thread. + /// + private void OnSucceeded() + { + // Only notify observers once + if (this._notifiedSucceeded) + return; + + this._notifiedSucceeded = true; + + if (Disconnected != null) + Disconnected(this); + } + + /// + /// Invoked on the UI thread. + /// + /// + private void OnError(Exception exception) + { + if (Error != null) + Error(this, exception); + } + + /// + /// Invoked on the background thread. + /// + /// An object in the graph of type parameter is not marked as serializable. + private void ReadPipe() + { + + while (this.IsConnected && this._streamWrapper.CanRead) + { + try + { + var obj = this._streamWrapper.ReadObject(); + if (obj == null) + { + this.CloseImpl(); + return; + } + if (ReceiveMessage != null) + ReceiveMessage(this, obj); + } + catch + { + //we must igonre exception, otherwise, the namepipe wrapper will stop work. + } + } + + } + + /// + /// Invoked on the background thread. + /// + /// An object in the graph of type parameter is not marked as serializable. + private void WritePipe() + { + + while (this.IsConnected && this._streamWrapper.CanWrite) + { + try + { + //using blockcollection, we needn't use singal to wait for result. + //_writeSignal.WaitOne(); + //while (_writeQueue.Count > 0) + { + this._streamWrapper.WriteObject(this._writeQueue.Take()); + this._streamWrapper.WaitForPipeDrain(); + } + } + catch + { + //we must igonre exception, otherwise, the namepipe wrapper will stop work. + } + } + + } + } + + static class ConnectionFactory + { + private static int _lastId; + + public static NamedPipeConnection CreateConnection(PipeStream pipeStream) + where TRead : class + where TWrite : class + { + return new NamedPipeConnection(++_lastId, "Client " + _lastId, pipeStream); + } + } + + /// + /// Handles new connections. + /// + /// The newly established connection + /// Reference type + /// Reference type + public delegate void ConnectionEventHandler(NamedPipeConnection connection) + where TRead : class + where TWrite : class; + + /// + /// Handles messages received from a named pipe. + /// + /// Reference type + /// Reference type + /// Connection that received the message + /// Message sent by the other end of the pipe + public delegate void ConnectionMessageEventHandler(NamedPipeConnection connection, TRead message) + where TRead : class + where TWrite : class; + + /// + /// Handles exceptions thrown during read/write operations. + /// + /// Reference type + /// Reference type + /// Connection that threw the exception + /// The exception that was thrown + public delegate void ConnectionExceptionEventHandler(NamedPipeConnection connection, Exception exception) + where TRead : class + where TWrite : class; +} diff --git a/src/UI/NamedPipeWrapper/NamedPipeServer.cs b/src/UI/NamedPipeWrapper/NamedPipeServer.cs new file mode 100644 index 00000000..2c3fdfd5 --- /dev/null +++ b/src/UI/NamedPipeWrapper/NamedPipeServer.cs @@ -0,0 +1,286 @@ +using NamedPipeWrapper.IO; +using NamedPipeWrapper.Threading; +using System; +using System.Collections.Generic; +using System.IO.Pipes; + +namespace NamedPipeWrapper +{ + /// + /// Wraps a and provides multiple simultaneous client connection handling. + /// + /// Reference type to read from and write to the named pipe + public class NamedPipeServer : Server where TReadWrite : class + { + /// + /// Constructs a new NamedPipeServer object that listens for client connections on the given . + /// + /// Name of the pipe to listen on + public NamedPipeServer(string pipeName) + : base(pipeName, null) + { + } + + /// + /// Constructs a new NamedPipeServer object that listens for client connections on the given . + /// + /// Name of the pipe to listen on + public NamedPipeServer(string pipeName, PipeSecurity pipeSecurity) + : base(pipeName, pipeSecurity) + { + } + } + + /// + /// Wraps a and provides multiple simultaneous client connection handling. + /// + /// Reference type to read from the named pipe + /// Reference type to write to the named pipe + public class Server + where TRead : class + where TWrite : class + { + /// + /// Invoked whenever a client connects to the server. + /// + public event ConnectionEventHandler ClientConnected; + + /// + /// Invoked whenever a client disconnects from the server. + /// + public event ConnectionEventHandler ClientDisconnected; + + /// + /// Invoked whenever a client sends a message to the server. + /// + public event ConnectionMessageEventHandler ClientMessage; + + /// + /// Invoked whenever an exception is thrown during a read or write operation. + /// + public event PipeExceptionEventHandler Error; + + private readonly string _pipeName; + private readonly PipeSecurity _pipeSecurity; + private readonly List> _connections = new List>(); + + private int _nextPipeId; + + private volatile bool _shouldKeepRunning; + private volatile bool _isRunning; + + /// + /// Constructs a new NamedPipeServer object that listens for client connections on the given . + /// + /// Name of the pipe to listen on + public Server(string pipeName, PipeSecurity pipeSecurity) + { + this._pipeName = pipeName; + this._pipeSecurity = pipeSecurity; + } + + /// + /// Begins listening for client connections in a separate background thread. + /// This method returns immediately. + /// + public void Start() + { + this._shouldKeepRunning = true; + var worker = new Worker(); + worker.Error += this.OnError; + worker.DoWork(this.ListenSync); + } + + /// + /// Sends a message to all connected clients asynchronously. + /// This method returns immediately, possibly before the message has been sent to all clients. + /// + /// + public void PushMessage(TWrite message) + { + lock (this._connections) + { + foreach (var client in this._connections) + { + client.PushMessage(message); + } + } + } + + /// + /// push message to the given client. + /// + /// + /// + public void PushMessage(TWrite message, string clientName) + { + lock (this._connections) + { + foreach (var client in this._connections) + { + if (client.Name == clientName) + client.PushMessage(message); + } + } + } + + /// + /// Closes all open client connections and stops listening for new ones. + /// + public void Stop() + { + this._shouldKeepRunning = false; + + lock (this._connections) + { + foreach (var client in this._connections.ToArray()) + { + client.Close(); + } + } + + // If background thread is still listening for a client to connect, + // initiate a dummy connection that will allow the thread to exit. + //dummy connection will use the local server name. + var dummyClient = new NamedPipeClient(this._pipeName, "."); + dummyClient.Start(); + dummyClient.WaitForConnection(TimeSpan.FromSeconds(2)); + dummyClient.Stop(); + dummyClient.WaitForDisconnection(TimeSpan.FromSeconds(2)); + } + + #region Private methods + + private void ListenSync() + { + this._isRunning = true; + while (this._shouldKeepRunning) + { + this.WaitForConnection(this._pipeName, this._pipeSecurity); + } + this._isRunning = false; + } + + private void WaitForConnection(string pipeName, PipeSecurity pipeSecurity) + { + NamedPipeServerStream handshakePipe = null; + NamedPipeServerStream dataPipe = null; + NamedPipeConnection connection = null; + + var connectionPipeName = this.GetNextConnectionPipeName(pipeName); + + try + { + // Send the client the name of the data pipe to use + handshakePipe = PipeServerFactory.CreateAndConnectPipe(pipeName, pipeSecurity); + var handshakeWrapper = new PipeStreamWrapper(handshakePipe); + handshakeWrapper.WriteObject(connectionPipeName); + handshakeWrapper.WaitForPipeDrain(); + handshakeWrapper.Close(); + + // Wait for the client to connect to the data pipe + dataPipe = PipeServerFactory.CreatePipe(connectionPipeName, pipeSecurity); + dataPipe.WaitForConnection(); + + // Add the client's connection to the list of connections + connection = ConnectionFactory.CreateConnection(dataPipe); + connection.ReceiveMessage += this.ClientOnReceiveMessage; + connection.Disconnected += this.ClientOnDisconnected; + connection.Error += this.ConnectionOnError; + connection.Open(); + + lock (this._connections) + { + this._connections.Add(connection); + } + + this.ClientOnConnected(connection); + } + // Catch the IOException that is raised if the pipe is broken or disconnected. + catch (Exception e) + { + Console.Error.WriteLine("Named pipe is broken or disconnected: {0}", e); + + Cleanup(handshakePipe); + Cleanup(dataPipe); + + this.ClientOnDisconnected(connection); + } + } + + private void ClientOnConnected(NamedPipeConnection connection) + { + if (ClientConnected != null) + ClientConnected(connection); + } + + private void ClientOnReceiveMessage(NamedPipeConnection connection, TRead message) + { + if (ClientMessage != null) + ClientMessage(connection, message); + } + + private void ClientOnDisconnected(NamedPipeConnection connection) + { + if (connection == null) + return; + + lock (this._connections) + { + this._connections.Remove(connection); + } + + if (ClientDisconnected != null) + ClientDisconnected(connection); + } + + /// + /// Invoked on the UI thread. + /// + private void ConnectionOnError(NamedPipeConnection connection, Exception exception) + { + this.OnError(exception); + } + + /// + /// Invoked on the UI thread. + /// + /// + private void OnError(Exception exception) + { + if (Error != null) + Error(exception); + } + + private string GetNextConnectionPipeName(string pipeName) + { + return string.Format("{0}_{1}", pipeName, ++this._nextPipeId); + } + + private static void Cleanup(NamedPipeServerStream pipe) + { + if (pipe == null) return; + using (var x = pipe) + { + x.Close(); + } + } + + #endregion + } + + static class PipeServerFactory + { + public static NamedPipeServerStream CreateAndConnectPipe(string pipeName, PipeSecurity pipeSecurity) + { + var pipe = CreatePipe(pipeName, pipeSecurity); + pipe.WaitForConnection(); + return pipe; + } + + public static NamedPipeServerStream CreatePipe(string pipeName, PipeSecurity pipeSecurity) + { + return new NamedPipeServerStream(pipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous | PipeOptions.WriteThrough, 0, 0, pipeSecurity); + } + } +} diff --git a/src/UI/NamedPipeWrapper/PipeExceptionEventHandler.cs b/src/UI/NamedPipeWrapper/PipeExceptionEventHandler.cs new file mode 100644 index 00000000..eb5fb3a2 --- /dev/null +++ b/src/UI/NamedPipeWrapper/PipeExceptionEventHandler.cs @@ -0,0 +1,10 @@ +using System; + +namespace NamedPipeWrapper +{ + /// + /// Handles exceptions thrown during a read or write operation on a named pipe. + /// + /// Exception that was thrown + public delegate void PipeExceptionEventHandler(Exception exception); +} \ No newline at end of file diff --git a/src/UI/NamedPipeWrapper/PipeStreamReader.cs b/src/UI/NamedPipeWrapper/PipeStreamReader.cs new file mode 100644 index 00000000..5a149709 --- /dev/null +++ b/src/UI/NamedPipeWrapper/PipeStreamReader.cs @@ -0,0 +1,87 @@ +using System; +using System.IO; +using System.IO.Pipes; +using System.Net; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; + +namespace NamedPipeWrapper.IO +{ + /// + /// Wraps a object and reads from it. Deserializes binary data sent by a + /// into a .NET CLR object specified by . + /// + /// Reference type to deserialize data to + public class PipeStreamReader where T : class + { + /// + /// Gets the underlying PipeStream object. + /// + public PipeStream BaseStream { get; private set; } + + /// + /// Gets a value indicating whether the pipe is connected or not. + /// + public bool IsConnected { get; private set; } + + private readonly BinaryFormatter _binaryFormatter = new BinaryFormatter(); + + /// + /// Constructs a new PipeStreamReader object that reads data from the given . + /// + /// Pipe to read from + public PipeStreamReader(PipeStream stream) + { + this.BaseStream = stream; + this.IsConnected = stream.IsConnected; + } + + #region Private stream readers + + /// + /// Reads the length of the next message (in bytes) from the client. + /// + /// Number of bytes of data the client will be sending. + /// The pipe is disconnected, waiting to connect, or the handle has not been set. + /// Any I/O error occurred. + private int ReadLength() + { + const int lensize = sizeof(int); + var lenbuf = new byte[lensize]; + var bytesRead = this.BaseStream.Read(lenbuf, 0, lensize); + if (bytesRead == 0) + { + this.IsConnected = false; + return 0; + } + if (bytesRead != lensize) + throw new IOException(string.Format("Expected {0} bytes but read {1}", lensize, bytesRead)); + return IPAddress.NetworkToHostOrder(BitConverter.ToInt32(lenbuf, 0)); + } + + /// An object in the graph of type parameter is not marked as serializable. + private T ReadObject(int len) + { + var data = new byte[len]; + this.BaseStream.Read(data, 0, len); + using (var memoryStream = new MemoryStream(data)) + { + return (T)this._binaryFormatter.Deserialize(memoryStream); + } + } + + #endregion + + /// + /// Reads the next object from the pipe. This method blocks until an object is sent + /// or the pipe is disconnected. + /// + /// The next object read from the pipe, or null if the pipe disconnected. + /// An object in the graph of type parameter is not marked as serializable. + public T ReadObject() + { + var len = this.ReadLength(); + return len == 0 ? default(T) : this.ReadObject(len); + } + } +} diff --git a/src/UI/NamedPipeWrapper/PipeStreamWrapper.cs b/src/UI/NamedPipeWrapper/PipeStreamWrapper.cs new file mode 100644 index 00000000..3f7364ad --- /dev/null +++ b/src/UI/NamedPipeWrapper/PipeStreamWrapper.cs @@ -0,0 +1,125 @@ +using System; +using System.IO; +using System.IO.Pipes; +using System.Runtime.Serialization; + +namespace NamedPipeWrapper.IO +{ + /// + /// Wraps a object to read and write .NET CLR objects. + /// + /// Reference type to read from and write to the pipe + public class PipeStreamWrapper : PipeStreamWrapper + where TReadWrite : class + { + /// + /// Constructs a new PipeStreamWrapper object that reads from and writes to the given . + /// + /// Stream to read from and write to + public PipeStreamWrapper(PipeStream stream) : base(stream) + { + } + } + + /// + /// Wraps a object to read and write .NET CLR objects. + /// + /// Reference type to read from the pipe + /// Reference type to write to the pipe + public class PipeStreamWrapper + where TRead : class + where TWrite : class + { + /// + /// Gets the underlying PipeStream object. + /// + public PipeStream BaseStream { get; private set; } + + /// + /// Gets a value indicating whether the object is connected or not. + /// + /// + /// true if the object is connected; otherwise, false. + /// + public bool IsConnected + { + get { return this.BaseStream.IsConnected && this._reader.IsConnected; } + } + + /// + /// Gets a value indicating whether the current stream supports read operations. + /// + /// + /// true if the stream supports read operations; otherwise, false. + /// + public bool CanRead + { + get { return this.BaseStream.CanRead; } + } + + /// + /// Gets a value indicating whether the current stream supports write operations. + /// + /// + /// true if the stream supports write operations; otherwise, false. + /// + public bool CanWrite + { + get { return this.BaseStream.CanWrite; } + } + + private readonly PipeStreamReader _reader; + private readonly PipeStreamWriter _writer; + + /// + /// Constructs a new PipeStreamWrapper object that reads from and writes to the given . + /// + /// Stream to read from and write to + public PipeStreamWrapper(PipeStream stream) + { + this.BaseStream = stream; + this._reader = new PipeStreamReader(this.BaseStream); + this._writer = new PipeStreamWriter(this.BaseStream); + } + + /// + /// Reads the next object from the pipe. This method blocks until an object is sent + /// or the pipe is disconnected. + /// + /// The next object read from the pipe, or null if the pipe disconnected. + /// An object in the graph of type parameter is not marked as serializable. + public TRead ReadObject() + { + return this._reader.ReadObject(); + } + + /// + /// Writes an object to the pipe. This method blocks until all data is sent. + /// + /// Object to write to the pipe + /// An object in the graph of type parameter is not marked as serializable. + public void WriteObject(TWrite obj) + { + this._writer.WriteObject(obj); + } + + /// + /// Waits for the other end of the pipe to read all sent bytes. + /// + /// The pipe is closed. + /// The pipe does not support write operations. + /// The pipe is broken or another I/O error occurred. + public void WaitForPipeDrain() + { + this._writer.WaitForPipeDrain(); + } + + /// + /// Closes the current stream and releases any resources (such as sockets and file handles) associated with the current stream. + /// + public void Close() + { + this.BaseStream.Close(); + } + } +} diff --git a/src/UI/NamedPipeWrapper/PipeStreamWriter.cs b/src/UI/NamedPipeWrapper/PipeStreamWriter.cs new file mode 100644 index 00000000..d296ac7d --- /dev/null +++ b/src/UI/NamedPipeWrapper/PipeStreamWriter.cs @@ -0,0 +1,95 @@ +using System; +using System.IO; +using System.IO.Pipes; +using System.Net; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; + +namespace NamedPipeWrapper.IO +{ + /// + /// Wraps a object and writes to it. Serializes .NET CLR objects specified by + /// into binary form and sends them over the named pipe for a to read and deserialize. + /// + /// Reference type to serialize + public class PipeStreamWriter where T : class + { + /// + /// Gets the underlying PipeStream object. + /// + public PipeStream BaseStream { get; private set; } + + private readonly BinaryFormatter _binaryFormatter = new BinaryFormatter(); + + /// + /// Constructs a new PipeStreamWriter object that writes to given . + /// + /// Pipe to write to + public PipeStreamWriter(PipeStream stream) + { + this.BaseStream = stream; + } + + #region Private stream writers + + /// An object in the graph of type parameter is not marked as serializable. + private byte[] Serialize(T obj) + { + try + { + using (var memoryStream = new MemoryStream()) + { + this._binaryFormatter.Serialize(memoryStream, obj); + return memoryStream.ToArray(); + } + } + catch + { + //if any exception in the serialize, it will stop named pipe wrapper, so there will ignore any exception. + return null; + } + } + + private void WriteLength(int len) + { + var lenbuf = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(len)); + this.BaseStream.Write(lenbuf, 0, lenbuf.Length); + } + + private void WriteObject(byte[] data) + { + this.BaseStream.Write(data, 0, data.Length); + } + + private void Flush() + { + this.BaseStream.Flush(); + } + + #endregion + + /// + /// Writes an object to the pipe. This method blocks until all data is sent. + /// + /// Object to write to the pipe + /// An object in the graph of type parameter is not marked as serializable. + public void WriteObject(T obj) + { + var data = this.Serialize(obj); + this.WriteLength(data.Length); + this.WriteObject(data); + this.Flush(); + } + + /// + /// Waits for the other end of the pipe to read all sent bytes. + /// + /// The pipe is closed. + /// The pipe does not support write operations. + /// The pipe is broken or another I/O error occurred. + public void WaitForPipeDrain() + { + this.BaseStream.WaitForPipeDrain(); + } + } +} \ No newline at end of file diff --git a/src/UI/NamedPipeWrapper/Worker.cs b/src/UI/NamedPipeWrapper/Worker.cs new file mode 100644 index 00000000..e589b3d7 --- /dev/null +++ b/src/UI/NamedPipeWrapper/Worker.cs @@ -0,0 +1,72 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace NamedPipeWrapper.Threading +{ + class Worker + { + private readonly TaskScheduler _callbackThread; + + private static TaskScheduler CurrentTaskScheduler + { + get + { + return (SynchronizationContext.Current != null + ? TaskScheduler.FromCurrentSynchronizationContext() + : TaskScheduler.Default); + } + } + + public event WorkerSucceededEventHandler Succeeded; + public event WorkerExceptionEventHandler Error; + + public Worker() : this(CurrentTaskScheduler) + { + } + + public Worker(TaskScheduler callbackThread) + { + this._callbackThread = callbackThread; + } + + public void DoWork(Action action) + { + new Task(this.DoWorkImpl, action, CancellationToken.None, TaskCreationOptions.LongRunning).Start(); + } + + private void DoWorkImpl(object oAction) + { + var action = (Action)oAction; + try + { + action(); + this.Callback(this.Succeed); + } + catch (Exception e) + { + this.Callback(() => this.Fail(e)); + } + } + + private void Succeed() + { + if (Succeeded != null) + Succeeded(); + } + + private void Fail(Exception exception) + { + if (Error != null) + Error(exception); + } + + private void Callback(Action action) + { + Task.Factory.StartNew(action, CancellationToken.None, TaskCreationOptions.None, this._callbackThread); + } + } + + internal delegate void WorkerSucceededEventHandler(); + internal delegate void WorkerExceptionEventHandler(Exception exception); +} diff --git a/src/UI/OSVersionExt/EnvironmentProvider.cs b/src/UI/OSVersionExt/EnvironmentProvider.cs new file mode 100644 index 00000000..04c408e2 --- /dev/null +++ b/src/UI/OSVersionExt/EnvironmentProvider.cs @@ -0,0 +1,22 @@ +using OSVersionExt.Environment; + + +namespace OSVersionExt +{ + public class EnvironmentProvider : IEnvironment + { + public EnvironmentProvider() + { + // NOP + } + + /// + /// Determines whether the current operating system is a 64-bit operating system. + /// + /// true if the operating system is 64-bit; otherwise, false. + public bool Is64BitOperatingSystem() + { + return System.Environment.Is64BitOperatingSystem; + } + } +} diff --git a/src/UI/OSVersionExt/IEnvironment.cs b/src/UI/OSVersionExt/IEnvironment.cs new file mode 100644 index 00000000..24e5fe50 --- /dev/null +++ b/src/UI/OSVersionExt/IEnvironment.cs @@ -0,0 +1,11 @@ +namespace OSVersionExt.Environment +{ + public interface IEnvironment + { + /// + /// Determines whether the current operating system is a 64-bit operating system. + /// + /// + bool Is64BitOperatingSystem(); + } +} diff --git a/src/UI/OSVersionExt/IRegistry.cs b/src/UI/OSVersionExt/IRegistry.cs new file mode 100644 index 00000000..f890be7f --- /dev/null +++ b/src/UI/OSVersionExt/IRegistry.cs @@ -0,0 +1,15 @@ +namespace OSVersionExt.Registry +{ + public interface IRegistry + { + /// + /// Retrieves the value associated with the specified name, in the specified registry key. + /// If the name is not found in the specified key, returns a default value that you provide, or null if the specified key does not exist. + /// + /// The full registry path of the key, beginning with a valid registry root, such as "HKEY_CURRENT_USER". + /// The name of the name/value pair. + /// The value to return if valueName does not exist. + /// + object GetValue(string keyName, string valueName, object defaultValue); + } +} diff --git a/src/UI/OSVersionExt/IWin32API.cs b/src/UI/OSVersionExt/IWin32API.cs new file mode 100644 index 00000000..50e4a076 --- /dev/null +++ b/src/UI/OSVersionExt/IWin32API.cs @@ -0,0 +1,8 @@ +namespace OSVersionExt.Win32API +{ + public interface IWin32API + { + NTSTATUS RtlGetVersion(ref OSVERSIONINFOEX versionInfo); + int GetSystemMetrics(SystemMetric smIndex); + } +} diff --git a/src/UI/OSVersionExt/MajorVersion10Properties.cs b/src/UI/OSVersionExt/MajorVersion10Properties.cs new file mode 100644 index 00000000..16797700 --- /dev/null +++ b/src/UI/OSVersionExt/MajorVersion10Properties.cs @@ -0,0 +1,83 @@ +using OSVersionExt.Registry; +using System; + +namespace OSVersionExt.MajorVersion10 +{ + /// + /// Get the release id and UBR (Update Build Revision) on Windows system having major version 10. + /// + public class MajorVersion10Properties + { + private const string registryCurrentVersionKeyName = @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion"; + private const string releaseIdKeyName = "ReleaseId"; + private const string releaseIdDefault = ""; + private const string UBRkeyName = "UBR"; + private const string UBRdefault = ""; + + private IRegistry _registryProvider; + + + private string _releaseId = releaseIdDefault; + private string _UBR = UBRdefault; + + /// + /// Returns the Windows release ID. + /// + /// returns the release id or null, if detection has failed. + public string ReleaseId { get => this._releaseId; } + + /// + /// Gets the Update Build Revision of a Windows 10 system + /// + /// returns null, if detection has failed. + public string UBR { get => this._UBR; } + + /// + /// Create instance with custom registry provider. + /// + /// + /// + public MajorVersion10Properties(IRegistry registryProvider) + { + if (registryProvider != null) + { + this._registryProvider = registryProvider; + this.GetAllProperties(); + } + else + throw new ArgumentNullException(); + } + + public MajorVersion10Properties() + { + this._registryProvider = new RegistryProviderDefault(); + this.GetAllProperties(); + } + + private void GetAllProperties() + { + this._releaseId = this.GetReleaseId(); + this._UBR = this.GetUBR(); + } + + /// + /// The version number representing feature updates, is referred as the release id, such as 1903, 1909. + /// + /// Returns the release id or null, if value is not available. + /// Feature updates for Windows 10 are released twice a year, around March and September, via the Semi-Annual Channel. + private string GetReleaseId() + { + return this._registryProvider.GetValue(registryCurrentVersionKeyName, releaseIdKeyName, releaseIdDefault)?.ToString(); + } + + /// + /// Gets the UBR (Update Build Revision). + /// + /// + /// E.g, it returns 778 for Microsoft Windows [Version 10.0.18363.778] + private string GetUBR() + { + return this._registryProvider.GetValue(registryCurrentVersionKeyName, UBRkeyName, UBRdefault)?.ToString(); + } + } +} diff --git a/src/UI/OSVersionExt/OSVersion.cs b/src/UI/OSVersionExt/OSVersion.cs new file mode 100644 index 00000000..b07d7569 --- /dev/null +++ b/src/UI/OSVersionExt/OSVersion.cs @@ -0,0 +1,256 @@ +using OSVersionExt; +using OSVersionExt.Environment; +using OSVersionExt.MajorVersion10; +using OSVersionExt.Win32API; + +using System; +using System.Runtime.InteropServices; +using System.Security; + +namespace OSVersionExtension +{ + /// + /// Detects Windows version. + /// + /// + /// References: + /// OSVERSIONINFOEXA structure https://docs.microsoft.com/de-de/windows/win32/api/winnt/ns-winnt-osversioninfoexa + /// OSVERSIONINFOEX uses incorrect charset with RtlGetVersion() https://github.com/windows-toolkit/WindowsCommunityToolkit/issues/2095 + /// taken from https://stackoverflow.com/a/49641055 + /// + [SecurityCritical] + public static class OSVersion + { + // default providers when class is instantiated or setted to default values + private static readonly IWin32API _win32ApiProviderDefault; + private static readonly IEnvironment _environmentProviderDefault; + + private static ProductType _productType; + private static SuiteMask _suiteMask; + + private static IWin32API _win32ApiProvider; + private static IEnvironment _environmentProvider; + + public static int MajorVersion { get; private set; } + public static int MinorVersion { get; private set; } + public static int BuildNumber { get; private set; } + public static bool IsWorkstation { get => GetIfWorkStation(); } + public static bool IsServer { get => GetIfServer(); } + public static bool Is64BitOperatingSystem { get => GetIf64BitOperatingSystem(); } + + [SecurityCritical] + static OSVersion() + { + _win32ApiProviderDefault = new Win32ApiProvider(); + _environmentProviderDefault = new EnvironmentProvider(); + + _win32ApiProvider = _win32ApiProviderDefault; + _environmentProvider = _environmentProviderDefault; + Initialize(); + } + + /// + /// Detects Windows version starting with Windows 2000 and also works on Windows 10/Server 2019/Server 2016 right away. + /// + /// Calls the Windows Kernel function RtlGetVersion routine. + /// + public static VersionInfo GetOSVersion() + { + return new VersionInfo(MajorVersion, MinorVersion, BuildNumber); + } + + /// + /// Returns the Windows version. + /// + /// + /// detection based on https://docs.microsoft.com/de-de/windows-hardware/drivers/ddi/wdm/ns-wdm-_osversioninfoexw#remarks + public static OperatingSystem GetOperatingSystem() + { + if (MajorVersion == 11 || MajorVersion == 10 && MinorVersion == 0 && BuildNumber >= 21990 && IsWorkstation) //22000, 21996, 21990, + return OperatingSystem.Windows11; + else if (MajorVersion == 10 && MinorVersion == 0 && IsWorkstation) + return OperatingSystem.Windows10; + else if (MajorVersion == 10 && MinorVersion == 0 && IsServer) + return OperatingSystem.WindowsServer20162019; + else if (MajorVersion == 6 && MinorVersion == 3 && IsServer) + return OperatingSystem.WindowsServer2012R2; + else if (MajorVersion == 6 && MinorVersion == 3 && IsWorkstation) + return OperatingSystem.Windows81; + else if (MajorVersion == 6 && MinorVersion == 2 && IsWorkstation) + return OperatingSystem.Windows8; + else if (MajorVersion == 6 && MinorVersion == 2 && IsServer) + return OperatingSystem.WindowsServer2012; + else if (MajorVersion == 6 && MinorVersion == 1 && IsWorkstation) + return OperatingSystem.Windows7; + else if (MajorVersion == 6 && MinorVersion == 1 && IsServer) + return OperatingSystem.WindowsServer2008R2; + else if (MajorVersion == 6 && MinorVersion == 0 && IsServer) + return OperatingSystem.WindowsServer2008; + else if (MajorVersion == 6 && MinorVersion == 0 && IsWorkstation) + return OperatingSystem.WindowsVista; + else if (MajorVersion == 5 && + MinorVersion == 2 && + IsServer && + ReadSystemMetrics(SystemMetric.SM_SERVERR2) != 0) + return OperatingSystem.WindowsServer2003R2; + else if (MajorVersion == 5 && + MinorVersion == 2 && + IsServer && + ReadSystemMetrics(SystemMetric.SM_SERVERR2) == 0 && + (_suiteMask & SuiteMask.VER_SUITE_WH_SERVER) != SuiteMask.VER_SUITE_WH_SERVER + ) + return OperatingSystem.WindowsServer2003; + else if (MajorVersion == 5 && MinorVersion == 2 && (_suiteMask & SuiteMask.VER_SUITE_WH_SERVER) == SuiteMask.VER_SUITE_WH_SERVER) + return OperatingSystem.WindowsHomeServer; + else if (MajorVersion == 5 && MinorVersion == 2 && IsWorkstation && Is64BitOperatingSystem) + return OperatingSystem.WindowsXPProx64; + else if (MajorVersion == 5 && MinorVersion == 1) + return OperatingSystem.WindowsXP; + else if (MajorVersion == 5 && MinorVersion == 0) + return OperatingSystem.Windows2000; + + return OperatingSystem.Unknown; + } + + /// + /// Use custom Win32 API provider. + /// + /// + /// + public static void SetWin32ApiProvider(IWin32API win32ApiProvider) + { + if (win32ApiProvider != null) + { + _win32ApiProvider = win32ApiProvider; + Initialize(); + } + else + throw new ArgumentNullException(); + } + + /// + /// Use custom Environment provider + /// + /// + /// + public static void SetEnvironmentProvider(IEnvironment environmentProvider) + { + if (environmentProvider != null) + { + _environmentProvider = environmentProvider; + Initialize(); + } + else + throw new ArgumentNullException(); + } + + /// + /// Sets the Win32API Provider to default. + /// + public static void SetWin32ApiProviderDefault() + { + _win32ApiProvider = _win32ApiProviderDefault; + Initialize(); + } + + /// + /// Sets the Environment provider to default. + /// + public static void SetEnvironmentProviderDefault() + { + _environmentProvider = _environmentProviderDefault; + Initialize(); + } + + /// + /// Returns additional properties for Windows systems having major version 10 or higher + /// + /// + /// Cannot be called on systems other than Windows 10 + public static MajorVersion10Properties MajorVersion10Properties() + { + // TODO: check, if will work on Server 2019 and 2016 + //if (MajorVersion < 10) + // throw new InvalidOperationException("Cannot be called on systems earlier than version 10."); + + MajorVersion10Properties majorVersion10Properties = new MajorVersion10Properties(); + + return majorVersion10Properties; + } + + private static void Initialize() + { + DetectWindowsVersion(_win32ApiProvider); + } + + private static void DetectWindowsVersion(IWin32API win32ApiProvider) + { + var osVersionInfo = new OSVERSIONINFOEX { OSVersionInfoSize = Marshal.SizeOf(typeof(OSVERSIONINFOEX)) }; + + if (win32ApiProvider.RtlGetVersion(ref osVersionInfo) != NTSTATUS.STATUS_SUCCESS) + { + // TODO: Error handling, call GetVersionEx, etc. + } + + MajorVersion = osVersionInfo.MajorVersion; + MinorVersion = osVersionInfo.MinorVersion; + BuildNumber = osVersionInfo.BuildNumber; + + _productType = osVersionInfo.ProductType; + _suiteMask = osVersionInfo.SuiteMask; + } + + private static bool GetIfServer() + { + return _productType == ProductType.DomainController + || _productType == ProductType.Server; + } + + private static bool GetIfWorkStation() + { + return _productType == ProductType.Workstation; + } + + private static bool GetIf64BitOperatingSystem() + { + return _environmentProvider.Is64BitOperatingSystem(); + } + + /// + /// The system metric or configuration setting to be retrieved. + /// Note that all SM_CX* values are widths and all SM_CY* values are heights. + /// Also note that all settings designed to return Boolean data represent TRUE as any nonzero value, and FALSE as a zero value. + /// + /// + /// + /// https://docs.microsoft.com/de-de/windows/win32/api/winuser/nf-winuser-getsystemmetrics + private static int ReadSystemMetrics(SystemMetric smIndex) + { + return _win32ApiProvider.GetSystemMetrics(smIndex); + } + + } + + public enum OperatingSystem + { + Unknown, + Windows2000, + WindowsXP, + WindowsXPProx64, + WindowsHomeServer, + WindowsServer2003, + WindowsServer2003R2, // tested + WindowsVista, + WindowsServer2008, + WindowsServer2008R2, + Windows7, + WindowsServer2012, + Windows8, + Windows81, + WindowsServer2012R2, // tested + WindowsServer20162019, // tested Server 2019 + Windows10, // tested + Windows11 + } + +} diff --git a/src/UI/OSVersionExt/RegistryProviderDefault.cs b/src/UI/OSVersionExt/RegistryProviderDefault.cs new file mode 100644 index 00000000..c4ca8b52 --- /dev/null +++ b/src/UI/OSVersionExt/RegistryProviderDefault.cs @@ -0,0 +1,16 @@ +using OSVersionExt.Registry; + +namespace OSVersionExt.MajorVersion10 +{ + public class RegistryProviderDefault : IRegistry + { + public RegistryProviderDefault() + { + // NOP + } + public object GetValue(string keyName, string valueName, object defaultValue) + { + return Microsoft.Win32.Registry.GetValue(keyName, valueName, defaultValue); + } + } +} diff --git a/src/UI/OSVersionExt/SystemMetrics.cs b/src/UI/OSVersionExt/SystemMetrics.cs new file mode 100644 index 00000000..8454ab0e --- /dev/null +++ b/src/UI/OSVersionExt/SystemMetrics.cs @@ -0,0 +1,549 @@ +namespace OSVersionExt +{ + /// + /// Flags used with the Windows API (User32.dll):GetSystemMetrics(SystemMetric smIndex) + /// + /// This Enum and declaration signature was written by Gabriel T. Sharp + /// ai_productions@verizon.net or osirisgothra@hotmail.com + /// Obtained on pinvoke.net, please contribute your code to support the wiki! + /// + /// https://docs.microsoft.com/de-de/windows/win32/api/winuser/nf-winuser-getsystemmetrics + public enum SystemMetric : int + { + /// + /// The flags that specify how the system arranged minimized windows. For more information, see the Remarks section in this topic. + /// + SM_ARRANGE = 56, + + /// + /// The value that specifies how the system is started: + /// 0 Normal boot + /// 1 Fail-safe boot + /// 2 Fail-safe with network boot + /// A fail-safe boot (also called SafeBoot, Safe Mode, or Clean Boot) bypasses the user startup files. + /// + SM_CLEANBOOT = 67, + + /// + /// The number of display monitors on a desktop. For more information, see the Remarks section in this topic. + /// + SM_CMONITORS = 80, + + /// + /// The number of buttons on a mouse, or zero if no mouse is installed. + /// + SM_CMOUSEBUTTONS = 43, + + /// + /// The width of a window border, in pixels. This is equivalent to the SM_CXEDGE value for windows with the 3-D look. + /// + SM_CXBORDER = 5, + + /// + /// The width of a cursor, in pixels. The system cannot create cursors of other sizes. + /// + SM_CXCURSOR = 13, + + /// + /// This value is the same as SM_CXFIXEDFRAME. + /// + SM_CXDLGFRAME = 7, + + /// + /// The width of the rectangle around the location of a first click in a double-click sequence, in pixels. , + /// The second click must occur within the rectangle that is defined by SM_CXDOUBLECLK and SM_CYDOUBLECLK for the system + /// to consider the two clicks a double-click. The two clicks must also occur within a specified time. + /// To set the width of the double-click rectangle, call SystemParametersInfo with SPI_SETDOUBLECLKWIDTH. + /// + SM_CXDOUBLECLK = 36, + + /// + /// The number of pixels on either side of a mouse-down point that the mouse pointer can move before a drag operation begins. + /// This allows the user to click and release the mouse button easily without unintentionally starting a drag operation. + /// If this value is negative, it is subtracted from the left of the mouse-down point and added to the right of it. + /// + SM_CXDRAG = 68, + + /// + /// The width of a 3-D border, in pixels. This metric is the 3-D counterpart of SM_CXBORDER. + /// + SM_CXEDGE = 45, + + /// + /// The thickness of the frame around the perimeter of a window that has a caption but is not sizable, in pixels. + /// SM_CXFIXEDFRAME is the height of the horizontal border, and SM_CYFIXEDFRAME is the width of the vertical border. + /// This value is the same as SM_CXDLGFRAME. + /// + SM_CXFIXEDFRAME = 7, + + /// + /// The width of the left and right edges of the focus rectangle that the DrawFocusRectdraws. + /// This value is in pixels. + /// Windows 2000: This value is not supported. + /// + SM_CXFOCUSBORDER = 83, + + /// + /// This value is the same as SM_CXSIZEFRAME. + /// + SM_CXFRAME = 32, + + /// + /// The width of the client area for a full-screen window on the primary display monitor, in pixels. + /// To get the coordinates of the portion of the screen that is not obscured by the system taskbar or by application desktop toolbars, + /// call the SystemParametersInfofunction with the SPI_GETWORKAREA value. + /// + SM_CXFULLSCREEN = 16, + + /// + /// The width of the arrow bitmap on a horizontal scroll bar, in pixels. + /// + SM_CXHSCROLL = 21, + + /// + /// The width of the thumb box in a horizontal scroll bar, in pixels. + /// + SM_CXHTHUMB = 10, + + /// + /// The default width of an icon, in pixels. The LoadIcon function can load only icons with the dimensions + /// that SM_CXICON and SM_CYICON specifies. + /// + SM_CXICON = 11, + + /// + /// The width of a grid cell for items in large icon view, in pixels. Each item fits into a rectangle of size + /// SM_CXICONSPACING by SM_CYICONSPACING when arranged. This value is always greater than or equal to SM_CXICON. + /// + SM_CXICONSPACING = 38, + + /// + /// The default width, in pixels, of a maximized top-level window on the primary display monitor. + /// + SM_CXMAXIMIZED = 61, + + /// + /// The default maximum width of a window that has a caption and sizing borders, in pixels. + /// This metric refers to the entire desktop. The user cannot drag the window frame to a size larger than these dimensions. + /// A window can override this value by processing the WM_GETMINMAXINFO message. + /// + SM_CXMAXTRACK = 59, + + /// + /// The width of the default menu check-mark bitmap, in pixels. + /// + SM_CXMENUCHECK = 71, + + /// + /// The width of menu bar buttons, such as the child window close button that is used in the multiple document interface, in pixels. + /// + SM_CXMENUSIZE = 54, + + /// + /// The minimum width of a window, in pixels. + /// + SM_CXMIN = 28, + + /// + /// The width of a minimized window, in pixels. + /// + SM_CXMINIMIZED = 57, + + /// + /// The width of a grid cell for a minimized window, in pixels. Each minimized window fits into a rectangle this size when arranged. + /// This value is always greater than or equal to SM_CXMINIMIZED. + /// + SM_CXMINSPACING = 47, + + /// + /// The minimum tracking width of a window, in pixels. The user cannot drag the window frame to a size smaller than these dimensions. + /// A window can override this value by processing the WM_GETMINMAXINFO message. + /// + SM_CXMINTRACK = 34, + + /// + /// The amount of border padding for captioned windows, in pixels. Windows XP/2000: This value is not supported. + /// + SM_CXPADDEDBORDER = 92, + + /// + /// The width of the screen of the primary display monitor, in pixels. This is the same value obtained by calling + /// GetDeviceCaps as follows: GetDeviceCaps( hdcPrimaryMonitor, HORZRES). + /// + SM_CXSCREEN = 0, + + /// + /// The width of a button in a window caption or title bar, in pixels. + /// + SM_CXSIZE = 30, + + /// + /// The thickness of the sizing border around the perimeter of a window that can be resized, in pixels. + /// SM_CXSIZEFRAME is the width of the horizontal border, and SM_CYSIZEFRAME is the height of the vertical border. + /// This value is the same as SM_CXFRAME. + /// + SM_CXSIZEFRAME = 32, + + /// + /// The recommended width of a small icon, in pixels. Small icons typically appear in window captions and in small icon view. + /// + SM_CXSMICON = 49, + + /// + /// The width of small caption buttons, in pixels. + /// + SM_CXSMSIZE = 52, + + /// + /// The width of the virtual screen, in pixels. The virtual screen is the bounding rectangle of all display monitors. + /// The SM_XVIRTUALSCREEN metric is the coordinates for the left side of the virtual screen. + /// + SM_CXVIRTUALSCREEN = 78, + + /// + /// The width of a vertical scroll bar, in pixels. + /// + SM_CXVSCROLL = 2, + + /// + /// The height of a window border, in pixels. This is equivalent to the SM_CYEDGE value for windows with the 3-D look. + /// + SM_CYBORDER = 6, + + /// + /// The height of a caption area, in pixels. + /// + SM_CYCAPTION = 4, + + /// + /// The height of a cursor, in pixels. The system cannot create cursors of other sizes. + /// + SM_CYCURSOR = 14, + + /// + /// This value is the same as SM_CYFIXEDFRAME. + /// + SM_CYDLGFRAME = 8, + + /// + /// The height of the rectangle around the location of a first click in a double-click sequence, in pixels. + /// The second click must occur within the rectangle defined by SM_CXDOUBLECLK and SM_CYDOUBLECLK for the system to consider + /// the two clicks a double-click. The two clicks must also occur within a specified time. To set the height of the double-click + /// rectangle, call SystemParametersInfo with SPI_SETDOUBLECLKHEIGHT. + /// + SM_CYDOUBLECLK = 37, + + /// + /// The number of pixels above and below a mouse-down point that the mouse pointer can move before a drag operation begins. + /// This allows the user to click and release the mouse button easily without unintentionally starting a drag operation. + /// If this value is negative, it is subtracted from above the mouse-down point and added below it. + /// + SM_CYDRAG = 69, + + /// + /// The height of a 3-D border, in pixels. This is the 3-D counterpart of SM_CYBORDER. + /// + SM_CYEDGE = 46, + + /// + /// The thickness of the frame around the perimeter of a window that has a caption but is not sizable, in pixels. + /// SM_CXFIXEDFRAME is the height of the horizontal border, and SM_CYFIXEDFRAME is the width of the vertical border. + /// This value is the same as SM_CYDLGFRAME. + /// + SM_CYFIXEDFRAME = 8, + + /// + /// The height of the top and bottom edges of the focus rectangle drawn byDrawFocusRect. + /// This value is in pixels. + /// Windows 2000: This value is not supported. + /// + SM_CYFOCUSBORDER = 84, + + /// + /// This value is the same as SM_CYSIZEFRAME. + /// + SM_CYFRAME = 33, + + /// + /// The height of the client area for a full-screen window on the primary display monitor, in pixels. + /// To get the coordinates of the portion of the screen not obscured by the system taskbar or by application desktop toolbars, + /// call the SystemParametersInfo function with the SPI_GETWORKAREA value. + /// + SM_CYFULLSCREEN = 17, + + /// + /// The height of a horizontal scroll bar, in pixels. + /// + SM_CYHSCROLL = 3, + + /// + /// The default height of an icon, in pixels. The LoadIcon function can load only icons with the dimensions SM_CXICON and SM_CYICON. + /// + SM_CYICON = 12, + + /// + /// The height of a grid cell for items in large icon view, in pixels. Each item fits into a rectangle of size + /// SM_CXICONSPACING by SM_CYICONSPACING when arranged. This value is always greater than or equal to SM_CYICON. + /// + SM_CYICONSPACING = 39, + + /// + /// For double byte character set versions of the system, this is the height of the Kanji window at the bottom of the screen, in pixels. + /// + SM_CYKANJIWINDOW = 18, + + /// + /// The default height, in pixels, of a maximized top-level window on the primary display monitor. + /// + SM_CYMAXIMIZED = 62, + + /// + /// The default maximum height of a window that has a caption and sizing borders, in pixels. This metric refers to the entire desktop. + /// The user cannot drag the window frame to a size larger than these dimensions. A window can override this value by processing + /// the WM_GETMINMAXINFO message. + /// + SM_CYMAXTRACK = 60, + + /// + /// The height of a single-line menu bar, in pixels. + /// + SM_CYMENU = 15, + + /// + /// The height of the default menu check-mark bitmap, in pixels. + /// + SM_CYMENUCHECK = 72, + + /// + /// The height of menu bar buttons, such as the child window close button that is used in the multiple document interface, in pixels. + /// + SM_CYMENUSIZE = 55, + + /// + /// The minimum height of a window, in pixels. + /// + SM_CYMIN = 29, + + /// + /// The height of a minimized window, in pixels. + /// + SM_CYMINIMIZED = 58, + + /// + /// The height of a grid cell for a minimized window, in pixels. Each minimized window fits into a rectangle this size when arranged. + /// This value is always greater than or equal to SM_CYMINIMIZED. + /// + SM_CYMINSPACING = 48, + + /// + /// The minimum tracking height of a window, in pixels. The user cannot drag the window frame to a size smaller than these dimensions. + /// A window can override this value by processing the WM_GETMINMAXINFO message. + /// + SM_CYMINTRACK = 35, + + /// + /// The height of the screen of the primary display monitor, in pixels. This is the same value obtained by calling + /// GetDeviceCaps as follows: GetDeviceCaps( hdcPrimaryMonitor, VERTRES). + /// + SM_CYSCREEN = 1, + + /// + /// The height of a button in a window caption or title bar, in pixels. + /// + SM_CYSIZE = 31, + + /// + /// The thickness of the sizing border around the perimeter of a window that can be resized, in pixels. + /// SM_CXSIZEFRAME is the width of the horizontal border, and SM_CYSIZEFRAME is the height of the vertical border. + /// This value is the same as SM_CYFRAME. + /// + SM_CYSIZEFRAME = 33, + + /// + /// The height of a small caption, in pixels. + /// + SM_CYSMCAPTION = 51, + + /// + /// The recommended height of a small icon, in pixels. Small icons typically appear in window captions and in small icon view. + /// + SM_CYSMICON = 50, + + /// + /// The height of small caption buttons, in pixels. + /// + SM_CYSMSIZE = 53, + + /// + /// The height of the virtual screen, in pixels. The virtual screen is the bounding rectangle of all display monitors. + /// The SM_YVIRTUALSCREEN metric is the coordinates for the top of the virtual screen. + /// + SM_CYVIRTUALSCREEN = 79, + + /// + /// The height of the arrow bitmap on a vertical scroll bar, in pixels. + /// + SM_CYVSCROLL = 20, + + /// + /// The height of the thumb box in a vertical scroll bar, in pixels. + /// + SM_CYVTHUMB = 9, + + /// + /// Nonzero if User32.dll supports DBCS; otherwise, 0. + /// + SM_DBCSENABLED = 42, + + /// + /// Nonzero if the debug version of User.exe is installed; otherwise, 0. + /// + SM_DEBUG = 22, + + /// + /// Nonzero if the current operating system is Windows 7 or Windows Server 2008 R2 and the Tablet PC Input + /// service is started; otherwise, 0. The return value is a bitmask that specifies the type of digitizer input supported by the device. + /// For more information, see Remarks. + /// Windows Server 2008, Windows Vista, and Windows XP/2000: This value is not supported. + /// + SM_DIGITIZER = 94, + + /// + /// Nonzero if Input Method Manager/Input Method Editor features are enabled; otherwise, 0. + /// SM_IMMENABLED indicates whether the system is ready to use a Unicode-based IME on a Unicode application. + /// To ensure that a language-dependent IME works, check SM_DBCSENABLED and the system ANSI code page. + /// Otherwise the ANSI-to-Unicode conversion may not be performed correctly, or some components like fonts + /// or registry settings may not be present. + /// + SM_IMMENABLED = 82, + + /// + /// Nonzero if there are digitizers in the system; otherwise, 0. SM_MAXIMUMTOUCHES returns the aggregate maximum of the + /// maximum number of contacts supported by every digitizer in the system. If the system has only single-touch digitizers, + /// the return value is 1. If the system has multi-touch digitizers, the return value is the number of simultaneous contacts + /// the hardware can provide. Windows Server 2008, Windows Vista, and Windows XP/2000: This value is not supported. + /// + SM_MAXIMUMTOUCHES = 95, + + /// + /// Nonzero if the current operating system is the Windows XP, Media Center Edition, 0 if not. + /// + SM_MEDIACENTER = 87, + + /// + /// Nonzero if drop-down menus are right-aligned with the corresponding menu-bar item; 0 if the menus are left-aligned. + /// + SM_MENUDROPALIGNMENT = 40, + + /// + /// Nonzero if the system is enabled for Hebrew and Arabic languages, 0 if not. + /// + SM_MIDEASTENABLED = 74, + + /// + /// Nonzero if a mouse is installed; otherwise, 0. This value is rarely zero, because of support for virtual mice and because + /// some systems detect the presence of the port instead of the presence of a mouse. + /// + SM_MOUSEPRESENT = 19, + + /// + /// Nonzero if a mouse with a horizontal scroll wheel is installed; otherwise 0. + /// + SM_MOUSEHORIZONTALWHEELPRESENT = 91, + + /// + /// Nonzero if a mouse with a vertical scroll wheel is installed; otherwise 0. + /// + SM_MOUSEWHEELPRESENT = 75, + + /// + /// The least significant bit is set if a network is present; otherwise, it is cleared. The other bits are reserved for future use. + /// + SM_NETWORK = 63, + + /// + /// Nonzero if the Microsoft Windows for Pen computing extensions are installed; zero otherwise. + /// + SM_PENWINDOWS = 41, + + /// + /// This system metric is used in a Terminal Services environment to determine if the current Terminal Server session is + /// being remotely controlled. Its value is nonzero if the current session is remotely controlled; otherwise, 0. + /// You can use terminal services management tools such as Terminal Services Manager (tsadmin.msc) and shadow.exe to + /// control a remote session. When a session is being remotely controlled, another user can view the contents of that session + /// and potentially interact with it. + /// + SM_REMOTECONTROL = 0x2001, + + /// + /// This system metric is used in a Terminal Services environment. If the calling process is associated with a Terminal Services + /// client session, the return value is nonzero. If the calling process is associated with the Terminal Services console session, + /// the return value is 0. + /// Windows Server 2003 and Windows XP: The console session is not necessarily the physical console. + /// For more information, seeWTSGetActiveConsoleSessionId. + /// + SM_REMOTESESSION = 0x1000, + + /// + /// Nonzero if all the display monitors have the same color format, otherwise, 0. Two displays can have the same bit depth, + /// but different color formats. For example, the red, green, and blue pixels can be encoded with different numbers of bits, + /// or those bits can be located in different places in a pixel color value. + /// + SM_SAMEDISPLAYFORMAT = 81, + + /// + /// This system metric should be ignored; it always returns 0. + /// + SM_SECURE = 44, + + /// + /// The build number if the system is Windows Server 2003 R2; otherwise, 0. + /// + SM_SERVERR2 = 89, + + /// + /// Nonzero if the user requires an application to present information visually in situations where it would otherwise present + /// the information only in audible form; otherwise, 0. + /// + SM_SHOWSOUNDS = 70, + + /// + /// Nonzero if the current session is shutting down; otherwise, 0. Windows 2000: This value is not supported. + /// + SM_SHUTTINGDOWN = 0x2000, + + /// + /// Nonzero if the computer has a low-end (slow) processor; otherwise, 0. + /// + SM_SLOWMACHINE = 73, + + /// + /// Nonzero if the current operating system is Windows 7 Starter Edition, Windows Vista Starter, or Windows XP Starter Edition; otherwise, 0. + /// + SM_STARTER = 88, + + /// + /// Nonzero if the meanings of the left and right mouse buttons are swapped; otherwise, 0. + /// + SM_SWAPBUTTON = 23, + + /// + /// Nonzero if the current operating system is the Windows XP Tablet PC edition or if the current operating system is Windows Vista + /// or Windows 7 and the Tablet PC Input service is started; otherwise, 0. The SM_DIGITIZER setting indicates the type of digitizer + /// input supported by a device running Windows 7 or Windows Server 2008 R2. For more information, see Remarks. + /// + SM_TABLETPC = 86, + + /// + /// The coordinates for the left side of the virtual screen. The virtual screen is the bounding rectangle of all display monitors. + /// The SM_CXVIRTUALSCREEN metric is the width of the virtual screen. + /// + SM_XVIRTUALSCREEN = 76, + + /// + /// The coordinates for the top of the virtual screen. The virtual screen is the bounding rectangle of all display monitors. + /// The SM_CYVIRTUALSCREEN metric is the height of the virtual screen. + /// + SM_YVIRTUALSCREEN = 77, + } +} diff --git a/src/UI/OSVersionExt/VersionInfo.cs b/src/UI/OSVersionExt/VersionInfo.cs new file mode 100644 index 00000000..c91afa6e --- /dev/null +++ b/src/UI/OSVersionExt/VersionInfo.cs @@ -0,0 +1,16 @@ +using System; + + +namespace OSVersionExt +{ + public class VersionInfo + { + public Version Version { get; private set; } + + public VersionInfo(int major, int minor, int build) + { + this.Version = new Version(major, minor, build); + } + } + +} diff --git a/src/UI/OSVersionExt/Win32ApiEnums.cs b/src/UI/OSVersionExt/Win32ApiEnums.cs new file mode 100644 index 00000000..274608e5 --- /dev/null +++ b/src/UI/OSVersionExt/Win32ApiEnums.cs @@ -0,0 +1,115 @@ +using System; +using System.Runtime.InteropServices; + +namespace OSVersionExt.Win32API +{ + public enum ProductType : byte + { + /// + /// The operating system is Windows 10, Windows 8, Windows 7,... + /// + /// VER_NT_WORKSTATION + Workstation = 0x0000001, + /// + /// The system is a domain controller and the operating system is Windows Server. + /// + /// VER_NT_DOMAIN_CONTROLLER + DomainController = 0x0000002, + /// + /// The operating system is Windows Server. Note that a server that is also a domain controller + /// is reported as VER_NT_DOMAIN_CONTROLLER, not VER_NT_SERVER. + /// + /// VER_NT_SERVER + Server = 0x0000003 + } + + + [Flags] + public enum SuiteMask : ushort + { + /// + /// Microsoft BackOffice components are installed. + /// + VER_SUITE_BACKOFFICE = 0x00000004, + /// + /// Windows Server 2003, Web Edition is installed + /// + VER_SUITE_BLADE = 0x00000400, + /// + /// Windows Server 2003, Compute Cluster Edition is installed. + /// + VER_SUITE_COMPUTE_SERVER = 0x00004000, + /// + /// Windows Server 2008 Datacenter, Windows Server 2003, Datacenter Edition, or Windows 2000 Datacenter Server is installed. + /// + VER_SUITE_DATACENTER = 0x00000080, + /// + /// Windows Server 2008 Enterprise, Windows Server 2003, Enterprise Edition, or Windows 2000 Advanced Server is installed. + /// Refer to the Remarks section for more information about this bit flag. + /// + VER_SUITE_ENTERPRISE = 0x00000002, + /// + /// Windows XP Embedded is installed. + /// + VER_SUITE_EMBEDDEDNT = 0x00000040, + /// + /// Windows Vista Home Premium, Windows Vista Home Basic, or Windows XP Home Edition is installed. + /// + VER_SUITE_PERSONAL = 0x00000200, + /// + /// Remote Desktop is supported, but only one interactive session is supported. This value is set unless the system is running in application server mode. + /// + VER_SUITE_SINGLEUSERTS = 0x00000100, + /// + /// Microsoft Small Business Server was once installed on the system, but may have been upgraded to another version of Windows. + /// Refer to the Remarks section for more information about this bit flag. + /// + VER_SUITE_SMALLBUSINESS = 0x00000001, + /// + /// Microsoft Small Business Server is installed with the restrictive client license in force. Refer to the Remarks section for more information about this bit flag. + /// + VER_SUITE_SMALLBUSINESS_RESTRICTED = 0x00000020, + /// + /// Windows Storage Server 2003 R2 or Windows Storage Server 2003is installed. + /// + VER_SUITE_STORAGE_SERVER = 0x00002000, + /// + /// Terminal Services is installed. This value is always set. + /// If VER_SUITE_TERMINAL is set but VER_SUITE_SINGLEUSERTS is not set, the system is running in application server mode. + /// + VER_SUITE_TERMINAL = 0x00000010, + /// + /// Windows Home Server is installed. + /// + VER_SUITE_WH_SERVER = 0x00008000 + + //VER_SUITE_MULTIUSERTS = 0x00020000 + } + + public enum NTSTATUS : uint + { + /// + /// The operation completed successfully. + /// + STATUS_SUCCESS = 0x00000000 + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct OSVERSIONINFOEX + { + // The OSVersionInfoSize field must be set to Marshal.SizeOf(typeof(OSVERSIONINFOEX)) + public int OSVersionInfoSize; + public int MajorVersion; + public int MinorVersion; + public int BuildNumber; + public int PlatformId; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] + public string CSDVersion; + public ushort ServicePackMajor; + public ushort ServicePackMinor; + public SuiteMask SuiteMask; + public ProductType ProductType; + public byte Reserved; + } + +} diff --git a/src/UI/OSVersionExt/Win32ApiProvider.cs b/src/UI/OSVersionExt/Win32ApiProvider.cs new file mode 100644 index 00000000..2363682e --- /dev/null +++ b/src/UI/OSVersionExt/Win32ApiProvider.cs @@ -0,0 +1,35 @@ +using OSVersionExt.Win32API; +using System; +using System.Runtime.InteropServices; +using System.Security; + +namespace OSVersionExt +{ + /// + /// Win32 API Provider + /// + /// CLR wrapper https://github.com/microsoft/referencesource/blob/master/mscorlib/microsoft/win32/win32native.cs + public class Win32ApiProvider : IWin32API + { + private const String NTDLL = "ntdll.dll"; + private const String USER32 = "user32.dll"; + + [SecurityCritical] + [DllImport(NTDLL, EntryPoint = "RtlGetVersion", SetLastError = true, CharSet = CharSet.Unicode)] + internal static extern NTSTATUS ntdll_RtlGetVersion(ref OSVERSIONINFOEX versionInfo); + + [DllImport(USER32, EntryPoint = "GetSystemMetrics")] + internal static extern int ntdll_GetSystemMetrics(SystemMetric smIndex); + + + public NTSTATUS RtlGetVersion(ref OSVERSIONINFOEX versionInfo) + { + return ntdll_RtlGetVersion(ref versionInfo); + } + + public int GetSystemMetrics(SystemMetric smIndex) + { + return ntdll_GetSystemMetrics(smIndex); + } + } +} diff --git a/src/UI/ObjectPosition.cs b/src/UI/ObjectPosition.cs new file mode 100644 index 00000000..f0d8ddcf --- /dev/null +++ b/src/UI/ObjectPosition.cs @@ -0,0 +1,153 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; + +using static AITool.AITOOL; + +namespace AITool +{ + public class ObjectPosition : IEquatable + { + public string Label { get; } = ""; + public string MaskName { get; } = ""; + public DateTime CreateDate { get; set; } = DateTime.MinValue; + public DateTime LastSeenDate { get; set; } = DateTime.MinValue; + public int Counter { get; set; } + public double PercentMatch { get; set; } + public double LastPercentMatch { get; set; } + public Boolean IsStatic { get; set; } = false; + public double Xmin { get; } + public double Ymin { get; } + public double Xmax { get; } + public double Ymax { get; } + public long Key { get; } + public double ImageWidth { get; set; } + public double ImageHeight { get; set; } + private Rectangle _objRectangle; + public string CameraName { get; set; } = ""; + public string ImagePath { get; set; } = ""; + + //scaling of object based on size + public double ScalePercent { get; set; } + public double ObjectImagePercent { get; } + + private ObjectScale _scaleConfig; + public ObjectScale ScaleConfig + { + get => this._scaleConfig; + set + { + this._scaleConfig = value; + this.ScalePercent = this.getImagePercentVariance(); + } + } + + + public ObjectPosition(double xmin, double xmax, double ymin, double ymax, string label, double imageWidth, double imageHeight, string cameraName, string imagePath) + { + this.CreateDate = DateTime.Now; + this.LastSeenDate = DateTime.Now; + this.CameraName = cameraName; + this.ImagePath = imagePath; + this.Label = label; + this.Xmin = xmin; + this.Ymin = ymin; + this.Xmax = xmax; + this.Ymax = ymax; + + this._objRectangle = Rectangle.FromLTRB(this.Xmin.ToInt(), this.Ymin.ToInt(), this.Xmax.ToInt(), this.Ymax.ToInt()); + + this.ImageHeight = imageHeight; + this.ImageWidth = imageWidth; + + //object percent of image area + this.ObjectImagePercent = (this._objRectangle.Width * this._objRectangle.Height) / (double)(imageWidth * imageHeight) * 100; + + //starting x * y point + width * height of rectangle - used for debugging only + this.Key = Convert.ToInt64((this.Xmin + 1) * (this.Ymin + 1) + (this._objRectangle.Width * this._objRectangle.Height)); + } + + /*Increases object variance percentage for smaller objects. + Due to thier size, smaller objects are more sensitive to slight changes in postion. + This settings allows for a custom percentage match value based on the object size.*/ + private int getImagePercentVariance() + { + int scalePercent = 0; + + if (this.ScaleConfig != null) + { + if (this.ScaleConfig.IsScaledObject) + { + if (this.ObjectImagePercent < this.ScaleConfig.SmallObjectMaxPercent) + { + scalePercent = this.ScaleConfig.SmallObjectMatchPercent; + } + else if (this.ObjectImagePercent >= this.ScaleConfig.MediumObjectMinPercent + && this.ObjectImagePercent <= this.ScaleConfig.MediumObjectMaxPercent) + { + scalePercent = this.ScaleConfig.MediumObjectMatchPercent; + } + } + } + return scalePercent; + } + + public override bool Equals(object obj) + { + return this.Equals(obj as ObjectPosition); + } + + public bool Equals(ObjectPosition other) + { + if (other == null) + return false; + + double percentageIntersect = this._objRectangle.IntersectPercent(other._objRectangle); + + if (percentageIntersect > other.LastPercentMatch) + { + this.LastPercentMatch = percentageIntersect; //parent object highest match for this detection cycle + other.LastPercentMatch = percentageIntersect; //current object highest match + Log($"Trace: Percentage Intersection of object: {percentageIntersect.ToPercent()} Current '{this.Label}' key={this.Key}, Tested '{other.Label}' key={other.Key}", "", other.CameraName, other.ImagePath); + } + + double percentMatch = this.ScalePercent == 0 ? this.PercentMatch : this.ScalePercent; + + return percentageIntersect >= percentMatch; + } + + public override int GetHashCode() + { + int hashCode = -853659638; + hashCode = hashCode * -1521134295 + this.Xmin.GetHashCode(); + hashCode = hashCode * -1521134295 + this.Ymin.GetHashCode(); + hashCode = hashCode * -1521134295 + this.Xmax.GetHashCode(); + hashCode = hashCode * -1521134295 + this.Ymax.GetHashCode(); + + return hashCode; + } + + public static bool operator ==(ObjectPosition left, ObjectPosition right) + { + return EqualityComparer.Default.Equals(left, right); + } + + public static bool operator !=(ObjectPosition left, ObjectPosition right) + { + return !(left == right); + } + + public long getKey() + { + return this.Key; + } + + public override string ToString() + { + string value = $"key={this.getKey()}, name={this.Label}, xmin={this.Xmin}, ymin={this.Ymin}, xmax={this.Xmax}, ymax={this.Ymax}, IsStatic={this.IsStatic}, counter={this.Counter}, camera={this.CameraName}, create date: {this.CreateDate}, imageName: {Path.GetFileName(this.ImagePath)}"; + + return value; + } + } +} diff --git a/src/UI/Program.cs b/src/UI/Program.cs index 28b5a035..46ae6bd1 100644 --- a/src/UI/Program.cs +++ b/src/UI/Program.cs @@ -1,10 +1,11 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using System.Diagnostics; +using System.IO; +using System.Reflection; using System.Windows.Forms; -namespace WindowsFormsApp2 +namespace AITool { static class Program { @@ -16,10 +17,39 @@ static class Program static void Main() { + + //To prevent more than one copy running in memory, all trying to access same log and settings files + List prcs = Global.GetProcessesByPath(Assembly.GetEntryAssembly().Location, true); + + if (prcs.Count > 0) + { + //wait for just a bit to see if it closes by itself - for example if we had just reset/restarted: + if (!Global.WaitForProcessToClose(prcs[0].process, 500, Assembly.GetEntryAssembly().Location)) + { + AppSettings.AlreadyRunning = true; + } + + } + + AppSettings.LastShutdownState = Global.GetRegSetting("LastShutdownState", "not set"); + AppSettings.LastLogEntry = Global.GetRegSetting("LastLogEntry", "not set"); + Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); - Application.Run(new Shell()); - + + try + { + Application.Run(new Shell()); + } + catch (Exception ex) + { + Debug.Print("Error: " + ex.ToString()); + } + //Shell frmshell = new Shell(); + //frmshell.WindowState = FormWindowState.Minimized; + //frmshell.ShowInTaskbar = false; + //pplication.Run(frmshell); + } } } diff --git a/src/UI/Properties/AssemblyInfo.cs b/src/UI/Properties/AssemblyInfo.cs deleted file mode 100644 index 9ef44215..00000000 --- a/src/UI/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// Allgemeine Informationen über eine Assembly werden über die folgenden -// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, -// die einer Assembly zugeordnet sind. -[assembly: AssemblyTitle("WindowsFormsApp2")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("WindowsFormsApp2")] -[assembly: AssemblyCopyright("Copyright © 2019")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Durch Festlegen von ComVisible auf FALSE werden die Typen in dieser Assembly -// für COM-Komponenten unsichtbar. Wenn Sie auf einen Typ in dieser Assembly von -// COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen. -[assembly: ComVisible(false)] - -// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird -[assembly: Guid("b8acbd49-0830-4e34-b5b6-c876beae6f65")] - -// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: -// -// Hauptversion -// Nebenversion -// Buildnummer -// Revision -// -// Sie können alle Werte angeben oder Standardwerte für die Build- und Revisionsnummern verwenden, -// übernehmen, indem Sie "*" eingeben: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/UI/Properties/Resources.Designer.cs b/src/UI/Properties/Resources.Designer.cs index 26dd0433..2b7cc28b 100644 --- a/src/UI/Properties/Resources.Designer.cs +++ b/src/UI/Properties/Resources.Designer.cs @@ -1,28 +1,28 @@ //------------------------------------------------------------------------------ // -// Dieser Code wurde von einem Tool generiert. -// Laufzeitversion:4.0.30319.42000 +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 // -// Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn -// der Code erneut generiert wird. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. // //------------------------------------------------------------------------------ -namespace WindowsFormsApp2.Properties { +namespace AITool.Properties { using System; /// - /// Eine stark typisierte Ressourcenklasse zum Suchen von lokalisierten Zeichenfolgen usw. + /// A strongly-typed resource class, for looking up localized strings, etc. /// - // Diese Klasse wurde von der StronglyTypedResourceBuilder automatisch generiert - // -Klasse über ein Tool wie ResGen oder Visual Studio automatisch generiert. - // Um einen Member hinzuzufügen oder zu entfernen, bearbeiten Sie die .ResX-Datei und führen dann ResGen - // mit der /str-Option erneut aus, oder Sie erstellen Ihr VS-Projekt neu. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { + public class Resources { private static global::System.Resources.ResourceManager resourceMan; @@ -33,13 +33,13 @@ internal Resources() { } /// - /// Gibt die zwischengespeicherte ResourceManager-Instanz zurück, die von dieser Klasse verwendet wird. + /// Returns the cached ResourceManager instance used by this class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { + public static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("WindowsFormsApp2.Properties.Resources", typeof(Resources).Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AITool.Properties.Resources", typeof(Resources).Assembly); resourceMan = temp; } return resourceMan; @@ -47,11 +47,11 @@ internal Resources() { } /// - /// Überschreibt die CurrentUICulture-Eigenschaft des aktuellen Threads für alle - /// Ressourcenzuordnungen, die diese stark typisierte Ressourcenklasse verwenden. + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { + public static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } @@ -61,13 +61,172 @@ internal Resources() { } /// - /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// Looks up a localized resource of type System.Drawing.Bitmap. /// - internal static System.Drawing.Bitmap logo { + public static System.Drawing.Bitmap arrow_down_double_3 { get { - object obj = ResourceManager.GetObject("logo", resourceCulture); + object obj = ResourceManager.GetObject("arrow-down-double-3", resourceCulture); return ((System.Drawing.Bitmap)(obj)); } } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + public static System.Drawing.Bitmap arrow_up_double_3 { + get { + object obj = ResourceManager.GetObject("arrow-up-double-3", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + public static System.Drawing.Bitmap camera_webcam { + get { + object obj = ResourceManager.GetObject("camera-webcam", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + public static System.Drawing.Bitmap camera_webcam_add { + get { + object obj = ResourceManager.GetObject("camera-webcam_add", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + public static System.Drawing.Bitmap camera_webcam_delete { + get { + object obj = ResourceManager.GetObject("camera-webcam_delete", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + public static System.Drawing.Bitmap Codeproject_2023_06_03_15_45_28 { + get { + object obj = ResourceManager.GetObject("Codeproject 2023-06-03_15-45-28", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + public static System.Drawing.Bitmap color_wheel { + get { + object obj = ResourceManager.GetObject("color-wheel", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + public static System.Drawing.Bitmap donate_button { + get { + object obj = ResourceManager.GetObject("donate_button", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + public static System.Drawing.Bitmap edit_delete_5 { + get { + object obj = ResourceManager.GetObject("edit-delete-5", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + public static System.Drawing.Bitmap image_x_generic { + get { + object obj = ResourceManager.GetObject("image-x-generic", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + public static System.Drawing.Bitmap Logo { + get { + object obj = ResourceManager.GetObject("Logo", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). + /// + public static System.Drawing.Icon Logo_Error_Ico { + get { + object obj = ResourceManager.GetObject("Logo_Error_Ico", resourceCulture); + return ((System.Drawing.Icon)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). + /// + public static System.Drawing.Icon Logo_Error_old { + get { + object obj = ResourceManager.GetObject("Logo_Error_old", resourceCulture); + return ((System.Drawing.Icon)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). + /// + public static System.Drawing.Icon Logo_Ico { + get { + object obj = ResourceManager.GetObject("Logo_Ico", resourceCulture); + return ((System.Drawing.Icon)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). + /// + public static System.Drawing.Icon Logo_old { + get { + object obj = ResourceManager.GetObject("Logo_old", resourceCulture); + return ((System.Drawing.Icon)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + public static System.Drawing.Bitmap network_server { + get { + object obj = ResourceManager.GetObject("network-server", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized string similar to Logo.ico. + /// + public static string String { + get { + return ResourceManager.GetString("String", resourceCulture); + } + } } } diff --git a/src/UI/Properties/Resources.resx b/src/UI/Properties/Resources.resx index 45dc3be7..b9d3e2e5 100644 --- a/src/UI/Properties/Resources.resx +++ b/src/UI/Properties/Resources.resx @@ -118,7 +118,334 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - ..\Resources\logo.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Resources\arrow-down-double-3.ico;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\image-x-generic.ico;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\donate_button.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\edit-delete-5.ico;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\arrow-up-double-3.ico;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\network-server.ico;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\color-wheel.ico;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\camera-webcam.ico;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\camera-webcam_add.ico;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\camera-webcam_delete.ico;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + + + 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 + /DCMMAIHsf4fMmBKVNmhLPsAAAAASUVORK5CYII= + + + + ..\AITOOL-ERROR.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + Logo.ico + + + ..\AITOOL.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\AITOOL.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Logo-Error-old.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Logo_old.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a \ No newline at end of file diff --git a/src/UI/Properties/Settings.Designer.cs b/src/UI/Properties/Settings.Designer.cs index 8a603dda..2e5613dd 100644 --- a/src/UI/Properties/Settings.Designer.cs +++ b/src/UI/Properties/Settings.Designer.cs @@ -1,18 +1,18 @@ //------------------------------------------------------------------------------ // -// Dieser Code wurde von einem Tool generiert. -// Laufzeitversion:4.0.30319.42000 +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 // -// Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn -// der Code erneut generiert wird. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. // //------------------------------------------------------------------------------ -namespace WindowsFormsApp2.Properties { +namespace AITool.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.9.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.6.0.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); @@ -22,89 +22,5 @@ public static Settings Default { return defaultInstance; } } - - [global::System.Configuration.UserScopedSettingAttribute()] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("")] - public string telegram_token { - get { - return ((string)(this["telegram_token"])); - } - set { - this["telegram_token"] = value; - } - } - - [global::System.Configuration.UserScopedSettingAttribute()] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("")] - public string telegram_chatid { - get { - return ((string)(this["telegram_chatid"])); - } - set { - this["telegram_chatid"] = value; - } - } - - [global::System.Configuration.UserScopedSettingAttribute()] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("")] - public string input_path { - get { - return ((string)(this["input_path"])); - } - set { - this["input_path"] = value; - } - } - - [global::System.Configuration.UserScopedSettingAttribute()] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("127.0.0.1:81")] - public string deepstack_url { - get { - return ((string)(this["deepstack_url"])); - } - set { - this["deepstack_url"] = value; - } - } - - [global::System.Configuration.UserScopedSettingAttribute()] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("False")] - public bool log_everything { - get { - return ((bool)(this["log_everything"])); - } - set { - this["log_everything"] = value; - } - } - - [global::System.Configuration.UserScopedSettingAttribute()] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("True")] - public bool send_errors { - get { - return ((bool)(this["send_errors"])); - } - set { - this["send_errors"] = value; - } - } - - [global::System.Configuration.UserScopedSettingAttribute()] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("-1")] - public int close_instantly { - get { - return ((int)(this["close_instantly"])); - } - set { - this["close_instantly"] = value; - } - } } } diff --git a/src/UI/Properties/Settings.settings b/src/UI/Properties/Settings.settings index c2e9953f..8e615f25 100644 --- a/src/UI/Properties/Settings.settings +++ b/src/UI/Properties/Settings.settings @@ -1,27 +1,5 @@  - + - - - - - - - - - - - - 127.0.0.1:81 - - - False - - - True - - - -1 - - + \ No newline at end of file diff --git a/src/UI/Properties/launchSettings.json b/src/UI/Properties/launchSettings.json new file mode 100644 index 00000000..bbc8c7b5 --- /dev/null +++ b/src/UI/Properties/launchSettings.json @@ -0,0 +1,16 @@ +{ + "profiles": { + "UI.NET6": { + "commandName": "Project", + "remoteDebugEnabled": false + }, + "AITOOL-REMOTE": { + "commandName": "Executable", + "executablePath": "C:\\Documents\\Projects\\_GIT\\VorlonCD\\bi-aidetection - NET6\\src\\UI\\bin\\Debug\\net8.0-windows10.0.19041.0\\AITool.exe", + "workingDirectory": "C:\\Documents\\Projects\\_GIT\\VorlonCD\\bi-aidetection - NET6\\src\\UI\\bin\\Debug\\net8.0-windows10.0.19041.0", + "remoteDebugEnabled": true, + "remoteDebugMachine": "k3:4026", + "authenticationMode": "Windows" + } + } +} \ No newline at end of file diff --git a/src/UI/README.md b/src/UI/README.md new file mode 100644 index 00000000..f8063a49 --- /dev/null +++ b/src/UI/README.md @@ -0,0 +1,36 @@ +# AI Detection for Blue Iris +Alarm system for Blue Iris based on Artificial Intellience. Can send alerts to Telegram. + +This is the VorlonCD fork of gentlepumpkins awesome AITool project. + +Changes are listed in the commit history (hit ... for each to expand): +https://github.com/VorlonCD/bi-aidetection/commits/master + +### Download +https://github.com/VorlonCD/bi-aidetection/tree/master/src/UI/Installer + +### Install guide and discussion +https://ipcamtalk.com/threads/tool-tutorial-free-ai-person-detection-for-blue-iris.37330/ + +* [MQTT Configuration Guide](mqtt.md) + +### Key Features +- analyze Blue Iris motion alerts using Artificial Intelligence and sort out false alerts +- detect humans and selected objects +- send alert images to Telegram using a bot (optional) +- one alert image per event +- statistics and individual configuration for every camera + +### Screenshot +![Screenshot](https://ipcamtalk.com/attachments/processing1-53-png.44807/) + +### How to contribute +1. install Visual Studio +2. download project as .zip and unpack somewhere +3. go to ./src/ and open bi-aidetection.sln with Visual Studio. + +Using the Github extension for Visual Studio: +1. install Visual Studio +1. install Github Extension from here https://visualstudio.github.com/ +2. Click the green button "clone or download" above this text +3. Select "Open in Visual Studio" diff --git a/src/UI/Resources/Codeproject 2023-06-03_15-45-28.png b/src/UI/Resources/Codeproject 2023-06-03_15-45-28.png new file mode 100644 index 00000000..6e1d4010 Binary files /dev/null and b/src/UI/Resources/Codeproject 2023-06-03_15-45-28.png differ diff --git a/src/UI/Resources/arrow-down-double-3.ico b/src/UI/Resources/arrow-down-double-3.ico new file mode 100644 index 00000000..f3952a8a Binary files /dev/null and b/src/UI/Resources/arrow-down-double-3.ico differ diff --git a/src/UI/Resources/arrow-up-double-3.ico b/src/UI/Resources/arrow-up-double-3.ico new file mode 100644 index 00000000..aaf9dc1d Binary files /dev/null and b/src/UI/Resources/arrow-up-double-3.ico differ diff --git a/src/UI/Resources/camera-webcam.ico b/src/UI/Resources/camera-webcam.ico new file mode 100644 index 00000000..c2d791ab Binary files /dev/null and b/src/UI/Resources/camera-webcam.ico differ diff --git a/src/UI/Resources/camera-webcam_add.ico b/src/UI/Resources/camera-webcam_add.ico new file mode 100644 index 00000000..f4b5f2ee Binary files /dev/null and b/src/UI/Resources/camera-webcam_add.ico differ diff --git a/src/UI/Resources/camera-webcam_delete.ico b/src/UI/Resources/camera-webcam_delete.ico new file mode 100644 index 00000000..82cec474 Binary files /dev/null and b/src/UI/Resources/camera-webcam_delete.ico differ diff --git a/src/UI/Resources/color-wheel.ico b/src/UI/Resources/color-wheel.ico new file mode 100644 index 00000000..387d96a7 Binary files /dev/null and b/src/UI/Resources/color-wheel.ico differ diff --git a/src/UI/Resources/donate_button.png b/src/UI/Resources/donate_button.png new file mode 100644 index 00000000..154983eb Binary files /dev/null and b/src/UI/Resources/donate_button.png differ diff --git a/src/UI/Resources/edit-delete-5.ico b/src/UI/Resources/edit-delete-5.ico new file mode 100644 index 00000000..e512d806 Binary files /dev/null and b/src/UI/Resources/edit-delete-5.ico differ diff --git a/src/UI/Resources/image-x-generic.ico b/src/UI/Resources/image-x-generic.ico new file mode 100644 index 00000000..83323f41 Binary files /dev/null and b/src/UI/Resources/image-x-generic.ico differ diff --git a/src/UI/Resources/network-server.ico b/src/UI/Resources/network-server.ico new file mode 100644 index 00000000..bc7ddc71 Binary files /dev/null and b/src/UI/Resources/network-server.ico differ diff --git a/src/UI/RichTextBoxEx.cs b/src/UI/RichTextBoxEx.cs new file mode 100644 index 00000000..2d3afa95 --- /dev/null +++ b/src/UI/RichTextBoxEx.cs @@ -0,0 +1,748 @@ +using AITool; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.Runtime.InteropServices; +using System.Text; +using System.Windows.Forms; + +public class RichTextBoxEx +{ + //Inherits RichTextBox + //#Region "Interop-Defines" + [StructLayout(LayoutKind.Sequential)] + private struct CHARFORMAT2_STRUCT + { + public UInt32 cbSize; + public UInt32 dwMask; + public UInt32 dwEffects; + public readonly Int32 yHeight; + public readonly Int32 yOffset; + public readonly Int32 crTextColor; + public readonly byte bCharSet; + public readonly byte bPitchAndFamily; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] + public char[] szFaceName; + public readonly UInt16 wWeight; + public readonly UInt16 sSpacing; + public readonly int crBackColor; + // Color.ToArgb() -> int + public readonly int lcid; + public readonly int dwReserved; + public readonly Int16 sStyle; + public readonly Int16 wKerning; + public readonly byte bUnderlineType; + public readonly byte bAnimation; + public readonly byte bRevAuthor; + public readonly byte bReserved1; + } + + [DllImport("user32.dll", CharSet = CharSet.Auto)] + private extern static IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam); + + private const int WM_USER = 0x400; + private const int EM_GETCHARFORMAT = WM_USER + 58; + private const int EM_SETCHARFORMAT = WM_USER + 68; + private const int SCF_SELECTION = 0x1; + private const int SCF_WORD = 0x2; + private const int SCF_ALL = 0x4; + //#Region "CHARFORMAT2 Flags" + private const UInt32 CFE_BOLD = 0x1; + private const UInt32 CFE_ITALIC = 0x2; + private const UInt32 CFE_UNDERLINE = 0x4; + private const UInt32 CFE_STRIKEOUT = 0x8; + private const UInt32 CFE_Public = 0x10; + private const UInt32 CFE_LINK = 0x20; + private const UInt32 CFE_AUTOCOLOR = 0x40000000; + private const UInt32 CFE_SUBSCRIPT = 0x10000; + // Superscript and subscript are + private const UInt32 CFE_SUPERSCRIPT = 0x20000; + // mutually exclusive + private const int CFM_SMALLCAPS = 0x40; + // (*) + private const int CFM_ALLCAPS = 0x80; + // Displayed by 3.0 + private const int CFM_HIDDEN = 0x100; + // Hidden by 3.0 + private const int CFM_OUTLINE = 0x200; + // (*) + private const int CFM_SHADOW = 0x400; + // (*) + private const int CFM_EMBOSS = 0x800; + // (*) + private const int CFM_IMPRINT = 0x1000; + // (*) + private const int CFM_DISABLED = 0x2000; + private const int CFM_REVISED = 0x4000; + private const int CFM_BACKCOLOR = 0x4000000; + private const int CFM_LCID = 0x2000000; + private const int CFM_UNDERLINETYPE = 0x800000; + // Many displayed by 3.0 + private const int CFM_WEIGHT = 0x400000; + private const int CFM_SPACING = 0x200000; + // Displayed by 3.0 + private const int CFM_KERNING = 0x100000; + // (*) + private const int CFM_STYLE = 0x80000; + // (*) + private const int CFM_ANIMATION = 0x40000; + // (*) + private const int CFM_REVAUTHOR = 0x8000; + private const UInt32 CFM_BOLD = 0x1; + private const UInt32 CFM_ITALIC = 0x2; + private const UInt32 CFM_UNDERLINE = 0x4; + private const UInt32 CFM_STRIKEOUT = 0x8; + private const UInt32 CFM_Public = 0x10; + private const UInt32 CFM_LINK = 0x20; + private const UInt32 CFM_SIZE = 0x8000000; + private const UInt32 CFM_COLOR = 0x40000000; + private const UInt32 CFM_FACE = 0x20000000; + private const UInt32 CFM_OFFSET = 0x10000000; + private const UInt32 CFM_CHARSET = 0x8000000; + private const UInt32 CFM_SUBSCRIPT = CFE_SUBSCRIPT | CFE_SUPERSCRIPT; + private const UInt32 CFM_SUPERSCRIPT = CFM_SUBSCRIPT; + private const byte CFU_UNDERLINENONE = 0x0; + private const byte CFU_UNDERLINE = 0x1; + private const byte CFU_UNDERLINEWORD = 0x2; + // (*) displayed as ordinary underline + private const byte CFU_UNDERLINEDOUBLE = 0x3; + // (*) displayed as ordinary underline + private const byte CFU_UNDERLINEDOTTED = 0x4; + private const byte CFU_UNDERLINEDASH = 0x5; + private const byte CFU_UNDERLINEDASHDOT = 0x6; + private const byte CFU_UNDERLINEDASHDOTDOT = 0x7; + private const byte CFU_UNDERLINEWAVE = 0x8; + private const byte CFU_UNDERLINETHICK = 0x9; + private const byte CFU_UNDERLINEHAIRLINE = 0xA; + // (*) displayed as ordinary underline + + public struct HighlightWord + { + public string Word; + public Color Clr; + public string RequiredChar; //required char {} + } + + public struct RtfColor + { + public Color Clr; + public string RtfClr; + } + + public struct FoundItem + { + public string Word; + public int Start; + public int Length; + public Color Clr; + } + + public Dictionary RtfColors = new Dictionary(); + + public int CurrentTextLength = 0; + public int MaxTextLength = 524288; //65536; + public long MaxRTFWriteTime = 200; + public long LastRTFWriteTime = 0; + public ThreadSafe.Boolean AutoScroll = new ThreadSafe.Boolean(true); + private System.Object LockObject = new object(); + private RichTextBox _RTF; + private Dictionary KnownColors { get; set; } = new Dictionary(); + public RichTextBoxEx(RichTextBox RTF, bool AutoScroll) + { + this.AutoScroll.WriteFullFence(AutoScroll); + this._RTF = RTF; + this._RTF.DetectUrls = false; + this._RTF.ForeColor = Color.White; + this._RTF.BackColor = Color.DarkBlue; + this._RTF.Font = new Font("Consolas", 8, FontStyle.Regular); //Lucida Console, 8.25pt + this._RTF.WordWrap = false; + + foreach (Color clr in (new ColorConverter()).GetStandardValues()) + { + this.KnownColors.Add(clr.Name.ToLower(), clr); + } + + } + + public void RtfColorUpdate() + { + + string[] colors = Enum.GetNames(typeof(KnownColor)); + + foreach (string c in colors) + { + ColorConverter cc = new ColorConverter(); + Color clr = (Color)cc.ConvertFromString(c); + //Console.WriteLine(clr.Name) + RtfColor myclr = new RtfColor(); + myclr.Clr = clr; + myclr.RtfClr = "\\red" + clr.R.ToString() + "\\green" + clr.G.ToString() + "\\blue" + clr.B.ToString(); + this.RtfColors.Add(clr.Name.ToLower(), myclr); + } + } + + public void LogToRTF(string Msg) + { + + + //Static Count As Long = 0 + //lock (LockObject) // to hopfully solve multithreading issues + //{ + + try + { + + if (this._RTF == null || this._RTF.IsDisposed) + { + return; + } + //the more text in the RTF window, the slower it is to update and scroll + if (this.LastRTFWriteTime >= this.MaxRTFWriteTime) + { + this.UIOp(this._RTF, () => + { + if (this._RTF.Visible) + { + this._RTF.Clear(); + this.CurrentTextLength = 0; + Msg = $"(Log window cleared for performance reasons @ {this.LastRTFWriteTime}ms text log time)\r\n{Msg}"; + } + }); + } + + this.UIOp(this._RTF, () => + { + if (this._RTF.TextLength + Msg.Length >= this.MaxTextLength) + { + this._RTF.Clear(); + this.CurrentTextLength = 0; + Msg = $"(Log window cleared for performance reasons @ {this.MaxTextLength} bytes)\r\n{Msg}"; + } + }); + + Stopwatch sw = Stopwatch.StartNew(); + + string[] parts = Msg.Split('{'); + + //parts(0) = "this is a " + //parts(1) = "red}red" + //parts(2) = "white} word" + Color clr = Color.White; + Color LastClr = clr; + foreach (string part in parts) + { + int eb = part.IndexOf("}"); + if (eb > -1) + { + string clrname = part.Substring(0, eb).Replace("Color.", ""); + string txt = part.Substring(eb + 1); + FontStyle fs = FontStyle.Regular; + clr = LastClr; + string[] clrs = clrname.Split('|'); + bool Updated = false; + foreach (string nm in clrs) + { + if (nm.ToLower() == "bold") + { + fs = FontStyle.Bold; + Updated = true; + } + else if (nm.ToLower() == "italic") + { + fs = FontStyle.Italic; + Updated = true; + } + else if (nm.ToLower() == "regular") + { + fs = FontStyle.Regular; + Updated = true; + } + else if (nm.ToLower() == "underline") + { + fs = FontStyle.Underline; + Updated = true; + } + else if (nm.ToLower() == "cr" || nm.ToLower() == "lf" || nm.ToLower() == "crlf") + { + //ignore + } + else + { + //assume a color + //clr = Color.FromName(clrname) + //Dim cc = New ColorConverter + Color c = new Color(); + if (this.KnownColors.TryGetValue(nm.ToLower(), out c)) + { + Updated = true; + clr = c; + if (clr.R == 0 && clr.B == 0 && clr.A == 0 && clr.G == 0) + { + clr = Color.White; + } + LastClr = clr; + } + + } + } + if (Updated) + { + this.UIOp(this._RTF, () => this.AppendText(txt, clr, fs)); + } + else //put it back the way it was + { + this.UIOp(this._RTF, () => this.AppendText("{" + part, Color.White, FontStyle.Regular)); + } + } + else + { + this.UIOp(this._RTF, () => this.AppendText(part, Color.White, FontStyle.Regular)); + } + } + + this.UIOp(this._RTF, () => + { + this._RTF.AppendText("\r\n"); + if (this._RTF.Visible) + { + this._RTF.SelectionStart = this._RTF.Text.Length; + + if (this.AutoScroll.ReadFullFence()) + { + + this._RTF.ScrollToCaret(); + } + } + }); + + + + this.LastRTFWriteTime = sw.ElapsedMilliseconds; + + + } + catch (Exception ex) + { + Console.WriteLine("Error: " + ex.Message, false); + } + finally + { + + } + + //} + } + + private void UIOp(Control c, Action action) + { + //UIOp(Me._RTF, Sub() Me._RTF.AppendText(txt + CRLF, clr, fs)) + if (c != null && !c.IsDisposed && !c.Disposing) + { + if (c.InvokeRequired) + { + c.Invoke(action); + } + else + { + action(); + } + } + } + public void HighlightRTF(string Source, List Tags, Color KeyWordColor, Color SymbolColor) + { + + //Dim tim As New Stopwatch + //tim.Start() + try + { + if (Source.Trim().Length > 0) + { + + //LockWindowUpdate(RtfBx.Handle) + if (this.RtfColors.Count == 0) + { + this.RtfColorUpdate(); + } + + StringBuilder RtfText = new StringBuilder(); + + //insert the header: + RtfText.Append("{\\rtf1\\ansi\\deff0{\\fonttbl{\\f0\\fnil\\fcharset0 Lucida Console;}}"); + + //Get all the colors used in tags: + //{\colortbl ;\red255\green255\blue0;\red0\green191\blue255;\red255\green255\blue255;} + // CF1 CF2 CF3 + RtfText.Append("{\\colortbl "); + + Hashtable CFClr = new Hashtable(); + int num = 1; + + foreach (HighlightWord item in Tags) + { + if (!CFClr.ContainsKey(item.Clr.Name.ToLower())) + { + CFClr.Add(item.Clr.Name.ToLower(), "\\cf" + num.ToString() + " "); + RtfText.Append(";" + this.RtfColors[item.Clr.Name.ToLower()].RtfClr); + num = num + 1; + } + } + + if (!KeyWordColor.IsEmpty) + { + if (!CFClr.ContainsKey(KeyWordColor.Name.ToLower())) + { + CFClr.Add(KeyWordColor.Name.ToLower(), "\\cf" + num.ToString() + " "); + RtfText.Append(";" + this.RtfColors[KeyWordColor.Name.ToLower()].RtfClr); + num = num + 1; + } + } + if (!SymbolColor.IsEmpty) + { + if (!CFClr.ContainsKey(SymbolColor.Name.ToLower())) + { + CFClr.Add(SymbolColor.Name.ToLower(), "\\cf" + num.ToString() + " "); + RtfText.Append(";" + this.RtfColors[SymbolColor.Name.ToLower()].RtfClr); + num = num + 1; + } + } + + //add the default forground color of the RTF box: + if (!CFClr.ContainsKey(this._RTF.ForeColor.Name.ToLower())) + { + CFClr.Add(this._RTF.ForeColor.Name.ToLower(), "\\cf" + num.ToString() + " "); + RtfText.Append(";" + this.RtfColors[this._RTF.ForeColor.Name.ToLower()].RtfClr); + } + + RtfText.Append(";}"); + + //look for all the hits first.... + SortedDictionary ScannerResult = new SortedDictionary(); + int Dupes = 0; + + if (Tags.Count > 0) + { + + foreach (HighlightWord item in Tags) + { + int pos = 0; + do + { + pos = Source.IndexOf(item.Word, pos, StringComparison.OrdinalIgnoreCase); + if (pos > -1) + { + //{=} + bool AddIt = false; + if (item.RequiredChar != null && item.RequiredChar.Length > 0) + { + int RcPos = pos + item.Word.Length; + if (RcPos < Source.Length) + { + string RC = Source.Substring(RcPos, item.RequiredChar.Length); + if (RC == item.RequiredChar) + { + AddIt = true; + } + } + //make sure we found a whole word: + if (AddIt) + { + int BefPos = pos - 1; + if (BefPos < 0) + { + AddIt = true; + } + else + { + string RC = Source.Substring(BefPos, 1); + if (RC == " " || RC == "\n" || RC == "\r") + { + AddIt = true; + } + else + { + AddIt = false; + } + } + } + } + else + { + AddIt = true; + } + if (AddIt) + { + FoundItem fi = new FoundItem(); + fi.Word = item.Word; + fi.Start = pos; + fi.Length = item.Word.Length; + fi.Clr = item.Clr; + if (!ScannerResult.ContainsKey(fi.Start)) + { + ScannerResult.Add(fi.Start, fi); + } + else + { + Dupes = Dupes + 1; + } + } + pos += 1; + } + } while (!(pos == -1)); + } + + } + else + { + //assume we look for meta{ } and = + int pos = 0; + for (pos = 0; pos < Source.Length; pos++) + { + char c = Source[pos]; + if (c == '{') + { + //add the { + FoundItem fi = new FoundItem(); + fi.Word = c.ToString(); + fi.Start = pos; + fi.Length = 1; + fi.Clr = SymbolColor; + if (!ScannerResult.ContainsKey(fi.Start)) + { + ScannerResult.Add(fi.Start, fi); + } + else + { + Dupes = Dupes + 1; + } + + //search back to the beginning of the keyword: + int Pos2 = 0; + int BegKeyPos = -1; + for (Pos2 = pos - 1; Pos2 >= 0; Pos2--) + { + string RC = Source.Substring(Pos2, 1); + if (RC == " " || RC == "\n" || RC == "\r" || RC == "}") + { + BegKeyPos = Pos2 + 1; + break; + } + } + if (BegKeyPos == -1) + { + BegKeyPos = 0; + } + int BegKeyLen = pos - BegKeyPos; + if (BegKeyLen > 0) + { + FoundItem fi2 = new FoundItem(); + fi2.Word = Source.Substring(BegKeyPos, BegKeyLen); + fi2.Start = BegKeyPos; + fi2.Length = BegKeyLen; + fi2.Clr = KeyWordColor; + if (!ScannerResult.ContainsKey(fi2.Start)) + { + ScannerResult.Add(fi2.Start, fi2); + } + else + { + Dupes = Dupes + 1; + } + } + + } + else if (c == '}' || c == '=') + { + FoundItem fi = new FoundItem(); + fi.Word = c.ToString(); + fi.Start = pos; + fi.Length = 1; + fi.Clr = SymbolColor; + if (!ScannerResult.ContainsKey(fi.Start)) + { + ScannerResult.Add(fi.Start, fi); + } + else + { + Dupes = Dupes + 1; + } + } + } + + } + + // Sort the collection based on the start index of the occurrence, to simplify the job of the parser. + //ScannerResult.Sort(Function(lhs As FoundItem, rhs As FoundItem) lhs.Start - rhs.Start) + + //Append body + RtfText.Append("\\viewkind4\\uc1\\pard" + CFClr[this._RTF.ForeColor.Name.ToLower()].ToString().Trim() + "\\lang1033\\f0\\fs17"); + int LastIndex = 0; + + string Chunk = ""; + + foreach (KeyValuePair itm in ScannerResult) + { + + if (LastIndex < itm.Value.Start) + { + //(LastIndex < (ScannerResult(i).Start + ScannerResult(i).Length)) Then + //append anything before the last search word was found + Chunk = this.ParseRtf(Source.Substring(LastIndex, (itm.Value.Start - LastIndex))); + + if (Chunk.Length > 0) + { + RtfText.Append(Chunk); + } + } + + //Append color for the word/tag + RtfText.Append(CFClr[itm.Value.Clr.Name.ToLower()]); + + //Append the word/tag + + Chunk = this.ParseRtf(Source.Substring(itm.Value.Start, itm.Value.Length)); + + RtfText.Append(Chunk); + + //Append the default rtf color for the LAST color + RtfText.Append(CFClr[this._RTF.ForeColor.Name.ToLower()]); + + LastIndex = (itm.Value.Start + itm.Value.Length); + } + + if (LastIndex < Source.Length) + { + Chunk = this.ParseRtf(Source.Substring(LastIndex, (Source.Length - LastIndex))); + + RtfText.Append(Chunk); + } + + //append the lst } + + RtfText.Append("}"); + + this._RTF.Rtf = RtfText.ToString().Replace("\r\n", "\\par" + "\r\n"); + + + //RtfBx.Update() + + } + + } + catch (Exception ex) + { + Debug.WriteLine("Error highlighting RTF box: " + ex.Message, false); + } + //tim.Stop() + //M(" HighlightRTF time: " & tim.ElapsedMilliseconds, True) + } + + public string ParseRtf(string token) + { + return token.Replace("\\", "\\\\").Replace("{", "\\{").Replace("}", "\\}"); + //.Replace("?", "\?").Replace("~", "\~").Replace("'", "\'").Replace("-", "\-").Replace("|", "\|").Replace(":", "\:").Replace("*", "\*") + } + + + public void AppendText(string text, Color color, FontStyle FS) + { + try + { + if (this._RTF.IsDisposed) + { + return; + + } + + //if (this.CurrentTextLength + text.Length >= 65536) + //{ + // //Debug.WriteLine($"Error: Too much in the RTF box! Clearing {this.CurrentTextLength}."); + // this._RTF.Clear(); + // this.CurrentTextLength = 0; + //} + + //If Me.Rtf.Length + text.Length >= Me.MaxLength OrElse Me.TextLength + text.Length >= Me.MaxLength Then + // Debug.WriteLine("Error: Too much in the RTF box! Clearing.") + // Me.Clear() + //End If + + this._RTF.SelectionStart = this._RTF.TextLength; + this._RTF.SelectionLength = 0; + + bool ChangedFont = false; + Font cf = this._RTF.SelectionFont; + + if (this._RTF.SelectionFont.Style != FS) + { + ChangedFont = true; + this._RTF.SelectionFont = new Font(cf, cf.Style | FS); + } + + this._RTF.SelectionColor = color; + + this.CurrentTextLength = this.CurrentTextLength + text.Length; + + this._RTF.AppendText(text); + + this._RTF.SelectionColor = this._RTF.ForeColor; + + if (ChangedFont) + { + this._RTF.SelectionFont = new Font(cf, FontStyle.Regular); + } + + + + } + catch (Exception ex) + { + Debug.WriteLine("Error: " + ex.Message); //assume overflow + this._RTF.Clear(); + } + } + + + private void SetSelectionStyle(UInt32 mask, UInt32 effect) + { + CHARFORMAT2_STRUCT cf = new CHARFORMAT2_STRUCT(); + cf.cbSize = Convert.ToUInt32(Marshal.SizeOf(cf)); + cf.dwMask = mask; + cf.dwEffects = effect; + IntPtr wpar = new IntPtr(SCF_SELECTION); + IntPtr lpar = Marshal.AllocCoTaskMem(Marshal.SizeOf(cf)); + Marshal.StructureToPtr(cf, lpar, false); + IntPtr res = SendMessage(this._RTF.Handle, EM_SETCHARFORMAT, wpar, lpar); + Marshal.FreeCoTaskMem(lpar); + } + + private int GetSelectionStyle(UInt32 mask, UInt32 effect) + { + CHARFORMAT2_STRUCT cf = new CHARFORMAT2_STRUCT(); + cf.cbSize = Convert.ToUInt32(Marshal.SizeOf(cf)); + cf.szFaceName = new char[32]; + IntPtr wpar = new IntPtr(SCF_SELECTION); + IntPtr lpar = Marshal.AllocCoTaskMem(Marshal.SizeOf(cf)); + Marshal.StructureToPtr(cf, lpar, false); + IntPtr res = SendMessage(this._RTF.Handle, EM_GETCHARFORMAT, wpar, lpar); + cf = (CHARFORMAT2_STRUCT)Marshal.PtrToStructure(lpar, typeof(CHARFORMAT2_STRUCT)); + int state = 0; + if ((cf.dwMask & mask) == mask) + { + if ((cf.dwEffects & effect) == effect) + { + state = 1; + } + else + { + state = 0; + } + } + else + { + state = -1; + } + Marshal.FreeCoTaskMem(lpar); + return state; + } +} diff --git a/src/UI/SQLite/SQLite.cs b/src/UI/SQLite/SQLite.cs new file mode 100644 index 00000000..525f4a74 --- /dev/null +++ b/src/UI/SQLite/SQLite.cs @@ -0,0 +1,4857 @@ +// +// Copyright (c) 2009-2019 Krueger Systems, 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. +// +#if WINDOWS_PHONE && !USE_WP8_NATIVE_SQLITE +#define USE_CSHARP_SQLITE +#endif + +using System; +using System.Collections; +using System.Diagnostics; +#if !USE_SQLITEPCL_RAW +using System.Runtime.InteropServices; +#endif +using System.Collections.Generic; +using System.Reflection; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading; + +#if USE_CSHARP_SQLITE +using Sqlite3 = Community.CsharpSqlite.Sqlite3; +using Sqlite3DatabaseHandle = Community.CsharpSqlite.Sqlite3.sqlite3; +using Sqlite3Statement = Community.CsharpSqlite.Sqlite3.Vdbe; +#elif USE_WP8_NATIVE_SQLITE +using Sqlite3 = Sqlite.Sqlite3; +using Sqlite3DatabaseHandle = Sqlite.Database; +using Sqlite3Statement = Sqlite.Statement; +#elif USE_SQLITEPCL_RAW +using Sqlite3DatabaseHandle = SQLitePCL.sqlite3; +using Sqlite3BackupHandle = SQLitePCL.sqlite3_backup; +using Sqlite3Statement = SQLitePCL.sqlite3_stmt; +using Sqlite3 = SQLitePCL.raw; +#else +using Sqlite3DatabaseHandle = System.IntPtr; +using Sqlite3BackupHandle = System.IntPtr; +using Sqlite3Statement = System.IntPtr; +#endif + +#pragma warning disable 1591 // XML Doc Comments + +namespace SQLite +{ + public class SQLiteException : Exception + { + public SQLite3.Result Result { get; private set; } + + protected SQLiteException(SQLite3.Result r, string message) : base(message) + { + this.Result = r; + } + + public static SQLiteException New(SQLite3.Result r, string message) + { + return new SQLiteException(r, message); + } + } + + public class NotNullConstraintViolationException : SQLiteException + { + public IEnumerable Columns { get; protected set; } + + protected NotNullConstraintViolationException(SQLite3.Result r, string message) + : this(r, message, null, null) + { + + } + + protected NotNullConstraintViolationException(SQLite3.Result r, string message, TableMapping mapping, object obj) + : base(r, message) + { + if (mapping != null && obj != null) + { + this.Columns = from c in mapping.Columns + where c.IsNullable == false && c.GetValue(obj) == null + select c; + } + } + + public static new NotNullConstraintViolationException New(SQLite3.Result r, string message) + { + return new NotNullConstraintViolationException(r, message); + } + + public static NotNullConstraintViolationException New(SQLite3.Result r, string message, TableMapping mapping, object obj) + { + return new NotNullConstraintViolationException(r, message, mapping, obj); + } + + public static NotNullConstraintViolationException New(SQLiteException exception, TableMapping mapping, object obj) + { + return new NotNullConstraintViolationException(exception.Result, exception.Message, mapping, obj); + } + } + + [Flags] + public enum SQLiteOpenFlags + { + ReadOnly = 1, ReadWrite = 2, Create = 4, + NoMutex = 0x8000, FullMutex = 0x10000, + SharedCache = 0x20000, PrivateCache = 0x40000, + ProtectionComplete = 0x00100000, + ProtectionCompleteUnlessOpen = 0x00200000, + ProtectionCompleteUntilFirstUserAuthentication = 0x00300000, + ProtectionNone = 0x00400000 + } + + [Flags] + public enum CreateFlags + { + /// + /// Use the default creation options + /// + None = 0x000, + /// + /// Create a primary key index for a property called 'Id' (case-insensitive). + /// This avoids the need for the [PrimaryKey] attribute. + /// + ImplicitPK = 0x001, + /// + /// Create indices for properties ending in 'Id' (case-insensitive). + /// + ImplicitIndex = 0x002, + /// + /// Create a primary key for a property called 'Id' and + /// create an indices for properties ending in 'Id' (case-insensitive). + /// + AllImplicit = 0x003, + /// + /// Force the primary key property to be auto incrementing. + /// This avoids the need for the [AutoIncrement] attribute. + /// The primary key property on the class should have type int or long. + /// + AutoIncPK = 0x004, + /// + /// Create virtual table using FTS3 + /// + FullTextSearch3 = 0x100, + /// + /// Create virtual table using FTS4 + /// + FullTextSearch4 = 0x200 + } + + /// + /// An open connection to a SQLite database. + /// + [Preserve(AllMembers = true)] + public partial class SQLiteConnection : IDisposable + { + private bool _open; + private TimeSpan _busyTimeout; + readonly static Dictionary _mappings = new Dictionary(); + private System.Diagnostics.Stopwatch _sw; + private long _elapsedMilliseconds = 0; + + private int _transactionDepth = 0; + private Random _rand = new Random(); + + public Sqlite3DatabaseHandle Handle { get; private set; } + static readonly Sqlite3DatabaseHandle NullHandle = default(Sqlite3DatabaseHandle); + static readonly Sqlite3BackupHandle NullBackupHandle = default(Sqlite3BackupHandle); + + /// + /// Gets the database path used by this connection. + /// + public string DatabasePath { get; private set; } + + /// + /// Gets the SQLite library version number. 3007014 would be v3.7.14 + /// + public int LibVersionNumber { get; private set; } + + /// + /// Whether Trace lines should be written that show the execution time of queries. + /// + public bool TimeExecution { get; set; } + + /// + /// Whether to write queries to during execution. + /// + public bool Trace { get; set; } + + /// + /// The delegate responsible for writing trace lines. + /// + /// The tracer. + public Action Tracer { get; set; } + + /// + /// Whether to store DateTime properties as ticks (true) or strings (false). + /// + public bool StoreDateTimeAsTicks { get; private set; } + + /// + /// Whether to store TimeSpan properties as ticks (true) or strings (false). + /// + public bool StoreTimeSpanAsTicks { get; private set; } + + /// + /// The format to use when storing DateTime properties as strings. Ignored if StoreDateTimeAsTicks is true. + /// + /// The date time string format. + public string DateTimeStringFormat { get; private set; } + + /// + /// The DateTimeStyles value to use when parsing a DateTime property string. + /// + /// The date time style. + internal System.Globalization.DateTimeStyles DateTimeStyle { get; private set; } + +#if USE_SQLITEPCL_RAW && !NO_SQLITEPCL_RAW_BATTERIES + static SQLiteConnection () + { + SQLitePCL.Batteries_V2.Init (); + } +#endif + + /// + /// Constructs a new SQLiteConnection and opens a SQLite database specified by databasePath. + /// + /// + /// Specifies the path to the database file. + /// + /// + /// Specifies whether to store DateTime properties as ticks (true) or strings (false). You + /// absolutely do want to store them as Ticks in all new projects. The value of false is + /// only here for backwards compatibility. There is a *significant* speed advantage, with no + /// down sides, when setting storeDateTimeAsTicks = true. + /// If you use DateTimeOffset properties, it will be always stored as ticks regardingless + /// the storeDateTimeAsTicks parameter. + /// + public SQLiteConnection(string databasePath, bool storeDateTimeAsTicks = true) + : this(new SQLiteConnectionString(databasePath, SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create, storeDateTimeAsTicks)) + { + } + + /// + /// Constructs a new SQLiteConnection and opens a SQLite database specified by databasePath. + /// + /// + /// Specifies the path to the database file. + /// + /// + /// Flags controlling how the connection should be opened. + /// + /// + /// Specifies whether to store DateTime properties as ticks (true) or strings (false). You + /// absolutely do want to store them as Ticks in all new projects. The value of false is + /// only here for backwards compatibility. There is a *significant* speed advantage, with no + /// down sides, when setting storeDateTimeAsTicks = true. + /// If you use DateTimeOffset properties, it will be always stored as ticks regardingless + /// the storeDateTimeAsTicks parameter. + /// + public SQLiteConnection(string databasePath, SQLiteOpenFlags openFlags, bool storeDateTimeAsTicks = true) + : this(new SQLiteConnectionString(databasePath, openFlags, storeDateTimeAsTicks)) + { + } + + /// + /// Constructs a new SQLiteConnection and opens a SQLite database specified by databasePath. + /// + /// + /// Details on how to find and open the database. + /// + public SQLiteConnection(SQLiteConnectionString connectionString) + { + if (connectionString == null) + throw new ArgumentNullException(nameof(connectionString)); + if (connectionString.DatabasePath == null) + throw new InvalidOperationException("DatabasePath must be specified"); + + this.DatabasePath = connectionString.DatabasePath; + + this.LibVersionNumber = SQLite3.LibVersionNumber(); + +#if NETFX_CORE + SQLite3.SetDirectory(/*temp directory type*/2, Windows.Storage.ApplicationData.Current.TemporaryFolder.Path); +#endif + + Sqlite3DatabaseHandle handle; + +#if SILVERLIGHT || USE_CSHARP_SQLITE || USE_SQLITEPCL_RAW + var r = SQLite3.Open (connectionString.DatabasePath, out handle, (int)connectionString.OpenFlags, connectionString.VfsName); +#else + // open using the byte[] + // in the case where the path may include Unicode + // force open to using UTF-8 using sqlite3_open_v2 + var databasePathAsBytes = GetNullTerminatedUtf8(connectionString.DatabasePath); + var r = SQLite3.Open(databasePathAsBytes, out handle, (int)connectionString.OpenFlags, connectionString.VfsName); +#endif + + this.Handle = handle; + if (r != SQLite3.Result.OK) + { + throw SQLiteException.New(r, String.Format("Could not open database file: {0} ({1})", this.DatabasePath, r)); + } + this._open = true; + + this.StoreDateTimeAsTicks = connectionString.StoreDateTimeAsTicks; + this.StoreTimeSpanAsTicks = connectionString.StoreTimeSpanAsTicks; + this.DateTimeStringFormat = connectionString.DateTimeStringFormat; + this.DateTimeStyle = connectionString.DateTimeStyle; + + this.BusyTimeout = TimeSpan.FromSeconds(1.0); + this.Tracer = line => Debug.WriteLine(line); + + connectionString.PreKeyAction?.Invoke(this); + if (connectionString.Key is string stringKey) + { + this.SetKey(stringKey); + } + else if (connectionString.Key is byte[] bytesKey) + { + this.SetKey(bytesKey); + } + else if (connectionString.Key != null) + { + throw new InvalidOperationException("Encryption keys must be strings or byte arrays"); + } + connectionString.PostKeyAction?.Invoke(this); + } + + /// + /// Enables the write ahead logging. WAL is significantly faster in most scenarios + /// by providing better concurrency and better disk IO performance than the normal + /// journal mode. You only need to call this function once in the lifetime of the database. + /// + public void EnableWriteAheadLogging() + { + this.ExecuteScalar("PRAGMA journal_mode=WAL"); + } + + /// + /// Convert an input string to a quoted SQL string that can be safely used in queries. + /// + /// The quoted string. + /// The unsafe string to quote. + static string Quote(string unsafeString) + { + // TODO: Doesn't call sqlite3_mprintf("%Q", u) because we're waiting on https://github.com/ericsink/SQLitePCL.raw/issues/153 + if (unsafeString == null) return "NULL"; + var safe = unsafeString.Replace("'", "''"); + return "'" + safe + "'"; + } + + /// + /// Sets the key used to encrypt/decrypt the database with "pragma key = ...". + /// This must be the first thing you call before doing anything else with this connection + /// if your database is encrypted. + /// This only has an effect if you are using the SQLCipher nuget package. + /// + /// Ecryption key plain text that is converted to the real encryption key using PBKDF2 key derivation + void SetKey(string key) + { + if (key == null) throw new ArgumentNullException(nameof(key)); + var q = Quote(key); + this.Execute("pragma key = " + q); + } + + /// + /// Sets the key used to encrypt/decrypt the database. + /// This must be the first thing you call before doing anything else with this connection + /// if your database is encrypted. + /// This only has an effect if you are using the SQLCipher nuget package. + /// + /// 256-bit (32 byte) ecryption key data + void SetKey(byte[] key) + { + if (key == null) throw new ArgumentNullException(nameof(key)); + if (key.Length != 32) throw new ArgumentException("Key must be 32 bytes (256-bit)", nameof(key)); + var s = String.Join("", key.Select(x => x.ToString("X2"))); + this.Execute("pragma key = \"x'" + s + "'\""); + } + + /// + /// Enable or disable extension loading. + /// + public void EnableLoadExtension(bool enabled) + { + SQLite3.Result r = SQLite3.EnableLoadExtension(this.Handle, enabled ? 1 : 0); + if (r != SQLite3.Result.OK) + { + string msg = SQLite3.GetErrmsg(this.Handle); + throw SQLiteException.New(r, msg); + } + } + +#if !USE_SQLITEPCL_RAW + static byte[] GetNullTerminatedUtf8(string s) + { + var utf8Length = System.Text.Encoding.UTF8.GetByteCount(s); + var bytes = new byte[utf8Length + 1]; + utf8Length = System.Text.Encoding.UTF8.GetBytes(s, 0, s.Length, bytes, 0); + return bytes; + } +#endif + + /// + /// Sets a busy handler to sleep the specified amount of time when a table is locked. + /// The handler will sleep multiple times until a total time of has accumulated. + /// + public TimeSpan BusyTimeout + { + get { return this._busyTimeout; } + set + { + this._busyTimeout = value; + if (this.Handle != NullHandle) + { + SQLite3.BusyTimeout(this.Handle, (int)this._busyTimeout.TotalMilliseconds); + } + } + } + + /// + /// Returns the mappings from types to tables that the connection + /// currently understands. + /// + public IEnumerable TableMappings + { + get + { + lock (_mappings) + { + return new List(_mappings.Values); + } + } + } + + /// + /// Retrieves the mapping that is automatically generated for the given type. + /// + /// + /// The type whose mapping to the database is returned. + /// + /// + /// Optional flags allowing implicit PK and indexes based on naming conventions + /// + /// + /// The mapping represents the schema of the columns of the database and contains + /// methods to set and get properties of objects. + /// + public TableMapping GetMapping(Type type, CreateFlags createFlags = CreateFlags.None) + { + TableMapping map; + var key = type.FullName; + lock (_mappings) + { + if (_mappings.TryGetValue(key, out map)) + { + if (createFlags != CreateFlags.None && createFlags != map.CreateFlags) + { + map = new TableMapping(type, createFlags); + _mappings[key] = map; + } + } + else + { + map = new TableMapping(type, createFlags); + _mappings.Add(key, map); + } + } + return map; + } + + /// + /// Retrieves the mapping that is automatically generated for the given type. + /// + /// + /// Optional flags allowing implicit PK and indexes based on naming conventions + /// + /// + /// The mapping represents the schema of the columns of the database and contains + /// methods to set and get properties of objects. + /// + public TableMapping GetMapping(CreateFlags createFlags = CreateFlags.None) + { + return this.GetMapping(typeof(T), createFlags); + } + + private struct IndexedColumn + { + public int Order; + public string ColumnName; + } + + private struct IndexInfo + { + public string IndexName; + public string TableName; + public bool Unique; + public List Columns; + } + + /// + /// Executes a "drop table" on the database. This is non-recoverable. + /// + public int DropTable() + { + return this.DropTable(this.GetMapping(typeof(T))); + } + + /// + /// Executes a "drop table" on the database. This is non-recoverable. + /// + /// + /// The TableMapping used to identify the table. + /// + public int DropTable(TableMapping map) + { + var query = string.Format("drop table if exists \"{0}\"", map.TableName); + return this.Execute(query); + } + + /// + /// Executes a "create table if not exists" on the database. It also + /// creates any specified indexes on the columns of the table. It uses + /// a schema automatically generated from the specified type. You can + /// later access this schema by calling GetMapping. + /// + /// + /// Whether the table was created or migrated. + /// + public CreateTableResult CreateTable(CreateFlags createFlags = CreateFlags.None) + { + return this.CreateTable(typeof(T), createFlags); + } + + /// + /// Executes a "create table if not exists" on the database. It also + /// creates any specified indexes on the columns of the table. It uses + /// a schema automatically generated from the specified type. You can + /// later access this schema by calling GetMapping. + /// + /// Type to reflect to a database table. + /// Optional flags allowing implicit PK and indexes based on naming conventions. + /// + /// Whether the table was created or migrated. + /// + public CreateTableResult CreateTable(Type ty, CreateFlags createFlags = CreateFlags.None) + { + var map = this.GetMapping(ty, createFlags); + + // Present a nice error if no columns specified + if (map.Columns.Length == 0) + { + throw new Exception(string.Format("Cannot create a table without columns (does '{0}' have public properties?)", ty.FullName)); + } + + // Check if the table exists + var result = CreateTableResult.Created; + var existingCols = this.GetTableInfo(map.TableName); + + // Create or migrate it + if (existingCols.Count == 0) + { + + // Facilitate virtual tables a.k.a. full-text search. + bool fts3 = (createFlags & CreateFlags.FullTextSearch3) != 0; + bool fts4 = (createFlags & CreateFlags.FullTextSearch4) != 0; + bool fts = fts3 || fts4; + var @virtual = fts ? "virtual " : string.Empty; + var @using = fts3 ? "using fts3 " : fts4 ? "using fts4 " : string.Empty; + + // Build query. + var query = "create " + @virtual + "table if not exists \"" + map.TableName + "\" " + @using + "(\n"; + var decls = map.Columns.Select(p => Orm.SqlDecl(p, this.StoreDateTimeAsTicks, this.StoreTimeSpanAsTicks)); + var decl = string.Join(",\n", decls.ToArray()); + query += decl; + query += ")"; + if (map.WithoutRowId) + { + query += " without rowid"; + } + + this.Execute(query); + } + else + { + result = CreateTableResult.Migrated; + this.MigrateTable(map, existingCols); + } + + var indexes = new Dictionary(); + foreach (var c in map.Columns) + { + foreach (var i in c.Indices) + { + var iname = i.Name ?? map.TableName + "_" + c.Name; + IndexInfo iinfo; + if (!indexes.TryGetValue(iname, out iinfo)) + { + iinfo = new IndexInfo + { + IndexName = iname, + TableName = map.TableName, + Unique = i.Unique, + Columns = new List() + }; + indexes.Add(iname, iinfo); + } + + if (i.Unique != iinfo.Unique) + throw new Exception("All the columns in an index must have the same value for their Unique property"); + + iinfo.Columns.Add(new IndexedColumn + { + Order = i.Order, + ColumnName = c.Name + }); + } + } + + foreach (var indexName in indexes.Keys) + { + var index = indexes[indexName]; + var columns = index.Columns.OrderBy(i => i.Order).Select(i => i.ColumnName).ToArray(); + this.CreateIndex(indexName, index.TableName, columns, index.Unique); + } + + return result; + } + + /// + /// Executes a "create table if not exists" on the database for each type. It also + /// creates any specified indexes on the columns of the table. It uses + /// a schema automatically generated from the specified type. You can + /// later access this schema by calling GetMapping. + /// + /// + /// Whether the table was created or migrated for each type. + /// + public CreateTablesResult CreateTables(CreateFlags createFlags = CreateFlags.None) + where T : new() + where T2 : new() + { + return this.CreateTables(createFlags, typeof(T), typeof(T2)); + } + + /// + /// Executes a "create table if not exists" on the database for each type. It also + /// creates any specified indexes on the columns of the table. It uses + /// a schema automatically generated from the specified type. You can + /// later access this schema by calling GetMapping. + /// + /// + /// Whether the table was created or migrated for each type. + /// + public CreateTablesResult CreateTables(CreateFlags createFlags = CreateFlags.None) + where T : new() + where T2 : new() + where T3 : new() + { + return this.CreateTables(createFlags, typeof(T), typeof(T2), typeof(T3)); + } + + /// + /// Executes a "create table if not exists" on the database for each type. It also + /// creates any specified indexes on the columns of the table. It uses + /// a schema automatically generated from the specified type. You can + /// later access this schema by calling GetMapping. + /// + /// + /// Whether the table was created or migrated for each type. + /// + public CreateTablesResult CreateTables(CreateFlags createFlags = CreateFlags.None) + where T : new() + where T2 : new() + where T3 : new() + where T4 : new() + { + return this.CreateTables(createFlags, typeof(T), typeof(T2), typeof(T3), typeof(T4)); + } + + /// + /// Executes a "create table if not exists" on the database for each type. It also + /// creates any specified indexes on the columns of the table. It uses + /// a schema automatically generated from the specified type. You can + /// later access this schema by calling GetMapping. + /// + /// + /// Whether the table was created or migrated for each type. + /// + public CreateTablesResult CreateTables(CreateFlags createFlags = CreateFlags.None) + where T : new() + where T2 : new() + where T3 : new() + where T4 : new() + where T5 : new() + { + return this.CreateTables(createFlags, typeof(T), typeof(T2), typeof(T3), typeof(T4), typeof(T5)); + } + + /// + /// Executes a "create table if not exists" on the database for each type. It also + /// creates any specified indexes on the columns of the table. It uses + /// a schema automatically generated from the specified type. You can + /// later access this schema by calling GetMapping. + /// + /// + /// Whether the table was created or migrated for each type. + /// + public CreateTablesResult CreateTables(CreateFlags createFlags = CreateFlags.None, params Type[] types) + { + var result = new CreateTablesResult(); + foreach (Type type in types) + { + var aResult = this.CreateTable(type, createFlags); + result.Results[type] = aResult; + } + return result; + } + + /// + /// Creates an index for the specified table and columns. + /// + /// Name of the index to create + /// Name of the database table + /// An array of column names to index + /// Whether the index should be unique + public int CreateIndex(string indexName, string tableName, string[] columnNames, bool unique = false) + { + const string sqlFormat = "create {2} index if not exists \"{3}\" on \"{0}\"(\"{1}\")"; + var sql = String.Format(sqlFormat, tableName, string.Join("\", \"", columnNames), unique ? "unique" : "", indexName); + return this.Execute(sql); + } + + /// + /// Creates an index for the specified table and column. + /// + /// Name of the index to create + /// Name of the database table + /// Name of the column to index + /// Whether the index should be unique + public int CreateIndex(string indexName, string tableName, string columnName, bool unique = false) + { + return this.CreateIndex(indexName, tableName, new string[] { columnName }, unique); + } + + /// + /// Creates an index for the specified table and column. + /// + /// Name of the database table + /// Name of the column to index + /// Whether the index should be unique + public int CreateIndex(string tableName, string columnName, bool unique = false) + { + return this.CreateIndex(tableName + "_" + columnName, tableName, columnName, unique); + } + + /// + /// Creates an index for the specified table and columns. + /// + /// Name of the database table + /// An array of column names to index + /// Whether the index should be unique + public int CreateIndex(string tableName, string[] columnNames, bool unique = false) + { + return this.CreateIndex(tableName + "_" + string.Join("_", columnNames), tableName, columnNames, unique); + } + + /// + /// Creates an index for the specified object property. + /// e.g. CreateIndex<Client>(c => c.Name); + /// + /// Type to reflect to a database table. + /// Property to index + /// Whether the index should be unique + public int CreateIndex(Expression> property, bool unique = false) + { + MemberExpression mx; + if (property.Body.NodeType == ExpressionType.Convert) + { + mx = ((UnaryExpression)property.Body).Operand as MemberExpression; + } + else + { + mx = (property.Body as MemberExpression); + } + var propertyInfo = mx.Member as PropertyInfo; + if (propertyInfo == null) + { + throw new ArgumentException("The lambda expression 'property' should point to a valid Property"); + } + + var propName = propertyInfo.Name; + + var map = this.GetMapping(); + var colName = map.FindColumnWithPropertyName(propName).Name; + + return this.CreateIndex(map.TableName, colName, unique); + } + + [Preserve(AllMembers = true)] + public class ColumnInfo + { + // public int cid { get; set; } + + [Column("name")] + public string Name { get; set; } + + // [Column ("type")] + // public string ColumnType { get; set; } + + public int notnull { get; set; } + + // public string dflt_value { get; set; } + + // public int pk { get; set; } + + public override string ToString() + { + return this.Name; + } + } + + /// + /// Query the built-in sqlite table_info table for a specific tables columns. + /// + /// The columns contains in the table. + /// Table name. + public List GetTableInfo(string tableName) + { + var query = "pragma table_info(\"" + tableName + "\")"; + return this.Query(query); + } + + void MigrateTable(TableMapping map, List existingCols) + { + var toBeAdded = new List(); + + foreach (var p in map.Columns) + { + var found = false; + foreach (var c in existingCols) + { + found = (string.Compare(p.Name, c.Name, StringComparison.OrdinalIgnoreCase) == 0); + if (found) + break; + } + if (!found) + { + toBeAdded.Add(p); + } + } + + foreach (var p in toBeAdded) + { + var addCol = "alter table \"" + map.TableName + "\" add column " + Orm.SqlDecl(p, this.StoreDateTimeAsTicks, this.StoreTimeSpanAsTicks); + this.Execute(addCol); + } + } + + /// + /// Creates a new SQLiteCommand. Can be overridden to provide a sub-class. + /// + /// + protected virtual SQLiteCommand NewCommand() + { + return new SQLiteCommand(this); + } + + /// + /// Creates a new SQLiteCommand given the command text with arguments. Place a '?' + /// in the command text for each of the arguments. + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the command text. + /// + /// + /// A + /// + public SQLiteCommand CreateCommand(string cmdText, params object[] ps) + { + if (!this._open) + throw SQLiteException.New(SQLite3.Result.Error, "Cannot create commands from unopened database"); + + var cmd = this.NewCommand(); + cmd.CommandText = cmdText; + foreach (var o in ps) + { + cmd.Bind(o); + } + return cmd; + } + + /// + /// Creates a new SQLiteCommand given the command text with named arguments. Place a "[@:$]VVV" + /// in the command text for each of the arguments. VVV represents an alphanumeric identifier. + /// For example, @name :name and $name can all be used in the query. + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of "[@:$]VVV" in the command text. + /// + /// + /// A + /// + public SQLiteCommand CreateCommand(string cmdText, Dictionary args) + { + if (!this._open) + throw SQLiteException.New(SQLite3.Result.Error, "Cannot create commands from unopened database"); + + SQLiteCommand cmd = this.NewCommand(); + cmd.CommandText = cmdText; + foreach (var kv in args) + { + cmd.Bind(kv.Key, kv.Value); + } + return cmd; + } + + /// + /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' + /// in the command text for each of the arguments and then executes that command. + /// Use this method instead of Query when you don't expect rows back. Such cases include + /// INSERTs, UPDATEs, and DELETEs. + /// You can set the Trace or TimeExecution properties of the connection + /// to profile execution. + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the query. + /// + /// + /// The number of rows modified in the database as a result of this execution. + /// + public int Execute(string query, params object[] args) + { + var cmd = this.CreateCommand(query, args); + + if (this.TimeExecution) + { + if (this._sw == null) + { + this._sw = new Stopwatch(); + } + this._sw.Reset(); + this._sw.Start(); + } + + var r = cmd.ExecuteNonQuery(); + + if (this.TimeExecution) + { + this._sw.Stop(); + this._elapsedMilliseconds += this._sw.ElapsedMilliseconds; + this.Tracer?.Invoke(string.Format("Finished in {0} ms ({1:0.0} s total)", this._sw.ElapsedMilliseconds, this._elapsedMilliseconds / 1000.0)); + } + + return r; + } + + /// + /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' + /// in the command text for each of the arguments and then executes that command. + /// Use this method when return primitive values. + /// You can set the Trace or TimeExecution properties of the connection + /// to profile execution. + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the query. + /// + /// + /// The number of rows modified in the database as a result of this execution. + /// + public T ExecuteScalar(string query, params object[] args) + { + var cmd = this.CreateCommand(query, args); + + if (this.TimeExecution) + { + if (this._sw == null) + { + this._sw = new Stopwatch(); + } + this._sw.Reset(); + this._sw.Start(); + } + + var r = cmd.ExecuteScalar(); + + if (this.TimeExecution) + { + this._sw.Stop(); + this._elapsedMilliseconds += this._sw.ElapsedMilliseconds; + this.Tracer?.Invoke(string.Format("Finished in {0} ms ({1:0.0} s total)", this._sw.ElapsedMilliseconds, this._elapsedMilliseconds / 1000.0)); + } + + return r; + } + + /// + /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' + /// in the command text for each of the arguments and then executes that command. + /// It returns each row of the result using the mapping automatically generated for + /// the given type. + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the query. + /// + /// + /// An enumerable with one result for each row returned by the query. + /// + public List Query(string query, params object[] args) where T : new() + { + var cmd = this.CreateCommand(query, args); + return cmd.ExecuteQuery(); + } + + /// + /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' + /// in the command text for each of the arguments and then executes that command. + /// It returns the first column of each row of the result. + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the query. + /// + /// + /// An enumerable with one result for the first column of each row returned by the query. + /// + public List QueryScalars(string query, params object[] args) + { + var cmd = this.CreateCommand(query, args); + return cmd.ExecuteQueryScalars().ToList(); + } + + /// + /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' + /// in the command text for each of the arguments and then executes that command. + /// It returns each row of the result using the mapping automatically generated for + /// the given type. + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the query. + /// + /// + /// An enumerable with one result for each row returned by the query. + /// The enumerator (retrieved by calling GetEnumerator() on the result of this method) + /// will call sqlite3_step on each call to MoveNext, so the database + /// connection must remain open for the lifetime of the enumerator. + /// + public IEnumerable DeferredQuery(string query, params object[] args) where T : new() + { + var cmd = this.CreateCommand(query, args); + return cmd.ExecuteDeferredQuery(); + } + + /// + /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' + /// in the command text for each of the arguments and then executes that command. + /// It returns each row of the result using the specified mapping. This function is + /// only used by libraries in order to query the database via introspection. It is + /// normally not used. + /// + /// + /// A to use to convert the resulting rows + /// into objects. + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the query. + /// + /// + /// An enumerable with one result for each row returned by the query. + /// + public List Query(TableMapping map, string query, params object[] args) + { + var cmd = this.CreateCommand(query, args); + return cmd.ExecuteQuery(map); + } + + /// + /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' + /// in the command text for each of the arguments and then executes that command. + /// It returns each row of the result using the specified mapping. This function is + /// only used by libraries in order to query the database via introspection. It is + /// normally not used. + /// + /// + /// A to use to convert the resulting rows + /// into objects. + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the query. + /// + /// + /// An enumerable with one result for each row returned by the query. + /// The enumerator (retrieved by calling GetEnumerator() on the result of this method) + /// will call sqlite3_step on each call to MoveNext, so the database + /// connection must remain open for the lifetime of the enumerator. + /// + public IEnumerable DeferredQuery(TableMapping map, string query, params object[] args) + { + var cmd = this.CreateCommand(query, args); + return cmd.ExecuteDeferredQuery(map); + } + + /// + /// Returns a queryable interface to the table represented by the given type. + /// + /// + /// A queryable object that is able to translate Where, OrderBy, and Take + /// queries into native SQL. + /// + public TableQuery Table() where T : new() + { + return new TableQuery(this); + } + + /// + /// Attempts to retrieve an object with the given primary key from the table + /// associated with the specified type. Use of this method requires that + /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute). + /// + /// + /// The primary key. + /// + /// + /// The object with the given primary key. Throws a not found exception + /// if the object is not found. + /// + public T Get(object pk) where T : new() + { + var map = this.GetMapping(typeof(T)); + return this.Query(map.GetByPrimaryKeySql, pk).First(); + } + + /// + /// Attempts to retrieve an object with the given primary key from the table + /// associated with the specified type. Use of this method requires that + /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute). + /// + /// + /// The primary key. + /// + /// + /// The TableMapping used to identify the table. + /// + /// + /// The object with the given primary key. Throws a not found exception + /// if the object is not found. + /// + public object Get(object pk, TableMapping map) + { + return this.Query(map, map.GetByPrimaryKeySql, pk).First(); + } + + /// + /// Attempts to retrieve the first object that matches the predicate from the table + /// associated with the specified type. + /// + /// + /// A predicate for which object to find. + /// + /// + /// The object that matches the given predicate. Throws a not found exception + /// if the object is not found. + /// + public T Get(Expression> predicate) where T : new() + { + return this.Table().Where(predicate).First(); + } + + /// + /// Attempts to retrieve an object with the given primary key from the table + /// associated with the specified type. Use of this method requires that + /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute). + /// + /// + /// The primary key. + /// + /// + /// The object with the given primary key or null + /// if the object is not found. + /// + public T Find(object pk) where T : new() + { + var map = this.GetMapping(typeof(T)); + return this.Query(map.GetByPrimaryKeySql, pk).FirstOrDefault(); + } + + /// + /// Attempts to retrieve an object with the given primary key from the table + /// associated with the specified type. Use of this method requires that + /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute). + /// + /// + /// The primary key. + /// + /// + /// The TableMapping used to identify the table. + /// + /// + /// The object with the given primary key or null + /// if the object is not found. + /// + public object Find(object pk, TableMapping map) + { + return this.Query(map, map.GetByPrimaryKeySql, pk).FirstOrDefault(); + } + + /// + /// Attempts to retrieve the first object that matches the predicate from the table + /// associated with the specified type. + /// + /// + /// A predicate for which object to find. + /// + /// + /// The object that matches the given predicate or null + /// if the object is not found. + /// + public T Find(Expression> predicate) where T : new() + { + return this.Table().Where(predicate).FirstOrDefault(); + } + + /// + /// Attempts to retrieve the first object that matches the query from the table + /// associated with the specified type. + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the query. + /// + /// + /// The object that matches the given predicate or null + /// if the object is not found. + /// + public T FindWithQuery(string query, params object[] args) where T : new() + { + return this.Query(query, args).FirstOrDefault(); + } + + /// + /// Attempts to retrieve the first object that matches the query from the table + /// associated with the specified type. + /// + /// + /// The TableMapping used to identify the table. + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the query. + /// + /// + /// The object that matches the given predicate or null + /// if the object is not found. + /// + public object FindWithQuery(TableMapping map, string query, params object[] args) + { + return this.Query(map, query, args).FirstOrDefault(); + } + + /// + /// Whether has been called and the database is waiting for a . + /// + public bool IsInTransaction + { + get { return this._transactionDepth > 0; } + } + + /// + /// Begins a new transaction. Call to end the transaction. + /// + /// Throws if a transaction has already begun. + public void BeginTransaction() + { + // The BEGIN command only works if the transaction stack is empty, + // or in other words if there are no pending transactions. + // If the transaction stack is not empty when the BEGIN command is invoked, + // then the command fails with an error. + // Rather than crash with an error, we will just ignore calls to BeginTransaction + // that would result in an error. + if (Interlocked.CompareExchange(ref this._transactionDepth, 1, 0) == 0) + { + try + { + this.Execute("begin transaction"); + } + catch (Exception ex) + { + var sqlExp = ex as SQLiteException; + if (sqlExp != null) + { + // It is recommended that applications respond to the errors listed below + // by explicitly issuing a ROLLBACK command. + // TODO: This rollback failsafe should be localized to all throw sites. + switch (sqlExp.Result) + { + case SQLite3.Result.IOError: + case SQLite3.Result.Full: + case SQLite3.Result.Busy: + case SQLite3.Result.NoMem: + case SQLite3.Result.Interrupt: + this.RollbackTo(null, true); + break; + } + } + else + { + // Call decrement and not VolatileWrite in case we've already + // created a transaction point in SaveTransactionPoint since the catch. + Interlocked.Decrement(ref this._transactionDepth); + } + + throw; + } + } + else + { + // Calling BeginTransaction on an already open transaction is invalid + throw new InvalidOperationException("Cannot begin a transaction while already in a transaction."); + } + } + + /// + /// Creates a savepoint in the database at the current point in the transaction timeline. + /// Begins a new transaction if one is not in progress. + /// + /// Call to undo transactions since the returned savepoint. + /// Call to commit transactions after the savepoint returned here. + /// Call to end the transaction, committing all changes. + /// + /// A string naming the savepoint. + public string SaveTransactionPoint() + { + int depth = Interlocked.Increment(ref this._transactionDepth) - 1; + string retVal = "S" + this._rand.Next(short.MaxValue) + "D" + depth; + + try + { + this.Execute("savepoint " + retVal); + } + catch (Exception ex) + { + var sqlExp = ex as SQLiteException; + if (sqlExp != null) + { + // It is recommended that applications respond to the errors listed below + // by explicitly issuing a ROLLBACK command. + // TODO: This rollback failsafe should be localized to all throw sites. + switch (sqlExp.Result) + { + case SQLite3.Result.IOError: + case SQLite3.Result.Full: + case SQLite3.Result.Busy: + case SQLite3.Result.NoMem: + case SQLite3.Result.Interrupt: + this.RollbackTo(null, true); + break; + } + } + else + { + Interlocked.Decrement(ref this._transactionDepth); + } + + throw; + } + + return retVal; + } + + /// + /// Rolls back the transaction that was begun by or . + /// + public void Rollback() + { + this.RollbackTo(null, false); + } + + /// + /// Rolls back the savepoint created by or SaveTransactionPoint. + /// + /// The name of the savepoint to roll back to, as returned by . If savepoint is null or empty, this method is equivalent to a call to + public void RollbackTo(string savepoint) + { + this.RollbackTo(savepoint, false); + } + + /// + /// Rolls back the transaction that was begun by . + /// + /// The name of the savepoint to roll back to, as returned by . If savepoint is null or empty, this method is equivalent to a call to + /// true to avoid throwing exceptions, false otherwise + void RollbackTo(string savepoint, bool noThrow) + { + // Rolling back without a TO clause rolls backs all transactions + // and leaves the transaction stack empty. + try + { + if (String.IsNullOrEmpty(savepoint)) + { + if (Interlocked.Exchange(ref this._transactionDepth, 0) > 0) + { + this.Execute("rollback"); + } + } + else + { + this.DoSavePointExecute(savepoint, "rollback to "); + } + } + catch (SQLiteException) + { + if (!noThrow) + throw; + + } + // No need to rollback if there are no transactions open. + } + + /// + /// Releases a savepoint returned from . Releasing a savepoint + /// makes changes since that savepoint permanent if the savepoint began the transaction, + /// or otherwise the changes are permanent pending a call to . + /// + /// The RELEASE command is like a COMMIT for a SAVEPOINT. + /// + /// The name of the savepoint to release. The string should be the result of a call to + public void Release(string savepoint) + { + try + { + this.DoSavePointExecute(savepoint, "release "); + } + catch (SQLiteException ex) + { + if (ex.Result == SQLite3.Result.Busy) + { + // Force a rollback since most people don't know this function can fail + // Don't call Rollback() since the _transactionDepth is 0 and it won't try + // Calling rollback makes our _transactionDepth variable correct. + // Writes to the database only happen at depth=0, so this failure will only happen then. + try + { + this.Execute("rollback"); + } + catch + { + // rollback can fail in all sorts of wonderful version-dependent ways. Let's just hope for the best + } + } + throw; + } + } + + void DoSavePointExecute(string savepoint, string cmd) + { + // Validate the savepoint + int firstLen = savepoint.IndexOf('D'); + if (firstLen >= 2 && savepoint.Length > firstLen + 1) + { + int depth; + if (Int32.TryParse(savepoint.Substring(firstLen + 1), out depth)) + { + // TODO: Mild race here, but inescapable without locking almost everywhere. + if (0 <= depth && depth < this._transactionDepth) + { +#if NETFX_CORE || USE_SQLITEPCL_RAW || NETCORE + Volatile.Write (ref _transactionDepth, depth); +#elif SILVERLIGHT + _transactionDepth = depth; +#else + Thread.VolatileWrite(ref this._transactionDepth, depth); +#endif + this.Execute(cmd + savepoint); + return; + } + } + } + + throw new ArgumentException("savePoint is not valid, and should be the result of a call to SaveTransactionPoint.", "savePoint"); + } + + /// + /// Commits the transaction that was begun by . + /// + public void Commit() + { + if (Interlocked.Exchange(ref this._transactionDepth, 0) != 0) + { + try + { + this.Execute("commit"); + } + catch + { + // Force a rollback since most people don't know this function can fail + // Don't call Rollback() since the _transactionDepth is 0 and it won't try + // Calling rollback makes our _transactionDepth variable correct. + try + { + this.Execute("rollback"); + } + catch + { + // rollback can fail in all sorts of wonderful version-dependent ways. Let's just hope for the best + } + throw; + } + } + // Do nothing on a commit with no open transaction + } + + /// + /// Executes within a (possibly nested) transaction by wrapping it in a SAVEPOINT. If an + /// exception occurs the whole transaction is rolled back, not just the current savepoint. The exception + /// is rethrown. + /// + /// + /// The to perform within a transaction. can contain any number + /// of operations on the connection but should never call or + /// . + /// + public void RunInTransaction(Action action) + { + try + { + var savePoint = this.SaveTransactionPoint(); + action(); + this.Release(savePoint); + } + catch (Exception) + { + this.Rollback(); + throw; + } + } + + /// + /// Inserts all specified objects. + /// + /// + /// An of the objects to insert. + /// + /// A boolean indicating if the inserts should be wrapped in a transaction. + /// + /// + /// The number of rows added to the table. + /// + public int InsertAll(System.Collections.IEnumerable objects, bool runInTransaction = true) + { + var c = 0; + if (runInTransaction) + { + this.RunInTransaction(() => + { + foreach (var r in objects) + { + c += this.Insert(r); + } + }); + } + else + { + foreach (var r in objects) + { + c += this.Insert(r); + } + } + return c; + } + + /// + /// Inserts all specified objects. + /// + /// + /// An of the objects to insert. + /// + /// + /// Literal SQL code that gets placed into the command. INSERT {extra} INTO ... + /// + /// + /// A boolean indicating if the inserts should be wrapped in a transaction. + /// + /// + /// The number of rows added to the table. + /// + public int InsertAll(System.Collections.IEnumerable objects, string extra, bool runInTransaction = true) + { + var c = 0; + if (runInTransaction) + { + this.RunInTransaction(() => + { + foreach (var r in objects) + { + c += this.Insert(r, extra); + } + }); + } + else + { + foreach (var r in objects) + { + c += this.Insert(r); + } + } + return c; + } + + /// + /// Inserts all specified objects. + /// + /// + /// An of the objects to insert. + /// + /// + /// The type of object to insert. + /// + /// + /// A boolean indicating if the inserts should be wrapped in a transaction. + /// + /// + /// The number of rows added to the table. + /// + public int InsertAll(System.Collections.IEnumerable objects, Type objType, bool runInTransaction = true) + { + var c = 0; + if (runInTransaction) + { + this.RunInTransaction(() => + { + foreach (var r in objects) + { + c += this.Insert(r, objType); + } + }); + } + else + { + foreach (var r in objects) + { + c += this.Insert(r, objType); + } + } + return c; + } + + /// + /// Inserts the given object (and updates its + /// auto incremented primary key if it has one). + /// The return value is the number of rows added to the table. + /// + /// + /// The object to insert. + /// + /// + /// The number of rows added to the table. + /// + public int Insert(object obj) + { + if (obj == null) + { + return 0; + } + return this.Insert(obj, "", Orm.GetType(obj)); + } + + /// + /// Inserts the given object (and updates its + /// auto incremented primary key if it has one). + /// The return value is the number of rows added to the table. + /// If a UNIQUE constraint violation occurs with + /// some pre-existing object, this function deletes + /// the old object. + /// + /// + /// The object to insert. + /// + /// + /// The number of rows modified. + /// + public int InsertOrReplace(object obj) + { + if (obj == null) + { + return 0; + } + return this.Insert(obj, "OR REPLACE", Orm.GetType(obj)); + } + + /// + /// Inserts the given object (and updates its + /// auto incremented primary key if it has one). + /// The return value is the number of rows added to the table. + /// + /// + /// The object to insert. + /// + /// + /// The type of object to insert. + /// + /// + /// The number of rows added to the table. + /// + public int Insert(object obj, Type objType) + { + return this.Insert(obj, "", objType); + } + + /// + /// Inserts the given object (and updates its + /// auto incremented primary key if it has one). + /// The return value is the number of rows added to the table. + /// If a UNIQUE constraint violation occurs with + /// some pre-existing object, this function deletes + /// the old object. + /// + /// + /// The object to insert. + /// + /// + /// The type of object to insert. + /// + /// + /// The number of rows modified. + /// + public int InsertOrReplace(object obj, Type objType) + { + return this.Insert(obj, "OR REPLACE", objType); + } + + /// + /// Inserts the given object (and updates its + /// auto incremented primary key if it has one). + /// The return value is the number of rows added to the table. + /// + /// + /// The object to insert. + /// + /// + /// Literal SQL code that gets placed into the command. INSERT {extra} INTO ... + /// + /// + /// The number of rows added to the table. + /// + public int Insert(object obj, string extra) + { + if (obj == null) + { + return 0; + } + return this.Insert(obj, extra, Orm.GetType(obj)); + } + + /// + /// Inserts the given object (and updates its + /// auto incremented primary key if it has one). + /// The return value is the number of rows added to the table. + /// + /// + /// The object to insert. + /// + /// + /// Literal SQL code that gets placed into the command. INSERT {extra} INTO ... + /// + /// + /// The type of object to insert. + /// + /// + /// The number of rows added to the table. + /// + public int Insert(object obj, string extra, Type objType) + { + if (obj == null || objType == null) + { + return 0; + } + + var map = this.GetMapping(objType); + + if (map.PK != null && map.PK.IsAutoGuid) + { + if (map.PK.GetValue(obj).Equals(Guid.Empty)) + { + map.PK.SetValue(obj, Guid.NewGuid()); + } + } + + var replacing = string.Compare(extra, "OR REPLACE", StringComparison.OrdinalIgnoreCase) == 0; + + var cols = replacing ? map.InsertOrReplaceColumns : map.InsertColumns; + var vals = new object[cols.Length]; + for (var i = 0; i < vals.Length; i++) + { + vals[i] = cols[i].GetValue(obj); + } + + var insertCmd = this.GetInsertCommand(map, extra); + int count; + + lock (insertCmd) + { + // We lock here to protect the prepared statement returned via GetInsertCommand. + // A SQLite prepared statement can be bound for only one operation at a time. + try + { + count = insertCmd.ExecuteNonQuery(vals); + } + catch (SQLiteException ex) + { + if (SQLite3.ExtendedErrCode(this.Handle) == SQLite3.ExtendedResult.ConstraintNotNull) + { + throw NotNullConstraintViolationException.New(ex.Result, ex.Message, map, obj); + } + throw; + } + + if (map.HasAutoIncPK) + { + var id = SQLite3.LastInsertRowid(this.Handle); + map.SetAutoIncPK(obj, id); + } + } + if (count > 0) + this.OnTableChanged(map, NotifyTableChangedAction.Insert); + + return count; + } + + readonly Dictionary, PreparedSqlLiteInsertCommand> _insertCommandMap = new Dictionary, PreparedSqlLiteInsertCommand>(); + + PreparedSqlLiteInsertCommand GetInsertCommand(TableMapping map, string extra) + { + PreparedSqlLiteInsertCommand prepCmd; + + var key = Tuple.Create(map.MappedType.FullName, extra); + + lock (this._insertCommandMap) + { + if (this._insertCommandMap.TryGetValue(key, out prepCmd)) + { + return prepCmd; + } + } + + prepCmd = this.CreateInsertCommand(map, extra); + + lock (this._insertCommandMap) + { + if (this._insertCommandMap.TryGetValue(key, out var existing)) + { + prepCmd.Dispose(); + return existing; + } + + this._insertCommandMap.Add(key, prepCmd); + } + + return prepCmd; + } + + PreparedSqlLiteInsertCommand CreateInsertCommand(TableMapping map, string extra) + { + var cols = map.InsertColumns; + string insertSql; + if (cols.Length == 0 && map.Columns.Length == 1 && map.Columns[0].IsAutoInc) + { + insertSql = string.Format("insert {1} into \"{0}\" default values", map.TableName, extra); + } + else + { + var replacing = string.Compare(extra, "OR REPLACE", StringComparison.OrdinalIgnoreCase) == 0; + + if (replacing) + { + cols = map.InsertOrReplaceColumns; + } + + insertSql = string.Format("insert {3} into \"{0}\"({1}) values ({2})", map.TableName, + string.Join(",", (from c in cols + select "\"" + c.Name + "\"").ToArray()), + string.Join(",", (from c in cols + select "?").ToArray()), extra); + + } + + var insertCommand = new PreparedSqlLiteInsertCommand(this, insertSql); + return insertCommand; + } + + /// + /// Updates all of the columns of a table using the specified object + /// except for its primary key. + /// The object is required to have a primary key. + /// + /// + /// The object to update. It must have a primary key designated using the PrimaryKeyAttribute. + /// + /// + /// The number of rows updated. + /// + public int Update(object obj) + { + if (obj == null) + { + return 0; + } + return this.Update(obj, Orm.GetType(obj)); + } + + /// + /// Updates all of the columns of a table using the specified object + /// except for its primary key. + /// The object is required to have a primary key. + /// + /// + /// The object to update. It must have a primary key designated using the PrimaryKeyAttribute. + /// + /// + /// The type of object to insert. + /// + /// + /// The number of rows updated. + /// + public int Update(object obj, Type objType) + { + int rowsAffected = 0; + if (obj == null || objType == null) + { + return 0; + } + + var map = this.GetMapping(objType); + + var pk = map.PK; + + if (pk == null) + { + throw new NotSupportedException("Cannot update " + map.TableName + ": it has no PK"); + } + + var cols = from p in map.Columns + where p != pk + select p; + var vals = from c in cols + select c.GetValue(obj); + var ps = new List(vals); + if (ps.Count == 0) + { + // There is a PK but no accompanying data, + // so reset the PK to make the UPDATE work. + cols = map.Columns; + vals = from c in cols + select c.GetValue(obj); + ps = new List(vals); + } + ps.Add(pk.GetValue(obj)); + var q = string.Format("update \"{0}\" set {1} where {2} = ? ", map.TableName, string.Join(",", (from c in cols + select "\"" + c.Name + "\" = ? ").ToArray()), pk.Name); + + try + { + rowsAffected = this.Execute(q, ps.ToArray()); + } + catch (SQLiteException ex) + { + + if (ex.Result == SQLite3.Result.Constraint && SQLite3.ExtendedErrCode(this.Handle) == SQLite3.ExtendedResult.ConstraintNotNull) + { + throw NotNullConstraintViolationException.New(ex, map, obj); + } + + throw ex; + } + + if (rowsAffected > 0) + this.OnTableChanged(map, NotifyTableChangedAction.Update); + + return rowsAffected; + } + + /// + /// Updates all specified objects. + /// + /// + /// An of the objects to insert. + /// + /// + /// A boolean indicating if the inserts should be wrapped in a transaction + /// + /// + /// The number of rows modified. + /// + public int UpdateAll(System.Collections.IEnumerable objects, bool runInTransaction = true) + { + var c = 0; + if (runInTransaction) + { + this.RunInTransaction(() => + { + foreach (var r in objects) + { + c += this.Update(r); + } + }); + } + else + { + foreach (var r in objects) + { + c += this.Update(r); + } + } + return c; + } + + /// + /// Deletes the given object from the database using its primary key. + /// + /// + /// The object to delete. It must have a primary key designated using the PrimaryKeyAttribute. + /// + /// + /// The number of rows deleted. + /// + public int Delete(object objectToDelete) + { + var map = this.GetMapping(Orm.GetType(objectToDelete)); + var pk = map.PK; + if (pk == null) + { + throw new NotSupportedException("Cannot delete " + map.TableName + ": it has no PK"); + } + var q = string.Format("delete from \"{0}\" where \"{1}\" = ?", map.TableName, pk.Name); + var count = this.Execute(q, pk.GetValue(objectToDelete)); + if (count > 0) + this.OnTableChanged(map, NotifyTableChangedAction.Delete); + return count; + } + + /// + /// Deletes the object with the specified primary key. + /// + /// + /// The primary key of the object to delete. + /// + /// + /// The number of objects deleted. + /// + /// + /// The type of object. + /// + public int Delete(object primaryKey) + { + return this.Delete(primaryKey, this.GetMapping(typeof(T))); + } + + /// + /// Deletes the object with the specified primary key. + /// + /// + /// The primary key of the object to delete. + /// + /// + /// The TableMapping used to identify the table. + /// + /// + /// The number of objects deleted. + /// + public int Delete(object primaryKey, TableMapping map) + { + var pk = map.PK; + if (pk == null) + { + throw new NotSupportedException("Cannot delete " + map.TableName + ": it has no PK"); + } + var q = string.Format("delete from \"{0}\" where \"{1}\" = ?", map.TableName, pk.Name); + var count = this.Execute(q, primaryKey); + if (count > 0) + this.OnTableChanged(map, NotifyTableChangedAction.Delete); + return count; + } + + /// + /// Deletes all the objects from the specified table. + /// WARNING WARNING: Let me repeat. It deletes ALL the objects from the + /// specified table. Do you really want to do that? + /// + /// + /// The number of objects deleted. + /// + /// + /// The type of objects to delete. + /// + public int DeleteAll() + { + var map = this.GetMapping(typeof(T)); + return this.DeleteAll(map); + } + + /// + /// Deletes all the objects from the specified table. + /// WARNING WARNING: Let me repeat. It deletes ALL the objects from the + /// specified table. Do you really want to do that? + /// + /// + /// The TableMapping used to identify the table. + /// + /// + /// The number of objects deleted. + /// + public int DeleteAll(TableMapping map) + { + var query = string.Format("delete from \"{0}\"", map.TableName); + var count = this.Execute(query); + if (count > 0) + this.OnTableChanged(map, NotifyTableChangedAction.Delete); + return count; + } + + /// + /// Backup the entire database to the specified path. + /// + /// Path to backup file. + /// The name of the database to backup (usually "main"). + public void Backup(string destinationDatabasePath, string databaseName = "main") + { + // Open the destination + var r = SQLite3.Open(destinationDatabasePath, out var destHandle); + if (r != SQLite3.Result.OK) + { + throw SQLiteException.New(r, "Failed to open destination database"); + } + + // Init the backup + var backup = SQLite3.BackupInit(destHandle, databaseName, this.Handle, databaseName); + if (backup == NullBackupHandle) + { + SQLite3.Close(destHandle); + throw new Exception("Failed to create backup"); + } + + // Perform it + SQLite3.BackupStep(backup, -1); + SQLite3.BackupFinish(backup); + + // Check for errors + r = SQLite3.GetResult(destHandle); + string msg = ""; + if (r != SQLite3.Result.OK) + { + msg = SQLite3.GetErrmsg(destHandle); + } + + // Close everything and report errors + SQLite3.Close(destHandle); + if (r != SQLite3.Result.OK) + { + throw SQLiteException.New(r, msg); + } + } + + ~SQLiteConnection() + { + this.Dispose(false); + } + + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + public void Close() + { + this.Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + var useClose2 = this.LibVersionNumber >= 3007014; + + if (this._open && this.Handle != NullHandle) + { + try + { + if (disposing) + { + lock (this._insertCommandMap) + { + foreach (var sqlInsertCommand in this._insertCommandMap.Values) + { + sqlInsertCommand.Dispose(); + } + this._insertCommandMap.Clear(); + } + + var r = useClose2 ? SQLite3.Close2(this.Handle) : SQLite3.Close(this.Handle); + if (r != SQLite3.Result.OK) + { + string msg = SQLite3.GetErrmsg(this.Handle); + throw SQLiteException.New(r, msg); + } + } + else + { + var r = useClose2 ? SQLite3.Close2(this.Handle) : SQLite3.Close(this.Handle); + } + } + finally + { + this.Handle = NullHandle; + this._open = false; + } + } + } + + void OnTableChanged(TableMapping table, NotifyTableChangedAction action) + { + var ev = TableChanged; + if (ev != null) + ev(this, new NotifyTableChangedEventArgs(table, action)); + } + + public event EventHandler TableChanged; + } + + public class NotifyTableChangedEventArgs : EventArgs + { + public TableMapping Table { get; private set; } + public NotifyTableChangedAction Action { get; private set; } + + public NotifyTableChangedEventArgs(TableMapping table, NotifyTableChangedAction action) + { + this.Table = table; + this.Action = action; + } + } + + public enum NotifyTableChangedAction + { + Insert, + Update, + Delete, + } + + /// + /// Represents a parsed connection string. + /// + public class SQLiteConnectionString + { + const string DateTimeSqliteDefaultFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff"; + + public string UniqueKey { get; } + public string DatabasePath { get; } + public bool StoreDateTimeAsTicks { get; } + public bool StoreTimeSpanAsTicks { get; } + public string DateTimeStringFormat { get; } + public System.Globalization.DateTimeStyles DateTimeStyle { get; } + public object Key { get; } + public SQLiteOpenFlags OpenFlags { get; } + public Action PreKeyAction { get; } + public Action PostKeyAction { get; } + public string VfsName { get; } + +#if NETFX_CORE + static readonly string MetroStyleDataPath = Windows.Storage.ApplicationData.Current.LocalFolder.Path; + + public static readonly string[] InMemoryDbPaths = new[] + { + ":memory:", + "file::memory:" + }; + + public static bool IsInMemoryPath(string databasePath) + { + return InMemoryDbPaths.Any(i => i.Equals(databasePath, StringComparison.OrdinalIgnoreCase)); + } + +#endif + + /// + /// Constructs a new SQLiteConnectionString with all the data needed to open an SQLiteConnection. + /// + /// + /// Specifies the path to the database file. + /// + /// + /// Specifies whether to store DateTime properties as ticks (true) or strings (false). You + /// absolutely do want to store them as Ticks in all new projects. The value of false is + /// only here for backwards compatibility. There is a *significant* speed advantage, with no + /// down sides, when setting storeDateTimeAsTicks = true. + /// If you use DateTimeOffset properties, it will be always stored as ticks regardingless + /// the storeDateTimeAsTicks parameter. + /// + public SQLiteConnectionString(string databasePath, bool storeDateTimeAsTicks = true) + : this(databasePath, SQLiteOpenFlags.Create | SQLiteOpenFlags.ReadWrite, storeDateTimeAsTicks) + { + } + + /// + /// Constructs a new SQLiteConnectionString with all the data needed to open an SQLiteConnection. + /// + /// + /// Specifies the path to the database file. + /// + /// + /// Specifies whether to store DateTime properties as ticks (true) or strings (false). You + /// absolutely do want to store them as Ticks in all new projects. The value of false is + /// only here for backwards compatibility. There is a *significant* speed advantage, with no + /// down sides, when setting storeDateTimeAsTicks = true. + /// If you use DateTimeOffset properties, it will be always stored as ticks regardingless + /// the storeDateTimeAsTicks parameter. + /// + /// + /// Specifies the encryption key to use on the database. Should be a string or a byte[]. + /// + /// + /// Executes prior to setting key for SQLCipher databases + /// + /// + /// Executes after setting key for SQLCipher databases + /// + /// + /// Specifies the Virtual File System to use on the database. + /// + public SQLiteConnectionString(string databasePath, bool storeDateTimeAsTicks, object key = null, Action preKeyAction = null, Action postKeyAction = null, string vfsName = null) + : this(databasePath, SQLiteOpenFlags.Create | SQLiteOpenFlags.ReadWrite, storeDateTimeAsTicks, key, preKeyAction, postKeyAction, vfsName) + { + } + + /// + /// Constructs a new SQLiteConnectionString with all the data needed to open an SQLiteConnection. + /// + /// + /// Specifies the path to the database file. + /// + /// + /// Flags controlling how the connection should be opened. + /// + /// + /// Specifies whether to store DateTime properties as ticks (true) or strings (false). You + /// absolutely do want to store them as Ticks in all new projects. The value of false is + /// only here for backwards compatibility. There is a *significant* speed advantage, with no + /// down sides, when setting storeDateTimeAsTicks = true. + /// If you use DateTimeOffset properties, it will be always stored as ticks regardingless + /// the storeDateTimeAsTicks parameter. + /// + /// + /// Specifies the encryption key to use on the database. Should be a string or a byte[]. + /// + /// + /// Executes prior to setting key for SQLCipher databases + /// + /// + /// Executes after setting key for SQLCipher databases + /// + /// + /// Specifies the Virtual File System to use on the database. + /// + /// + /// Specifies the format to use when storing DateTime properties as strings. + /// + /// + /// Specifies whether to store TimeSpan properties as ticks (true) or strings (false). You + /// absolutely do want to store them as Ticks in all new projects. The value of false is + /// only here for backwards compatibility. There is a *significant* speed advantage, with no + /// down sides, when setting storeTimeSpanAsTicks = true. + /// + public SQLiteConnectionString(string databasePath, SQLiteOpenFlags openFlags, bool storeDateTimeAsTicks, object key = null, Action preKeyAction = null, Action postKeyAction = null, string vfsName = null, string dateTimeStringFormat = DateTimeSqliteDefaultFormat, bool storeTimeSpanAsTicks = true) + { + if (key != null && !((key is byte[]) || (key is string))) + throw new ArgumentException("Encryption keys must be strings or byte arrays", nameof(key)); + + this.UniqueKey = string.Format("{0}_{1:X8}", databasePath, (uint)openFlags); + this.StoreDateTimeAsTicks = storeDateTimeAsTicks; + this.StoreTimeSpanAsTicks = storeTimeSpanAsTicks; + this.DateTimeStringFormat = dateTimeStringFormat; + this.DateTimeStyle = "o".Equals(this.DateTimeStringFormat, StringComparison.OrdinalIgnoreCase) || "r".Equals(this.DateTimeStringFormat, StringComparison.OrdinalIgnoreCase) ? System.Globalization.DateTimeStyles.RoundtripKind : System.Globalization.DateTimeStyles.None; + this.Key = key; + this.PreKeyAction = preKeyAction; + this.PostKeyAction = postKeyAction; + this.OpenFlags = openFlags; + this.VfsName = vfsName; + +#if NETFX_CORE + DatabasePath = IsInMemoryPath(databasePath) + ? databasePath + : System.IO.Path.Combine(MetroStyleDataPath, databasePath); + +#else + this.DatabasePath = databasePath; +#endif + } + } + + [AttributeUsage(AttributeTargets.Class)] + public class TableAttribute : Attribute + { + public string Name { get; set; } + + /// + /// Flag whether to create the table without rowid (see https://sqlite.org/withoutrowid.html) + /// + /// The default is false so that sqlite adds an implicit rowid to every table created. + /// + public bool WithoutRowId { get; set; } + + public TableAttribute(string name) + { + this.Name = name; + } + } + + [AttributeUsage(AttributeTargets.Property)] + public class ColumnAttribute : Attribute + { + public string Name { get; set; } + + public ColumnAttribute(string name) + { + this.Name = name; + } + } + + [AttributeUsage(AttributeTargets.Property)] + public class PrimaryKeyAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Property)] + public class AutoIncrementAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Property)] + public class IndexedAttribute : Attribute + { + public string Name { get; set; } + public int Order { get; set; } + public virtual bool Unique { get; set; } + + public IndexedAttribute() + { + } + + public IndexedAttribute(string name, int order) + { + this.Name = name; + this.Order = order; + } + } + + [AttributeUsage(AttributeTargets.Property)] + public class IgnoreAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Property)] + public class UniqueAttribute : IndexedAttribute + { + public override bool Unique + { + get { return true; } + set { /* throw? */ } + } + } + + [AttributeUsage(AttributeTargets.Property)] + public class MaxLengthAttribute : Attribute + { + public int Value { get; private set; } + + public MaxLengthAttribute(int length) + { + this.Value = length; + } + } + + public sealed class PreserveAttribute : System.Attribute + { + public bool AllMembers; + public bool Conditional; + } + + /// + /// Select the collating sequence to use on a column. + /// "BINARY", "NOCASE", and "RTRIM" are supported. + /// "BINARY" is the default. + /// + [AttributeUsage(AttributeTargets.Property)] + public class CollationAttribute : Attribute + { + public string Value { get; private set; } + + public CollationAttribute(string collation) + { + this.Value = collation; + } + } + + [AttributeUsage(AttributeTargets.Property)] + public class NotNullAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Enum)] + public class StoreAsTextAttribute : Attribute + { + } + + public class TableMapping + { + public Type MappedType { get; private set; } + + public string TableName { get; private set; } + + public bool WithoutRowId { get; private set; } + + public Column[] Columns { get; private set; } + + public Column PK { get; private set; } + + public string GetByPrimaryKeySql { get; private set; } + + public CreateFlags CreateFlags { get; private set; } + + readonly Column _autoPk; + readonly Column[] _insertColumns; + readonly Column[] _insertOrReplaceColumns; + + public TableMapping(Type type, CreateFlags createFlags = CreateFlags.None) + { + this.MappedType = type; + this.CreateFlags = createFlags; + + var typeInfo = type.GetTypeInfo(); + var tableAttr = + typeInfo.CustomAttributes + .Where(x => x.AttributeType == typeof(TableAttribute)) + .Select(x => (TableAttribute)Orm.InflateAttribute(x)) + .FirstOrDefault(); + + this.TableName = (tableAttr != null && !string.IsNullOrEmpty(tableAttr.Name)) ? tableAttr.Name : this.MappedType.Name; + this.WithoutRowId = tableAttr != null ? tableAttr.WithoutRowId : false; + + var props = new List(); + var baseType = type; + var propNames = new HashSet(); + while (baseType != typeof(object)) + { + var ti = baseType.GetTypeInfo(); + var newProps = ( + from p in ti.DeclaredProperties + where + !propNames.Contains(p.Name) && + p.CanRead && p.CanWrite && + (p.GetMethod != null) && (p.SetMethod != null) && + (p.GetMethod.IsPublic && p.SetMethod.IsPublic) && + (!p.GetMethod.IsStatic) && (!p.SetMethod.IsStatic) + select p).ToList(); + foreach (var p in newProps) + { + propNames.Add(p.Name); + } + props.AddRange(newProps); + baseType = ti.BaseType; + } + + var cols = new List(); + foreach (var p in props) + { + var ignore = p.IsDefined(typeof(IgnoreAttribute), true); + if (!ignore) + { + cols.Add(new Column(p, createFlags)); + } + } + this.Columns = cols.ToArray(); + foreach (var c in this.Columns) + { + if (c.IsAutoInc && c.IsPK) + { + this._autoPk = c; + } + if (c.IsPK) + { + this.PK = c; + } + } + + this.HasAutoIncPK = this._autoPk != null; + + if (this.PK != null) + { + this.GetByPrimaryKeySql = string.Format("select * from \"{0}\" where \"{1}\" = ?", this.TableName, this.PK.Name); + } + else + { + // People should not be calling Get/Find without a PK + this.GetByPrimaryKeySql = string.Format("select * from \"{0}\" limit 1", this.TableName); + } + + this._insertColumns = this.Columns.Where(c => !c.IsAutoInc).ToArray(); + this._insertOrReplaceColumns = this.Columns.ToArray(); + } + + public bool HasAutoIncPK { get; private set; } + + public void SetAutoIncPK(object obj, long id) + { + if (this._autoPk != null) + { + this._autoPk.SetValue(obj, Convert.ChangeType(id, this._autoPk.ColumnType, null)); + } + } + + public Column[] InsertColumns + { + get + { + return this._insertColumns; + } + } + + public Column[] InsertOrReplaceColumns + { + get + { + return this._insertOrReplaceColumns; + } + } + + public Column FindColumnWithPropertyName(string propertyName) + { + var exact = this.Columns.FirstOrDefault(c => c.PropertyName == propertyName); + return exact; + } + + public Column FindColumn(string columnName) + { + var exact = this.Columns.FirstOrDefault(c => c.Name.ToLower() == columnName.ToLower()); + return exact; + } + + public class Column + { + PropertyInfo _prop; + + public string Name { get; private set; } + + public PropertyInfo PropertyInfo => this._prop; + + public string PropertyName { get { return this._prop.Name; } } + + public Type ColumnType { get; private set; } + + public string Collation { get; private set; } + + public bool IsAutoInc { get; private set; } + public bool IsAutoGuid { get; private set; } + + public bool IsPK { get; private set; } + + public IEnumerable Indices { get; set; } + + public bool IsNullable { get; private set; } + + public int? MaxStringLength { get; private set; } + + public bool StoreAsText { get; private set; } + + public Column(PropertyInfo prop, CreateFlags createFlags = CreateFlags.None) + { + var colAttr = prop.CustomAttributes.FirstOrDefault(x => x.AttributeType == typeof(ColumnAttribute)); + + this._prop = prop; + this.Name = (colAttr != null && colAttr.ConstructorArguments.Count > 0) ? + colAttr.ConstructorArguments[0].Value?.ToString() : + prop.Name; + //If this type is Nullable then Nullable.GetUnderlyingType returns the T, otherwise it returns null, so get the actual type instead + this.ColumnType = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType; + this.Collation = Orm.Collation(prop); + + this.IsPK = Orm.IsPK(prop) || + (((createFlags & CreateFlags.ImplicitPK) == CreateFlags.ImplicitPK) && + string.Compare(prop.Name, Orm.ImplicitPkName, StringComparison.OrdinalIgnoreCase) == 0); + + var isAuto = Orm.IsAutoInc(prop) || (this.IsPK && ((createFlags & CreateFlags.AutoIncPK) == CreateFlags.AutoIncPK)); + this.IsAutoGuid = isAuto && this.ColumnType == typeof(Guid); + this.IsAutoInc = isAuto && !this.IsAutoGuid; + + this.Indices = Orm.GetIndices(prop); + if (!this.Indices.Any() + && !this.IsPK + && ((createFlags & CreateFlags.ImplicitIndex) == CreateFlags.ImplicitIndex) + && this.Name.EndsWith(Orm.ImplicitIndexSuffix, StringComparison.OrdinalIgnoreCase) + ) + { + this.Indices = new IndexedAttribute[] { new IndexedAttribute() }; + } + this.IsNullable = !(this.IsPK || Orm.IsMarkedNotNull(prop)); + this.MaxStringLength = Orm.MaxStringLength(prop); + + this.StoreAsText = prop.PropertyType.GetTypeInfo().CustomAttributes.Any(x => x.AttributeType == typeof(StoreAsTextAttribute)); + } + + public void SetValue(object obj, object val) + { + if (val != null && this.ColumnType.GetTypeInfo().IsEnum) + { + this._prop.SetValue(obj, Enum.ToObject(this.ColumnType, val)); + } + else + { + this._prop.SetValue(obj, val, null); + } + } + + public object GetValue(object obj) + { + return this._prop.GetValue(obj, null); + } + } + } + + class EnumCacheInfo + { + public EnumCacheInfo(Type type) + { + var typeInfo = type.GetTypeInfo(); + + this.IsEnum = typeInfo.IsEnum; + + if (this.IsEnum) + { + this.StoreAsText = typeInfo.CustomAttributes.Any(x => x.AttributeType == typeof(StoreAsTextAttribute)); + + if (this.StoreAsText) + { + this.EnumValues = new Dictionary(); + foreach (object e in Enum.GetValues(type)) + { + this.EnumValues[Convert.ToInt32(e)] = e.ToString(); + } + } + } + } + + public bool IsEnum { get; private set; } + + public bool StoreAsText { get; private set; } + + public Dictionary EnumValues { get; private set; } + } + + static class EnumCache + { + static readonly Dictionary Cache = new Dictionary(); + + public static EnumCacheInfo GetInfo() + { + return GetInfo(typeof(T)); + } + + public static EnumCacheInfo GetInfo(Type type) + { + lock (Cache) + { + EnumCacheInfo info = null; + if (!Cache.TryGetValue(type, out info)) + { + info = new EnumCacheInfo(type); + Cache[type] = info; + } + + return info; + } + } + } + + public static class Orm + { + public const int DefaultMaxStringLength = 140; + public const string ImplicitPkName = "Id"; + public const string ImplicitIndexSuffix = "Id"; + + public static Type GetType(object obj) + { + if (obj == null) + return typeof(object); + var rt = obj as IReflectableType; + if (rt != null) + return rt.GetTypeInfo().AsType(); + return obj.GetType(); + } + + public static string SqlDecl(TableMapping.Column p, bool storeDateTimeAsTicks, bool storeTimeSpanAsTicks) + { + string decl = "\"" + p.Name + "\" " + SqlType(p, storeDateTimeAsTicks, storeTimeSpanAsTicks) + " "; + + if (p.IsPK) + { + decl += "primary key "; + } + if (p.IsAutoInc) + { + decl += "autoincrement "; + } + if (!p.IsNullable) + { + decl += "not null "; + } + if (!string.IsNullOrEmpty(p.Collation)) + { + decl += "collate " + p.Collation + " "; + } + + return decl; + } + + public static string SqlType(TableMapping.Column p, bool storeDateTimeAsTicks, bool storeTimeSpanAsTicks) + { + var clrType = p.ColumnType; + if (clrType == typeof(Boolean) || clrType == typeof(Byte) || clrType == typeof(UInt16) || clrType == typeof(SByte) || clrType == typeof(Int16) || clrType == typeof(Int32) || clrType == typeof(UInt32) || clrType == typeof(Int64)) + { + return "integer"; + } + else if (clrType == typeof(Single) || clrType == typeof(Double) || clrType == typeof(Decimal)) + { + return "float"; + } + else if (clrType == typeof(String) || clrType == typeof(StringBuilder) || clrType == typeof(Uri) || clrType == typeof(UriBuilder)) + { + int? len = p.MaxStringLength; + + if (len.HasValue) + return "varchar(" + len.Value + ")"; + + return "varchar"; + } + else if (clrType == typeof(TimeSpan)) + { + return storeTimeSpanAsTicks ? "bigint" : "time"; + } + else if (clrType == typeof(DateTime)) + { + return storeDateTimeAsTicks ? "bigint" : "datetime"; + } + else if (clrType == typeof(DateTimeOffset)) + { + return "bigint"; + } + else if (clrType.GetTypeInfo().IsEnum) + { + if (p.StoreAsText) + return "varchar"; + else + return "integer"; + } + else if (clrType == typeof(byte[])) + { + return "blob"; + } + else if (clrType == typeof(Guid)) + { + return "varchar(36)"; + } + else + { + throw new NotSupportedException("Don't know about " + clrType); + } + } + + public static bool IsPK(MemberInfo p) + { + return p.CustomAttributes.Any(x => x.AttributeType == typeof(PrimaryKeyAttribute)); + } + + public static string Collation(MemberInfo p) + { + return + (p.CustomAttributes + .Where(x => typeof(CollationAttribute) == x.AttributeType) + .Select(x => + { + var args = x.ConstructorArguments; + return args.Count > 0 ? ((args[0].Value as string) ?? "") : ""; + }) + .FirstOrDefault()) ?? ""; + } + + public static bool IsAutoInc(MemberInfo p) + { + return p.CustomAttributes.Any(x => x.AttributeType == typeof(AutoIncrementAttribute)); + } + + public static FieldInfo GetField(TypeInfo t, string name) + { + var f = t.GetDeclaredField(name); + if (f != null) + return f; + return GetField(t.BaseType.GetTypeInfo(), name); + } + + public static PropertyInfo GetProperty(TypeInfo t, string name) + { + var f = t.GetDeclaredProperty(name); + if (f != null) + return f; + return GetProperty(t.BaseType.GetTypeInfo(), name); + } + + public static object InflateAttribute(CustomAttributeData x) + { + var atype = x.AttributeType; + var typeInfo = atype.GetTypeInfo(); + var args = x.ConstructorArguments.Select(a => a.Value).ToArray(); + var r = Activator.CreateInstance(x.AttributeType, args); + foreach (var arg in x.NamedArguments) + { + if (arg.IsField) + { + GetField(typeInfo, arg.MemberName).SetValue(r, arg.TypedValue.Value); + } + else + { + GetProperty(typeInfo, arg.MemberName).SetValue(r, arg.TypedValue.Value); + } + } + return r; + } + + public static IEnumerable GetIndices(MemberInfo p) + { + var indexedInfo = typeof(IndexedAttribute).GetTypeInfo(); + return + p.CustomAttributes + .Where(x => indexedInfo.IsAssignableFrom(x.AttributeType.GetTypeInfo())) + .Select(x => (IndexedAttribute)InflateAttribute(x)); + } + + public static int? MaxStringLength(PropertyInfo p) + { + var attr = p.CustomAttributes.FirstOrDefault(x => x.AttributeType == typeof(MaxLengthAttribute)); + if (attr != null) + { + var attrv = (MaxLengthAttribute)InflateAttribute(attr); + return attrv.Value; + } + return null; + } + + public static bool IsMarkedNotNull(MemberInfo p) + { + return p.CustomAttributes.Any(x => x.AttributeType == typeof(NotNullAttribute)); + } + } + + public partial class SQLiteCommand + { + SQLiteConnection _conn; + private List _bindings; + + public string CommandText { get; set; } + + public SQLiteCommand(SQLiteConnection conn) + { + this._conn = conn; + this._bindings = new List(); + this.CommandText = ""; + } + + public int ExecuteNonQuery() + { + if (this._conn.Trace) + { + this._conn.Tracer?.Invoke("Executing: " + this); + } + + var r = SQLite3.Result.OK; + var stmt = this.Prepare(); + r = SQLite3.Step(stmt); + this.Finalize(stmt); + if (r == SQLite3.Result.Done) + { + int rowsAffected = SQLite3.Changes(this._conn.Handle); + return rowsAffected; + } + else if (r == SQLite3.Result.Error) + { + string msg = SQLite3.GetErrmsg(this._conn.Handle); + throw SQLiteException.New(r, msg); + } + else if (r == SQLite3.Result.Constraint) + { + if (SQLite3.ExtendedErrCode(this._conn.Handle) == SQLite3.ExtendedResult.ConstraintNotNull) + { + throw NotNullConstraintViolationException.New(r, SQLite3.GetErrmsg(this._conn.Handle)); + } + } + + throw SQLiteException.New(r, SQLite3.GetErrmsg(this._conn.Handle)); + } + + public IEnumerable ExecuteDeferredQuery() + { + return this.ExecuteDeferredQuery(this._conn.GetMapping(typeof(T))); + } + + public List ExecuteQuery() + { + return this.ExecuteDeferredQuery(this._conn.GetMapping(typeof(T))).ToList(); + } + + public List ExecuteQuery(TableMapping map) + { + return this.ExecuteDeferredQuery(map).ToList(); + } + + /// + /// Invoked every time an instance is loaded from the database. + /// + /// + /// The newly created object. + /// + /// + /// This can be overridden in combination with the + /// method to hook into the life-cycle of objects. + /// + protected virtual void OnInstanceCreated(object obj) + { + // Can be overridden. + } + + public IEnumerable ExecuteDeferredQuery(TableMapping map) + { + if (this._conn.Trace) + { + this._conn.Tracer?.Invoke("Executing Query: " + this); + } + + var stmt = this.Prepare(); + try + { + var cols = new TableMapping.Column[SQLite3.ColumnCount(stmt)]; + + for (int i = 0; i < cols.Length; i++) + { + var name = SQLite3.ColumnName16(stmt, i); + cols[i] = map.FindColumn(name); + } + + while (SQLite3.Step(stmt) == SQLite3.Result.Row) + { + var obj = Activator.CreateInstance(map.MappedType); + for (int i = 0; i < cols.Length; i++) + { + if (cols[i] == null) + continue; + var colType = SQLite3.ColumnType(stmt, i); + var val = this.ReadCol(stmt, i, colType, cols[i].ColumnType); + cols[i].SetValue(obj, val); + } + this.OnInstanceCreated(obj); + yield return (T)obj; + } + } + finally + { + SQLite3.Finalize(stmt); + } + } + + public T ExecuteScalar() + { + if (this._conn.Trace) + { + this._conn.Tracer?.Invoke("Executing Query: " + this); + } + + T val = default(T); + + var stmt = this.Prepare(); + + try + { + var r = SQLite3.Step(stmt); + if (r == SQLite3.Result.Row) + { + var colType = SQLite3.ColumnType(stmt, 0); + var colval = this.ReadCol(stmt, 0, colType, typeof(T)); + if (colval != null) + { + val = (T)colval; + } + } + else if (r == SQLite3.Result.Done) + { + } + else + { + throw SQLiteException.New(r, SQLite3.GetErrmsg(this._conn.Handle)); + } + } + finally + { + this.Finalize(stmt); + } + + return val; + } + + public IEnumerable ExecuteQueryScalars() + { + if (this._conn.Trace) + { + this._conn.Tracer?.Invoke("Executing Query: " + this); + } + var stmt = this.Prepare(); + try + { + if (SQLite3.ColumnCount(stmt) < 1) + { + throw new InvalidOperationException("QueryScalars should return at least one column"); + } + while (SQLite3.Step(stmt) == SQLite3.Result.Row) + { + var colType = SQLite3.ColumnType(stmt, 0); + var val = this.ReadCol(stmt, 0, colType, typeof(T)); + if (val == null) + { + yield return default(T); + } + else + { + yield return (T)val; + } + } + } + finally + { + this.Finalize(stmt); + } + } + + public void Bind(string name, object val) + { + this._bindings.Add(new Binding + { + Name = name, + Value = val + }); + } + + public void Bind(object val) + { + this.Bind(null, val); + } + + public override string ToString() + { + var parts = new string[1 + this._bindings.Count]; + parts[0] = this.CommandText; + var i = 1; + foreach (var b in this._bindings) + { + parts[i] = string.Format(" {0}: {1}", i - 1, b.Value); + i++; + } + return string.Join(Environment.NewLine, parts); + } + + Sqlite3Statement Prepare() + { + var stmt = SQLite3.Prepare2(this._conn.Handle, this.CommandText); + this.BindAll(stmt); + return stmt; + } + + void Finalize(Sqlite3Statement stmt) + { + SQLite3.Finalize(stmt); + } + + void BindAll(Sqlite3Statement stmt) + { + int nextIdx = 1; + foreach (var b in this._bindings) + { + if (b.Name != null) + { + b.Index = SQLite3.BindParameterIndex(stmt, b.Name); + } + else + { + b.Index = nextIdx++; + } + + BindParameter(stmt, b.Index, b.Value, this._conn.StoreDateTimeAsTicks, this._conn.DateTimeStringFormat, this._conn.StoreTimeSpanAsTicks); + } + } + + static IntPtr NegativePointer = new IntPtr(-1); + + internal static void BindParameter(Sqlite3Statement stmt, int index, object value, bool storeDateTimeAsTicks, string dateTimeStringFormat, bool storeTimeSpanAsTicks) + { + if (value == null) + { + SQLite3.BindNull(stmt, index); + } + else + { + if (value is Int32) + { + SQLite3.BindInt(stmt, index, (int)value); + } + else if (value is String) + { + SQLite3.BindText(stmt, index, (string)value, -1, NegativePointer); + } + else if (value is Byte || value is UInt16 || value is SByte || value is Int16) + { + SQLite3.BindInt(stmt, index, Convert.ToInt32(value)); + } + else if (value is Boolean) + { + SQLite3.BindInt(stmt, index, (bool)value ? 1 : 0); + } + else if (value is UInt32 || value is Int64) + { + SQLite3.BindInt64(stmt, index, Convert.ToInt64(value)); + } + else if (value is Single || value is Double || value is Decimal) + { + SQLite3.BindDouble(stmt, index, Convert.ToDouble(value)); + } + else if (value is TimeSpan) + { + if (storeTimeSpanAsTicks) + { + SQLite3.BindInt64(stmt, index, ((TimeSpan)value).Ticks); + } + else + { + SQLite3.BindText(stmt, index, ((TimeSpan)value).ToString(), -1, NegativePointer); + } + } + else if (value is DateTime) + { + if (storeDateTimeAsTicks) + { + SQLite3.BindInt64(stmt, index, ((DateTime)value).Ticks); + } + else + { + SQLite3.BindText(stmt, index, ((DateTime)value).ToString(dateTimeStringFormat, System.Globalization.CultureInfo.InvariantCulture), -1, NegativePointer); + } + } + else if (value is DateTimeOffset) + { + SQLite3.BindInt64(stmt, index, ((DateTimeOffset)value).UtcTicks); + } + else if (value is byte[]) + { + SQLite3.BindBlob(stmt, index, (byte[])value, ((byte[])value).Length, NegativePointer); + } + else if (value is Guid) + { + SQLite3.BindText(stmt, index, ((Guid)value).ToString(), 72, NegativePointer); + } + else if (value is Uri) + { + SQLite3.BindText(stmt, index, ((Uri)value).ToString(), -1, NegativePointer); + } + else if (value is StringBuilder) + { + SQLite3.BindText(stmt, index, ((StringBuilder)value).ToString(), -1, NegativePointer); + } + else if (value is UriBuilder) + { + SQLite3.BindText(stmt, index, ((UriBuilder)value).ToString(), -1, NegativePointer); + } + else + { + // Now we could possibly get an enum, retrieve cached info + var valueType = value.GetType(); + var enumInfo = EnumCache.GetInfo(valueType); + if (enumInfo.IsEnum) + { + var enumIntValue = Convert.ToInt32(value); + if (enumInfo.StoreAsText) + SQLite3.BindText(stmt, index, enumInfo.EnumValues[enumIntValue], -1, NegativePointer); + else + SQLite3.BindInt(stmt, index, enumIntValue); + } + else + { + throw new NotSupportedException("Cannot store type: " + Orm.GetType(value)); + } + } + } + } + + class Binding + { + public string Name { get; set; } + + public object Value { get; set; } + + public int Index { get; set; } + } + + object ReadCol(Sqlite3Statement stmt, int index, SQLite3.ColType type, Type clrType) + { + if (type == SQLite3.ColType.Null) + { + return null; + } + else + { + var clrTypeInfo = clrType.GetTypeInfo(); + if (clrTypeInfo.IsGenericType && clrTypeInfo.GetGenericTypeDefinition() == typeof(Nullable<>)) + { + clrType = clrTypeInfo.GenericTypeArguments[0]; + clrTypeInfo = clrType.GetTypeInfo(); + } + + if (clrType == typeof(String)) + { + return SQLite3.ColumnString(stmt, index); + } + else if (clrType == typeof(Int32)) + { + return SQLite3.ColumnInt(stmt, index); + } + else if (clrType == typeof(Boolean)) + { + return SQLite3.ColumnInt(stmt, index) == 1; + } + else if (clrType == typeof(double)) + { + return SQLite3.ColumnDouble(stmt, index); + } + else if (clrType == typeof(float)) + { + return (float)SQLite3.ColumnDouble(stmt, index); + } + else if (clrType == typeof(TimeSpan)) + { + if (this._conn.StoreTimeSpanAsTicks) + { + return new TimeSpan(SQLite3.ColumnInt64(stmt, index)); + } + else + { + var text = SQLite3.ColumnString(stmt, index); + TimeSpan resultTime; + if (!TimeSpan.TryParseExact(text, "c", System.Globalization.CultureInfo.InvariantCulture, System.Globalization.TimeSpanStyles.None, out resultTime)) + { + resultTime = TimeSpan.Parse(text); + } + return resultTime; + } + } + else if (clrType == typeof(DateTime)) + { + if (this._conn.StoreDateTimeAsTicks) + { + return new DateTime(SQLite3.ColumnInt64(stmt, index)); + } + else + { + var text = SQLite3.ColumnString(stmt, index); + DateTime resultDate; + if (!DateTime.TryParseExact(text, this._conn.DateTimeStringFormat, System.Globalization.CultureInfo.InvariantCulture, this._conn.DateTimeStyle, out resultDate)) + { + resultDate = DateTime.Parse(text); + } + return resultDate; + } + } + else if (clrType == typeof(DateTimeOffset)) + { + return new DateTimeOffset(SQLite3.ColumnInt64(stmt, index), TimeSpan.Zero); + } + else if (clrTypeInfo.IsEnum) + { + if (type == SQLite3.ColType.Text) + { + var value = SQLite3.ColumnString(stmt, index); + return Enum.Parse(clrType, value.ToString(), true); + } + else + return SQLite3.ColumnInt(stmt, index); + } + else if (clrType == typeof(Int64)) + { + return SQLite3.ColumnInt64(stmt, index); + } + else if (clrType == typeof(UInt32)) + { + return (uint)SQLite3.ColumnInt64(stmt, index); + } + else if (clrType == typeof(decimal)) + { + return (decimal)SQLite3.ColumnDouble(stmt, index); + } + else if (clrType == typeof(Byte)) + { + return (byte)SQLite3.ColumnInt(stmt, index); + } + else if (clrType == typeof(UInt16)) + { + return (ushort)SQLite3.ColumnInt(stmt, index); + } + else if (clrType == typeof(Int16)) + { + return (short)SQLite3.ColumnInt(stmt, index); + } + else if (clrType == typeof(sbyte)) + { + return (sbyte)SQLite3.ColumnInt(stmt, index); + } + else if (clrType == typeof(byte[])) + { + return SQLite3.ColumnByteArray(stmt, index); + } + else if (clrType == typeof(Guid)) + { + var text = SQLite3.ColumnString(stmt, index); + return new Guid(text); + } + else if (clrType == typeof(Uri)) + { + var text = SQLite3.ColumnString(stmt, index); + return new Uri(text); + } + else if (clrType == typeof(StringBuilder)) + { + var text = SQLite3.ColumnString(stmt, index); + return new StringBuilder(text); + } + else if (clrType == typeof(UriBuilder)) + { + var text = SQLite3.ColumnString(stmt, index); + return new UriBuilder(text); + } + else + { + throw new NotSupportedException("Don't know how to read " + clrType); + } + } + } + } + + /// + /// Since the insert never changed, we only need to prepare once. + /// + class PreparedSqlLiteInsertCommand : IDisposable + { + bool Initialized; + + SQLiteConnection Connection; + + string CommandText; + + Sqlite3Statement Statement; + static readonly Sqlite3Statement NullStatement = default(Sqlite3Statement); + + public PreparedSqlLiteInsertCommand(SQLiteConnection conn, string commandText) + { + this.Connection = conn; + this.CommandText = commandText; + } + + public int ExecuteNonQuery(object[] source) + { + if (this.Initialized && this.Statement == NullStatement) + { + throw new ObjectDisposedException(nameof(PreparedSqlLiteInsertCommand)); + } + + if (this.Connection.Trace) + { + this.Connection.Tracer?.Invoke("Executing: " + this.CommandText); + } + + var r = SQLite3.Result.OK; + + if (!this.Initialized) + { + this.Statement = SQLite3.Prepare2(this.Connection.Handle, this.CommandText); + this.Initialized = true; + } + + //bind the values. + if (source != null) + { + for (int i = 0; i < source.Length; i++) + { + SQLiteCommand.BindParameter(this.Statement, i + 1, source[i], this.Connection.StoreDateTimeAsTicks, this.Connection.DateTimeStringFormat, this.Connection.StoreTimeSpanAsTicks); + } + } + r = SQLite3.Step(this.Statement); + + if (r == SQLite3.Result.Done) + { + int rowsAffected = SQLite3.Changes(this.Connection.Handle); + SQLite3.Reset(this.Statement); + return rowsAffected; + } + else if (r == SQLite3.Result.Error) + { + string msg = SQLite3.GetErrmsg(this.Connection.Handle); + SQLite3.Reset(this.Statement); + throw SQLiteException.New(r, msg); + } + else if (r == SQLite3.Result.Constraint && SQLite3.ExtendedErrCode(this.Connection.Handle) == SQLite3.ExtendedResult.ConstraintNotNull) + { + SQLite3.Reset(this.Statement); + throw NotNullConstraintViolationException.New(r, SQLite3.GetErrmsg(this.Connection.Handle)); + } + else + { + SQLite3.Reset(this.Statement); + throw SQLiteException.New(r, SQLite3.GetErrmsg(this.Connection.Handle)); + } + } + + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + void Dispose(bool disposing) + { + var s = this.Statement; + this.Statement = NullStatement; + this.Connection = null; + if (s != NullStatement) + { + SQLite3.Finalize(s); + } + } + + ~PreparedSqlLiteInsertCommand() + { + this.Dispose(false); + } + } + + public enum CreateTableResult + { + Created, + Migrated, + } + + public class CreateTablesResult + { + public Dictionary Results { get; private set; } + + public CreateTablesResult() + { + this.Results = new Dictionary(); + } + } + + public abstract class BaseTableQuery + { + protected class Ordering + { + public string ColumnName { get; set; } + public bool Ascending { get; set; } + } + } + + public class TableQuery : BaseTableQuery, IEnumerable + { + public SQLiteConnection Connection { get; private set; } + + public TableMapping Table { get; private set; } + + Expression _where; + List _orderBys; + int? _limit; + int? _offset; + + BaseTableQuery _joinInner; + Expression _joinInnerKeySelector; + BaseTableQuery _joinOuter; + Expression _joinOuterKeySelector; + Expression _joinSelector; + + Expression _selector; + + TableQuery(SQLiteConnection conn, TableMapping table) + { + this.Connection = conn; + this.Table = table; + } + + public TableQuery(SQLiteConnection conn) + { + this.Connection = conn; + this.Table = this.Connection.GetMapping(typeof(T)); + } + + public TableQuery Clone() + { + var q = new TableQuery(this.Connection, this.Table); + q._where = this._where; + q._deferred = this._deferred; + if (this._orderBys != null) + { + q._orderBys = new List(this._orderBys); + } + q._limit = this._limit; + q._offset = this._offset; + q._joinInner = this._joinInner; + q._joinInnerKeySelector = this._joinInnerKeySelector; + q._joinOuter = this._joinOuter; + q._joinOuterKeySelector = this._joinOuterKeySelector; + q._joinSelector = this._joinSelector; + q._selector = this._selector; + return q; + } + + /// + /// Filters the query based on a predicate. + /// + public TableQuery Where(Expression> predExpr) + { + if (predExpr.NodeType == ExpressionType.Lambda) + { + var lambda = (LambdaExpression)predExpr; + var pred = lambda.Body; + var q = this.Clone(); + q.AddWhere(pred); + return q; + } + else + { + throw new NotSupportedException("Must be a predicate"); + } + } + + /// + /// Delete all the rows that match this query. + /// + public int Delete() + { + return this.Delete(null); + } + + /// + /// Delete all the rows that match this query and the given predicate. + /// + public int Delete(Expression> predExpr) + { + if (this._limit.HasValue || this._offset.HasValue) + throw new InvalidOperationException("Cannot delete with limits or offsets"); + + if (this._where == null && predExpr == null) + throw new InvalidOperationException("No condition specified"); + + var pred = this._where; + + if (predExpr != null && predExpr.NodeType == ExpressionType.Lambda) + { + var lambda = (LambdaExpression)predExpr; + pred = pred != null ? Expression.AndAlso(pred, lambda.Body) : lambda.Body; + } + + var args = new List(); + var cmdText = "delete from \"" + this.Table.TableName + "\""; + var w = this.CompileExpr(pred, args); + cmdText += " where " + w.CommandText; + + var command = this.Connection.CreateCommand(cmdText, args.ToArray()); + + int result = command.ExecuteNonQuery(); + return result; + } + + /// + /// Yields a given number of elements from the query and then skips the remainder. + /// + public TableQuery Take(int n) + { + var q = this.Clone(); + q._limit = n; + return q; + } + + /// + /// Skips a given number of elements from the query and then yields the remainder. + /// + public TableQuery Skip(int n) + { + var q = this.Clone(); + q._offset = n; + return q; + } + + /// + /// Returns the element at a given index + /// + public T ElementAt(int index) + { + return this.Skip(index).Take(1).First(); + } + + bool _deferred; + public TableQuery Deferred() + { + var q = this.Clone(); + q._deferred = true; + return q; + } + + /// + /// Order the query results according to a key. + /// + public TableQuery OrderBy(Expression> orderExpr) + { + return this.AddOrderBy(orderExpr, true); + } + + /// + /// Order the query results according to a key. + /// + public TableQuery OrderByDescending(Expression> orderExpr) + { + return this.AddOrderBy(orderExpr, false); + } + + /// + /// Order the query results according to a key. + /// + public TableQuery ThenBy(Expression> orderExpr) + { + return this.AddOrderBy(orderExpr, true); + } + + /// + /// Order the query results according to a key. + /// + public TableQuery ThenByDescending(Expression> orderExpr) + { + return this.AddOrderBy(orderExpr, false); + } + + TableQuery AddOrderBy(Expression> orderExpr, bool asc) + { + if (orderExpr.NodeType == ExpressionType.Lambda) + { + var lambda = (LambdaExpression)orderExpr; + + MemberExpression mem = null; + + var unary = lambda.Body as UnaryExpression; + if (unary != null && unary.NodeType == ExpressionType.Convert) + { + mem = unary.Operand as MemberExpression; + } + else + { + mem = lambda.Body as MemberExpression; + } + + if (mem != null && (mem.Expression.NodeType == ExpressionType.Parameter)) + { + var q = this.Clone(); + if (q._orderBys == null) + { + q._orderBys = new List(); + } + q._orderBys.Add(new Ordering + { + ColumnName = this.Table.FindColumnWithPropertyName(mem.Member.Name).Name, + Ascending = asc + }); + return q; + } + else + { + throw new NotSupportedException("Order By does not support: " + orderExpr); + } + } + else + { + throw new NotSupportedException("Must be a predicate"); + } + } + + private void AddWhere(Expression pred) + { + if (this._where == null) + { + this._where = pred; + } + else + { + this._where = Expression.AndAlso(this._where, pred); + } + } + + ///// + ///// Performs an inner join of two queries based on matching keys extracted from the elements. + ///// + //public TableQuery Join ( + // TableQuery inner, + // Expression> outerKeySelector, + // Expression> innerKeySelector, + // Expression> resultSelector) + //{ + // var q = new TableQuery (Connection, Connection.GetMapping (typeof (TResult))) { + // _joinOuter = this, + // _joinOuterKeySelector = outerKeySelector, + // _joinInner = inner, + // _joinInnerKeySelector = innerKeySelector, + // _joinSelector = resultSelector, + // }; + // return q; + //} + + // Not needed until Joins are supported + // Keeping this commented out forces the default Linq to objects processor to run + //public TableQuery Select (Expression> selector) + //{ + // var q = Clone (); + // q._selector = selector; + // return q; + //} + + private SQLiteCommand GenerateCommand(string selectionList) + { + if (this._joinInner != null && this._joinOuter != null) + { + throw new NotSupportedException("Joins are not supported."); + } + else + { + var cmdText = "select " + selectionList + " from \"" + this.Table.TableName + "\""; + var args = new List(); + if (this._where != null) + { + var w = this.CompileExpr(this._where, args); + cmdText += " where " + w.CommandText; + } + if ((this._orderBys != null) && (this._orderBys.Count > 0)) + { + var t = string.Join(", ", this._orderBys.Select(o => "\"" + o.ColumnName + "\"" + (o.Ascending ? "" : " desc")).ToArray()); + cmdText += " order by " + t; + } + if (this._limit.HasValue) + { + cmdText += " limit " + this._limit.Value; + } + if (this._offset.HasValue) + { + if (!this._limit.HasValue) + { + cmdText += " limit -1 "; + } + cmdText += " offset " + this._offset.Value; + } + return this.Connection.CreateCommand(cmdText, args.ToArray()); + } + } + + class CompileResult + { + public string CommandText { get; set; } + + public object Value { get; set; } + } + + private CompileResult CompileExpr(Expression expr, List queryArgs) + { + if (expr == null) + { + throw new NotSupportedException("Expression is NULL"); + } + else if (expr is BinaryExpression) + { + var bin = (BinaryExpression)expr; + + // VB turns 'x=="foo"' into 'CompareString(x,"foo",true/false)==0', so we need to unwrap it + // http://blogs.msdn.com/b/vbteam/archive/2007/09/18/vb-expression-trees-string-comparisons.aspx + if (bin.Left.NodeType == ExpressionType.Call) + { + var call = (MethodCallExpression)bin.Left; + if (call.Method.DeclaringType.FullName == "Microsoft.VisualBasic.CompilerServices.Operators" + && call.Method.Name == "CompareString") + bin = Expression.MakeBinary(bin.NodeType, call.Arguments[0], call.Arguments[1]); + } + + + var leftr = this.CompileExpr(bin.Left, queryArgs); + var rightr = this.CompileExpr(bin.Right, queryArgs); + + //If either side is a parameter and is null, then handle the other side specially (for "is null"/"is not null") + string text; + if (leftr.CommandText == "?" && leftr.Value == null) + text = this.CompileNullBinaryExpression(bin, rightr); + else if (rightr.CommandText == "?" && rightr.Value == null) + text = this.CompileNullBinaryExpression(bin, leftr); + else + text = "(" + leftr.CommandText + " " + this.GetSqlName(bin) + " " + rightr.CommandText + ")"; + return new CompileResult { CommandText = text }; + } + else if (expr.NodeType == ExpressionType.Not) + { + var operandExpr = ((UnaryExpression)expr).Operand; + var opr = this.CompileExpr(operandExpr, queryArgs); + object val = opr.Value; + if (val is bool) + val = !((bool)val); + return new CompileResult + { + CommandText = "NOT(" + opr.CommandText + ")", + Value = val + }; + } + else if (expr.NodeType == ExpressionType.Call) + { + + var call = (MethodCallExpression)expr; + var args = new CompileResult[call.Arguments.Count]; + var obj = call.Object != null ? this.CompileExpr(call.Object, queryArgs) : null; + + for (var i = 0; i < args.Length; i++) + { + args[i] = this.CompileExpr(call.Arguments[i], queryArgs); + } + + var sqlCall = ""; + + if (call.Method.Name == "Like" && args.Length == 2) + { + sqlCall = "(" + args[0].CommandText + " like " + args[1].CommandText + ")"; + } + else if (call.Method.Name == "Contains" && args.Length == 2) + { + sqlCall = "(" + args[1].CommandText + " in " + args[0].CommandText + ")"; + } + else if (call.Method.Name == "Contains" && args.Length == 1) + { + if (call.Object != null && call.Object.Type == typeof(string)) + { + sqlCall = "( instr(" + obj.CommandText + "," + args[0].CommandText + ") >0 )"; + } + else + { + sqlCall = "(" + args[0].CommandText + " in " + obj.CommandText + ")"; + } + } + else if (call.Method.Name == "StartsWith" && args.Length >= 1) + { + var startsWithCmpOp = StringComparison.CurrentCulture; + if (args.Length == 2) + { + startsWithCmpOp = (StringComparison)args[1].Value; + } + switch (startsWithCmpOp) + { + case StringComparison.Ordinal: + case StringComparison.CurrentCulture: + sqlCall = "( substr(" + obj.CommandText + ", 1, " + args[0].Value.ToString().Length + ") = " + args[0].CommandText + ")"; + break; + case StringComparison.OrdinalIgnoreCase: + case StringComparison.CurrentCultureIgnoreCase: + sqlCall = "(" + obj.CommandText + " like (" + args[0].CommandText + " || '%'))"; + break; + } + + } + else if (call.Method.Name == "EndsWith" && args.Length >= 1) + { + var endsWithCmpOp = StringComparison.CurrentCulture; + if (args.Length == 2) + { + endsWithCmpOp = (StringComparison)args[1].Value; + } + switch (endsWithCmpOp) + { + case StringComparison.Ordinal: + case StringComparison.CurrentCulture: + sqlCall = "( substr(" + obj.CommandText + ", length(" + obj.CommandText + ") - " + args[0].Value.ToString().Length + "+1, " + args[0].Value.ToString().Length + ") = " + args[0].CommandText + ")"; + break; + case StringComparison.OrdinalIgnoreCase: + case StringComparison.CurrentCultureIgnoreCase: + sqlCall = "(" + obj.CommandText + " like ('%' || " + args[0].CommandText + "))"; + break; + } + } + else if (call.Method.Name == "Equals" && args.Length == 1) + { + sqlCall = "(" + obj.CommandText + " = (" + args[0].CommandText + "))"; + } + else if (call.Method.Name == "ToLower") + { + sqlCall = "(lower(" + obj.CommandText + "))"; + } + else if (call.Method.Name == "ToUpper") + { + sqlCall = "(upper(" + obj.CommandText + "))"; + } + else if (call.Method.Name == "Replace" && args.Length == 2) + { + sqlCall = "(replace(" + obj.CommandText + "," + args[0].CommandText + "," + args[1].CommandText + "))"; + } + else if (call.Method.Name == "IsNullOrEmpty" && args.Length == 1) + { + sqlCall = "(" + args[0].CommandText + " is null or" + args[0].CommandText + " ='' )"; + } + else + { + sqlCall = call.Method.Name.ToLower() + "(" + string.Join(",", args.Select(a => a.CommandText).ToArray()) + ")"; + } + return new CompileResult { CommandText = sqlCall }; + + } + else if (expr.NodeType == ExpressionType.Constant) + { + var c = (ConstantExpression)expr; + queryArgs.Add(c.Value); + return new CompileResult + { + CommandText = "?", + Value = c.Value + }; + } + else if (expr.NodeType == ExpressionType.Convert) + { + var u = (UnaryExpression)expr; + var ty = u.Type; + var valr = this.CompileExpr(u.Operand, queryArgs); + return new CompileResult + { + CommandText = valr.CommandText, + Value = valr.Value != null ? ConvertTo(valr.Value, ty) : null + }; + } + else if (expr.NodeType == ExpressionType.MemberAccess) + { + var mem = (MemberExpression)expr; + + var paramExpr = mem.Expression as ParameterExpression; + if (paramExpr == null) + { + var convert = mem.Expression as UnaryExpression; + if (convert != null && convert.NodeType == ExpressionType.Convert) + { + paramExpr = convert.Operand as ParameterExpression; + } + } + + if (paramExpr != null) + { + // + // This is a column of our table, output just the column name + // Need to translate it if that column name is mapped + // + var columnName = this.Table.FindColumnWithPropertyName(mem.Member.Name).Name; + return new CompileResult { CommandText = "\"" + columnName + "\"" }; + } + else + { + object obj = null; + if (mem.Expression != null) + { + var r = this.CompileExpr(mem.Expression, queryArgs); + if (r.Value == null) + { + throw new NotSupportedException("Member access failed to compile expression"); + } + if (r.CommandText == "?") + { + queryArgs.RemoveAt(queryArgs.Count - 1); + } + obj = r.Value; + } + + // + // Get the member value + // + object val = null; + + if (mem.Member is PropertyInfo) + { + var m = (PropertyInfo)mem.Member; + val = m.GetValue(obj, null); + } + else if (mem.Member is FieldInfo) + { + var m = (FieldInfo)mem.Member; + val = m.GetValue(obj); + } + else + { + throw new NotSupportedException("MemberExpr: " + mem.Member.GetType()); + } + + // + // Work special magic for enumerables + // + if (val != null && val is System.Collections.IEnumerable && !(val is string) && !(val is System.Collections.Generic.IEnumerable)) + { + var sb = new System.Text.StringBuilder(); + sb.Append("("); + var head = ""; + foreach (var a in (System.Collections.IEnumerable)val) + { + queryArgs.Add(a); + sb.Append(head); + sb.Append("?"); + head = ","; + } + sb.Append(")"); + return new CompileResult + { + CommandText = sb.ToString(), + Value = val + }; + } + else + { + queryArgs.Add(val); + return new CompileResult + { + CommandText = "?", + Value = val + }; + } + } + } + throw new NotSupportedException("Cannot compile: " + expr.NodeType.ToString()); + } + + static object ConvertTo(object obj, Type t) + { + Type nut = Nullable.GetUnderlyingType(t); + + if (nut != null) + { + if (obj == null) return null; + return Convert.ChangeType(obj, nut); + } + else + { + return Convert.ChangeType(obj, t); + } + } + + /// + /// Compiles a BinaryExpression where one of the parameters is null. + /// + /// The expression to compile + /// The non-null parameter + private string CompileNullBinaryExpression(BinaryExpression expression, CompileResult parameter) + { + if (expression.NodeType == ExpressionType.Equal) + return "(" + parameter.CommandText + " is ?)"; + else if (expression.NodeType == ExpressionType.NotEqual) + return "(" + parameter.CommandText + " is not ?)"; + else if (expression.NodeType == ExpressionType.GreaterThan + || expression.NodeType == ExpressionType.GreaterThanOrEqual + || expression.NodeType == ExpressionType.LessThan + || expression.NodeType == ExpressionType.LessThanOrEqual) + return "(" + parameter.CommandText + " < ?)"; // always false + else + throw new NotSupportedException("Cannot compile Null-BinaryExpression with type " + expression.NodeType.ToString()); + } + + string GetSqlName(Expression expr) + { + var n = expr.NodeType; + if (n == ExpressionType.GreaterThan) + return ">"; + else if (n == ExpressionType.GreaterThanOrEqual) + { + return ">="; + } + else if (n == ExpressionType.LessThan) + { + return "<"; + } + else if (n == ExpressionType.LessThanOrEqual) + { + return "<="; + } + else if (n == ExpressionType.And) + { + return "&"; + } + else if (n == ExpressionType.AndAlso) + { + return "and"; + } + else if (n == ExpressionType.Or) + { + return "|"; + } + else if (n == ExpressionType.OrElse) + { + return "or"; + } + else if (n == ExpressionType.Equal) + { + return "="; + } + else if (n == ExpressionType.NotEqual) + { + return "!="; + } + else + { + throw new NotSupportedException("Cannot get SQL for: " + n); + } + } + + /// + /// Execute SELECT COUNT(*) on the query + /// + public int Count() + { + return this.GenerateCommand("count(*)").ExecuteScalar(); + } + + /// + /// Execute SELECT COUNT(*) on the query with an additional WHERE clause. + /// + public int Count(Expression> predExpr) + { + return this.Where(predExpr).Count(); + } + + public IEnumerator GetEnumerator() + { + if (!this._deferred) + return this.GenerateCommand("*").ExecuteQuery().GetEnumerator(); + + return this.GenerateCommand("*").ExecuteDeferredQuery().GetEnumerator(); + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + + /// + /// Queries the database and returns the results as a List. + /// + public List ToList() + { + return this.GenerateCommand("*").ExecuteQuery(); + } + + /// + /// Queries the database and returns the results as an array. + /// + public T[] ToArray() + { + return this.GenerateCommand("*").ExecuteQuery().ToArray(); + } + + /// + /// Returns the first element of this query. + /// + public T First() + { + var query = this.Take(1); + return query.ToList().First(); + } + + /// + /// Returns the first element of this query, or null if no element is found. + /// + public T FirstOrDefault() + { + var query = this.Take(1); + return query.ToList().FirstOrDefault(); + } + + /// + /// Returns the first element of this query that matches the predicate. + /// + public T First(Expression> predExpr) + { + return this.Where(predExpr).First(); + } + + /// + /// Returns the first element of this query that matches the predicate, or null + /// if no element is found. + /// + public T FirstOrDefault(Expression> predExpr) + { + return this.Where(predExpr).FirstOrDefault(); + } + } + + public static class SQLite3 + { + public enum Result : int + { + OK = 0, + Error = 1, + Internal = 2, + Perm = 3, + Abort = 4, + Busy = 5, + Locked = 6, + NoMem = 7, + ReadOnly = 8, + Interrupt = 9, + IOError = 10, + Corrupt = 11, + NotFound = 12, + Full = 13, + CannotOpen = 14, + LockErr = 15, + Empty = 16, + SchemaChngd = 17, + TooBig = 18, + Constraint = 19, + Mismatch = 20, + Misuse = 21, + NotImplementedLFS = 22, + AccessDenied = 23, + Format = 24, + Range = 25, + NonDBFile = 26, + Notice = 27, + Warning = 28, + Row = 100, + Done = 101 + } + + public enum ExtendedResult : int + { + IOErrorRead = (Result.IOError | (1 << 8)), + IOErrorShortRead = (Result.IOError | (2 << 8)), + IOErrorWrite = (Result.IOError | (3 << 8)), + IOErrorFsync = (Result.IOError | (4 << 8)), + IOErrorDirFSync = (Result.IOError | (5 << 8)), + IOErrorTruncate = (Result.IOError | (6 << 8)), + IOErrorFStat = (Result.IOError | (7 << 8)), + IOErrorUnlock = (Result.IOError | (8 << 8)), + IOErrorRdlock = (Result.IOError | (9 << 8)), + IOErrorDelete = (Result.IOError | (10 << 8)), + IOErrorBlocked = (Result.IOError | (11 << 8)), + IOErrorNoMem = (Result.IOError | (12 << 8)), + IOErrorAccess = (Result.IOError | (13 << 8)), + IOErrorCheckReservedLock = (Result.IOError | (14 << 8)), + IOErrorLock = (Result.IOError | (15 << 8)), + IOErrorClose = (Result.IOError | (16 << 8)), + IOErrorDirClose = (Result.IOError | (17 << 8)), + IOErrorSHMOpen = (Result.IOError | (18 << 8)), + IOErrorSHMSize = (Result.IOError | (19 << 8)), + IOErrorSHMLock = (Result.IOError | (20 << 8)), + IOErrorSHMMap = (Result.IOError | (21 << 8)), + IOErrorSeek = (Result.IOError | (22 << 8)), + IOErrorDeleteNoEnt = (Result.IOError | (23 << 8)), + IOErrorMMap = (Result.IOError | (24 << 8)), + LockedSharedcache = (Result.Locked | (1 << 8)), + BusyRecovery = (Result.Busy | (1 << 8)), + CannottOpenNoTempDir = (Result.CannotOpen | (1 << 8)), + CannotOpenIsDir = (Result.CannotOpen | (2 << 8)), + CannotOpenFullPath = (Result.CannotOpen | (3 << 8)), + CorruptVTab = (Result.Corrupt | (1 << 8)), + ReadonlyRecovery = (Result.ReadOnly | (1 << 8)), + ReadonlyCannotLock = (Result.ReadOnly | (2 << 8)), + ReadonlyRollback = (Result.ReadOnly | (3 << 8)), + AbortRollback = (Result.Abort | (2 << 8)), + ConstraintCheck = (Result.Constraint | (1 << 8)), + ConstraintCommitHook = (Result.Constraint | (2 << 8)), + ConstraintForeignKey = (Result.Constraint | (3 << 8)), + ConstraintFunction = (Result.Constraint | (4 << 8)), + ConstraintNotNull = (Result.Constraint | (5 << 8)), + ConstraintPrimaryKey = (Result.Constraint | (6 << 8)), + ConstraintTrigger = (Result.Constraint | (7 << 8)), + ConstraintUnique = (Result.Constraint | (8 << 8)), + ConstraintVTab = (Result.Constraint | (9 << 8)), + NoticeRecoverWAL = (Result.Notice | (1 << 8)), + NoticeRecoverRollback = (Result.Notice | (2 << 8)) + } + + + public enum ConfigOption : int + { + SingleThread = 1, + MultiThread = 2, + Serialized = 3 + } + + const string LibraryPath = "sqlite3"; + +#if !USE_CSHARP_SQLITE && !USE_WP8_NATIVE_SQLITE && !USE_SQLITEPCL_RAW + [DllImport(LibraryPath, EntryPoint = "sqlite3_threadsafe", CallingConvention = CallingConvention.Cdecl)] + public static extern int Threadsafe(); + + [DllImport(LibraryPath, EntryPoint = "sqlite3_open", CallingConvention = CallingConvention.Cdecl)] + public static extern Result Open([MarshalAs(UnmanagedType.LPStr)] string filename, out IntPtr db); + + [DllImport(LibraryPath, EntryPoint = "sqlite3_open_v2", CallingConvention = CallingConvention.Cdecl)] + public static extern Result Open([MarshalAs(UnmanagedType.LPStr)] string filename, out IntPtr db, int flags, [MarshalAs(UnmanagedType.LPStr)] string zvfs); + + [DllImport(LibraryPath, EntryPoint = "sqlite3_open_v2", CallingConvention = CallingConvention.Cdecl)] + public static extern Result Open(byte[] filename, out IntPtr db, int flags, [MarshalAs(UnmanagedType.LPStr)] string zvfs); + + [DllImport(LibraryPath, EntryPoint = "sqlite3_open16", CallingConvention = CallingConvention.Cdecl)] + public static extern Result Open16([MarshalAs(UnmanagedType.LPWStr)] string filename, out IntPtr db); + + [DllImport(LibraryPath, EntryPoint = "sqlite3_enable_load_extension", CallingConvention = CallingConvention.Cdecl)] + public static extern Result EnableLoadExtension(IntPtr db, int onoff); + + [DllImport(LibraryPath, EntryPoint = "sqlite3_close", CallingConvention = CallingConvention.Cdecl)] + public static extern Result Close(IntPtr db); + + [DllImport(LibraryPath, EntryPoint = "sqlite3_close_v2", CallingConvention = CallingConvention.Cdecl)] + public static extern Result Close2(IntPtr db); + + [DllImport(LibraryPath, EntryPoint = "sqlite3_initialize", CallingConvention = CallingConvention.Cdecl)] + public static extern Result Initialize(); + + [DllImport(LibraryPath, EntryPoint = "sqlite3_shutdown", CallingConvention = CallingConvention.Cdecl)] + public static extern Result Shutdown(); + + [DllImport(LibraryPath, EntryPoint = "sqlite3_config", CallingConvention = CallingConvention.Cdecl)] + public static extern Result Config(ConfigOption option); + + [DllImport(LibraryPath, EntryPoint = "sqlite3_win32_set_directory", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] + public static extern int SetDirectory(uint directoryType, string directoryPath); + + [DllImport(LibraryPath, EntryPoint = "sqlite3_busy_timeout", CallingConvention = CallingConvention.Cdecl)] + public static extern Result BusyTimeout(IntPtr db, int milliseconds); + + [DllImport(LibraryPath, EntryPoint = "sqlite3_changes", CallingConvention = CallingConvention.Cdecl)] + public static extern int Changes(IntPtr db); + + [DllImport(LibraryPath, EntryPoint = "sqlite3_prepare_v2", CallingConvention = CallingConvention.Cdecl)] + public static extern Result Prepare2(IntPtr db, [MarshalAs(UnmanagedType.LPStr)] string sql, int numBytes, out IntPtr stmt, IntPtr pzTail); + +#if NETFX_CORE + [DllImport (LibraryPath, EntryPoint = "sqlite3_prepare_v2", CallingConvention = CallingConvention.Cdecl)] + public static extern Result Prepare2 (IntPtr db, byte[] queryBytes, int numBytes, out IntPtr stmt, IntPtr pzTail); +#endif + + public static IntPtr Prepare2(IntPtr db, string query) + { + IntPtr stmt; +#if NETFX_CORE + byte[] queryBytes = System.Text.UTF8Encoding.UTF8.GetBytes (query); + var r = Prepare2 (db, queryBytes, queryBytes.Length, out stmt, IntPtr.Zero); +#else + var r = Prepare2(db, query, System.Text.UTF8Encoding.UTF8.GetByteCount(query), out stmt, IntPtr.Zero); +#endif + if (r != Result.OK) + { + throw SQLiteException.New(r, GetErrmsg(db)); + } + return stmt; + } + + [DllImport(LibraryPath, EntryPoint = "sqlite3_step", CallingConvention = CallingConvention.Cdecl)] + public static extern Result Step(IntPtr stmt); + + [DllImport(LibraryPath, EntryPoint = "sqlite3_reset", CallingConvention = CallingConvention.Cdecl)] + public static extern Result Reset(IntPtr stmt); + + [DllImport(LibraryPath, EntryPoint = "sqlite3_finalize", CallingConvention = CallingConvention.Cdecl)] + public static extern Result Finalize(IntPtr stmt); + + [DllImport(LibraryPath, EntryPoint = "sqlite3_last_insert_rowid", CallingConvention = CallingConvention.Cdecl)] + public static extern long LastInsertRowid(IntPtr db); + + [DllImport(LibraryPath, EntryPoint = "sqlite3_errmsg16", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr Errmsg(IntPtr db); + + public static string GetErrmsg(IntPtr db) + { + return Marshal.PtrToStringUni(Errmsg(db)); + } + + [DllImport(LibraryPath, EntryPoint = "sqlite3_bind_parameter_index", CallingConvention = CallingConvention.Cdecl)] + public static extern int BindParameterIndex(IntPtr stmt, [MarshalAs(UnmanagedType.LPStr)] string name); + + [DllImport(LibraryPath, EntryPoint = "sqlite3_bind_null", CallingConvention = CallingConvention.Cdecl)] + public static extern int BindNull(IntPtr stmt, int index); + + [DllImport(LibraryPath, EntryPoint = "sqlite3_bind_int", CallingConvention = CallingConvention.Cdecl)] + public static extern int BindInt(IntPtr stmt, int index, int val); + + [DllImport(LibraryPath, EntryPoint = "sqlite3_bind_int64", CallingConvention = CallingConvention.Cdecl)] + public static extern int BindInt64(IntPtr stmt, int index, long val); + + [DllImport(LibraryPath, EntryPoint = "sqlite3_bind_double", CallingConvention = CallingConvention.Cdecl)] + public static extern int BindDouble(IntPtr stmt, int index, double val); + + [DllImport(LibraryPath, EntryPoint = "sqlite3_bind_text16", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] + public static extern int BindText(IntPtr stmt, int index, [MarshalAs(UnmanagedType.LPWStr)] string val, int n, IntPtr free); + + [DllImport(LibraryPath, EntryPoint = "sqlite3_bind_blob", CallingConvention = CallingConvention.Cdecl)] + public static extern int BindBlob(IntPtr stmt, int index, byte[] val, int n, IntPtr free); + + [DllImport(LibraryPath, EntryPoint = "sqlite3_column_count", CallingConvention = CallingConvention.Cdecl)] + public static extern int ColumnCount(IntPtr stmt); + + [DllImport(LibraryPath, EntryPoint = "sqlite3_column_name", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr ColumnName(IntPtr stmt, int index); + + [DllImport(LibraryPath, EntryPoint = "sqlite3_column_name16", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr ColumnName16Internal(IntPtr stmt, int index); + public static string ColumnName16(IntPtr stmt, int index) + { + return Marshal.PtrToStringUni(ColumnName16Internal(stmt, index)); + } + + [DllImport(LibraryPath, EntryPoint = "sqlite3_column_type", CallingConvention = CallingConvention.Cdecl)] + public static extern ColType ColumnType(IntPtr stmt, int index); + + [DllImport(LibraryPath, EntryPoint = "sqlite3_column_int", CallingConvention = CallingConvention.Cdecl)] + public static extern int ColumnInt(IntPtr stmt, int index); + + [DllImport(LibraryPath, EntryPoint = "sqlite3_column_int64", CallingConvention = CallingConvention.Cdecl)] + public static extern long ColumnInt64(IntPtr stmt, int index); + + [DllImport(LibraryPath, EntryPoint = "sqlite3_column_double", CallingConvention = CallingConvention.Cdecl)] + public static extern double ColumnDouble(IntPtr stmt, int index); + + [DllImport(LibraryPath, EntryPoint = "sqlite3_column_text", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr ColumnText(IntPtr stmt, int index); + + [DllImport(LibraryPath, EntryPoint = "sqlite3_column_text16", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr ColumnText16(IntPtr stmt, int index); + + [DllImport(LibraryPath, EntryPoint = "sqlite3_column_blob", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr ColumnBlob(IntPtr stmt, int index); + + [DllImport(LibraryPath, EntryPoint = "sqlite3_column_bytes", CallingConvention = CallingConvention.Cdecl)] + public static extern int ColumnBytes(IntPtr stmt, int index); + + public static string ColumnString(IntPtr stmt, int index) + { + return Marshal.PtrToStringUni(SQLite3.ColumnText16(stmt, index)); + } + + public static byte[] ColumnByteArray(IntPtr stmt, int index) + { + int length = ColumnBytes(stmt, index); + var result = new byte[length]; + if (length > 0) + Marshal.Copy(ColumnBlob(stmt, index), result, 0, length); + return result; + } + + [DllImport(LibraryPath, EntryPoint = "sqlite3_errcode", CallingConvention = CallingConvention.Cdecl)] + public static extern Result GetResult(Sqlite3DatabaseHandle db); + + [DllImport(LibraryPath, EntryPoint = "sqlite3_extended_errcode", CallingConvention = CallingConvention.Cdecl)] + public static extern ExtendedResult ExtendedErrCode(IntPtr db); + + [DllImport(LibraryPath, EntryPoint = "sqlite3_libversion_number", CallingConvention = CallingConvention.Cdecl)] + public static extern int LibVersionNumber(); + + [DllImport(LibraryPath, EntryPoint = "sqlite3_backup_init", CallingConvention = CallingConvention.Cdecl)] + public static extern Sqlite3BackupHandle BackupInit(Sqlite3DatabaseHandle destDb, [MarshalAs(UnmanagedType.LPStr)] string destName, Sqlite3DatabaseHandle sourceDb, [MarshalAs(UnmanagedType.LPStr)] string sourceName); + + [DllImport(LibraryPath, EntryPoint = "sqlite3_backup_step", CallingConvention = CallingConvention.Cdecl)] + public static extern Result BackupStep(Sqlite3BackupHandle backup, int numPages); + + [DllImport(LibraryPath, EntryPoint = "sqlite3_backup_finish", CallingConvention = CallingConvention.Cdecl)] + public static extern Result BackupFinish(Sqlite3BackupHandle backup); +#else + public static Result Open (string filename, out Sqlite3DatabaseHandle db) + { + return (Result)Sqlite3.sqlite3_open (filename, out db); + } + + public static Result Open (string filename, out Sqlite3DatabaseHandle db, int flags, string vfsName) + { +#if USE_WP8_NATIVE_SQLITE + return (Result)Sqlite3.sqlite3_open_v2(filename, out db, flags, vfsName ?? ""); +#else + return (Result)Sqlite3.sqlite3_open_v2 (filename, out db, flags, vfsName); +#endif + } + + public static Result Close (Sqlite3DatabaseHandle db) + { + return (Result)Sqlite3.sqlite3_close (db); + } + + public static Result Close2 (Sqlite3DatabaseHandle db) + { + return (Result)Sqlite3.sqlite3_close_v2 (db); + } + + public static Result BusyTimeout (Sqlite3DatabaseHandle db, int milliseconds) + { + return (Result)Sqlite3.sqlite3_busy_timeout (db, milliseconds); + } + + public static int Changes (Sqlite3DatabaseHandle db) + { + return Sqlite3.sqlite3_changes (db); + } + + public static Sqlite3Statement Prepare2 (Sqlite3DatabaseHandle db, string query) + { + Sqlite3Statement stmt = default (Sqlite3Statement); +#if USE_WP8_NATIVE_SQLITE || USE_SQLITEPCL_RAW + var r = Sqlite3.sqlite3_prepare_v2 (db, query, out stmt); +#else + stmt = new Sqlite3Statement(); + var r = Sqlite3.sqlite3_prepare_v2(db, query, -1, ref stmt, 0); +#endif + if (r != 0) { + throw SQLiteException.New ((Result)r, GetErrmsg (db)); + } + return stmt; + } + + public static Result Step (Sqlite3Statement stmt) + { + return (Result)Sqlite3.sqlite3_step (stmt); + } + + public static Result Reset (Sqlite3Statement stmt) + { + return (Result)Sqlite3.sqlite3_reset (stmt); + } + + public static Result Finalize (Sqlite3Statement stmt) + { + return (Result)Sqlite3.sqlite3_finalize (stmt); + } + + public static long LastInsertRowid (Sqlite3DatabaseHandle db) + { + return Sqlite3.sqlite3_last_insert_rowid (db); + } + + public static string GetErrmsg (Sqlite3DatabaseHandle db) + { + return Sqlite3.sqlite3_errmsg (db).utf8_to_string(); + } + + public static int BindParameterIndex (Sqlite3Statement stmt, string name) + { + return Sqlite3.sqlite3_bind_parameter_index (stmt, name); + } + + public static int BindNull (Sqlite3Statement stmt, int index) + { + return Sqlite3.sqlite3_bind_null (stmt, index); + } + + public static int BindInt (Sqlite3Statement stmt, int index, int val) + { + return Sqlite3.sqlite3_bind_int (stmt, index, val); + } + + public static int BindInt64 (Sqlite3Statement stmt, int index, long val) + { + return Sqlite3.sqlite3_bind_int64 (stmt, index, val); + } + + public static int BindDouble (Sqlite3Statement stmt, int index, double val) + { + return Sqlite3.sqlite3_bind_double (stmt, index, val); + } + + public static int BindText (Sqlite3Statement stmt, int index, string val, int n, IntPtr free) + { +#if USE_WP8_NATIVE_SQLITE + return Sqlite3.sqlite3_bind_text(stmt, index, val, n); +#elif USE_SQLITEPCL_RAW + return Sqlite3.sqlite3_bind_text (stmt, index, val); +#else + return Sqlite3.sqlite3_bind_text(stmt, index, val, n, null); +#endif + } + + public static int BindBlob (Sqlite3Statement stmt, int index, byte[] val, int n, IntPtr free) + { +#if USE_WP8_NATIVE_SQLITE + return Sqlite3.sqlite3_bind_blob(stmt, index, val, n); +#elif USE_SQLITEPCL_RAW + return Sqlite3.sqlite3_bind_blob (stmt, index, val); +#else + return Sqlite3.sqlite3_bind_blob(stmt, index, val, n, null); +#endif + } + + public static int ColumnCount (Sqlite3Statement stmt) + { + return Sqlite3.sqlite3_column_count (stmt); + } + + public static string ColumnName (Sqlite3Statement stmt, int index) + { + return Sqlite3.sqlite3_column_name (stmt, index).utf8_to_string (); + } + + public static string ColumnName16 (Sqlite3Statement stmt, int index) + { + return Sqlite3.sqlite3_column_name (stmt, index).utf8_to_string (); + } + + public static ColType ColumnType (Sqlite3Statement stmt, int index) + { + return (ColType)Sqlite3.sqlite3_column_type (stmt, index); + } + + public static int ColumnInt (Sqlite3Statement stmt, int index) + { + return Sqlite3.sqlite3_column_int (stmt, index); + } + + public static long ColumnInt64 (Sqlite3Statement stmt, int index) + { + return Sqlite3.sqlite3_column_int64 (stmt, index); + } + + public static double ColumnDouble (Sqlite3Statement stmt, int index) + { + return Sqlite3.sqlite3_column_double (stmt, index); + } + + public static string ColumnText (Sqlite3Statement stmt, int index) + { + return Sqlite3.sqlite3_column_text (stmt, index).utf8_to_string (); + } + + public static string ColumnText16 (Sqlite3Statement stmt, int index) + { + return Sqlite3.sqlite3_column_text (stmt, index).utf8_to_string (); + } + + public static byte[] ColumnBlob (Sqlite3Statement stmt, int index) + { + return Sqlite3.sqlite3_column_blob (stmt, index).ToArray (); + } + + public static int ColumnBytes (Sqlite3Statement stmt, int index) + { + return Sqlite3.sqlite3_column_bytes (stmt, index); + } + + public static string ColumnString (Sqlite3Statement stmt, int index) + { + return Sqlite3.sqlite3_column_text (stmt, index).utf8_to_string (); + } + + public static byte[] ColumnByteArray (Sqlite3Statement stmt, int index) + { + int length = ColumnBytes (stmt, index); + if (length > 0) { + return ColumnBlob (stmt, index); + } + return new byte[0]; + } + + public static Result EnableLoadExtension (Sqlite3DatabaseHandle db, int onoff) + { + return (Result)Sqlite3.sqlite3_enable_load_extension (db, onoff); + } + + public static int LibVersionNumber () + { + return Sqlite3.sqlite3_libversion_number (); + } + + public static Result GetResult (Sqlite3DatabaseHandle db) + { + return (Result)Sqlite3.sqlite3_errcode (db); + } + + public static ExtendedResult ExtendedErrCode (Sqlite3DatabaseHandle db) + { + return (ExtendedResult)Sqlite3.sqlite3_extended_errcode (db); + } + + public static Sqlite3BackupHandle BackupInit (Sqlite3DatabaseHandle destDb, string destName, Sqlite3DatabaseHandle sourceDb, string sourceName) + { + return Sqlite3.sqlite3_backup_init (destDb, destName, sourceDb, sourceName); + } + + public static Result BackupStep (Sqlite3BackupHandle backup, int numPages) + { + return (Result)Sqlite3.sqlite3_backup_step (backup, numPages); + } + + public static Result BackupFinish (Sqlite3BackupHandle backup) + { + return (Result)Sqlite3.sqlite3_backup_finish (backup); + } +#endif + + public enum ColType : int + { + Integer = 1, + Float = 2, + Text = 3, + Blob = 4, + Null = 5 + } + } +} diff --git a/src/UI/SQLite/SQLiteAsync.cs b/src/UI/SQLite/SQLiteAsync.cs new file mode 100644 index 00000000..da25894f --- /dev/null +++ b/src/UI/SQLite/SQLiteAsync.cs @@ -0,0 +1,1542 @@ +// +// Copyright (c) 2012-2019 Krueger Systems, 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. +// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Threading; +using System.Threading.Tasks; + +namespace SQLite +{ + /// + /// A pooled asynchronous connection to a SQLite database. + /// + public partial class SQLiteAsyncConnection + { + readonly SQLiteConnectionString _connectionString; + + /// + /// Constructs a new SQLiteAsyncConnection and opens a pooled SQLite database specified by databasePath. + /// + /// + /// Specifies the path to the database file. + /// + /// + /// Specifies whether to store DateTime properties as ticks (true) or strings (false). You + /// absolutely do want to store them as Ticks in all new projects. The value of false is + /// only here for backwards compatibility. There is a *significant* speed advantage, with no + /// down sides, when setting storeDateTimeAsTicks = true. + /// If you use DateTimeOffset properties, it will be always stored as ticks regardingless + /// the storeDateTimeAsTicks parameter. + /// + public SQLiteAsyncConnection(string databasePath, bool storeDateTimeAsTicks = true) + : this(new SQLiteConnectionString(databasePath, SQLiteOpenFlags.Create | SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.FullMutex, storeDateTimeAsTicks)) + { + } + + /// + /// Constructs a new SQLiteAsyncConnection and opens a pooled SQLite database specified by databasePath. + /// + /// + /// Specifies the path to the database file. + /// + /// + /// Flags controlling how the connection should be opened. + /// Async connections should have the FullMutex flag set to provide best performance. + /// + /// + /// Specifies whether to store DateTime properties as ticks (true) or strings (false). You + /// absolutely do want to store them as Ticks in all new projects. The value of false is + /// only here for backwards compatibility. There is a *significant* speed advantage, with no + /// down sides, when setting storeDateTimeAsTicks = true. + /// If you use DateTimeOffset properties, it will be always stored as ticks regardingless + /// the storeDateTimeAsTicks parameter. + /// + public SQLiteAsyncConnection(string databasePath, SQLiteOpenFlags openFlags, bool storeDateTimeAsTicks = true) + : this(new SQLiteConnectionString(databasePath, openFlags, storeDateTimeAsTicks)) + { + } + + /// + /// Constructs a new SQLiteAsyncConnection and opens a pooled SQLite database + /// using the given connection string. + /// + /// + /// Details on how to find and open the database. + /// + public SQLiteAsyncConnection(SQLiteConnectionString connectionString) + { + this._connectionString = connectionString; + } + + /// + /// Gets the database path used by this connection. + /// + public string DatabasePath => this.GetConnection().DatabasePath; + + /// + /// Gets the SQLite library version number. 3007014 would be v3.7.14 + /// + public int LibVersionNumber => this.GetConnection().LibVersionNumber; + + /// + /// The format to use when storing DateTime properties as strings. Ignored if StoreDateTimeAsTicks is true. + /// + /// The date time string format. + public string DateTimeStringFormat => this.GetConnection().DateTimeStringFormat; + + /// + /// The amount of time to wait for a table to become unlocked. + /// + public TimeSpan GetBusyTimeout() + { + return this.GetConnection().BusyTimeout; + } + + /// + /// Sets the amount of time to wait for a table to become unlocked. + /// + public Task SetBusyTimeoutAsync(TimeSpan value) + { + return this.ReadAsync(conn => + { + conn.BusyTimeout = value; + return null; + }); + } + + /// + /// Enables the write ahead logging. WAL is significantly faster in most scenarios + /// by providing better concurrency and better disk IO performance than the normal + /// journal mode. You only need to call this function once in the lifetime of the database. + /// + public Task EnableWriteAheadLoggingAsync() + { + return this.WriteAsync(conn => + { + conn.EnableWriteAheadLogging(); + return null; + }); + } + + /// + /// Whether to store DateTime properties as ticks (true) or strings (false). + /// + public bool StoreDateTimeAsTicks => this.GetConnection().StoreDateTimeAsTicks; + + /// + /// Whether to store TimeSpan properties as ticks (true) or strings (false). + /// + public bool StoreTimeSpanAsTicks => this.GetConnection().StoreTimeSpanAsTicks; + + /// + /// Whether to writer queries to during execution. + /// + /// The tracer. + public bool Trace + { + get { return this.GetConnection().Trace; } + set { this.GetConnection().Trace = value; } + } + + /// + /// The delegate responsible for writing trace lines. + /// + /// The tracer. + public Action Tracer + { + get { return this.GetConnection().Tracer; } + set { this.GetConnection().Tracer = value; } + } + + /// + /// Whether Trace lines should be written that show the execution time of queries. + /// + public bool TimeExecution + { + get { return this.GetConnection().TimeExecution; } + set { this.GetConnection().TimeExecution = value; } + } + + /// + /// Returns the mappings from types to tables that the connection + /// currently understands. + /// + public IEnumerable TableMappings => this.GetConnection().TableMappings; + + /// + /// Closes all connections to all async databases. + /// You should *never* need to do this. + /// This is a blocking operation that will return when all connections + /// have been closed. + /// + public static void ResetPool() + { + SQLiteConnectionPool.Shared.Reset(); + } + + /// + /// Gets the pooled lockable connection used by this async connection. + /// You should never need to use this. This is provided only to add additional + /// functionality to SQLite-net. If you use this connection, you must use + /// the Lock method on it while using it. + /// + public SQLiteConnectionWithLock GetConnection() + { + return SQLiteConnectionPool.Shared.GetConnection(this._connectionString); + } + + SQLiteConnectionWithLock GetConnectionAndTransactionLock(out object transactionLock) + { + return SQLiteConnectionPool.Shared.GetConnectionAndTransactionLock(this._connectionString, out transactionLock); + } + + /// + /// Closes any pooled connections used by the database. + /// + public Task CloseAsync() + { + return Task.Factory.StartNew(() => + { + SQLiteConnectionPool.Shared.CloseConnection(this._connectionString); + }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); + } + + Task ReadAsync(Func read) + { + return Task.Factory.StartNew(() => + { + var conn = this.GetConnection(); + using (conn.Lock()) + { + return read(conn); + } + }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); + } + + Task WriteAsync(Func write) + { + return Task.Factory.StartNew(() => + { + var conn = this.GetConnection(); + using (conn.Lock()) + { + return write(conn); + } + }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); + } + + Task TransactAsync(Func transact) + { + return Task.Factory.StartNew(() => + { + var conn = this.GetConnectionAndTransactionLock(out var transactionLock); + lock (transactionLock) + { + using (conn.Lock()) + { + return transact(conn); + } + } + }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); + } + + /// + /// Enable or disable extension loading. + /// + public Task EnableLoadExtensionAsync(bool enabled) + { + return this.WriteAsync(conn => + { + conn.EnableLoadExtension(enabled); + return null; + }); + } + + /// + /// Executes a "create table if not exists" on the database. It also + /// creates any specified indexes on the columns of the table. It uses + /// a schema automatically generated from the specified type. You can + /// later access this schema by calling GetMapping. + /// + /// + /// Whether the table was created or migrated. + /// + public Task CreateTableAsync(CreateFlags createFlags = CreateFlags.None) + where T : new() + { + return this.WriteAsync(conn => conn.CreateTable(createFlags)); + } + + /// + /// Executes a "create table if not exists" on the database. It also + /// creates any specified indexes on the columns of the table. It uses + /// a schema automatically generated from the specified type. You can + /// later access this schema by calling GetMapping. + /// + /// Type to reflect to a database table. + /// Optional flags allowing implicit PK and indexes based on naming conventions. + /// + /// Whether the table was created or migrated. + /// + public Task CreateTableAsync(Type ty, CreateFlags createFlags = CreateFlags.None) + { + return this.WriteAsync(conn => conn.CreateTable(ty, createFlags)); + } + + /// + /// Executes a "create table if not exists" on the database for each type. It also + /// creates any specified indexes on the columns of the table. It uses + /// a schema automatically generated from the specified type. You can + /// later access this schema by calling GetMapping. + /// + /// + /// Whether the table was created or migrated for each type. + /// + public Task CreateTablesAsync(CreateFlags createFlags = CreateFlags.None) + where T : new() + where T2 : new() + { + return this.CreateTablesAsync(createFlags, typeof(T), typeof(T2)); + } + + /// + /// Executes a "create table if not exists" on the database for each type. It also + /// creates any specified indexes on the columns of the table. It uses + /// a schema automatically generated from the specified type. You can + /// later access this schema by calling GetMapping. + /// + /// + /// Whether the table was created or migrated for each type. + /// + public Task CreateTablesAsync(CreateFlags createFlags = CreateFlags.None) + where T : new() + where T2 : new() + where T3 : new() + { + return this.CreateTablesAsync(createFlags, typeof(T), typeof(T2), typeof(T3)); + } + + /// + /// Executes a "create table if not exists" on the database for each type. It also + /// creates any specified indexes on the columns of the table. It uses + /// a schema automatically generated from the specified type. You can + /// later access this schema by calling GetMapping. + /// + /// + /// Whether the table was created or migrated for each type. + /// + public Task CreateTablesAsync(CreateFlags createFlags = CreateFlags.None) + where T : new() + where T2 : new() + where T3 : new() + where T4 : new() + { + return this.CreateTablesAsync(createFlags, typeof(T), typeof(T2), typeof(T3), typeof(T4)); + } + + /// + /// Executes a "create table if not exists" on the database for each type. It also + /// creates any specified indexes on the columns of the table. It uses + /// a schema automatically generated from the specified type. You can + /// later access this schema by calling GetMapping. + /// + /// + /// Whether the table was created or migrated for each type. + /// + public Task CreateTablesAsync(CreateFlags createFlags = CreateFlags.None) + where T : new() + where T2 : new() + where T3 : new() + where T4 : new() + where T5 : new() + { + return this.CreateTablesAsync(createFlags, typeof(T), typeof(T2), typeof(T3), typeof(T4), typeof(T5)); + } + + /// + /// Executes a "create table if not exists" on the database for each type. It also + /// creates any specified indexes on the columns of the table. It uses + /// a schema automatically generated from the specified type. You can + /// later access this schema by calling GetMapping. + /// + /// + /// Whether the table was created or migrated for each type. + /// + public Task CreateTablesAsync(CreateFlags createFlags = CreateFlags.None, params Type[] types) + { + return this.WriteAsync(conn => conn.CreateTables(createFlags, types)); + } + + /// + /// Executes a "drop table" on the database. This is non-recoverable. + /// + public Task DropTableAsync() + where T : new() + { + return this.WriteAsync(conn => conn.DropTable()); + } + + /// + /// Executes a "drop table" on the database. This is non-recoverable. + /// + /// + /// The TableMapping used to identify the table. + /// + public Task DropTableAsync(TableMapping map) + { + return this.WriteAsync(conn => conn.DropTable(map)); + } + + /// + /// Creates an index for the specified table and column. + /// + /// Name of the database table + /// Name of the column to index + /// Whether the index should be unique + public Task CreateIndexAsync(string tableName, string columnName, bool unique = false) + { + return this.WriteAsync(conn => conn.CreateIndex(tableName, columnName, unique)); + } + + /// + /// Creates an index for the specified table and column. + /// + /// Name of the index to create + /// Name of the database table + /// Name of the column to index + /// Whether the index should be unique + public Task CreateIndexAsync(string indexName, string tableName, string columnName, bool unique = false) + { + return this.WriteAsync(conn => conn.CreateIndex(indexName, tableName, columnName, unique)); + } + + /// + /// Creates an index for the specified table and columns. + /// + /// Name of the database table + /// An array of column names to index + /// Whether the index should be unique + public Task CreateIndexAsync(string tableName, string[] columnNames, bool unique = false) + { + return this.WriteAsync(conn => conn.CreateIndex(tableName, columnNames, unique)); + } + + /// + /// Creates an index for the specified table and columns. + /// + /// Name of the index to create + /// Name of the database table + /// An array of column names to index + /// Whether the index should be unique + public Task CreateIndexAsync(string indexName, string tableName, string[] columnNames, bool unique = false) + { + return this.WriteAsync(conn => conn.CreateIndex(indexName, tableName, columnNames, unique)); + } + + /// + /// Creates an index for the specified object property. + /// e.g. CreateIndex<Client>(c => c.Name); + /// + /// Type to reflect to a database table. + /// Property to index + /// Whether the index should be unique + public Task CreateIndexAsync(Expression> property, bool unique = false) + { + return this.WriteAsync(conn => conn.CreateIndex(property, unique)); + } + + /// + /// Inserts the given object and retrieves its + /// auto incremented primary key if it has one. + /// + /// + /// The object to insert. + /// + /// + /// The number of rows added to the table. + /// + public Task InsertAsync(object obj) + { + return this.WriteAsync(conn => conn.Insert(obj)); + } + + /// + /// Inserts the given object (and updates its + /// auto incremented primary key if it has one). + /// The return value is the number of rows added to the table. + /// + /// + /// The object to insert. + /// + /// + /// The type of object to insert. + /// + /// + /// The number of rows added to the table. + /// + public Task InsertAsync(object obj, Type objType) + { + return this.WriteAsync(conn => conn.Insert(obj, objType)); + } + + /// + /// Inserts the given object (and updates its + /// auto incremented primary key if it has one). + /// The return value is the number of rows added to the table. + /// + /// + /// The object to insert. + /// + /// + /// Literal SQL code that gets placed into the command. INSERT {extra} INTO ... + /// + /// + /// The number of rows added to the table. + /// + public Task InsertAsync(object obj, string extra) + { + return this.WriteAsync(conn => conn.Insert(obj, extra)); + } + + /// + /// Inserts the given object (and updates its + /// auto incremented primary key if it has one). + /// The return value is the number of rows added to the table. + /// + /// + /// The object to insert. + /// + /// + /// Literal SQL code that gets placed into the command. INSERT {extra} INTO ... + /// + /// + /// The type of object to insert. + /// + /// + /// The number of rows added to the table. + /// + public Task InsertAsync(object obj, string extra, Type objType) + { + return this.WriteAsync(conn => conn.Insert(obj, extra, objType)); + } + + /// + /// Inserts the given object (and updates its + /// auto incremented primary key if it has one). + /// The return value is the number of rows added to the table. + /// If a UNIQUE constraint violation occurs with + /// some pre-existing object, this function deletes + /// the old object. + /// + /// + /// The object to insert. + /// + /// + /// The number of rows modified. + /// + public Task InsertOrReplaceAsync(object obj) + { + return this.WriteAsync(conn => conn.InsertOrReplace(obj)); + } + + /// + /// Inserts the given object (and updates its + /// auto incremented primary key if it has one). + /// The return value is the number of rows added to the table. + /// If a UNIQUE constraint violation occurs with + /// some pre-existing object, this function deletes + /// the old object. + /// + /// + /// The object to insert. + /// + /// + /// The type of object to insert. + /// + /// + /// The number of rows modified. + /// + public Task InsertOrReplaceAsync(object obj, Type objType) + { + return this.WriteAsync(conn => conn.InsertOrReplace(obj, objType)); + } + + /// + /// Updates all of the columns of a table using the specified object + /// except for its primary key. + /// The object is required to have a primary key. + /// + /// + /// The object to update. It must have a primary key designated using the PrimaryKeyAttribute. + /// + /// + /// The number of rows updated. + /// + public Task UpdateAsync(object obj) + { + return this.WriteAsync(conn => conn.Update(obj)); + } + + /// + /// Updates all of the columns of a table using the specified object + /// except for its primary key. + /// The object is required to have a primary key. + /// + /// + /// The object to update. It must have a primary key designated using the PrimaryKeyAttribute. + /// + /// + /// The type of object to insert. + /// + /// + /// The number of rows updated. + /// + public Task UpdateAsync(object obj, Type objType) + { + return this.WriteAsync(conn => conn.Update(obj, objType)); + } + + /// + /// Updates all specified objects. + /// + /// + /// An of the objects to insert. + /// + /// + /// A boolean indicating if the inserts should be wrapped in a transaction + /// + /// + /// The number of rows modified. + /// + public Task UpdateAllAsync(IEnumerable objects, bool runInTransaction = true) + { + return this.WriteAsync(conn => conn.UpdateAll(objects, runInTransaction)); + } + + /// + /// Deletes the given object from the database using its primary key. + /// + /// + /// The object to delete. It must have a primary key designated using the PrimaryKeyAttribute. + /// + /// + /// The number of rows deleted. + /// + public Task DeleteAsync(object objectToDelete) + { + return this.WriteAsync(conn => conn.Delete(objectToDelete)); + } + + /// + /// Deletes the object with the specified primary key. + /// + /// + /// The primary key of the object to delete. + /// + /// + /// The number of objects deleted. + /// + /// + /// The type of object. + /// + public Task DeleteAsync(object primaryKey) + { + return this.WriteAsync(conn => conn.Delete(primaryKey)); + } + + /// + /// Deletes the object with the specified primary key. + /// + /// + /// The primary key of the object to delete. + /// + /// + /// The TableMapping used to identify the table. + /// + /// + /// The number of objects deleted. + /// + public Task DeleteAsync(object primaryKey, TableMapping map) + { + return this.WriteAsync(conn => conn.Delete(primaryKey, map)); + } + + /// + /// Deletes all the objects from the specified table. + /// WARNING WARNING: Let me repeat. It deletes ALL the objects from the + /// specified table. Do you really want to do that? + /// + /// + /// The number of objects deleted. + /// + /// + /// The type of objects to delete. + /// + public Task DeleteAllAsync() + { + return this.WriteAsync(conn => conn.DeleteAll()); + } + + /// + /// Deletes all the objects from the specified table. + /// WARNING WARNING: Let me repeat. It deletes ALL the objects from the + /// specified table. Do you really want to do that? + /// + /// + /// The TableMapping used to identify the table. + /// + /// + /// The number of objects deleted. + /// + public Task DeleteAllAsync(TableMapping map) + { + return this.WriteAsync(conn => conn.DeleteAll(map)); + } + + /// + /// Backup the entire database to the specified path. + /// + /// Path to backup file. + /// The name of the database to backup (usually "main"). + public Task BackupAsync(string destinationDatabasePath, string databaseName = "main") + { + return this.WriteAsync(conn => + { + conn.Backup(destinationDatabasePath, databaseName); + return 0; + }); + } + + /// + /// Attempts to retrieve an object with the given primary key from the table + /// associated with the specified type. Use of this method requires that + /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute). + /// + /// + /// The primary key. + /// + /// + /// The object with the given primary key. Throws a not found exception + /// if the object is not found. + /// + public Task GetAsync(object pk) + where T : new() + { + return this.ReadAsync(conn => conn.Get(pk)); + } + + /// + /// Attempts to retrieve an object with the given primary key from the table + /// associated with the specified type. Use of this method requires that + /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute). + /// + /// + /// The primary key. + /// + /// + /// The TableMapping used to identify the table. + /// + /// + /// The object with the given primary key. Throws a not found exception + /// if the object is not found. + /// + public Task GetAsync(object pk, TableMapping map) + { + return this.ReadAsync(conn => conn.Get(pk, map)); + } + + /// + /// Attempts to retrieve the first object that matches the predicate from the table + /// associated with the specified type. + /// + /// + /// A predicate for which object to find. + /// + /// + /// The object that matches the given predicate. Throws a not found exception + /// if the object is not found. + /// + public Task GetAsync(Expression> predicate) + where T : new() + { + return this.ReadAsync(conn => conn.Get(predicate)); + } + + /// + /// Attempts to retrieve an object with the given primary key from the table + /// associated with the specified type. Use of this method requires that + /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute). + /// + /// + /// The primary key. + /// + /// + /// The object with the given primary key or null + /// if the object is not found. + /// + public Task FindAsync(object pk) + where T : new() + { + return this.ReadAsync(conn => conn.Find(pk)); + } + + /// + /// Attempts to retrieve an object with the given primary key from the table + /// associated with the specified type. Use of this method requires that + /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute). + /// + /// + /// The primary key. + /// + /// + /// The TableMapping used to identify the table. + /// + /// + /// The object with the given primary key or null + /// if the object is not found. + /// + public Task FindAsync(object pk, TableMapping map) + { + return this.ReadAsync(conn => conn.Find(pk, map)); + } + + /// + /// Attempts to retrieve the first object that matches the predicate from the table + /// associated with the specified type. + /// + /// + /// A predicate for which object to find. + /// + /// + /// The object that matches the given predicate or null + /// if the object is not found. + /// + public Task FindAsync(Expression> predicate) + where T : new() + { + return this.ReadAsync(conn => conn.Find(predicate)); + } + + /// + /// Attempts to retrieve the first object that matches the query from the table + /// associated with the specified type. + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the query. + /// + /// + /// The object that matches the given predicate or null + /// if the object is not found. + /// + public Task FindWithQueryAsync(string query, params object[] args) + where T : new() + { + return this.ReadAsync(conn => conn.FindWithQuery(query, args)); + } + + /// + /// Attempts to retrieve the first object that matches the query from the table + /// associated with the specified type. + /// + /// + /// The TableMapping used to identify the table. + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the query. + /// + /// + /// The object that matches the given predicate or null + /// if the object is not found. + /// + public Task FindWithQueryAsync(TableMapping map, string query, params object[] args) + { + return this.ReadAsync(conn => conn.FindWithQuery(map, query, args)); + } + + /// + /// Retrieves the mapping that is automatically generated for the given type. + /// + /// + /// The type whose mapping to the database is returned. + /// + /// + /// Optional flags allowing implicit PK and indexes based on naming conventions + /// + /// + /// The mapping represents the schema of the columns of the database and contains + /// methods to set and get properties of objects. + /// + public Task GetMappingAsync(Type type, CreateFlags createFlags = CreateFlags.None) + { + return this.ReadAsync(conn => conn.GetMapping(type, createFlags)); + } + + /// + /// Retrieves the mapping that is automatically generated for the given type. + /// + /// + /// Optional flags allowing implicit PK and indexes based on naming conventions + /// + /// + /// The mapping represents the schema of the columns of the database and contains + /// methods to set and get properties of objects. + /// + public Task GetMappingAsync(CreateFlags createFlags = CreateFlags.None) + where T : new() + { + return this.ReadAsync(conn => conn.GetMapping(createFlags)); + } + + /// + /// Query the built-in sqlite table_info table for a specific tables columns. + /// + /// The columns contains in the table. + /// Table name. + public Task> GetTableInfoAsync(string tableName) + { + return this.ReadAsync(conn => conn.GetTableInfo(tableName)); + } + + /// + /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' + /// in the command text for each of the arguments and then executes that command. + /// Use this method instead of Query when you don't expect rows back. Such cases include + /// INSERTs, UPDATEs, and DELETEs. + /// You can set the Trace or TimeExecution properties of the connection + /// to profile execution. + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the query. + /// + /// + /// The number of rows modified in the database as a result of this execution. + /// + public Task ExecuteAsync(string query, params object[] args) + { + return this.WriteAsync(conn => conn.Execute(query, args)); + } + + /// + /// Inserts all specified objects. + /// + /// + /// An of the objects to insert. + /// + /// A boolean indicating if the inserts should be wrapped in a transaction. + /// + /// + /// The number of rows added to the table. + /// + public Task InsertAllAsync(IEnumerable objects, bool runInTransaction = true) + { + return this.WriteAsync(conn => conn.InsertAll(objects, runInTransaction)); + } + + /// + /// Inserts all specified objects. + /// + /// + /// An of the objects to insert. + /// + /// + /// Literal SQL code that gets placed into the command. INSERT {extra} INTO ... + /// + /// + /// A boolean indicating if the inserts should be wrapped in a transaction. + /// + /// + /// The number of rows added to the table. + /// + public Task InsertAllAsync(IEnumerable objects, string extra, bool runInTransaction = true) + { + return this.WriteAsync(conn => conn.InsertAll(objects, extra, runInTransaction)); + } + + /// + /// Inserts all specified objects. + /// + /// + /// An of the objects to insert. + /// + /// + /// The type of object to insert. + /// + /// + /// A boolean indicating if the inserts should be wrapped in a transaction. + /// + /// + /// The number of rows added to the table. + /// + public Task InsertAllAsync(IEnumerable objects, Type objType, bool runInTransaction = true) + { + return this.WriteAsync(conn => conn.InsertAll(objects, objType, runInTransaction)); + } + + /// + /// Executes within a (possibly nested) transaction by wrapping it in a SAVEPOINT. If an + /// exception occurs the whole transaction is rolled back, not just the current savepoint. The exception + /// is rethrown. + /// + /// + /// The to perform within a transaction. can contain any number + /// of operations on the connection but should never call or + /// . + /// + public Task RunInTransactionAsync(Action action) + { + return this.TransactAsync(conn => + { + conn.BeginTransaction(); + try + { + action(conn); + conn.Commit(); + return null; + } + catch (Exception) + { + conn.Rollback(); + throw; + } + }); + } + + /// + /// Returns a queryable interface to the table represented by the given type. + /// + /// + /// A queryable object that is able to translate Where, OrderBy, and Take + /// queries into native SQL. + /// + public AsyncTableQuery Table() + where T : new() + { + // + // This isn't async as the underlying connection doesn't go out to the database + // until the query is performed. The Async methods are on the query iteself. + // + var conn = this.GetConnection(); + return new AsyncTableQuery(conn.Table()); + } + + /// + /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' + /// in the command text for each of the arguments and then executes that command. + /// Use this method when return primitive values. + /// You can set the Trace or TimeExecution properties of the connection + /// to profile execution. + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the query. + /// + /// + /// The number of rows modified in the database as a result of this execution. + /// + public Task ExecuteScalarAsync(string query, params object[] args) + { + return this.WriteAsync(conn => + { + var command = conn.CreateCommand(query, args); + return command.ExecuteScalar(); + }); + } + + /// + /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' + /// in the command text for each of the arguments and then executes that command. + /// It returns each row of the result using the mapping automatically generated for + /// the given type. + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the query. + /// + /// + /// A list with one result for each row returned by the query. + /// + public Task> QueryAsync(string query, params object[] args) + where T : new() + { + return this.ReadAsync(conn => conn.Query(query, args)); + } + + /// + /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' + /// in the command text for each of the arguments and then executes that command. + /// It returns the first column of each row of the result. + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the query. + /// + /// + /// A list with one result for the first column of each row returned by the query. + /// + public Task> QueryScalarsAsync(string query, params object[] args) + { + return this.ReadAsync(conn => conn.QueryScalars(query, args)); + } + + /// + /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' + /// in the command text for each of the arguments and then executes that command. + /// It returns each row of the result using the specified mapping. This function is + /// only used by libraries in order to query the database via introspection. It is + /// normally not used. + /// + /// + /// A to use to convert the resulting rows + /// into objects. + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the query. + /// + /// + /// An enumerable with one result for each row returned by the query. + /// + public Task> QueryAsync(TableMapping map, string query, params object[] args) + { + return this.ReadAsync(conn => conn.Query(map, query, args)); + } + + /// + /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' + /// in the command text for each of the arguments and then executes that command. + /// It returns each row of the result using the mapping automatically generated for + /// the given type. + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the query. + /// + /// + /// An enumerable with one result for each row returned by the query. + /// The enumerator will call sqlite3_step on each call to MoveNext, so the database + /// connection must remain open for the lifetime of the enumerator. + /// + public Task> DeferredQueryAsync(string query, params object[] args) + where T : new() + { + return this.ReadAsync(conn => (IEnumerable)conn.DeferredQuery(query, args).ToList()); + } + + /// + /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' + /// in the command text for each of the arguments and then executes that command. + /// It returns each row of the result using the specified mapping. This function is + /// only used by libraries in order to query the database via introspection. It is + /// normally not used. + /// + /// + /// A to use to convert the resulting rows + /// into objects. + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the query. + /// + /// + /// An enumerable with one result for each row returned by the query. + /// The enumerator will call sqlite3_step on each call to MoveNext, so the database + /// connection must remain open for the lifetime of the enumerator. + /// + public Task> DeferredQueryAsync(TableMapping map, string query, params object[] args) + { + return this.ReadAsync(conn => (IEnumerable)conn.DeferredQuery(map, query, args).ToList()); + } + } + + /// + /// Query to an asynchronous database connection. + /// + public class AsyncTableQuery + where T : new() + { + TableQuery _innerQuery; + + /// + /// Creates a new async query that uses given the synchronous query. + /// + public AsyncTableQuery(TableQuery innerQuery) + { + this._innerQuery = innerQuery; + } + + Task ReadAsync(Func read) + { + return Task.Factory.StartNew(() => + { + var conn = (SQLiteConnectionWithLock)this._innerQuery.Connection; + using (conn.Lock()) + { + return read(conn); + } + }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); + } + + Task WriteAsync(Func write) + { + return Task.Factory.StartNew(() => + { + var conn = (SQLiteConnectionWithLock)this._innerQuery.Connection; + using (conn.Lock()) + { + return write(conn); + } + }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); + } + + /// + /// Filters the query based on a predicate. + /// + public AsyncTableQuery Where(Expression> predExpr) + { + return new AsyncTableQuery(this._innerQuery.Where(predExpr)); + } + + /// + /// Skips a given number of elements from the query and then yields the remainder. + /// + public AsyncTableQuery Skip(int n) + { + return new AsyncTableQuery(this._innerQuery.Skip(n)); + } + + /// + /// Yields a given number of elements from the query and then skips the remainder. + /// + public AsyncTableQuery Take(int n) + { + return new AsyncTableQuery(this._innerQuery.Take(n)); + } + + /// + /// Order the query results according to a key. + /// + public AsyncTableQuery OrderBy(Expression> orderExpr) + { + return new AsyncTableQuery(this._innerQuery.OrderBy(orderExpr)); + } + + /// + /// Order the query results according to a key. + /// + public AsyncTableQuery OrderByDescending(Expression> orderExpr) + { + return new AsyncTableQuery(this._innerQuery.OrderByDescending(orderExpr)); + } + + /// + /// Order the query results according to a key. + /// + public AsyncTableQuery ThenBy(Expression> orderExpr) + { + return new AsyncTableQuery(this._innerQuery.ThenBy(orderExpr)); + } + + /// + /// Order the query results according to a key. + /// + public AsyncTableQuery ThenByDescending(Expression> orderExpr) + { + return new AsyncTableQuery(this._innerQuery.ThenByDescending(orderExpr)); + } + + /// + /// Queries the database and returns the results as a List. + /// + public Task> ToListAsync() + { + return this.ReadAsync(conn => this._innerQuery.ToList()); + } + + /// + /// Queries the database and returns the results as an array. + /// + public Task ToArrayAsync() + { + return this.ReadAsync(conn => this._innerQuery.ToArray()); + } + + /// + /// Execute SELECT COUNT(*) on the query + /// + public Task CountAsync() + { + return this.ReadAsync(conn => this._innerQuery.Count()); + } + + /// + /// Execute SELECT COUNT(*) on the query with an additional WHERE clause. + /// + public Task CountAsync(Expression> predExpr) + { + return this.ReadAsync(conn => this._innerQuery.Count(predExpr)); + } + + /// + /// Returns the element at a given index + /// + public Task ElementAtAsync(int index) + { + return this.ReadAsync(conn => this._innerQuery.ElementAt(index)); + } + + /// + /// Returns the first element of this query. + /// + public Task FirstAsync() + { + return this.ReadAsync(conn => this._innerQuery.First()); + } + + /// + /// Returns the first element of this query, or null if no element is found. + /// + public Task FirstOrDefaultAsync() + { + return this.ReadAsync(conn => this._innerQuery.FirstOrDefault()); + } + + /// + /// Returns the first element of this query that matches the predicate. + /// + public Task FirstAsync(Expression> predExpr) + { + return this.ReadAsync(conn => this._innerQuery.First(predExpr)); + } + + /// + /// Returns the first element of this query that matches the predicate. + /// + public Task FirstOrDefaultAsync(Expression> predExpr) + { + return this.ReadAsync(conn => this._innerQuery.FirstOrDefault(predExpr)); + } + + /// + /// Delete all the rows that match this query and the given predicate. + /// + public Task DeleteAsync(Expression> predExpr) + { + return this.WriteAsync(conn => this._innerQuery.Delete(predExpr)); + } + + /// + /// Delete all the rows that match this query. + /// + public Task DeleteAsync() + { + return this.WriteAsync(conn => this._innerQuery.Delete()); + } + } + + class SQLiteConnectionPool + { + class Entry + { + public SQLiteConnectionWithLock Connection { get; private set; } + + public SQLiteConnectionString ConnectionString { get; } + + public object TransactionLock { get; } = new object(); + + public Entry(SQLiteConnectionString connectionString) + { + this.ConnectionString = connectionString; + this.Connection = new SQLiteConnectionWithLock(this.ConnectionString); + + // If the database is FullMutex, then we don't need to bother locking + if (this.ConnectionString.OpenFlags.HasFlag(SQLiteOpenFlags.FullMutex)) + { + this.Connection.SkipLock = true; + } + } + + public void Close() + { + var wc = this.Connection; + this.Connection = null; + if (wc != null) + { + wc.Close(); + } + } + } + + readonly Dictionary _entries = new Dictionary(); + readonly object _entriesLock = new object(); + + static readonly SQLiteConnectionPool _shared = new SQLiteConnectionPool(); + + /// + /// Gets the singleton instance of the connection tool. + /// + public static SQLiteConnectionPool Shared + { + get + { + return _shared; + } + } + + public SQLiteConnectionWithLock GetConnection(SQLiteConnectionString connectionString) + { + return this.GetConnectionAndTransactionLock(connectionString, out var _); + } + + public SQLiteConnectionWithLock GetConnectionAndTransactionLock(SQLiteConnectionString connectionString, out object transactionLock) + { + var key = connectionString.UniqueKey; + Entry entry; + lock (this._entriesLock) + { + if (!this._entries.TryGetValue(key, out entry)) + { + // The opens the database while we're locked + // This is to ensure another thread doesn't get an unopened database + entry = new Entry(connectionString); + this._entries[key] = entry; + } + transactionLock = entry.TransactionLock; + return entry.Connection; + } + } + + public void CloseConnection(SQLiteConnectionString connectionString) + { + var key = connectionString.UniqueKey; + Entry entry; + lock (this._entriesLock) + { + if (this._entries.TryGetValue(key, out entry)) + { + this._entries.Remove(key); + } + } + entry?.Close(); + } + + /// + /// Closes all connections managed by this pool. + /// + public void Reset() + { + List entries; + lock (this._entriesLock) + { + entries = new List(this._entries.Values); + this._entries.Clear(); + } + + foreach (var e in entries) + { + e.Close(); + } + } + } + + /// + /// This is a normal connection except it contains a Lock method that + /// can be used to serialize access to the database + /// in lieu of using the sqlite's FullMutex support. + /// + public class SQLiteConnectionWithLock : SQLiteConnection + { + readonly object _lockPoint = new object(); + + /// + /// Initializes a new instance of the class. + /// + /// Connection string containing the DatabasePath. + public SQLiteConnectionWithLock(SQLiteConnectionString connectionString) + : base(connectionString) + { + } + + /// + /// Gets or sets a value indicating whether this skip lock. + /// + /// true if skip lock; otherwise, false. + public bool SkipLock { get; set; } + + /// + /// Lock the database to serialize access to it. To unlock it, call Dispose + /// on the returned object. + /// + /// The lock. + public IDisposable Lock() + { + return this.SkipLock ? (IDisposable)new FakeLockWrapper() : new LockWrapper(this._lockPoint); + } + + class LockWrapper : IDisposable + { + object _lockPoint; + + public LockWrapper(object lockPoint) + { + this._lockPoint = lockPoint; + Monitor.Enter(this._lockPoint); + } + + public void Dispose() + { + Monitor.Exit(this._lockPoint); + } + } + class FakeLockWrapper : IDisposable + { + public void Dispose() + { + } + } + } +} + diff --git a/src/UI/SQLiteHistory.cs b/src/UI/SQLiteHistory.cs new file mode 100644 index 00000000..96d4d38f --- /dev/null +++ b/src/UI/SQLiteHistory.cs @@ -0,0 +1,963 @@ +using SQLite; + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +using Telegram.Bot.Requests; + +using static AITool.AITOOL; + + +namespace AITool +{ + //this is mainly so we only need one thread for adding and deleting + public class DBQueueHistoryItem + { + public bool add = false; //otherwise delete + public History hist = new History(); + public DBQueueHistoryItem(History hist, bool add) + { + this.add = add; + this.hist = hist; + } + } + public class SQLiteHistory:IDisposable + { + private bool disposedValue; + + public string Filename { get; } = ""; + public bool ReadOnly { get; } = false; + public bool IsNew { get; } = false; + public ConcurrentDictionary HistoryDic { get; } = new ConcurrentDictionary(); + public DateTime InitializeTime { get; } = DateTime.Now; + public DateTime LastUpdateTime { get; set; } = DateTime.MinValue; + public ThreadSafe.Boolean HasInitialized { get; set; } = new ThreadSafe.Boolean(false); + private SQLiteConnection sqlite_conn { get; set; } = null; + public ConcurrentQueue RecentlyAdded { get; set; } = new ConcurrentQueue(); + public ConcurrentQueue RecentlyDeleted { get; set; } = new ConcurrentQueue(); + public ThreadSafe.Integer AddedCount { get; set; } = new ThreadSafe.Integer(0); + public ThreadSafe.Integer DeletedCount { get; set; } = new ThreadSafe.Integer(0); + //private ThreadSafe.Boolean IsUpdating { get; set; } = new ThreadSafe.Boolean(false); + private BlockingCollection DBQueueHistory = new BlockingCollection(); + public MovingCalcs AddTimeCalc { get; set; } = new MovingCalcs(1000, "DB Items", true); + public MovingCalcs DeleteTimeCalc { get; set; } = new MovingCalcs(1000, "DB Items", true); + + public static object DBLock = new object(); + + private DateTime LastCleanTime = DateTime.MinValue; + public SQLiteHistory(string Filename, bool ReadOnly) + { + if (string.IsNullOrEmpty(Filename)) + throw new System.ArgumentException("Parameter cannot be empty", nameof(Filename)); + + this.Filename = Filename; + this.ReadOnly = ReadOnly; + this.IsNew = !File.Exists(Filename); + + } + public async Task Initialize() + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + try + { + await this.UpdateHistoryListAsync(true); + + if (this.HistoryDic.Count == 0) + this.MigrateHistoryCSV(AppSettings.Settings.HistoryFileName); + + } + catch (Exception ex) + { + Log("Error: " + ex.Msg()); + } + finally + { + Task.Run(this.HistoryJobQueueLoop); + this.HasInitialized = true; + Global.SendMessage(MessageType.DatabaseInitialized); + } + + } + + private void HistoryJobQueueLoop() + { + //this runs forever and blocks if nothing is in the queue + + try + { + foreach (DBQueueHistoryItem hitm in this.DBQueueHistory.GetConsumingEnumerable(MasterCTS.Token)) + { + if (MasterCTS.IsCancellationRequested) + break; + + string file = ""; + try + { + if (hitm == null || hitm.hist == null || string.IsNullOrEmpty(hitm.hist.Filename)) + { + Log("Error: hist should not be null?"); + } + else + { + file = hitm.hist.Filename; + if (hitm.add) + this.InsertHistoryItem(hitm.hist); + else + this.DeleteHistoryItem(hitm.hist); //Getting nullreferenceexception here and cant figure out why + } + } + catch (Exception ex) + { + + Log($"Error: ({file})" + ex.ToString()); + } + + } + } + catch (Exception ex) + { + + Log($"Debug: HistoryJobQueueLoop canceled: {ex.Message}"); + } + + Log($"Debug: HistoryJobQueueLoop exited."); + + this.Dispose(); + + } + + private bool IsSQLiteDBConnected() + { + + //make sure only one thread updating at a time + //await Semaphore_Updating.WaitAsync(); + + try + { + return (this.sqlite_conn != null && !string.IsNullOrEmpty(this.sqlite_conn.DatabasePath)); + } + catch (Exception ex) + { + Log("Error: " + ex.Msg(), "None", "None", "None"); + + return false; + } + finally + { + //Semaphore_Updating.Release(); + } + } + + private void DisposeConnection() + { + + if (this.sqlite_conn == null) + return; + + try + { + lock (DBLock) + { + this.sqlite_conn.Close(); + this.sqlite_conn = null; + } + + } + catch (Exception ex) + { + Log("Error: " + ex.Msg()); + + } + + } + private bool CreateConnection() + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + int retries = 0; + bool restorebackup = false; + + RetryConnection: + + retries++; + + bool ret = false; + string sflags = "SharedCache"; + + try + { + Stopwatch sw = Stopwatch.StartNew(); + + Global.UpdateProgressBar("Debug: Initializing history database...", 1, 1, 1); + + this.HistoryDic.Clear(); + this.RecentlyDeleted = new ConcurrentQueue(); + this.RecentlyAdded = new ConcurrentQueue(); + + //https://www.sqlite.org/threadsafe.html + SQLiteOpenFlags flags = SQLiteOpenFlags.SharedCache; // SQLiteOpenFlags.Create; // | SQLiteOpenFlags.NoMutex; //| SQLiteOpenFlags.FullMutex; + + if (this.ReadOnly) + { + flags = flags | SQLiteOpenFlags.ReadOnly; + sflags += ";ReadOnly"; + } + else + { + flags = flags | SQLiteOpenFlags.Create | SQLiteOpenFlags.ReadWrite; + sflags += ";Create;ReadWrite"; + } + + if (this.IsSQLiteDBConnected()) + { + this.DisposeConnection(); + } + + string fldr = Path.GetDirectoryName(this.Filename); + + if (!Directory.Exists(fldr)) + Directory.CreateDirectory(fldr); + + string backupfile = this.Filename + ".bak"; + bool dbFileExisted = File.Exists(this.Filename); + bool bakFileExisted = File.Exists(backupfile); + + if (restorebackup && bakFileExisted) + { + Log($"Debug: Restoring backup database: {backupfile}..."); + File.Copy(backupfile, this.Filename, true); + File.Delete(backupfile); + } + + restorebackup = false; + + //If the database file doesn't exist, the default behavior is to create a new file + this.sqlite_conn = new SQLiteConnection(this.Filename, flags, true); + + if (!this.ReadOnly) + { + //make sure table exists: + CreateTableResult ctr = this.sqlite_conn.CreateTable(); + } + + this.sqlite_conn.ExecuteScalar(@"PRAGMA journal_mode = 'WAL';", new object[] { }); + this.sqlite_conn.ExecuteScalar(@"PRAGMA busy_timeout = 30000;", new object[] { }); + + if (!this.ReadOnly && dbFileExisted) + { + //backup if we didnt just create the main db file + this.sqlite_conn.Backup(this.Filename + ".bak"); + } + + ret = true; + + sw.Stop(); + + Log($"Debug: Created connection to SQLite db v{this.sqlite_conn.LibVersionNumber} in {sw.ElapsedMilliseconds}ms, Flags='{sflags}': {this.Filename}", "None", "None", "None"); + + + } + catch (Exception ex) + { + + Log($"Error: Flags='{sflags}', Error=" + ex.Msg(), "None", "None", "None"); + } + + if (!ret) + { + if (retries > 3) + { + Log($"Error: Retries exceeded, could not create history database? {this.Filename}"); + return false; + } + + Log($"Debug: Retrying database connection #{retries} of 3..."); + + //close the database if open + DisposeConnection(); + + //first try to restore the backup + if (retries == 1) + { + restorebackup = true; + } + + //delete the database file and try again + Global.SafeFileDelete(this.Filename, "SQLiteHistoryError"); + Global.SafeFileDelete(this.Filename + "-shm", "SQLiteHistoryError"); + Global.SafeFileDelete(this.Filename + "-wal", "SQLiteHistoryError"); + + goto RetryConnection; + } + + return ret; + } + + public bool InsertHistoryQueue(History hist) + { + //simply add to the queue + DBQueueHistoryItem ditm = new DBQueueHistoryItem(hist, true); + return this.DBQueueHistory.TryAdd(ditm); + } + + private bool InsertHistoryItem(History hist) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + bool ret = false; + int iret = 0; + + //make sure only one thread updating at a time + //await Semaphore_Updating.WaitAsync(); + + //this.IsUpdating= true; + lock (DBLock) + { + + try + { + ret = this.HistoryDic.TryAdd(hist.Filename.ToLower(), hist); + + if (!ret) + { + Log($"debug: File already existed in dictionary: {hist.Filename}", hist.AIServer, hist.Camera, hist.Filename); + } + + Stopwatch sw = Stopwatch.StartNew(); + + iret = this.sqlite_conn.Insert(hist); + + this.AddTimeCalc.AddToCalc(sw.ElapsedMilliseconds); + + if (iret != 1) + { + Log($"Error: When trying to insert database entry, RowsAdded count was {ret} but we expected 1. StackDepth={new StackTrace().FrameCount}, TID={Thread.CurrentThread.ManagedThreadId}, TCNT={Process.GetCurrentProcess().Threads.Count}", hist.AIServer, hist.Camera, hist.Filename); + } + + if (sw.ElapsedMilliseconds > 3000) + Log($"Debug: It took a long time to add a history item @ {sw.ElapsedMilliseconds}ms, (Count={this.AddTimeCalc.Count}, Min={this.AddTimeCalc.Min}ms, Max={this.AddTimeCalc.Max}ms, Avg={this.AddTimeCalc.Avg.ToString("#####")}ms), StackDepth={new StackTrace().FrameCount}, TID={Thread.CurrentThread.ManagedThreadId}, TCNT={Process.GetCurrentProcess().Threads.Count}: {this.Filename}", hist.AIServer, hist.Camera, hist.Filename); + + } + catch (Exception ex) + { + + if (ex.Message.IndexOf("UNIQUE constraint failed", StringComparison.OrdinalIgnoreCase) >= 0) + { + Log($"Debug: File was already in the database: {hist.Filename}", hist.AIServer, hist.Camera, hist.Filename); + } + else + { + Log($"Error: StackDepth={new StackTrace().FrameCount}, TID={Thread.CurrentThread.ManagedThreadId}, TCNT={Process.GetCurrentProcess().Threads.Count}: {hist.Filename}: " + ex.Msg(), hist.AIServer, hist.Camera, hist.Filename); + } + } + + if (ret || iret > 0) + { + this.RecentlyAdded.Enqueue(hist); + this.AddedCount++; + this.LastUpdateTime = DateTime.Now; + } + + } + + //Semaphore_Updating.Release(); + + //this.IsUpdating = false; + + return ret; + + } + + public bool DeleteHistoryQueue(History hist) + { + //simply add to the queue + DBQueueHistoryItem ditm = new DBQueueHistoryItem(hist, false); + return this.DBQueueHistory.TryAdd(ditm); + } + public bool DeleteHistoryQueue(string Filename) + { + //simply add to the queue + History hist; + this.HistoryDic.TryGetValue(Filename.ToLower(), out hist); + + if (hist == null) + hist = new History().Create(Filename, DateTime.Now, "unknown", "", "", false, "", "", 0, false); + + DBQueueHistoryItem ditm = new DBQueueHistoryItem(hist, false); + + return this.DBQueueHistory.TryAdd(ditm); + + } + + private bool DeleteHistoryItem(History hist) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + bool ret = false; + int dret = 0; + lock (DBLock) + { + try + { + + History thist = null; + ret = this.HistoryDic.TryRemove(hist.Filename.ToLower(), out thist); + + Stopwatch sw = Stopwatch.StartNew(); + //Error: Cannot delete String: it has no PK [NotSupportedException] Mod: d__18 Line:150:5 + //trying to use object rather than primarykey to delete + + if (thist != null) + hist = thist; //probably dont have to do this in order to delete from db + + dret = this.sqlite_conn.Delete(hist); + + this.DeleteTimeCalc.AddToCalc(sw.ElapsedMilliseconds); + + //if (dret != 1) + //{ + // Log($"Error: When trying to delete database entry '{Filename}', RowsDeleted count was {ret} but we expected 1. StackDepth={new StackTrace().FrameCount}, TID={Thread.CurrentThread.ManagedThreadId}, TCNT={Process.GetCurrentProcess().Threads.Count}"); + //} + //else + //{ + // Log($"Removed {dret} database entry for '{Filename}'"); + //} + + if (sw.ElapsedMilliseconds > 2000) + Log($"Debug: It took a long time to delete a history item @ {sw.ElapsedMilliseconds}ms, (Count={this.DeleteTimeCalc.Count}, Min={this.DeleteTimeCalc.Min}ms, Max={this.DeleteTimeCalc.Max}ms, Avg={this.DeleteTimeCalc.Avg.ToString("#####")}ms), StackDepth={new StackTrace().FrameCount}, TID={Thread.CurrentThread.ManagedThreadId}, TCNT={Process.GetCurrentProcess().Threads.Count}: {this.Filename}", hist.AIServer, hist.Camera, hist.Filename); + + } + catch (Exception ex) + { + + Log($"Error: File='{hist.Filename}' - " + ex.Msg(), hist.AIServer, hist.Camera, hist.Filename); + } + + if (ret || dret > 0) + { + this.DeletedCount++; + this.LastUpdateTime = DateTime.Now; + this.RecentlyDeleted.Enqueue(hist); + } + + //Semaphore_Updating.Release(); + + //this.IsUpdating = false; + + } + + return ret; + + } + + public bool MigrateHistoryCSV(string Filename) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + bool ret = false; + lock (DBLock) + { + try + { + + //if (!await this.IsSQLiteDBConnectedAsync()) + // await this.CreateConnectionAsync(); + + //run in another thread so we dont block UI + //await Task.Run(async () => + //{ + + if (System.IO.File.Exists(Filename)) + { + Global.UpdateProgressBar("Migrating history.csv...", 1, 1, 1); + + Log($"Debug: Migrating history list from {Filename} ..."); + + Stopwatch SW = Stopwatch.StartNew(); + + //delete obsolete entries from history.csv + //CleanCSVList(); //removed to load the history list faster + + List result = new List(); //List that later on will be containing all lines of the csv file + + Global.WaitFileAccessResult wresult = Global.WaitForFileAccess(Filename); + + if (wresult.Success) + { + //load all lines except the first line into List (the first line is the table heading and not an alert entry) + foreach (string line in System.IO.File.ReadAllLines(Filename).Skip(1)) + { + result.Add(line); + } + + Log($"...Found {result.Count} lines."); + + List itemsToDelete = new List(); //stores all filenames of history.csv entries that need to be removed + + //load all List elements into the ListView for each row + int added = 0; + int removed = 0; + int cnt = 0; + foreach (var val in result) + { + cnt++; + + //if (cnt == 1 || cnt == result.Count || (cnt % (result.Count / 10) > 0)) + //{ + // Global.UpdateProgressBar("Migrating history.csv...", cnt, 1, result.Count); + //} + + History hist = new History().CreateFromCSV(val); + if (File.Exists(hist.Filename)) + { + if (this.InsertHistoryItem(hist)) + { + added++; + //this.AddedCount++; + } + else + { + removed++; + } + } + else + { + removed++; + } + } + + + ret = (added > 0); + + //this.AddedCount.AtomicAddAndGet(added); + //this.DeletedCount.AtomicAddAndGet(removed); + + //try to get a better feel how much time this function consumes - Vorlon + Log($"Debug: ...Added {added} out of {result.Count} history items ({removed} removed) in {SW.ElapsedMilliseconds}ms, {this.HistoryDic.Count} lines."); + + } + else + { + Log($"Error: Could not gain access to history file for {wresult.TimeMS}ms with {wresult.ErrRetryCnt} retries - {AppSettings.Settings.HistoryFileName}"); + + } + + } + else + { + Log($"Debug: Old history file does not exist, could not migrate: {Filename}"); + } + + + //}); + + + } + catch (Exception ex) + { + + Log("Error: " + ex.Msg()); + } + + } + + Global.UpdateProgressBar("", 0, 0, 0); + + + return ret; + + + } + + public List GetRecentlyDeleted() + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + List ret = new List(); + while (!this.RecentlyDeleted.IsEmpty) + { + History hist; + if (this.RecentlyDeleted.TryDequeue(out hist)) + ret.Add(hist); + } + return ret; + } + public List GetAllValues() + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + //because the dictionary doesnt give a proper sorted list + if ((DateTime.Now - this.LastCleanTime).Hours >= AppSettings.Settings.HistoryHoursBetweenCleaning) + this.CleanHistoryList(); + + return this.HistoryDic.Values.OrderBy(c => c.Date).ToList(); + } + + public List GetRecentlyAdded() + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + if ((DateTime.Now - this.LastCleanTime).Hours >= AppSettings.Settings.HistoryHoursBetweenCleaning) + this.CleanHistoryList(); + + List ret = new List(); + while (!this.RecentlyAdded.IsEmpty) + { + History hist; + if (this.RecentlyAdded.TryDequeue(out hist)) + ret.Add(hist); + } + + + return ret; + } + + public async Task HasUpdates() + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + try + { + + if (!this.ReadOnly) //assume if we had to wait the database was just updating + { + //we are running in the same process as the database updater + //no need to re-check sqlite db every time + + if (!this.RecentlyAdded.IsEmpty || !this.RecentlyDeleted.IsEmpty) + { + return true; + } + } + else + { + //do a full update for now, later figure out best way to communicate updates from service to gui + return await this.UpdateHistoryListAsync(false); + } + + } + catch (Exception) + { + + throw; + } + finally + { + //Semaphore_Updating.Release(); + } + + return false; + + } + + public async Task UpdateHistoryListAsync(bool Clean) + { + return await Task.Run(() => this.UpdateHistoryList(Clean)); + } + + public bool UpdateHistoryList(bool Clean) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + bool ret = false; + + lock (DBLock) + { + try + { + + Global.UpdateProgressBar("Reading database...", 1, 1, 1); + + Stopwatch sw = Stopwatch.StartNew(); + + if (!this.IsSQLiteDBConnected()) + { + if (!this.CreateConnection()) + return false; + } + + TableQuery query = this.sqlite_conn.Table(); + List CurList = query.ToList(); + + int added = 0; + int removed = 0; + bool isnew = (this.HistoryDic.Count == 0); + + Dictionary tmpdic = new Dictionary(); + + //lets try to keep the original objects in memory and not create them new + int cnt = 0; + long LastMS = sw.ElapsedMilliseconds; + int CurListCount = CurList.Count; + int HalfList = CurListCount / 2; + + foreach (History hist in CurList) + { + cnt++; + + if (cnt == 1 || cnt == HalfList || cnt > (CurListCount - 5) || (sw.ElapsedMilliseconds - LastMS >= 500)) + { + Global.UpdateProgressBar("Reading database...", cnt, 1, CurList.Count); + LastMS = sw.ElapsedMilliseconds; + } + + if (!isnew) + { + //add missing + if (this.HistoryDic.TryAdd(hist.Filename.ToLower(), hist)) + { + added++; + } + tmpdic.Add(hist.Filename.ToLower(), hist.Filename); + } + else + { + this.HistoryDic.TryAdd(hist.Filename.ToLower(), hist); + added++; + } + } + + if (!isnew) + { + //subtract + foreach (History hist in this.HistoryDic.Values) + { + if (!tmpdic.ContainsKey(hist.Filename.ToLower())) + { + History th; + this.HistoryDic.TryRemove(hist.Filename.ToLower(), out th); + removed++; + } + + } + + } + + if (Clean && !this.ReadOnly) + this.CleanHistoryList(); + + ret = (added > 0 || removed > 0); + + if (ret) + { + //this.DeletedCount.AtomicAddAndGet(removed); + if (!isnew) + this.AddedCount = added; + + this.LastUpdateTime = DateTime.Now; + } + + Log($"Debug: Update History Database: Added={added}, removed={removed}, total={this.HistoryDic.Count}, StackDepth={new StackTrace().FrameCount}, TID={Thread.CurrentThread.ManagedThreadId}, TCNT={Process.GetCurrentProcess().Threads.Count} in {sw.ElapsedMilliseconds}ms"); + + } + catch (Exception ex) + { + + Log("Error: " + ex.Msg()); + } + + } + + Global.UpdateProgressBar("", 0, 0, 0); + + + return ret; + + } + + private async Task CleanHistoryList() + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + bool ret = false; + + try + { + Stopwatch sw = Stopwatch.StartNew(); + + + if (!this.IsSQLiteDBConnected()) + { + if (!this.CreateConnection()) + return false; + } + + ConcurrentBag removed = new ConcurrentBag(); //this may only need to be a list, but just being safe in threading + + //run the file exists check in another thread so we dont freeze the UI, but WAIT for it + await Task.Run(async () => + { + Log("Debug: Removing missing files from in-memory database..."); + + try + { + int missing = 0; + int tooold = 0; + int failedcnt = 0; + + int cnt = 0; + int HistCount = this.HistoryDic.Count; + long LastMS = sw.ElapsedMilliseconds; + int HalfCount = HistCount / 2; + //we are allowed to do this with a ConcurrentDictionary... + foreach (History hist in this.HistoryDic.Values) + { + cnt++; + + if (cnt == 1 || cnt == HalfCount || cnt > (HistCount - 5) || (sw.ElapsedMilliseconds - LastMS >= 500)) + { + Global.UpdateProgressBar("Cleaning database (1 of 2)...", cnt, 1, HistCount); + LastMS = sw.ElapsedMilliseconds; + } + + bool IsTooOld = (DateTime.Now - hist.Date).TotalDays >= AppSettings.Settings.MaxHistoryAgeDays; + + if (IsTooOld || !File.Exists(hist.Filename)) + { + if (IsTooOld) + tooold++; + else + missing++; + + removed.Add(hist); + this.RecentlyDeleted.Enqueue(hist); + + History rhist; + + if (!this.HistoryDic.TryRemove(hist.Filename.ToLower(), out rhist)) + { + failedcnt++; + Log($"Trace: Could not remove from in-memory database: {hist.Filename}"); + } + } + + } + + if (removed.Count > 0) + { + Log($"Debug: Removing {missing} files and {tooold} database entries that are older than {AppSettings.Settings.MaxHistoryAgeDays} days (MaxHistoryAgeDays) from file database..."); + //start another thread to finish cleaning the database so that the app gets the list faster... + //the db should be thread safe due to opening with fullmutex flag + Stopwatch swr = Stopwatch.StartNew(); + int rcnt = 0; + cnt = 0; + LastMS = sw.ElapsedMilliseconds; + int RemovedCount = removed.Count; + int HalfRemoved = RemovedCount / 2; + + foreach (History hist in removed) + { + cnt++; + + if (cnt == 1 || cnt == HalfRemoved || cnt > (RemovedCount - 5) || (sw.ElapsedMilliseconds - LastMS >= 500)) + { + Global.UpdateProgressBar("Cleaning database (2 of 2)...", cnt, 1, removed.Count); + LastMS = sw.ElapsedMilliseconds; + } + + int rowsdeleted = 0; + + try + { + Stopwatch dsw = Stopwatch.StartNew(); + + rowsdeleted = this.sqlite_conn.Delete(hist); + + this.DeleteTimeCalc.AddToCalc(dsw.ElapsedMilliseconds); + + this.RecentlyDeleted.Enqueue(hist); + + if (rowsdeleted != 1) + { + failedcnt++; + Log($"Trace: When trying to delete database entry, RowsDeleted count was {rowsdeleted} but we expected 1."); + } + } + catch (Exception ex) + { + failedcnt++; + Log($"Error: StackDepth={new StackTrace().FrameCount}, TID={Thread.CurrentThread.ManagedThreadId}, TCNT={Process.GetCurrentProcess().Threads.Count}: '{this.Filename}' - " + ex.Msg()); + } + rcnt += rowsdeleted; + } + + + //this.DeletedCount.AtomicAddAndGet(rcnt); + + Log($"Debug: ...Cleaned {rcnt} of {removed.Count} history file database items. {failedcnt} failed, {missing} missing, {tooold} too old. Time={swr.ElapsedMilliseconds}ms (Count={this.DeleteTimeCalc.Count}, Min={this.DeleteTimeCalc.Min}ms, Max={this.DeleteTimeCalc.Max}ms, Avg={this.DeleteTimeCalc.Avg.ToString("#####")}ms)"); + + } + else + { + Log("debug: No missing or old files to clean from database."); + } + + } + catch (Exception ex) + { + + Log("Error: " + ex.ToString()); + } + + + }); + + + Log($"Debug: ...Cleaned {removed.Count} items in {sw.ElapsedMilliseconds}ms"); + + + ret = removed.Count > 0; + + + } + catch (Exception ex) + { + + Log("Error: " + ex.ToString()); + } + finally + { + Global.UpdateProgressBar("", 0, 0, 0); + this.LastCleanTime = DateTime.Now; + } + + return ret; + + } + + + protected virtual async void Dispose(bool disposing) + { + if (!this.disposedValue) + { + if (disposing) + { + if (this.IsSQLiteDBConnected()) + { + this.DisposeConnection(); + } + } + + // TODO: free unmanaged resources (unmanaged objects) and override finalizer + // TODO: set large fields to null + this.disposedValue = true; + } + } + + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + this.Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} + diff --git a/src/UI/Settings.cs b/src/UI/Settings.cs new file mode 100644 index 00000000..3051b33f --- /dev/null +++ b/src/UI/Settings.cs @@ -0,0 +1,900 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using System.Xml.Linq; +using System.Xml.XPath; + +using Newtonsoft.Json; + +using static AITool.AITOOL; + +namespace AITool +{ + + public static class AppSettings + { + public static ClsSettings Settings = new ClsSettings(); + public static bool AlreadyRunning = false; + public static string LastShutdownState = ""; + public static string LastLogEntry = ""; + private static string LastSettingsJSON = ""; + private static ClsDeepstackDetection ThreadLock = new ClsDeepstackDetection(); + private static SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1); + private static ThreadSafe.DateTime _LastSaveTime = new ThreadSafe.DateTime(DateTime.MinValue, AppSettings.Settings.DateFormat); + private static ThreadSafe.Boolean _AlreadySaving = new ThreadSafe.Boolean(false); + public class ClsSettings + { + [JsonIgnore] + public string SettingsFileName = ""; //Path.Combine(AppDomain.CurrentDomain.BaseDirectory, Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().Location) + ".Settings.JSON"); + [JsonIgnore] + public string LogFileName = ""; //Path.Combine(AppDomain.CurrentDomain.BaseDirectory, Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().Location) + ".LOG"); + public string CustomLogFilePath = ""; //Path.Combine(AppDomain.CurrentDomain.BaseDirectory, Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().Location) + ".LOG"); + public string FacesPath = ""; + [JsonIgnore] + public string HistoryFileName = ""; //Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "cameras\\history.csv"); + [JsonIgnore] + public string HistoryDBFileName = ""; //Path.Combine(AppDomain.CurrentDomain.BaseDirectory, Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().Location) + ".Database.SQLITE3"); + + public bool SettingsPortable = true; //portable means the settings stay under EXE folder, otherwise they get dropped in %appdata% + public string telegram_token = ""; + public double telegram_cooldown_minutes = -1; //Default to no more often than 5 seconds. In minutes (How many minutes must have passed since the last detection. Used to separate event to ensure that every event only causes one telegram message.) + public int telegram_cooldown_seconds = 5; + public bool telegram_monitor_commands = true; + public int Telegram_RetryAfterFailSeconds = 300; //default to 5 minutes if telegram exception + public string input_path = ""; + public bool input_path_includesubfolders = false; + public List telegram_chatids = new List(); + public bool log_everything = false; + public string LogLevel = "Debug"; + [JsonProperty("send_errors")] + public bool send_telegram_errors = true; + public bool send_pushover_errors = true; + + public bool startwithwindows = false; + public int close_instantly = -1; + public List CameraList = new List(); + public string deepstack_url = ""; + public string deepstack_adminkey = ""; + public string deepstack_apikey = ""; + public string deepstack_installfolder = "C:\\DeepStack"; + public string deepstack_port = "81"; + public string deepstack_mode = "Medium"; + public string deepstack_customModelPath = ""; + public string deepstack_customModelName = ""; + public string deepstack_customModelPort = "82"; + public string deepstack_customModelMode = "Medium"; //might be case sensitive + public bool deepstack_stopbeforestart = true; + public bool deepstack_urls_are_queued = true; + public bool deepstack_autostart = false; + public bool deepstack_autoadd = true; + public bool deepstack_debug = false; + public bool deepstack_highpriority = true; + public bool deepstack_sceneapienabled = false; + public bool deepstack_faceapienabled = false; + public bool deepstack_detectionapienabled = false; + public bool deepstack_customModelApiEnabled = false; + public bool deepstack_autorestart = true; + public int deepstack_autorestart_fail_count = 3; //this many fails in a row will trigger restart + public double deepstack_autorestart_minutes_between_restart_attempts = 10.0; + + public int loop_delay_ms = 50; //the small delay applied in a loop after a failed file access check + public bool SettingsValid = false; + public int MaxLogFileAgeDays = 14; + public long MaxLogFileSize = ((1024 * 1024) * 10); //10mb in bytes + + public int MaxImageQueueSize = 100; + public int MaxActionQueueSize = 100; + public double MaxImageQueueTimeMinutes = 30; //Take an image out of the queue if it sits in there over this time + public int MaxQueueItemRetries = 5; //will be disabled if fails this many times - Also applies to individual image failures + public int URLResetAfterDisabledMinutes = 60; //If any AI/Deepstack URL's have been disabled for over this time, all URLs will be reset to try again + public int MinSecondsBetweenFailedURLRetry = 30; //if a URL has failed, dont retry try more often than xx seconds + public int MaxErrorsInARowBeforeDisable = 60; + [JsonProperty("HTTPClientTimeoutSeconds")] + public int HTTPClientLocalTimeoutSeconds = 120; //httpclient.timeout - https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient.timeout?view=netcore-3.1 + public int HTTPClientRemoteTimeoutSeconds = 120; //httpclient.timeout - https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient.timeout?view=netcore-3.1 + + public int RectRelevantColorAlpha = 150; //255=solid, 127 half transparent + public int RectIrrelevantColorAlpha = 150; + public int RectMaskedColorAlpha = 150; + + public int RectBorderWidth = 3; + + public int RectDetectionTextSize = 14; + public string RectDetectionTextFont = "Segoe UI"; + + public string DefaultFont = "Segoe UI, 8.25pt"; + + public System.Drawing.Color RectDetectionTextBackColor = System.Drawing.Color.Gainsboro; //a magic color randomly picked that means "Use Relevant or Irrelevant colors" + public System.Drawing.Color RectDetectionTextForeColor = System.Drawing.Color.Black; + public System.Drawing.Color RectRelevantColor = System.Drawing.Color.Red; + public System.Drawing.Color RectIrrelevantColor = System.Drawing.Color.Silver; + public System.Drawing.Color RectMaskedColor = System.Drawing.Color.DarkSlateGray; + + public string image_copy_folder = ""; + + public string mqtt_serverandport = "mqtt:1883"; + public string mqtt_username = "user"; + public bool mqtt_UseTLS = false; + public string mqtt_password = "password"; + public string mqtt_clientid = "AITool"; + public string mqtt_LastWillTopic = "AITool/status"; + public string mqtt_LastWillPayload = "Offline"; + public string mqtt_OnlinePayload = "Online"; + + + public string pushover_APIKey = ""; + public string pushover_UserKey = ""; + public int pushover_cooldown_seconds = 5; + public int Pushover_RetryAfterFailSeconds = 300; //default to 5 minutes if telegram exception + + public bool Autoscroll_log = true; + public bool log_mnu_Filter = true; + public bool log_mnu_Highlight = false; + public int MaxGUILogItems = 5000; //makes to slow to work with if too high + public string DateFormat = "yyyy-MM-dd hh:mm:ss"; //"dd.MM.yy, HH:mm:ss"; + public string DisplayPercentageFormat = "({0:0}%)"; + public int TimeBetweenListRefreshsMS = 5000; + + public bool HistoryShowMask = true; + public bool HistoryShowObjects = true; + public bool HistoryOnlyDisplayRelevantObjects = true; + public bool HistoryFollow = true; + public bool HistoryAutoRefresh = true; + public bool HistoryStoreFalseAlerts = true; + public bool HistoryStoreMaskedAlerts = true; + public bool HistoryRestrictMinThresholdAtSource = true; + public bool HistoryFilterRelevant = false; + public bool HistoryFilterNoSuccess = false; + public bool HistoryFilterPeople = false; + public bool HistoryFilterAnimals = false; + public bool HistoryFilterVehicles = false; + public bool HistoryFilterSkipped = false; + public bool HistoryFilterMasked = false; + public bool HistoryMergeDuplicatePredictions = true; + public int HistoryHoursBetweenCleaning = 24; + + public int MaxHistoryAgeDays = 14; + + public string ObjectPriority = "Ambulance, person, people, bear, fox, elephant, car, truck, pickup truck, SUV, van, bicycle, motorcycle, motorbike, bus, license plate, dog, horse, kangaroo, boat, train, zebra, giraffe, cow, pig, skunk, raccoon, sheep, rabbit, cat, bird, squirrel"; + public string ObjectsExcluded = "Airplane, apple, backpack, banana, baseball bat, baseball glove, bed, bench, book, bottle, bowl, broccoli, cake, carrot, cell phone, chair, clock, couch, cup, dining table, donut, fire hydrant, fork, frisbee, handbag, hot dog, keyboard, kite, laptop, microwave, mouse, orange, oven, parking meter, pizza, potted plant, refrigerator, remote, sandwich, scissors, sink, skateboard, skis, snowboard, spoon, sports ball, stop sign, suitcase, surfboard, table, teddy bear, tennis racket, tie, toilet, toothbrush, traffic light, tv, umbrella,vase, wine glass"; + [JsonIgnore] + //make the following dictionary case insensitive, and do not store it + public Dictionary ObjectsExcludedDict = new Dictionary(StringComparer.OrdinalIgnoreCase); + + public string DefaultUserName = "Username"; + public string DefaultPasswordEncrypted = ""; + + public string BlueIrisServer = "127.0.0.1"; + + public string DOODSDetectorName = "default"; + public bool ScrewPutinTrumpAndWinniethePooh = true; + public int FileSystemWatcherRetryOnErrorTimeMS = 300000; //5 mins default + + public string AmazonAccessKeyId = ""; + public string AmazonSecretKey = ""; + public string AmazonRegionEndpoint = ""; //https://docs.aws.amazon.com/general/latest/gr/rande.html + public int AmazonMaxLabels = 15; + public int AmazonMinConfidence = 25; + + public string SightHoundAPIKey = ""; //https://accounts.sighthound.com/#/sighthound-cloud + + public int ActionCancelSeconds = 30; + public int ActionDelayMS = 250; + + public bool MinimizeToTray = true; + + public int MaxWaitForAIServerMS = 5000; + public bool MaxWaitForAIServerTimeoutError = true; + + public double LocalLatitude = 39.809734; //mid USA lat long used as default value since BI also does this + public double LocalLongitude = -98.555620; + + public List AIURLList = new List(); + + public List ImageAdjustProfiles = new List { new ClsImageAdjust("Default") }; + + public int SaveSettingsIntervalSeconds = 30; + + public bool Tick = false; + public string TickImageAddedSoundFile = "tick.wav"; + + //public bool TopMost = true; //keep the main form on top of all other windows + } + + public static ClsURLItem GetURL(string url = "", URLTypeEnum type = URLTypeEnum.Unknown) + { + ClsURLItem ret = null; + foreach (var cu in Settings.AIURLList) + { + if (string.Equals(cu.url, url, StringComparison.OrdinalIgnoreCase) || cu.Type == type) + { + return cu; + } + } + return ret; + + } + + + public static async Task SaveAsync(bool force = false) + { + //using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + double lastsecs = (DateTime.Now - _LastSaveTime).TotalSeconds; + + if (!force && lastsecs < AppSettings.Settings.SaveSettingsIntervalSeconds) + return false; + + + if (_AlreadySaving) + { + Log("Trace: *** Had to exit from save? ***"); + return false; + } + + //await semaphoreSlim.WaitAsync(); + _AlreadySaving = true; + + bool Ret = false; + try + { + //if (!Global.IsClassEqual(AppSettings.LastSettingsJSON, AppSettings.Settings)) + //{ + //keep a backup file in case of corruption + if (await IsFileValidAsync(AppSettings.Settings.SettingsFileName)) + { + // Only create the backup if we are forcing settings to be saved (like a regular setting is changed) + if (force) + { + if (File.Exists(AppSettings.Settings.SettingsFileName + ".bak")) + File.Delete(AppSettings.Settings.SettingsFileName + ".bak"); + if (File.Exists(AppSettings.Settings.SettingsFileName)) + File.Move(AppSettings.Settings.SettingsFileName, AppSettings.Settings.SettingsFileName + ".bak"); + } + } + else + { + //file corrupt or doesnt exist + if (File.Exists(AppSettings.Settings.SettingsFileName)) + { + Log("Error: Deleting corrupt settings file before resave: " + AppSettings.Settings.SettingsFileName); + File.Delete(AppSettings.Settings.SettingsFileName); + } + } + + if (GetCamera("default", ReturnDefault: true) == null) + { + //add a default camera + Camera cam = new Camera("Default"); + Settings.CameraList.Add(cam); + } + //update threshold in all masks if changed during session + foreach (Camera cam in AppSettings.Settings.CameraList) + { + cam.UpdateCamera(); + } + + Settings.SettingsValid = true; + String CurSettingsJSON = Global.WriteToJsonFile(AppSettings.Settings.SettingsFileName, Settings); + + if (!string.IsNullOrEmpty(CurSettingsJSON) && await IsFileValidAsync(AppSettings.Settings.SettingsFileName)) + { + Settings.SettingsValid = true; + Ret = true; + AppSettings.LastSettingsJSON = CurSettingsJSON; + //save a backup of settings to the registry since I've had a few times my raid array was going bad and I lost both backup and json files + Global.SaveRegSetting("BackupSettingsJSON", CurSettingsJSON); + Log($"Trace: JSON Settings saved to REGISTRY and {AppSettings.Settings.SettingsFileName}"); + } + else + { + Settings.SettingsValid = false; + Log($"Error: Failed to save Settings to {AppSettings.Settings.SettingsFileName}"); + } + + + if (!AITOOL.FaceMan.IsNull()) + { + AITOOL.FaceMan.SaveFaces(); + } + + //} + //else + //{ + // //does not need saving + // //Log("Settings have not changed, skipping save."); + // Ret = true; + // Settings.SettingsValid = true; + //} + + + } + catch (Exception ex) + { + + Log("Error: Could not save settings: " + ex.Msg()); + } + + if (!Ret) + { + try + { + //Revert to backup copy if exists AND is not corrupt + if (await IsFileValidAsync(AppSettings.Settings.SettingsFileName + ".bak")) + { + Log("Error: Settings save failed, reverting to backup copy: " + AppSettings.Settings.SettingsFileName + ".bak"); + + if (File.Exists(AppSettings.Settings.SettingsFileName)) + File.Delete(AppSettings.Settings.SettingsFileName); + + File.Move(AppSettings.Settings.SettingsFileName + ".bak", AppSettings.Settings.SettingsFileName); + } + else + { + Log("Error: Settings save failed, Backup copy is not found or is corrupt: " + AppSettings.Settings.SettingsFileName + ".bak"); + } + + } + catch (Exception ex) + { + + Log("Error: Could not save settings: " + ex.Msg()); + } + } + + //semaphoreSlim.Release(); + + _AlreadySaving = false; + + _LastSaveTime = DateTime.Now; + + return Ret; + } + + public static async Task IsFileValidAsync(string Filename, long MinSize = 800) + { + return await Task.Run(() => IsFileValidInternal(Filename, MinSize)); + } + + public static bool IsFileValidInternal(string Filename, long MinSize = 800) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + bool Ret = false; + try + { + if (File.Exists(Filename)) + { + FileInfo fi = new FileInfo(Filename); + if (fi.Length > MinSize) + { + //try to prevent multiple threads from erroring out writing the json file... + Global.WaitFileAccessResult result = Global.WaitForFileAccess(Filename, FileAccess.Read, FileShare.ReadWrite, 5000); + if (result.Success) + { + //check its contents, 0 bytes indicate corruption + string contents = File.ReadAllText(Filename); + if (!contents.Contains("\0")) + { + if (contents.TrimStart().StartsWith("{") && contents.TrimEnd().EndsWith("}")) + { + Ret = true; + } + else + { + Log($"Error: Settings file does not look like JSON (size={fi.Length} bytes): {Filename}"); + } + } + else + { + Log($"Error: Settings file contains null bytes, corrupt: (size={fi.Length} bytes): " + Filename); + } + } + else + { + Log($"Error: Could not gain access to file for {result.TimeMS}ms - {Filename}"); + } + + } + else + { + Log($"Error: Settings file is too small at {fi.Length} bytes: {Filename}"); + } + } + + else + { + Log("Settings file does not exist yet: " + Filename); + } + } + catch (Exception ex) + { + + Log($"Error: While validating settings file '{Filename}' got error '{ex.Message}'."); + } + return Ret; + } + + public static void UpdateSettingsLocation() + { + try + { + //original: + //public string SettingsFileName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().Location) + ".Settings.JSON"); + //public string LogFileName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().Location) + ".LOG"); + //public string HistoryDBFileName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().Location) + ".Database.SQLITE3"); + + string OrigFolder = AppDomain.CurrentDomain.BaseDirectory.TrimEnd(@"\".ToCharArray()); + string OldFolder = !string.IsNullOrEmpty(Settings.SettingsFileName) ? Path.GetDirectoryName(Settings.SettingsFileName) : OrigFolder; + string OldSettingsFile = Settings.SettingsFileName; + + string ExePath = AppDomain.CurrentDomain.BaseDirectory.TrimEnd(@"\".ToCharArray()); + string FullExePath = System.IO.Path.Combine(System.AppContext.BaseDirectory, System.Reflection.Assembly.GetEntryAssembly().GetName().Name + ".exe"); + + //string OrigSettingsFilename = Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().Location) + ".Settings.JSON"; + string OrigSettingsFilename = Path.GetFileNameWithoutExtension(FullExePath) + ".Settings.JSON"; + string OrigLogFilename = Path.GetFileNameWithoutExtension(FullExePath) + ".LOG"; + string OrigHistoryDBFilename = Path.GetFileNameWithoutExtension(FullExePath) + ".Database.SQLITE3"; + + //Just always look in original location for legacy import + Settings.HistoryFileName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "cameras\\history.csv"); + + string NewFolder = ""; + string OrigSettingsFile = ""; + string NewSettingsFile = ""; + string NewLogFile = ""; + string NewHistoryFile = ""; + + if (!Settings.SettingsPortable) + { + //Set to appdata folder + NewFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "AITOOL", "_Settings"); + } + else + { + //set to aitool subfolder + NewFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "_Settings"); + } + + + bool NewDirExists = Directory.Exists(NewFolder); + + if (!NewDirExists) + Directory.CreateDirectory(NewFolder); + + OrigSettingsFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "AITOOL.Settings.JSON"); + OldSettingsFile = Path.Combine(OldFolder, OrigSettingsFilename); + NewSettingsFile = Path.Combine(NewFolder, OrigSettingsFilename); + + if (Settings.CustomLogFilePath.IsEmpty() || !Directory.Exists(Settings.CustomLogFilePath)) + NewLogFile = Path.Combine(NewFolder, "Logs", OrigLogFilename); + else + NewLogFile = Path.Combine(Settings.CustomLogFilePath, OrigLogFilename); + + + NewHistoryFile = Path.Combine(NewFolder, OrigHistoryDBFilename); + + bool OrigSettingsFileExists = File.Exists(OrigSettingsFile); + bool OldSettingsFileExists = File.Exists(OldSettingsFile); + bool NewSettingFileExists = File.Exists(NewSettingsFile); + bool ChangedLocation = !OldFolder.Equals(NewFolder, StringComparison.OrdinalIgnoreCase) || !NewSettingFileExists; + + Settings.SettingsFileName = NewSettingsFile; + Settings.LogFileName = NewLogFile; + Settings.HistoryDBFileName = NewHistoryFile; + + string OldCamFolder = Path.GetDirectoryName(Settings.HistoryFileName); + string OldCamFolderWarn = Path.Combine(OldCamFolder, "_THIS_FOLDER_NOT_USED"); + if (Directory.Exists(OldCamFolder) && !File.Exists(OldCamFolderWarn)) + File.WriteAllText(OldCamFolderWarn, "6, 28, 496, 8128 oh and 42"); + + if (ChangedLocation) + { + + //make sure log folder exists + + if (OldSettingsFileExists) + { + //this is only for when changing from portable and back... assume everything in \_settings subfolder and \_settings\Logs + Log($"Debug: Moving settings folder from old '{OldFolder}' to '{NewFolder}'..."); + + //Get from old camera folder first + Global.MoveFiles(Settings.HistoryFileName, NewFolder, "*.BMP|*.PNG", true); + Global.MoveFiles(OldFolder, NewFolder, "*.BMP|*.PNG", true); + Global.MoveFiles(OldFolder + "\\Logs", Settings.LogFileName, "AITOOL*.log|AITOOL.[*.ZIP", true); + Global.MoveFiles(OldFolder, Settings.LogFileName, "AITOOL*.log|AITOOL.[*.ZIP", true); + Global.MoveFiles(OldFolder, NewFolder, "*.JSON|*.JSON.BAK|*.BMP|*.PNG|*.SQLITE3|*.SQLITE3.BAK|*.CSV", true); + } + else if (OrigSettingsFileExists) + { + Log($"Debug: Moving settings folder from original '{OrigFolder}' to '{NewFolder}'..."); + Global.MoveFiles(Settings.HistoryFileName, NewFolder, "*.BMP|*.PNG", true); + Global.MoveFiles(OrigFolder, NewFolder, "*.BMP|*.PNG", true); + Global.MoveFiles(OrigFolder, Settings.LogFileName, "AITOOL*.log|AITOOL.[*.ZIP", true); + Global.MoveFiles(OrigFolder + "\\Logs", Settings.LogFileName, "AITOOL*.log|AITOOL.[*.ZIP", true); + Global.MoveFiles(OrigFolder, NewFolder, "*.JSON|*.JSON.BAK|*.BMP|*.PNG|*.SQLITE3|*.SQLITE3.BAK|*.CSV", true); + } + else + { + Log($"Debug: Using settings folder '{NewFolder}'."); + } + + } + else + { + Log("Debug: Settings folder did not change."); + } + + + + } + catch (Exception ex) + { + + Log("Error: " + ex.Msg()); + } + + } + public static async Task LoadAsync() + { + await semaphoreSlim.WaitAsync(); + + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + bool Ret = false; + try + { + //////multiple threads may be trying to save at the same time + ////lock (ThreadLock) + //{ + Settings.SettingsValid = false; //assume failure + bool Resave = false; + + UpdateSettingsLocation(); //save to \settings folder or appdata\settings + + //get backup json from registry + + AppSettings.LastSettingsJSON = Global.GetRegSetting("BackupSettingsJSON", ""); + + bool IsSettingsFileValid = await IsFileValidAsync(AppSettings.Settings.SettingsFileName); + bool SettingsFileExists = File.Exists(AppSettings.Settings.SettingsFileName); + bool IsSettingsBakFileValid = await IsFileValidAsync(AppSettings.Settings.SettingsFileName + ".bak"); + string LastRunPath = Global.GetRegSetting("LastRunPath", ""); + string LastSettingsFile = ""; + string LastSettingsFolder = ""; + + if (!LastRunPath.IsEmpty() && !Directory.GetCurrentDirectory().EqualsIgnoreCase(LastRunPath) && Directory.Exists(LastRunPath)) + { + LastSettingsFolder = Path.Combine(LastRunPath, "_SETTINGS"); + LastSettingsFile = Path.Combine(LastSettingsFolder, "AITOOL.Settings.JSON"); + if (!File.Exists(LastSettingsFile)) + { + LastSettingsFile = ""; + LastSettingsFolder = ""; + } + } + //read the old configuration file + if (!IsSettingsFileValid && !IsSettingsBakFileValid && string.IsNullOrEmpty(AppSettings.LastSettingsJSON)) + { + //-------------------------------------------------------------------------------------------------------------------- + //try to read in OLD aitool.exe.config or user.config files - they were + //unreliable because of strict versioning, etc + // + // NO NEED to add NEW settings to this area + //-------------------------------------------------------------------------------------------------------------------- + + List filist = new List(); + FileInfo fi = new FileInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, Path.GetFileName(Assembly.GetEntryAssembly().Location) + ".config")); + if (fi.Exists) + filist.Add(fi); + filist.AddRange(Global.GetFiles(Path.Combine(Environment.GetEnvironmentVariable("LOCALAPPDATA"), "WindowsFormsApp2"), "user.config")); + //sort by date + filist = filist.OrderByDescending((d) => d.LastWriteTime).ToList(); + + Log("First time load, reading old config file: " + filist[0].FullName); + + XDocument xmlfile = XDocument.Load(filist[0].FullName); + // + // + // + // + // + // + // + // + // + // + // D:\BlueIrisStorage\AIInput + // + + IEnumerable els = xmlfile.XPathSelectElements("/configuration/userSettings/WindowsFormsApp2.Properties.Settings/setting"); + if (els == null || els.Count() == 0) + els = xmlfile.XPathSelectElements("/configuration/applicationSettings/WindowsFormsApp2.Properties.Settings/setting"); + + int cnt = 0; + foreach (XElement el in els) + { + string val = el.Value; + if (!string.IsNullOrEmpty(val)) + { + if (el.ToString().Contains("telegram_token")) { Settings.telegram_token = val; cnt += 1; } + if (el.ToString().Contains("telegram_chatids")) { Settings.telegram_chatids = val.SplitStr(","); cnt += 1; } + if (el.ToString().Contains("input_path")) { Settings.input_path = val; cnt += 1; } + if (el.ToString().Contains("deepstack_url")) { Settings.deepstack_url = val; cnt += 1; } + if (el.ToString().Contains("log_everything")) { Settings.log_everything = Convert.ToBoolean(val); cnt += 1; } + if (el.ToString().Contains("send_errors")) { Settings.send_telegram_errors = Convert.ToBoolean(val); cnt += 1; } + if (el.ToString().Contains("close_instantly")) { Settings.close_instantly = Convert.ToInt32(val); cnt += 1; } + + } + } + + Resave = (cnt > 0); + Settings.SettingsValid = true; + + } + else if (IsSettingsFileValid) + { + //Load regular settings file + Log("Debug: Loading settings from " + AppSettings.Settings.SettingsFileName); + Settings = Global.ReadFromJsonFile(AppSettings.Settings.SettingsFileName); + } + else if (IsSettingsBakFileValid) + { + //revert to backup if its good + Log("Error: Reverting to backup settings file: " + AppSettings.Settings.SettingsFileName + ".bak"); + Log("Loading settings from " + AppSettings.Settings.SettingsFileName + ".bak"); + Settings = Global.ReadFromJsonFile(AppSettings.Settings.SettingsFileName + ".bak"); + } + else if (!string.IsNullOrEmpty(AppSettings.LastSettingsJSON) && SettingsFileExists) + { + //revert to REGISTRY backup if its good AND the main settings file doesnt exist at all (so someone can delete the settings file to reset settings) + Log("Error: Reverting to REGISTRY backup settings..."); + Settings = Global.SetJSONString(AppSettings.LastSettingsJSON); + + } + else if (!LastSettingsFile.IsEmpty() && !SettingsFileExists && !Global.IsService) + { + //revert to REGISTRY backup and look for the last folder to migrate settings from + string newfolder = Path.GetDirectoryName(Settings.SettingsFileName); + Log($"Debug: Previous settings found at {LastSettingsFolder} but not {newfolder}..."); + if (System.Windows.Forms.MessageBox.Show($"Would you like to migrate settings from the last folder?\r\n\r\n{LastSettingsFolder}", "Migrate Settings?", System.Windows.Forms.MessageBoxButtons.YesNo, System.Windows.Forms.MessageBoxIcon.Question) == System.Windows.Forms.DialogResult.Yes) + { + //copy the old settings folder: + Log($"Debug: Copying settings folder from original '{LastSettingsFolder}' to '{newfolder}'..."); + Global.MoveFiles(LastSettingsFolder, newfolder, "*.*", true, true); + Log("Debug: Loading settings from " + AppSettings.Settings.SettingsFileName); + Settings = Global.ReadFromJsonFile(AppSettings.Settings.SettingsFileName); + } + else + { + Log("Debug: Did NOT import settings from folder."); + } + + } + + else if (!AppSettings.LastSettingsJSON.IsEmpty() && !SettingsFileExists && !Global.IsService) + { + //revert to REGISTRY backup and look for the last folder to migrate settings from + string newfolder = Path.GetDirectoryName(Settings.SettingsFileName); + Log($"Debug: Previous settings found in the registry but not {newfolder}..."); + if (System.Windows.Forms.MessageBox.Show($"Would you like to migrate settings from the last saved REGISTRY copy?", "Migrate Settings?", System.Windows.Forms.MessageBoxButtons.YesNo, System.Windows.Forms.MessageBoxIcon.Question) == System.Windows.Forms.DialogResult.Yes) + { + //copy the old settings folder: + Log("Debug: Loading settings from previous REGISTRY..."); + Settings = Global.SetJSONString(AppSettings.LastSettingsJSON); + } + else + { + Log("Debug: Did NOT import settings from previous registry copy."); + } + + } + else + { + + //nothing valid + Log("Error: Settings file AND backup were missing or corrupt."); + + if (File.Exists(AppSettings.Settings.SettingsFileName)) + File.Delete(AppSettings.Settings.SettingsFileName); + + if (File.Exists(AppSettings.Settings.SettingsFileName + ".bak")) + File.Delete(AppSettings.Settings.SettingsFileName + ".bak"); + + } + + if (Settings != null) + { + + //The client identifier (ClientId) identifies each MQTT client that connects to an MQTT broker. + //The broker uses the ClientID to identify the client and the current state of the client.Therefore, + //this ID should be unique per client and broker. In MQTT 3.1.1 (the current standard), you can send + //an empty ClientId, if you don’t need a state to be held by the broker. The empty ClientID results + //in a connection without any state. In this case, the clean session flag must be set to true or + //the broker will reject the connection. + + if (Settings.mqtt_clientid.EqualsIgnoreCase("aitool")) + Settings.mqtt_clientid = "AITool-BATMAN-" + Global.GetMacAddress(); //set the id to a unique number that should not *usually* change on a machine - the mac address of an active adapter + //but only set it once if the default clientid is set + + if (Settings.telegram_cooldown_minutes > -1) + { + Settings.telegram_cooldown_seconds = Convert.ToInt32(Math.Round(TimeSpan.FromMinutes(Settings.telegram_cooldown_minutes).TotalSeconds, 0)); + Settings.telegram_cooldown_minutes = -1; + } + + UpdateSettingsLocation(); //save to \settings folder or appdata\settings + + + //load cameras the old way if needed + if (Settings.CameraList.Count == 0) + { + string camerafolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "cameras"); + Log("No cameras loaded in settings, trying to load old camera files from " + camerafolder); + List files = Global.GetFiles(camerafolder, "*.txt"); //load all settings files in a string array + //Sort so more recent files are processed first - to make sure any dupes that are skipped are older + //I *think* this logic works? + files = files.OrderByDescending((d) => d.LastWriteTime).ToList(); + //create a camera object for every camera settings file + int cnt = 0; + foreach (FileInfo file in files) + { + + //check if camera with specified name or its prefix already exists. If yes, then abort. + bool fnd = false; + foreach (Camera c in AppSettings.Settings.CameraList) + { + if (string.Equals(c.Name, Path.GetFileNameWithoutExtension(file.FullName), StringComparison.OrdinalIgnoreCase)) + { + fnd = true; + } + else if (string.Equals(c.Prefix, System.IO.File.ReadAllLines(file.FullName)[2].Split('"')[1], StringComparison.OrdinalIgnoreCase)) + { + fnd = true; + } + } + if (!fnd) + { + cnt++; + Camera cam = new Camera(); //create new camera object + cam.ReadConfig(file.FullName); //read camera's config from file + AppSettings.Settings.CameraList.Add(cam); //add created camera object to CameraList + } + else + { + Log("Skipped duplicate camera: " + file); + } + + } + + if (cnt > 0) + { + Log($"...Loaded {cnt} camera files."); + } + else + { + Log($"...NO old camera txt files could be loaded."); + } + + Resave = (cnt > 1); + + } + + Camera DefaultCam = GetCamera("default", ReturnDefault: true); + if (DefaultCam == null) + { + //add a default camera + Camera cam = new Camera("Default"); + Settings.CameraList.Add(cam); + } + else if (DefaultCam.DefaultTriggeringObjects == null) + { + //replace the old default camera with a new one or else we have trouble with the triggering objects manager + Camera cam = new Camera(DefaultCam.Name); + Settings.CameraList.Remove(DefaultCam); + Settings.CameraList.Add(cam); + } + + //make sure we have the updated default object list + if (!Settings.ObjectPriority.Has("suv") || !Settings.ObjectPriority.Has("van") || !Settings.ObjectPriority.Has("people") || !Settings.ObjectPriority.Has("fox") || !Settings.ObjectPriority.Has("raccoon") || !Settings.ObjectPriority.Has("license plate") || !Settings.ObjectPriority.Has("kangaroo")) + Settings.ObjectPriority = "Ambulance, person, people, bear, fox, elephant, car, truck, pickup truck, SUV, van, bicycle, motorcycle, motorbike, bus, license plate, dog, kangaroo, horse, boat, train, zebra, giraffe, cow, pig, skunk, raccoon, sheep, rabbit, cat, bird, squirrel"; + + //convert comma delimited list of objects stored in Settings.ObjectsExcluded to a dictionary + List splt = Settings.ObjectsExcluded.SplitStr(",", true); + foreach (string obj in splt) + { + if (!Settings.ObjectsExcludedDict.ContainsKey(obj)) + Settings.ObjectsExcludedDict.Add(obj, obj); + } + + + //make sure everything in the cameras look correct: + foreach (Camera cam in Settings.CameraList) + { + cam.UpdateCamera(); + } + + + //sort the camera list: + AppSettings.Settings.CameraList = AppSettings.Settings.CameraList.OrderBy((d) => d.Name).ToList(); + + AITOOL.UpdateAIURLList(true); + + //clean up image adjust list + List iaps = AppSettings.Settings.ImageAdjustProfiles; + + AppSettings.Settings.ImageAdjustProfiles.Clear(); + + for (int i = 0; i < iaps.Count; i++) + { + if (!AppSettings.Settings.ImageAdjustProfiles.Contains(iaps[i])) + Settings.ImageAdjustProfiles.Add(iaps[i]); + } + + for (int i = 0; i < AppSettings.Settings.AIURLList.Count; i++) + { + AppSettings.Settings.AIURLList[i].Order = i + 1; + //if (!AITOOL.HasImageAdjustProfile(AppSettings.Settings.AIURLList[i].ImageAdjustProfile)) + //{ + // AppSettings.Settings.AIURLList[i].ImageAdjustProfile = "Default"; + //} + } + + string facefile = Path.Combine(Settings.FacesPath, "Faces.JSON"); + + if (Settings.FacesPath.IsEmpty() || !Global.IsDirWritable(Settings.FacesPath, true) || !File.Exists(facefile)) + { + string pth = Path.GetDirectoryName(Settings.SettingsFileName); + Settings.FacesPath = Path.Combine(pth, "FaceStorage"); + facefile = Path.Combine(Settings.FacesPath, "Faces.JSON"); + } + + + if (await IsFileValidAsync(facefile, 400)) + AITOOL.FaceMan = Global.ReadFromJsonFile(facefile); + else + AITOOL.FaceMan = new ClsFaceManager(); + + if (AITOOL.FaceMan == null) + AITOOL.FaceMan = new ClsFaceManager(); + + + Ret = true; + } + else + { + Log("Error: Could not load settings?"); + } + + if (Resave) + { + //we imported old settings, save them + SaveAsync(true); + } + + //} + + } + catch (Exception ex) + { + + Log("Error: Could not save settings: " + ex.Msg()); + } + finally + { + semaphoreSlim.Release(); + } + + + return Ret; + } + + + } + + +} diff --git a/src/UI/Shell.Designer.cs b/src/UI/Shell.Designer.cs index 2293a05b..e809d140 100644 --- a/src/UI/Shell.Designer.cs +++ b/src/UI/Shell.Designer.cs @@ -1,5 +1,4 @@ -using System.IO; -namespace WindowsFormsApp2 +namespace AITool { partial class Shell { @@ -29,7 +28,7 @@ protected override void Dispose(bool disposing) /// private void InitializeComponent() { - this.components = new System.ComponentModel.Container(); + components = new System.ComponentModel.Container(); System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Shell)); System.Windows.Forms.DataVisualization.Charting.ChartArea chartArea1 = new System.Windows.Forms.DataVisualization.Charting.ChartArea(); System.Windows.Forms.DataVisualization.Charting.Series series1 = new System.Windows.Forms.DataVisualization.Charting.Series(); @@ -39,383 +38,623 @@ private void InitializeComponent() System.Windows.Forms.DataVisualization.Charting.Series series4 = new System.Windows.Forms.DataVisualization.Charting.Series(); System.Windows.Forms.DataVisualization.Charting.Series series5 = new System.Windows.Forms.DataVisualization.Charting.Series(); System.Windows.Forms.DataVisualization.Charting.Series series6 = new System.Windows.Forms.DataVisualization.Charting.Series(); + System.Windows.Forms.DataVisualization.Charting.Series series7 = new System.Windows.Forms.DataVisualization.Charting.Series(); System.Windows.Forms.DataVisualization.Charting.ChartArea chartArea3 = new System.Windows.Forms.DataVisualization.Charting.ChartArea(); System.Windows.Forms.DataVisualization.Charting.Legend legend1 = new System.Windows.Forms.DataVisualization.Charting.Legend(); - System.Windows.Forms.DataVisualization.Charting.Series series7 = new System.Windows.Forms.DataVisualization.Charting.Series(); + System.Windows.Forms.DataVisualization.Charting.Series series8 = new System.Windows.Forms.DataVisualization.Charting.Series(); System.Windows.Forms.DataVisualization.Charting.DataPoint dataPoint1 = new System.Windows.Forms.DataVisualization.Charting.DataPoint(1D, 30D); System.Windows.Forms.DataVisualization.Charting.DataPoint dataPoint2 = new System.Windows.Forms.DataVisualization.Charting.DataPoint(1D, 30D); System.Windows.Forms.DataVisualization.Charting.DataPoint dataPoint3 = new System.Windows.Forms.DataVisualization.Charting.DataPoint(1D, 30D); System.Windows.Forms.DataVisualization.Charting.Title title1 = new System.Windows.Forms.DataVisualization.Charting.Title(); - this.notifyIcon = new System.Windows.Forms.NotifyIcon(this.components); - this.tabControl1 = new System.Windows.Forms.TabControl(); - this.tabOverview = new System.Windows.Forms.TabPage(); - this.tableLayoutPanel14 = new WindowsFormsApp2.DBLayoutPanel(this.components); - this.tableLayoutPanel15 = new WindowsFormsApp2.DBLayoutPanel(this.components); - this.pictureBox2 = new System.Windows.Forms.PictureBox(); - this.label2 = new System.Windows.Forms.Label(); - this.label3 = new System.Windows.Forms.Label(); - this.lbl_version = new System.Windows.Forms.Label(); - this.lbl_errors = new System.Windows.Forms.Label(); - this.lbl_info = new System.Windows.Forms.Label(); - this.tabStats = new System.Windows.Forms.TabPage(); - this.tableLayoutPanel16 = new WindowsFormsApp2.DBLayoutPanel(this.components); - this.tableLayoutPanel23 = new WindowsFormsApp2.DBLayoutPanel(this.components); - this.label8 = new System.Windows.Forms.Label(); - this.chart_confidence = new System.Windows.Forms.DataVisualization.Charting.Chart(); - this.timeline = new System.Windows.Forms.DataVisualization.Charting.Chart(); - this.label7 = new System.Windows.Forms.Label(); - this.tableLayoutPanel17 = new WindowsFormsApp2.DBLayoutPanel(this.components); - this.chart1 = new System.Windows.Forms.DataVisualization.Charting.Chart(); - this.comboBox1 = new System.Windows.Forms.ComboBox(); - this.tabHistory = new System.Windows.Forms.TabPage(); - this.tableLayoutPanel1 = new WindowsFormsApp2.DBLayoutPanel(this.components); - this.tableLayoutPanel21 = new WindowsFormsApp2.DBLayoutPanel(this.components); - this.pictureBox1 = new System.Windows.Forms.PictureBox(); - this.tableLayoutPanel22 = new WindowsFormsApp2.DBLayoutPanel(this.components); - this.cb_showObjects = new System.Windows.Forms.CheckBox(); - this.cb_showMask = new System.Windows.Forms.CheckBox(); - this.lbl_objects = new System.Windows.Forms.Label(); - this.splitContainer1 = new System.Windows.Forms.SplitContainer(); - this.tableLayoutPanel19 = new WindowsFormsApp2.DBLayoutPanel(this.components); - this.cb_showFilters = new System.Windows.Forms.CheckBox(); - this.list1 = new System.Windows.Forms.ListView(); - this.panel1 = new System.Windows.Forms.Panel(); - this.comboBox_filter_camera = new System.Windows.Forms.ComboBox(); - this.cb_filter_nosuccess = new System.Windows.Forms.CheckBox(); - this.cb_filter_success = new System.Windows.Forms.CheckBox(); - this.cb_filter_person = new System.Windows.Forms.CheckBox(); - this.cb_filter_vehicle = new System.Windows.Forms.CheckBox(); - this.cb_filter_animal = new System.Windows.Forms.CheckBox(); - this.tabCameras = new System.Windows.Forms.TabPage(); - this.tableLayoutPanel2 = new WindowsFormsApp2.DBLayoutPanel(this.components); - this.tableLayoutPanel3 = new WindowsFormsApp2.DBLayoutPanel(this.components); - this.list2 = new System.Windows.Forms.ListView(); - this.btnCameraAdd = new System.Windows.Forms.Button(); - this.tableLayoutPanel6 = new WindowsFormsApp2.DBLayoutPanel(this.components); - this.tableLayoutPanel7 = new WindowsFormsApp2.DBLayoutPanel(this.components); - this.tableLayoutPanel8 = new WindowsFormsApp2.DBLayoutPanel(this.components); - this.cb_person = new System.Windows.Forms.CheckBox(); - this.cb_bicycle = new System.Windows.Forms.CheckBox(); - this.cb_motorcycle = new System.Windows.Forms.CheckBox(); - this.cb_bear = new System.Windows.Forms.CheckBox(); - this.cb_cow = new System.Windows.Forms.CheckBox(); - this.cb_sheep = new System.Windows.Forms.CheckBox(); - this.cb_horse = new System.Windows.Forms.CheckBox(); - this.cb_bird = new System.Windows.Forms.CheckBox(); - this.cb_dog = new System.Windows.Forms.CheckBox(); - this.cb_cat = new System.Windows.Forms.CheckBox(); - this.cb_airplane = new System.Windows.Forms.CheckBox(); - this.cb_boat = new System.Windows.Forms.CheckBox(); - this.cb_bus = new System.Windows.Forms.CheckBox(); - this.cb_truck = new System.Windows.Forms.CheckBox(); - this.cb_car = new System.Windows.Forms.CheckBox(); - this.label1 = new System.Windows.Forms.Label(); - this.tableLayoutPanel9 = new WindowsFormsApp2.DBLayoutPanel(this.components); - this.tableLayoutPanel10 = new WindowsFormsApp2.DBLayoutPanel(this.components); - this.lblTriggerUrl = new System.Windows.Forms.Label(); - this.tbTriggerUrl = new System.Windows.Forms.TextBox(); - this.cb_telegram = new System.Windows.Forms.CheckBox(); - this.tableLayoutPanel20 = new WindowsFormsApp2.DBLayoutPanel(this.components); - this.label5 = new System.Windows.Forms.Label(); - this.tb_cooldown = new System.Windows.Forms.TextBox(); - this.label6 = new System.Windows.Forms.Label(); - this.lblPrefix = new System.Windows.Forms.Label(); - this.lblName = new System.Windows.Forms.Label(); - this.tableLayoutPanel12 = new WindowsFormsApp2.DBLayoutPanel(this.components); - this.tbPrefix = new System.Windows.Forms.TextBox(); - this.lbl_prefix = new System.Windows.Forms.Label(); - this.tableLayoutPanel13 = new WindowsFormsApp2.DBLayoutPanel(this.components); - this.tbName = new System.Windows.Forms.TextBox(); - this.cb_enabled = new System.Windows.Forms.CheckBox(); - this.lblRelevantObjects = new System.Windows.Forms.Label(); - this.lbl_threshold = new System.Windows.Forms.Label(); - this.tableLayoutPanel24 = new WindowsFormsApp2.DBLayoutPanel(this.components); - this.lbl_threshold_lower = new System.Windows.Forms.Label(); - this.tb_threshold_upper = new System.Windows.Forms.TextBox(); - this.lbl_threshold_upper = new System.Windows.Forms.Label(); - this.tb_threshold_lower = new System.Windows.Forms.TextBox(); - this.label9 = new System.Windows.Forms.Label(); - this.label10 = new System.Windows.Forms.Label(); - this.tableLayoutPanel11 = new WindowsFormsApp2.DBLayoutPanel(this.components); - this.btnCameraSave = new System.Windows.Forms.Button(); - this.btnCameraDel = new System.Windows.Forms.Button(); - this.lbl_camstats = new System.Windows.Forms.Label(); - this.tabSettings = new System.Windows.Forms.TabPage(); - this.tableLayoutPanel4 = new WindowsFormsApp2.DBLayoutPanel(this.components); - this.tableLayoutPanel5 = new WindowsFormsApp2.DBLayoutPanel(this.components); - this.label4 = new System.Windows.Forms.Label(); - this.tableLayoutPanel25 = new WindowsFormsApp2.DBLayoutPanel(this.components); - this.btn_open_log = new System.Windows.Forms.Button(); - this.cb_log = new System.Windows.Forms.CheckBox(); - this.label12 = new System.Windows.Forms.Label(); - this.cb_send_errors = new System.Windows.Forms.CheckBox(); - this.lbl_deepstackurl = new System.Windows.Forms.Label(); - this.lbl_input = new System.Windows.Forms.Label(); - this.tbDeepstackUrl = new System.Windows.Forms.TextBox(); - this.lbl_telegram_token = new System.Windows.Forms.Label(); - this.lbl_telegram_chatid = new System.Windows.Forms.Label(); - this.tb_telegram_token = new System.Windows.Forms.TextBox(); - this.tb_telegram_chatid = new System.Windows.Forms.TextBox(); - this.tableLayoutPanel18 = new WindowsFormsApp2.DBLayoutPanel(this.components); - this.tbInput = new System.Windows.Forms.TextBox(); - this.btn_input_path = new System.Windows.Forms.Button(); - this.BtnSettingsSave = new System.Windows.Forms.Button(); - this.tabControl1.SuspendLayout(); - this.tabOverview.SuspendLayout(); - this.tableLayoutPanel14.SuspendLayout(); - this.tableLayoutPanel15.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox2)).BeginInit(); - this.tabStats.SuspendLayout(); - this.tableLayoutPanel16.SuspendLayout(); - this.tableLayoutPanel23.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.chart_confidence)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.timeline)).BeginInit(); - this.tableLayoutPanel17.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.chart1)).BeginInit(); - this.tabHistory.SuspendLayout(); - this.tableLayoutPanel1.SuspendLayout(); - this.tableLayoutPanel21.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit(); - this.tableLayoutPanel22.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); - this.splitContainer1.Panel1.SuspendLayout(); - this.splitContainer1.Panel2.SuspendLayout(); - this.splitContainer1.SuspendLayout(); - this.tableLayoutPanel19.SuspendLayout(); - this.panel1.SuspendLayout(); - this.tabCameras.SuspendLayout(); - this.tableLayoutPanel2.SuspendLayout(); - this.tableLayoutPanel3.SuspendLayout(); - this.tableLayoutPanel6.SuspendLayout(); - this.tableLayoutPanel7.SuspendLayout(); - this.tableLayoutPanel8.SuspendLayout(); - this.tableLayoutPanel9.SuspendLayout(); - this.tableLayoutPanel10.SuspendLayout(); - this.tableLayoutPanel20.SuspendLayout(); - this.tableLayoutPanel12.SuspendLayout(); - this.tableLayoutPanel13.SuspendLayout(); - this.tableLayoutPanel24.SuspendLayout(); - this.tableLayoutPanel11.SuspendLayout(); - this.tabSettings.SuspendLayout(); - this.tableLayoutPanel4.SuspendLayout(); - this.tableLayoutPanel5.SuspendLayout(); - this.tableLayoutPanel25.SuspendLayout(); - this.tableLayoutPanel18.SuspendLayout(); - this.SuspendLayout(); + notifyIcon = new System.Windows.Forms.NotifyIcon(components); + TraycontextMenuStrip = new System.Windows.Forms.ContextMenuStrip(components); + pauseAllToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + resumeAllToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + pauseToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + exitToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + tabControl1 = new System.Windows.Forms.TabControl(); + tabOverview = new System.Windows.Forms.TabPage(); + tableLayoutPanel14 = new DBLayoutPanel(components); + tableLayoutPanel15 = new DBLayoutPanel(components); + pictureBox2 = new System.Windows.Forms.PictureBox(); + label2 = new System.Windows.Forms.Label(); + label3 = new System.Windows.Forms.Label(); + lbl_version = new System.Windows.Forms.Label(); + lbl_errors = new System.Windows.Forms.Label(); + lbl_info = new System.Windows.Forms.Label(); + lblQueue = new System.Windows.Forms.Label(); + tabStats = new System.Windows.Forms.TabPage(); + tableLayoutPanel16 = new DBLayoutPanel(components); + tableLayoutPanel23 = new DBLayoutPanel(components); + label8 = new System.Windows.Forms.Label(); + chart_confidence = new System.Windows.Forms.DataVisualization.Charting.Chart(); + timeline = new System.Windows.Forms.DataVisualization.Charting.Chart(); + label7 = new System.Windows.Forms.Label(); + tableLayoutPanel17 = new DBLayoutPanel(components); + chart1 = new System.Windows.Forms.DataVisualization.Charting.Chart(); + comboBox1 = new System.Windows.Forms.ComboBox(); + btn_resetstats = new System.Windows.Forms.Button(); + tabHistory = new System.Windows.Forms.TabPage(); + toolStrip1 = new System.Windows.Forms.ToolStrip(); + comboBox_filter_camera = new System.Windows.Forms.ToolStripComboBox(); + toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator(); + toolStripDropDownButtonFilters = new System.Windows.Forms.ToolStripDropDownButton(); + cb_filter_success = new System.Windows.Forms.ToolStripMenuItem(); + cb_filter_nosuccess = new System.Windows.Forms.ToolStripMenuItem(); + cb_filter_person = new System.Windows.Forms.ToolStripMenuItem(); + cb_filter_animal = new System.Windows.Forms.ToolStripMenuItem(); + cb_filter_vehicle = new System.Windows.Forms.ToolStripMenuItem(); + cb_filter_skipped = new System.Windows.Forms.ToolStripMenuItem(); + cb_filter_masked = new System.Windows.Forms.ToolStripMenuItem(); + toolStripDropDownButtonOptions = new System.Windows.Forms.ToolStripDropDownButton(); + cb_showMask = new System.Windows.Forms.ToolStripMenuItem(); + cb_showObjects = new System.Windows.Forms.ToolStripMenuItem(); + showOnlyRelevantObjectsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + cb_follow = new System.Windows.Forms.ToolStripMenuItem(); + automaticallyRefreshToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + storeFalseAlertsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + storeMaskedAlertsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + restrictThresholdAtSourceToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + mergeDuplicatePredictionsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + toolStripButtonDetails = new System.Windows.Forms.ToolStripButton(); + toolStripButtonMaskDetails = new System.Windows.Forms.ToolStripButton(); + toolStripButtonEditImageMask = new System.Windows.Forms.ToolStripButton(); + toolStripSeparator9 = new System.Windows.Forms.ToolStripSeparator(); + toolStripButtonEditURL = new System.Windows.Forms.ToolStripButton(); + toolStripButtonAdjustAnno = new System.Windows.Forms.ToolStripButton(); + splitContainer2 = new System.Windows.Forms.SplitContainer(); + groupBox8 = new System.Windows.Forms.GroupBox(); + folv_history = new BrightIdeasSoftware.FastObjectListView(); + contextMenuStripHistory = new System.Windows.Forms.ContextMenuStrip(components); + testDetectionAgainToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + detailsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + refreshToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + dynamicMaskDetailsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + locateInLogToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + manuallyAddImagesToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + viewImageToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + jumpToImageToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + lbl_objects = new System.Windows.Forms.Label(); + pictureBox1 = new System.Windows.Forms.PictureBox(); + toolStripContainer1 = new System.Windows.Forms.ToolStripContainer(); + tabCameras = new System.Windows.Forms.TabPage(); + splitContainer1 = new System.Windows.Forms.SplitContainer(); + splitContainer3 = new System.Windows.Forms.SplitContainer(); + FOLV_Cameras = new BrightIdeasSoftware.FastObjectListView(); + CameraImageList = new System.Windows.Forms.ImageList(components); + pictureBoxCamera = new System.Windows.Forms.PictureBox(); + tableLayoutPanel6 = new DBLayoutPanel(components); + tableLayoutPanel11 = new DBLayoutPanel(components); + btnCameraAdd = new System.Windows.Forms.Button(); + btnCameraDel = new System.Windows.Forms.Button(); + btnSaveTo = new System.Windows.Forms.Button(); + btnCameraSave = new System.Windows.Forms.Button(); + btnPause = new System.Windows.Forms.Button(); + lbl_camstats = new System.Windows.Forms.Label(); + tableLayoutPanel7 = new DBLayoutPanel(components); + label26 = new System.Windows.Forms.Label(); + label14 = new System.Windows.Forms.Label(); + label1 = new System.Windows.Forms.Label(); + lblPrefix = new System.Windows.Forms.Label(); + lblName = new System.Windows.Forms.Label(); + tableLayoutPanel12 = new DBLayoutPanel(components); + lbl_prefix = new System.Windows.Forms.Label(); + tbPrefix = new System.Windows.Forms.TextBox(); + tableLayoutPanel13 = new DBLayoutPanel(components); + cb_enabled = new System.Windows.Forms.CheckBox(); + tbName = new System.Windows.Forms.TextBox(); + lblRelevantObjects = new System.Windows.Forms.Label(); + tableLayoutPanel26 = new System.Windows.Forms.TableLayoutPanel(); + cmbcaminput = new System.Windows.Forms.ComboBox(); + cb_monitorCamInputfolder = new System.Windows.Forms.CheckBox(); + button2 = new System.Windows.Forms.Button(); + tableLayoutPanel27 = new System.Windows.Forms.TableLayoutPanel(); + cb_masking_enabled = new System.Windows.Forms.CheckBox(); + BtnDynamicMaskingSettings = new System.Windows.Forms.Button(); + btnDetails = new System.Windows.Forms.Button(); + btnCustomMask = new System.Windows.Forms.Button(); + lblDrawMask = new System.Windows.Forms.Label(); + label25 = new System.Windows.Forms.Label(); + dbLayoutPanel6 = new DBLayoutPanel(components); + tbBiCamName = new System.Windows.Forms.TextBox(); + dbLayoutPanel7 = new DBLayoutPanel(components); + tb_camera_telegram_chatid = new System.Windows.Forms.TextBox(); + tbCustomMaskFile = new System.Windows.Forms.TextBox(); + label21 = new System.Windows.Forms.Label(); + label39 = new System.Windows.Forms.Label(); + dbLayoutPanel11 = new DBLayoutPanel(components); + BtnRelevantObjects = new System.Windows.Forms.Button(); + lbl_RelevantObjects = new System.Windows.Forms.Label(); + label15 = new System.Windows.Forms.Label(); + dbLayoutPanel12 = new DBLayoutPanel(components); + Lbl_PredictionTolerances = new System.Windows.Forms.Label(); + BtnPredictionSize = new System.Windows.Forms.Button(); + dbLayoutPanel13 = new DBLayoutPanel(components); + Lbl_Actions = new System.Windows.Forms.Label(); + btnActions = new System.Windows.Forms.Button(); + tabSettings = new System.Windows.Forms.TabPage(); + tableLayoutPanel4 = new DBLayoutPanel(components); + tableLayoutPanel5 = new DBLayoutPanel(components); + lbl_input = new System.Windows.Forms.Label(); + lbl_telegram_token = new System.Windows.Forms.Label(); + tableLayoutPanel18 = new DBLayoutPanel(components); + btn_input_path = new System.Windows.Forms.Button(); + cmbInput = new System.Windows.Forms.ComboBox(); + cb_inputpathsubfolders = new System.Windows.Forms.CheckBox(); + lbl_deepstackurl = new System.Windows.Forms.Label(); + dbLayoutPanel1 = new DBLayoutPanel(components); + cb_DeepStackURLsQueued = new System.Windows.Forms.CheckBox(); + button1 = new System.Windows.Forms.Button(); + dbLayoutPanel2 = new DBLayoutPanel(components); + tb_telegram_cooldown = new System.Windows.Forms.TextBox(); + label5 = new System.Windows.Forms.Label(); + tb_telegram_chatid = new System.Windows.Forms.TextBox(); + lbl_telegram_chatid = new System.Windows.Forms.Label(); + tb_telegram_token = new System.Windows.Forms.TextBox(); + label12 = new System.Windows.Forms.Label(); + dbLayoutPanel3 = new DBLayoutPanel(components); + cb_send_telegram_errors = new System.Windows.Forms.CheckBox(); + cb_send_pushover_errors = new System.Windows.Forms.CheckBox(); + label4 = new System.Windows.Forms.Label(); + dbLayoutPanel4 = new DBLayoutPanel(components); + label6 = new System.Windows.Forms.Label(); + tb_username = new System.Windows.Forms.TextBox(); + tb_password = new System.Windows.Forms.TextBox(); + label16 = new System.Windows.Forms.Label(); + label17 = new System.Windows.Forms.Label(); + label18 = new System.Windows.Forms.Label(); + dbLayoutPanel5 = new DBLayoutPanel(components); + tb_BlueIrisServer = new System.Windows.Forms.TextBox(); + label19 = new System.Windows.Forms.Label(); + lbl_blueirisserver = new System.Windows.Forms.Label(); + label29 = new System.Windows.Forms.Label(); + dbLayoutPanel8 = new DBLayoutPanel(components); + tb_Pushover_Cooldown = new System.Windows.Forms.TextBox(); + tb_Pushover_APIKey = new System.Windows.Forms.TextBox(); + label31 = new System.Windows.Forms.Label(); + label30 = new System.Windows.Forms.Label(); + tb_Pushover_UserKey = new System.Windows.Forms.TextBox(); + dbLayoutPanel9 = new DBLayoutPanel(components); + cbStartWithWindows = new System.Windows.Forms.CheckBox(); + cbMinimizeToTray = new System.Windows.Forms.CheckBox(); + label13 = new System.Windows.Forms.Label(); + dbLayoutPanel10 = new DBLayoutPanel(components); + button3 = new System.Windows.Forms.Button(); + BtnSettingsSave = new System.Windows.Forms.Button(); + bt_CheckUpdates = new System.Windows.Forms.Button(); + tabDeepStack = new System.Windows.Forms.TabPage(); + chk_AutoAdd = new System.Windows.Forms.CheckBox(); + label34 = new System.Windows.Forms.Label(); + label33 = new System.Windows.Forms.Label(); + label32 = new System.Windows.Forms.Label(); + txt_DeepstackNoMoreOftenThanMins = new System.Windows.Forms.TextBox(); + txt_DeepstackRestartFailCount = new System.Windows.Forms.TextBox(); + Chk_AutoReStart = new System.Windows.Forms.CheckBox(); + Btn_ViewLog = new System.Windows.Forms.Button(); + Btn_DeepstackReset = new System.Windows.Forms.Button(); + linkLabel1 = new System.Windows.Forms.LinkLabel(); + groupBox11 = new System.Windows.Forms.GroupBox(); + tb_DeepStackURLs = new System.Windows.Forms.TextBox(); + groupBox10 = new System.Windows.Forms.GroupBox(); + tb_DeepstackCommandLine = new System.Windows.Forms.TextBox(); + lbl_DeepstackType = new System.Windows.Forms.Label(); + lbl_Deepstackversion = new System.Windows.Forms.Label(); + lbl_deepstackname = new System.Windows.Forms.Label(); + label28 = new System.Windows.Forms.Label(); + label24 = new System.Windows.Forms.Label(); + label23 = new System.Windows.Forms.Label(); + label22 = new System.Windows.Forms.Label(); + chk_stopbeforestart = new System.Windows.Forms.CheckBox(); + chk_HighPriority = new System.Windows.Forms.CheckBox(); + Chk_DSDebug = new System.Windows.Forms.CheckBox(); + Lbl_BlueStackRunning = new System.Windows.Forms.Label(); + Btn_Save = new System.Windows.Forms.Button(); + label11 = new System.Windows.Forms.Label(); + groupBox1 = new System.Windows.Forms.GroupBox(); + Chk_DetectionAPI = new System.Windows.Forms.CheckBox(); + Chk_FaceAPI = new System.Windows.Forms.CheckBox(); + Chk_SceneAPI = new System.Windows.Forms.CheckBox(); + groupBox2 = new System.Windows.Forms.GroupBox(); + RB_High = new System.Windows.Forms.RadioButton(); + RB_Medium = new System.Windows.Forms.RadioButton(); + RB_Low = new System.Windows.Forms.RadioButton(); + groupBox4 = new System.Windows.Forms.GroupBox(); + Txt_DeepStackInstallFolder = new System.Windows.Forms.TextBox(); + groupBox3 = new System.Windows.Forms.GroupBox(); + label27 = new System.Windows.Forms.Label(); + Txt_Port = new System.Windows.Forms.TextBox(); + Chk_AutoStart = new System.Windows.Forms.CheckBox(); + Btn_Start = new System.Windows.Forms.Button(); + Btn_Stop = new System.Windows.Forms.Button(); + groupBoxCustomModel = new System.Windows.Forms.GroupBox(); + Chk_CustomModelAPI = new System.Windows.Forms.CheckBox(); + label38 = new System.Windows.Forms.Label(); + label9 = new System.Windows.Forms.Label(); + label37 = new System.Windows.Forms.Label(); + label36 = new System.Windows.Forms.Label(); + label35 = new System.Windows.Forms.Label(); + Txt_CustomModelMode = new System.Windows.Forms.TextBox(); + Txt_CustomModelPort = new System.Windows.Forms.TextBox(); + Txt_CustomModelName = new System.Windows.Forms.TextBox(); + Txt_CustomModelPath = new System.Windows.Forms.TextBox(); + tabLog = new System.Windows.Forms.TabPage(); + toolStrip2 = new System.Windows.Forms.ToolStrip(); + toolStripLabel1 = new System.Windows.Forms.ToolStripLabel(); + ToolStripComboBoxSearch = new System.Windows.Forms.ToolStripComboBox(); + toolStripDropDownButton1 = new System.Windows.Forms.ToolStripDropDownButton(); + mnu_Filter = new System.Windows.Forms.ToolStripMenuItem(); + mnu_Highlight = new System.Windows.Forms.ToolStripMenuItem(); + toolStripSeparator3 = new System.Windows.Forms.ToolStripSeparator(); + toolStripSeparator = new System.Windows.Forms.ToolStripSeparator(); + toolStripDropDownButtonSettings = new System.Windows.Forms.ToolStripDropDownButton(); + Chk_AutoScroll = new System.Windows.Forms.ToolStripMenuItem(); + clearRecentErrorsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + toolStripMenuItemLogLevel = new System.Windows.Forms.ToolStripMenuItem(); + mnu_log_filter_off = new System.Windows.Forms.ToolStripMenuItem(); + mnu_log_filter_fatal = new System.Windows.Forms.ToolStripMenuItem(); + mnu_log_filter_error = new System.Windows.Forms.ToolStripMenuItem(); + mnu_log_filter_warn = new System.Windows.Forms.ToolStripMenuItem(); + mnu_log_filter_info = new System.Windows.Forms.ToolStripMenuItem(); + mnu_log_filter_debug = new System.Windows.Forms.ToolStripMenuItem(); + mnu_log_filter_trace = new System.Windows.Forms.ToolStripMenuItem(); + toolStripSeparator4 = new System.Windows.Forms.ToolStripSeparator(); + toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator(); + openToolStripButton = new System.Windows.Forms.ToolStripButton(); + toolStripButtonLoad = new System.Windows.Forms.ToolStripButton(); + toolStripSeparator7 = new System.Windows.Forms.ToolStripSeparator(); + toolStripButtonReload = new System.Windows.Forms.ToolStripButton(); + toolStripSeparator5 = new System.Windows.Forms.ToolStripSeparator(); + toolStripButtonPauseLog = new System.Windows.Forms.ToolStripButton(); + toolStripSeparator6 = new System.Windows.Forms.ToolStripSeparator(); + toolStripSeparator8 = new System.Windows.Forms.ToolStripSeparator(); + chk_filterErrors = new System.Windows.Forms.ToolStripButton(); + chk_filterErrorsAll = new System.Windows.Forms.ToolStripButton(); + groupBox7 = new System.Windows.Forms.GroupBox(); + folv_log = new BrightIdeasSoftware.FastObjectListView(); + toolTip1 = new System.Windows.Forms.ToolTip(components); + HistoryImageList = new System.Windows.Forms.ImageList(components); + HistoryUpdateListTimer = new System.Windows.Forms.Timer(components); + statusStrip1 = new System.Windows.Forms.StatusStrip(); + toolStripProgressBar1 = new System.Windows.Forms.ToolStripProgressBar(); + toolStripStatusLabelHistoryItems = new System.Windows.Forms.ToolStripStatusLabel(); + toolStripStatusErrors = new System.Windows.Forms.ToolStripStatusLabel(); + toolStripStatusLabel1 = new System.Windows.Forms.ToolStripStatusLabel(); + toolStripStatusLabelInfo = new System.Windows.Forms.ToolStripStatusLabel(); + LogUpdateListTimer = new System.Windows.Forms.Timer(components); + colorDialog1 = new System.Windows.Forms.ColorDialog(); + TraycontextMenuStrip.SuspendLayout(); + tabControl1.SuspendLayout(); + tabOverview.SuspendLayout(); + tableLayoutPanel14.SuspendLayout(); + tableLayoutPanel15.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)pictureBox2).BeginInit(); + tabStats.SuspendLayout(); + tableLayoutPanel16.SuspendLayout(); + tableLayoutPanel23.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)chart_confidence).BeginInit(); + ((System.ComponentModel.ISupportInitialize)timeline).BeginInit(); + tableLayoutPanel17.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)chart1).BeginInit(); + tabHistory.SuspendLayout(); + toolStrip1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)splitContainer2).BeginInit(); + splitContainer2.Panel1.SuspendLayout(); + splitContainer2.Panel2.SuspendLayout(); + splitContainer2.SuspendLayout(); + groupBox8.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)folv_history).BeginInit(); + contextMenuStripHistory.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)pictureBox1).BeginInit(); + toolStripContainer1.SuspendLayout(); + tabCameras.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)splitContainer1).BeginInit(); + splitContainer1.Panel1.SuspendLayout(); + splitContainer1.Panel2.SuspendLayout(); + splitContainer1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)splitContainer3).BeginInit(); + splitContainer3.Panel1.SuspendLayout(); + splitContainer3.Panel2.SuspendLayout(); + splitContainer3.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)FOLV_Cameras).BeginInit(); + ((System.ComponentModel.ISupportInitialize)pictureBoxCamera).BeginInit(); + tableLayoutPanel6.SuspendLayout(); + tableLayoutPanel11.SuspendLayout(); + tableLayoutPanel7.SuspendLayout(); + tableLayoutPanel12.SuspendLayout(); + tableLayoutPanel13.SuspendLayout(); + tableLayoutPanel26.SuspendLayout(); + tableLayoutPanel27.SuspendLayout(); + dbLayoutPanel6.SuspendLayout(); + dbLayoutPanel7.SuspendLayout(); + dbLayoutPanel11.SuspendLayout(); + dbLayoutPanel12.SuspendLayout(); + dbLayoutPanel13.SuspendLayout(); + tabSettings.SuspendLayout(); + tableLayoutPanel4.SuspendLayout(); + tableLayoutPanel5.SuspendLayout(); + tableLayoutPanel18.SuspendLayout(); + dbLayoutPanel1.SuspendLayout(); + dbLayoutPanel2.SuspendLayout(); + dbLayoutPanel3.SuspendLayout(); + dbLayoutPanel4.SuspendLayout(); + dbLayoutPanel5.SuspendLayout(); + dbLayoutPanel8.SuspendLayout(); + dbLayoutPanel9.SuspendLayout(); + dbLayoutPanel10.SuspendLayout(); + tabDeepStack.SuspendLayout(); + groupBox11.SuspendLayout(); + groupBox10.SuspendLayout(); + groupBox1.SuspendLayout(); + groupBox2.SuspendLayout(); + groupBox4.SuspendLayout(); + groupBox3.SuspendLayout(); + groupBoxCustomModel.SuspendLayout(); + tabLog.SuspendLayout(); + toolStrip2.SuspendLayout(); + groupBox7.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)folv_log).BeginInit(); + statusStrip1.SuspendLayout(); + SuspendLayout(); // // notifyIcon // - this.notifyIcon.BalloonTipText = "Running in the background"; - this.notifyIcon.BalloonTipTitle = "AI Tool"; - this.notifyIcon.Icon = ((System.Drawing.Icon)(resources.GetObject("notifyIcon.Icon"))); - this.notifyIcon.Text = "AI Tool"; - this.notifyIcon.MouseDoubleClick += new System.Windows.Forms.MouseEventHandler(this.notifyIcon_MouseDoubleClick); + notifyIcon.BalloonTipText = "Running in the background"; + notifyIcon.BalloonTipTitle = "AI Tool"; + notifyIcon.ContextMenuStrip = TraycontextMenuStrip; + notifyIcon.Icon = (System.Drawing.Icon)resources.GetObject("notifyIcon.Icon"); + notifyIcon.Text = "AI Tool"; + notifyIcon.MouseClick += notifyIcon_MouseClick; + notifyIcon.MouseDoubleClick += notifyIcon_MouseDoubleClick; + // + // TraycontextMenuStrip + // + TraycontextMenuStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { pauseAllToolStripMenuItem, resumeAllToolStripMenuItem, pauseToolStripMenuItem, exitToolStripMenuItem }); + TraycontextMenuStrip.Name = "TraycontextMenuStrip"; + TraycontextMenuStrip.Size = new System.Drawing.Size(134, 92); + // + // pauseAllToolStripMenuItem + // + pauseAllToolStripMenuItem.Name = "pauseAllToolStripMenuItem"; + pauseAllToolStripMenuItem.Size = new System.Drawing.Size(133, 22); + pauseAllToolStripMenuItem.Text = "Pause All"; + pauseAllToolStripMenuItem.Click += pauseAllToolStripMenuItem_Click; + // + // resumeAllToolStripMenuItem + // + resumeAllToolStripMenuItem.Name = "resumeAllToolStripMenuItem"; + resumeAllToolStripMenuItem.Size = new System.Drawing.Size(133, 22); + resumeAllToolStripMenuItem.Text = "Resume All"; + resumeAllToolStripMenuItem.Click += resumeAllToolStripMenuItem_Click; + // + // pauseToolStripMenuItem + // + pauseToolStripMenuItem.Name = "pauseToolStripMenuItem"; + pauseToolStripMenuItem.Size = new System.Drawing.Size(133, 22); + pauseToolStripMenuItem.Text = "Pause..."; + pauseToolStripMenuItem.Click += pauseToolStripMenuItem_Click; + // + // exitToolStripMenuItem + // + exitToolStripMenuItem.Name = "exitToolStripMenuItem"; + exitToolStripMenuItem.Size = new System.Drawing.Size(133, 22); + exitToolStripMenuItem.Text = "Exit"; + exitToolStripMenuItem.Click += exitToolStripMenuItem_Click; // // tabControl1 // - this.tabControl1.Controls.Add(this.tabOverview); - this.tabControl1.Controls.Add(this.tabStats); - this.tabControl1.Controls.Add(this.tabHistory); - this.tabControl1.Controls.Add(this.tabCameras); - this.tabControl1.Controls.Add(this.tabSettings); - this.tabControl1.Dock = System.Windows.Forms.DockStyle.Fill; - this.tabControl1.Location = new System.Drawing.Point(0, 0); - this.tabControl1.Name = "tabControl1"; - this.tabControl1.SelectedIndex = 0; - this.tabControl1.Size = new System.Drawing.Size(954, 462); - this.tabControl1.TabIndex = 4; - this.tabControl1.SelectedIndexChanged += new System.EventHandler(this.tabControl1_SelectedIndexChanged); - this.tabControl1.MouseDown += new System.Windows.Forms.MouseEventHandler(this.tabControl1_MouseDown); + tabControl1.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + tabControl1.Controls.Add(tabOverview); + tabControl1.Controls.Add(tabStats); + tabControl1.Controls.Add(tabHistory); + tabControl1.Controls.Add(tabCameras); + tabControl1.Controls.Add(tabSettings); + tabControl1.Controls.Add(tabDeepStack); + tabControl1.Controls.Add(tabLog); + tabControl1.Location = new System.Drawing.Point(0, 2); + tabControl1.Name = "tabControl1"; + tabControl1.SelectedIndex = 0; + tabControl1.Size = new System.Drawing.Size(1107, 520); + tabControl1.TabIndex = 4; + tabControl1.SelectedIndexChanged += tabControl1_SelectedIndexChanged; + tabControl1.MouseDown += tabControl1_MouseDown; // // tabOverview // - this.tabOverview.Controls.Add(this.tableLayoutPanel14); - this.tabOverview.Location = new System.Drawing.Point(4, 22); - this.tabOverview.Name = "tabOverview"; - this.tabOverview.Size = new System.Drawing.Size(946, 436); - this.tabOverview.TabIndex = 4; - this.tabOverview.Text = "Overview"; - this.tabOverview.UseVisualStyleBackColor = true; + tabOverview.Controls.Add(tableLayoutPanel14); + tabOverview.Location = new System.Drawing.Point(4, 22); + tabOverview.Name = "tabOverview"; + tabOverview.Size = new System.Drawing.Size(1099, 494); + tabOverview.TabIndex = 4; + tabOverview.Text = "Overview"; + tabOverview.UseVisualStyleBackColor = true; // // tableLayoutPanel14 // - this.tableLayoutPanel14.CellBorderStyle = System.Windows.Forms.TableLayoutPanelCellBorderStyle.Single; - this.tableLayoutPanel14.ColumnCount = 1; - this.tableLayoutPanel14.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.tableLayoutPanel14.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20F)); - this.tableLayoutPanel14.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20F)); - this.tableLayoutPanel14.Controls.Add(this.tableLayoutPanel15, 0, 0); - this.tableLayoutPanel14.Dock = System.Windows.Forms.DockStyle.Fill; - this.tableLayoutPanel14.Location = new System.Drawing.Point(0, 0); - this.tableLayoutPanel14.Name = "tableLayoutPanel14"; - this.tableLayoutPanel14.RowCount = 1; - this.tableLayoutPanel14.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.tableLayoutPanel14.Size = new System.Drawing.Size(946, 436); - this.tableLayoutPanel14.TabIndex = 3; + tableLayoutPanel14.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + tableLayoutPanel14.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + tableLayoutPanel14.CellBorderStyle = System.Windows.Forms.TableLayoutPanelCellBorderStyle.Single; + tableLayoutPanel14.ColumnCount = 1; + tableLayoutPanel14.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + tableLayoutPanel14.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 21F)); + tableLayoutPanel14.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 21F)); + tableLayoutPanel14.Controls.Add(tableLayoutPanel15, 0, 0); + tableLayoutPanel14.Location = new System.Drawing.Point(0, 0); + tableLayoutPanel14.Name = "tableLayoutPanel14"; + tableLayoutPanel14.RowCount = 1; + tableLayoutPanel14.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + tableLayoutPanel14.Size = new System.Drawing.Size(1096, 479); + tableLayoutPanel14.TabIndex = 3; // // tableLayoutPanel15 // - this.tableLayoutPanel15.ColumnCount = 1; - this.tableLayoutPanel15.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.tableLayoutPanel15.Controls.Add(this.pictureBox2, 0, 0); - this.tableLayoutPanel15.Controls.Add(this.label2, 0, 2); - this.tableLayoutPanel15.Controls.Add(this.label3, 0, 1); - this.tableLayoutPanel15.Controls.Add(this.lbl_version, 0, 4); - this.tableLayoutPanel15.Controls.Add(this.lbl_errors, 0, 3); - this.tableLayoutPanel15.Controls.Add(this.lbl_info, 0, 4); - this.tableLayoutPanel15.Dock = System.Windows.Forms.DockStyle.Fill; - this.tableLayoutPanel15.Location = new System.Drawing.Point(4, 4); - this.tableLayoutPanel15.Name = "tableLayoutPanel15"; - this.tableLayoutPanel15.RowCount = 5; - this.tableLayoutPanel15.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 49.5F)); - this.tableLayoutPanel15.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 1F)); - this.tableLayoutPanel15.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 30F)); - this.tableLayoutPanel15.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 14.5F)); - this.tableLayoutPanel15.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 5F)); - this.tableLayoutPanel15.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F)); - this.tableLayoutPanel15.Size = new System.Drawing.Size(938, 428); - this.tableLayoutPanel15.TabIndex = 0; + tableLayoutPanel15.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + tableLayoutPanel15.ColumnCount = 1; + tableLayoutPanel15.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + tableLayoutPanel15.Controls.Add(pictureBox2, 0, 0); + tableLayoutPanel15.Controls.Add(label2, 0, 2); + tableLayoutPanel15.Controls.Add(label3, 0, 1); + tableLayoutPanel15.Controls.Add(lbl_version, 0, 5); + tableLayoutPanel15.Controls.Add(lbl_errors, 0, 3); + tableLayoutPanel15.Controls.Add(lbl_info, 0, 5); + tableLayoutPanel15.Controls.Add(lblQueue, 0, 4); + tableLayoutPanel15.Location = new System.Drawing.Point(4, 4); + tableLayoutPanel15.Name = "tableLayoutPanel15"; + tableLayoutPanel15.RowCount = 6; + tableLayoutPanel15.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 47.14285F)); + tableLayoutPanel15.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 0.9523811F)); + tableLayoutPanel15.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 28.57143F)); + tableLayoutPanel15.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 13.80951F)); + tableLayoutPanel15.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 4.761905F)); + tableLayoutPanel15.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 4.761905F)); + tableLayoutPanel15.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 21F)); + tableLayoutPanel15.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F)); + tableLayoutPanel15.Size = new System.Drawing.Size(1088, 471); + tableLayoutPanel15.TabIndex = 0; // // pictureBox2 // - this.pictureBox2.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.pictureBox2.BackgroundImageLayout = System.Windows.Forms.ImageLayout.None; - this.pictureBox2.Image = global::WindowsFormsApp2.Properties.Resources.logo; - this.pictureBox2.Location = new System.Drawing.Point(3, 74); - this.pictureBox2.Name = "pictureBox2"; - this.pictureBox2.Size = new System.Drawing.Size(932, 124); - this.pictureBox2.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; - this.pictureBox2.TabIndex = 4; - this.pictureBox2.TabStop = false; + pictureBox2.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + pictureBox2.BackgroundImageLayout = System.Windows.Forms.ImageLayout.None; + pictureBox2.Image = (System.Drawing.Image)resources.GetObject("pictureBox2.Image"); + pictureBox2.Location = new System.Drawing.Point(3, 78); + pictureBox2.Name = "pictureBox2"; + pictureBox2.Size = new System.Drawing.Size(1082, 131); + pictureBox2.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + pictureBox2.TabIndex = 4; + pictureBox2.TabStop = false; // // label2 // - this.label2.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.label2.Font = new System.Drawing.Font("Segoe UI", 48F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.label2.ForeColor = System.Drawing.Color.Green; - this.label2.Location = new System.Drawing.Point(3, 205); - this.label2.Name = "label2"; - this.label2.Size = new System.Drawing.Size(932, 92); - this.label2.TabIndex = 3; - this.label2.Text = "Running"; - this.label2.TextAlign = System.Drawing.ContentAlignment.TopCenter; + label2.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + label2.AutoEllipsis = true; + label2.AutoSize = true; + label2.Font = new System.Drawing.Font("Segoe UI", 15.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, 0); + label2.ForeColor = System.Drawing.Color.Green; + label2.Location = new System.Drawing.Point(3, 216); + label2.Name = "label2"; + label2.Size = new System.Drawing.Size(1082, 30); + label2.TabIndex = 3; + label2.Text = "Running"; + label2.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; // // label3 // - this.label3.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.label3.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; - this.label3.Location = new System.Drawing.Point(60, 201); - this.label3.Margin = new System.Windows.Forms.Padding(60, 0, 60, 0); - this.label3.Name = "label3"; - this.label3.Size = new System.Drawing.Size(818, 2); - this.label3.TabIndex = 5; - this.label3.Text = "label3"; + label3.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + label3.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; + label3.Location = new System.Drawing.Point(63, 212); + label3.Margin = new System.Windows.Forms.Padding(63, 0, 63, 0); + label3.Name = "label3"; + label3.Size = new System.Drawing.Size(962, 2); + label3.TabIndex = 5; + label3.Text = "label3"; // // lbl_version // - this.lbl_version.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.lbl_version.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.lbl_version.Location = new System.Drawing.Point(3, 406); - this.lbl_version.Name = "lbl_version"; - this.lbl_version.Size = new System.Drawing.Size(932, 22); - this.lbl_version.TabIndex = 6; - this.lbl_version.Text = "Version 1.67 preview 7"; - this.lbl_version.TextAlign = System.Drawing.ContentAlignment.BottomCenter; + lbl_version.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + lbl_version.Location = new System.Drawing.Point(3, 427); + lbl_version.Name = "lbl_version"; + lbl_version.Size = new System.Drawing.Size(1082, 21); + lbl_version.TabIndex = 6; + lbl_version.Text = "Version 1.67 preview 7 (VorlonCD MOD)"; + lbl_version.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; // // lbl_errors // - this.lbl_errors.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.lbl_errors.Font = new System.Drawing.Font("Segoe UI Semibold", 20.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.lbl_errors.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(192)))), ((int)(((byte)(0)))), ((int)(((byte)(0))))); - this.lbl_errors.Location = new System.Drawing.Point(3, 327); - this.lbl_errors.Name = "lbl_errors"; - this.lbl_errors.Size = new System.Drawing.Size(932, 59); - this.lbl_errors.TabIndex = 7; - this.lbl_errors.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - this.lbl_errors.Visible = false; - this.lbl_errors.Click += new System.EventHandler(this.lbl_errors_Click); + lbl_errors.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + lbl_errors.Font = new System.Drawing.Font("Segoe UI Semibold", 20.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, 0); + lbl_errors.ForeColor = System.Drawing.Color.FromArgb(192, 0, 0); + lbl_errors.Location = new System.Drawing.Point(3, 344); + lbl_errors.Name = "lbl_errors"; + lbl_errors.Size = new System.Drawing.Size(1082, 62); + lbl_errors.TabIndex = 7; + lbl_errors.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + lbl_errors.Visible = false; + lbl_errors.Click += lbl_errors_Click; // // lbl_info // - this.lbl_info.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.lbl_info.AutoSize = true; - this.lbl_info.Location = new System.Drawing.Point(3, 386); - this.lbl_info.Name = "lbl_info"; - this.lbl_info.Size = new System.Drawing.Size(932, 20); - this.lbl_info.TabIndex = 8; - this.lbl_info.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + lbl_info.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + lbl_info.AutoSize = true; + lbl_info.Location = new System.Drawing.Point(3, 448); + lbl_info.Name = "lbl_info"; + lbl_info.Size = new System.Drawing.Size(1082, 23); + lbl_info.TabIndex = 8; + lbl_info.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // lblQueue + // + lblQueue.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + lblQueue.AutoSize = true; + lblQueue.ForeColor = System.Drawing.Color.DarkOrange; + lblQueue.Location = new System.Drawing.Point(3, 406); + lblQueue.Name = "lblQueue"; + lblQueue.Size = new System.Drawing.Size(1082, 21); + lblQueue.TabIndex = 9; + lblQueue.Text = "Images in Queue: 0"; + lblQueue.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; // // tabStats // - this.tabStats.Controls.Add(this.tableLayoutPanel16); - this.tabStats.Location = new System.Drawing.Point(4, 22); - this.tabStats.Name = "tabStats"; - this.tabStats.Size = new System.Drawing.Size(946, 436); - this.tabStats.TabIndex = 5; - this.tabStats.Text = "Stats"; - this.tabStats.UseVisualStyleBackColor = true; + tabStats.Controls.Add(tableLayoutPanel16); + tabStats.Location = new System.Drawing.Point(4, 24); + tabStats.Name = "tabStats"; + tabStats.Size = new System.Drawing.Size(1099, 492); + tabStats.TabIndex = 5; + tabStats.Text = "Stats"; + tabStats.UseVisualStyleBackColor = true; // // tableLayoutPanel16 // - this.tableLayoutPanel16.ColumnCount = 2; - this.tableLayoutPanel16.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 30F)); - this.tableLayoutPanel16.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 70F)); - this.tableLayoutPanel16.Controls.Add(this.tableLayoutPanel23, 0, 0); - this.tableLayoutPanel16.Controls.Add(this.tableLayoutPanel17, 0, 0); - this.tableLayoutPanel16.Dock = System.Windows.Forms.DockStyle.Fill; - this.tableLayoutPanel16.Location = new System.Drawing.Point(0, 0); - this.tableLayoutPanel16.Name = "tableLayoutPanel16"; - this.tableLayoutPanel16.RowCount = 1; - this.tableLayoutPanel16.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.tableLayoutPanel16.Size = new System.Drawing.Size(946, 436); - this.tableLayoutPanel16.TabIndex = 0; + tableLayoutPanel16.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + tableLayoutPanel16.ColumnCount = 2; + tableLayoutPanel16.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 30F)); + tableLayoutPanel16.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 70F)); + tableLayoutPanel16.Controls.Add(tableLayoutPanel23, 0, 0); + tableLayoutPanel16.Controls.Add(tableLayoutPanel17, 0, 0); + tableLayoutPanel16.Location = new System.Drawing.Point(0, 0); + tableLayoutPanel16.Name = "tableLayoutPanel16"; + tableLayoutPanel16.RowCount = 1; + tableLayoutPanel16.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + tableLayoutPanel16.Size = new System.Drawing.Size(1096, 478); + tableLayoutPanel16.TabIndex = 0; // // tableLayoutPanel23 // - this.tableLayoutPanel23.ColumnCount = 1; - this.tableLayoutPanel23.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.tableLayoutPanel23.Controls.Add(this.label8, 0, 2); - this.tableLayoutPanel23.Controls.Add(this.chart_confidence, 0, 2); - this.tableLayoutPanel23.Controls.Add(this.timeline, 0, 1); - this.tableLayoutPanel23.Controls.Add(this.label7, 0, 0); - this.tableLayoutPanel23.Dock = System.Windows.Forms.DockStyle.Fill; - this.tableLayoutPanel23.Location = new System.Drawing.Point(286, 3); - this.tableLayoutPanel23.Name = "tableLayoutPanel23"; - this.tableLayoutPanel23.RowCount = 3; - this.tableLayoutPanel23.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 33F)); - this.tableLayoutPanel23.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F)); - this.tableLayoutPanel23.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 33F)); - this.tableLayoutPanel23.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F)); - this.tableLayoutPanel23.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F)); - this.tableLayoutPanel23.Size = new System.Drawing.Size(657, 430); - this.tableLayoutPanel23.TabIndex = 7; + tableLayoutPanel23.ColumnCount = 1; + tableLayoutPanel23.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + tableLayoutPanel23.Controls.Add(label8, 0, 2); + tableLayoutPanel23.Controls.Add(chart_confidence, 0, 2); + tableLayoutPanel23.Controls.Add(timeline, 0, 1); + tableLayoutPanel23.Controls.Add(label7, 0, 0); + tableLayoutPanel23.Dock = System.Windows.Forms.DockStyle.Fill; + tableLayoutPanel23.Location = new System.Drawing.Point(331, 3); + tableLayoutPanel23.Name = "tableLayoutPanel23"; + tableLayoutPanel23.RowCount = 3; + tableLayoutPanel23.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 35F)); + tableLayoutPanel23.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F)); + tableLayoutPanel23.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 35F)); + tableLayoutPanel23.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F)); + tableLayoutPanel23.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 21F)); + tableLayoutPanel23.Size = new System.Drawing.Size(762, 472); + tableLayoutPanel23.TabIndex = 7; // // label8 // - this.label8.AutoSize = true; - this.label8.Dock = System.Windows.Forms.DockStyle.Top; - this.label8.Font = new System.Drawing.Font("Segoe UI", 15.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.label8.Location = new System.Drawing.Point(3, 218); - this.label8.Margin = new System.Windows.Forms.Padding(3); - this.label8.Name = "label8"; - this.label8.Size = new System.Drawing.Size(651, 27); - this.label8.TabIndex = 9; - this.label8.Text = "Frequencies of alert result confidences"; - this.label8.TextAlign = System.Drawing.ContentAlignment.TopCenter; + label8.AutoSize = true; + label8.Dock = System.Windows.Forms.DockStyle.Top; + label8.Font = new System.Drawing.Font("Segoe UI", 15.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, 0); + label8.Location = new System.Drawing.Point(3, 239); + label8.Margin = new System.Windows.Forms.Padding(3); + label8.Name = "label8"; + label8.Size = new System.Drawing.Size(756, 29); + label8.TabIndex = 9; + label8.Text = "Frequencies of alert result confidences"; + label8.TextAlign = System.Drawing.ContentAlignment.TopCenter; // // chart_confidence // - this.chart_confidence.BackColor = System.Drawing.Color.Transparent; - this.chart_confidence.BorderlineColor = System.Drawing.Color.Transparent; + chart_confidence.BackColor = System.Drawing.Color.Transparent; + chart_confidence.BorderlineColor = System.Drawing.Color.Transparent; chartArea1.AxisX.Interval = 10D; chartArea1.AxisX.MajorGrid.Interval = 6D; chartArea1.AxisX.MajorGrid.LineColor = System.Drawing.Color.LightGray; @@ -424,17 +663,15 @@ private void InitializeComponent() chartArea1.AxisX.Maximum = 100D; chartArea1.AxisX.Minimum = 0D; chartArea1.AxisX.Title = "Alert confidence"; - chartArea1.AxisX.TitleFont = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); chartArea1.AxisY.MajorGrid.LineColor = System.Drawing.Color.LightGray; chartArea1.AxisY.MajorGrid.LineDashStyle = System.Windows.Forms.DataVisualization.Charting.ChartDashStyle.Dot; chartArea1.AxisY.Title = "Frequency"; - chartArea1.AxisY.TitleFont = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); chartArea1.BackColor = System.Drawing.Color.Transparent; chartArea1.Name = "ChartArea1"; - this.chart_confidence.ChartAreas.Add(chartArea1); - this.chart_confidence.Dock = System.Windows.Forms.DockStyle.Fill; - this.chart_confidence.Location = new System.Drawing.Point(3, 251); - this.chart_confidence.Name = "chart_confidence"; + chart_confidence.ChartAreas.Add(chartArea1); + chart_confidence.Dock = System.Windows.Forms.DockStyle.Fill; + chart_confidence.Location = new System.Drawing.Point(3, 274); + chart_confidence.Name = "chart_confidence"; series1.BorderWidth = 4; series1.ChartArea = "ChartArea1"; series1.ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.Spline; @@ -446,15 +683,15 @@ private void InitializeComponent() series2.Color = System.Drawing.Color.Green; series2.Legend = "Legend1"; series2.Name = "alert"; - this.chart_confidence.Series.Add(series1); - this.chart_confidence.Series.Add(series2); - this.chart_confidence.Size = new System.Drawing.Size(651, 176); - this.chart_confidence.TabIndex = 8; + chart_confidence.Series.Add(series1); + chart_confidence.Series.Add(series2); + chart_confidence.Size = new System.Drawing.Size(756, 195); + chart_confidence.TabIndex = 8; // // timeline // - this.timeline.BackColor = System.Drawing.Color.Transparent; - this.timeline.BorderlineColor = System.Drawing.Color.Transparent; + timeline.BackColor = System.Drawing.Color.Transparent; + timeline.BorderlineColor = System.Drawing.Color.Transparent; chartArea2.AxisX.Interval = 3D; chartArea2.AxisX.MajorGrid.Interval = 6D; chartArea2.AxisX.MajorGrid.LineColor = System.Drawing.Color.LightGray; @@ -462,17 +699,15 @@ private void InitializeComponent() chartArea2.AxisX.MajorTickMark.Interval = 1D; chartArea2.AxisX.Maximum = 24D; chartArea2.AxisX.Minimum = 0D; - chartArea2.AxisX.TitleFont = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); chartArea2.AxisY.MajorGrid.LineColor = System.Drawing.Color.LightGray; chartArea2.AxisY.MajorGrid.LineDashStyle = System.Windows.Forms.DataVisualization.Charting.ChartDashStyle.Dot; chartArea2.AxisY.Title = "Number"; - chartArea2.AxisY.TitleFont = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); chartArea2.BackColor = System.Drawing.Color.Transparent; chartArea2.Name = "ChartArea1"; - this.timeline.ChartAreas.Add(chartArea2); - this.timeline.Dock = System.Windows.Forms.DockStyle.Fill; - this.timeline.Location = new System.Drawing.Point(3, 36); - this.timeline.Name = "timeline"; + timeline.ChartAreas.Add(chartArea2); + timeline.Dock = System.Windows.Forms.DockStyle.Fill; + timeline.Location = new System.Drawing.Point(3, 38); + timeline.Name = "timeline"; series3.ChartArea = "ChartArea1"; series3.ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.SplineArea; series3.Color = System.Drawing.Color.Silver; @@ -496,1428 +731,3042 @@ private void InitializeComponent() series6.Color = System.Drawing.Color.Green; series6.Legend = "Legend1"; series6.Name = "relevant"; - this.timeline.Series.Add(series3); - this.timeline.Series.Add(series4); - this.timeline.Series.Add(series5); - this.timeline.Series.Add(series6); - this.timeline.Size = new System.Drawing.Size(651, 176); - this.timeline.TabIndex = 6; + series7.ChartArea = "ChartArea1"; + series7.ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.Spline; + series7.Color = System.Drawing.Color.Purple; + series7.Name = "skipped"; + timeline.Series.Add(series3); + timeline.Series.Add(series4); + timeline.Series.Add(series5); + timeline.Series.Add(series6); + timeline.Series.Add(series7); + timeline.Size = new System.Drawing.Size(756, 195); + timeline.TabIndex = 6; // // label7 // - this.label7.AutoSize = true; - this.label7.Dock = System.Windows.Forms.DockStyle.Top; - this.label7.Font = new System.Drawing.Font("Segoe UI", 15.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.label7.Location = new System.Drawing.Point(3, 3); - this.label7.Margin = new System.Windows.Forms.Padding(3); - this.label7.Name = "label7"; - this.label7.Size = new System.Drawing.Size(651, 27); - this.label7.TabIndex = 0; - this.label7.Text = "Timeline"; - this.label7.TextAlign = System.Drawing.ContentAlignment.TopCenter; + label7.AutoSize = true; + label7.Dock = System.Windows.Forms.DockStyle.Top; + label7.Font = new System.Drawing.Font("Segoe UI", 15.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, 0); + label7.Location = new System.Drawing.Point(3, 3); + label7.Margin = new System.Windows.Forms.Padding(3); + label7.Name = "label7"; + label7.Size = new System.Drawing.Size(756, 29); + label7.TabIndex = 0; + label7.Text = "Timeline"; + label7.TextAlign = System.Drawing.ContentAlignment.TopCenter; // // tableLayoutPanel17 // - this.tableLayoutPanel17.ColumnCount = 1; - this.tableLayoutPanel17.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.tableLayoutPanel17.Controls.Add(this.chart1, 0, 1); - this.tableLayoutPanel17.Controls.Add(this.comboBox1, 0, 0); - this.tableLayoutPanel17.Dock = System.Windows.Forms.DockStyle.Fill; - this.tableLayoutPanel17.Location = new System.Drawing.Point(3, 3); - this.tableLayoutPanel17.Name = "tableLayoutPanel17"; - this.tableLayoutPanel17.RowCount = 2; - this.tableLayoutPanel17.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 33F)); - this.tableLayoutPanel17.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.tableLayoutPanel17.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F)); - this.tableLayoutPanel17.Size = new System.Drawing.Size(277, 430); - this.tableLayoutPanel17.TabIndex = 3; + tableLayoutPanel17.ColumnCount = 1; + tableLayoutPanel17.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + tableLayoutPanel17.Controls.Add(chart1, 0, 2); + tableLayoutPanel17.Controls.Add(comboBox1, 0, 0); + tableLayoutPanel17.Controls.Add(btn_resetstats, 0, 1); + tableLayoutPanel17.Dock = System.Windows.Forms.DockStyle.Fill; + tableLayoutPanel17.Location = new System.Drawing.Point(3, 3); + tableLayoutPanel17.Name = "tableLayoutPanel17"; + tableLayoutPanel17.RowCount = 3; + tableLayoutPanel17.RowStyles.Add(new System.Windows.Forms.RowStyle()); + tableLayoutPanel17.RowStyles.Add(new System.Windows.Forms.RowStyle()); + tableLayoutPanel17.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + tableLayoutPanel17.Size = new System.Drawing.Size(322, 472); + tableLayoutPanel17.TabIndex = 3; // // chart1 // - this.chart1.BackColor = System.Drawing.Color.Transparent; - this.chart1.BorderlineColor = System.Drawing.Color.Transparent; + chart1.BackColor = System.Drawing.Color.Transparent; + chart1.BorderlineColor = System.Drawing.Color.DimGray; + chart1.BorderlineDashStyle = System.Windows.Forms.DataVisualization.Charting.ChartDashStyle.Dash; chartArea3.Area3DStyle.Enable3D = true; - chartArea3.Area3DStyle.Inclination = 35; - chartArea3.Area3DStyle.LightStyle = System.Windows.Forms.DataVisualization.Charting.LightStyle.None; + chartArea3.Area3DStyle.IsRightAngleAxes = false; + chartArea3.Area3DStyle.LightStyle = System.Windows.Forms.DataVisualization.Charting.LightStyle.Realistic; + chartArea3.Area3DStyle.Perspective = 10; + chartArea3.Area3DStyle.WallWidth = 6; + chartArea3.AxisX.LabelAutoFitStyle = System.Windows.Forms.DataVisualization.Charting.LabelAutoFitStyles.IncreaseFont | System.Windows.Forms.DataVisualization.Charting.LabelAutoFitStyles.DecreaseFont | System.Windows.Forms.DataVisualization.Charting.LabelAutoFitStyles.StaggeredLabels | System.Windows.Forms.DataVisualization.Charting.LabelAutoFitStyles.LabelsAngleStep30 | System.Windows.Forms.DataVisualization.Charting.LabelAutoFitStyles.LabelsAngleStep45 | System.Windows.Forms.DataVisualization.Charting.LabelAutoFitStyles.WordWrap; + chartArea3.AxisY.LabelAutoFitStyle = System.Windows.Forms.DataVisualization.Charting.LabelAutoFitStyles.IncreaseFont | System.Windows.Forms.DataVisualization.Charting.LabelAutoFitStyles.DecreaseFont | System.Windows.Forms.DataVisualization.Charting.LabelAutoFitStyles.StaggeredLabels | System.Windows.Forms.DataVisualization.Charting.LabelAutoFitStyles.LabelsAngleStep30 | System.Windows.Forms.DataVisualization.Charting.LabelAutoFitStyles.LabelsAngleStep45 | System.Windows.Forms.DataVisualization.Charting.LabelAutoFitStyles.LabelsAngleStep90 | System.Windows.Forms.DataVisualization.Charting.LabelAutoFitStyles.WordWrap; chartArea3.BackColor = System.Drawing.Color.Transparent; chartArea3.Name = "ChartArea1"; - this.chart1.ChartAreas.Add(chartArea3); - this.chart1.Dock = System.Windows.Forms.DockStyle.Fill; + chart1.ChartAreas.Add(chartArea3); + chart1.Dock = System.Windows.Forms.DockStyle.Fill; legend1.Alignment = System.Drawing.StringAlignment.Center; legend1.BackColor = System.Drawing.Color.Transparent; legend1.Docking = System.Windows.Forms.DataVisualization.Charting.Docking.Bottom; - legend1.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); legend1.IsTextAutoFit = false; legend1.LegendStyle = System.Windows.Forms.DataVisualization.Charting.LegendStyle.Column; legend1.Name = "Legend1"; - this.chart1.Legends.Add(legend1); - this.chart1.Location = new System.Drawing.Point(3, 36); - this.chart1.Name = "chart1"; - series7.ChartArea = "ChartArea1"; - series7.ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.Pie; - series7.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - series7.IsValueShownAsLabel = true; - series7.Legend = "Legend1"; - series7.Name = "s1"; + legend1.Title = "Legend"; + chart1.Legends.Add(legend1); + chart1.Location = new System.Drawing.Point(3, 55); + chart1.Name = "chart1"; + series8.ChartArea = "ChartArea1"; + series8.ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.Pie; + series8.IsValueShownAsLabel = true; + series8.Legend = "Legend1"; + series8.Name = "s1"; dataPoint1.IsVisibleInLegend = true; - series7.Points.Add(dataPoint1); - series7.Points.Add(dataPoint2); - series7.Points.Add(dataPoint3); - this.chart1.Series.Add(series7); - this.chart1.Size = new System.Drawing.Size(271, 391); - this.chart1.TabIndex = 2; - this.chart1.Text = "chart1"; - title1.Font = new System.Drawing.Font("Segoe UI", 15.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + series8.Points.Add(dataPoint1); + series8.Points.Add(dataPoint2); + series8.Points.Add(dataPoint3); + chart1.Series.Add(series8); + chart1.Size = new System.Drawing.Size(316, 414); + chart1.TabIndex = 2; + chart1.Text = "chart1"; + title1.Font = new System.Drawing.Font("Segoe UI", 15.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, 0); title1.Name = "Title1"; title1.Text = "Input Rates"; - this.chart1.Titles.Add(title1); + chart1.Titles.Add(title1); // // comboBox1 // - this.comboBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.comboBox1.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.comboBox1.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.comboBox1.FormattingEnabled = true; - this.comboBox1.Location = new System.Drawing.Point(3, 3); - this.comboBox1.Name = "comboBox1"; - this.comboBox1.Size = new System.Drawing.Size(271, 25); - this.comboBox1.TabIndex = 3; - this.comboBox1.SelectedIndexChanged += new System.EventHandler(this.comboBox1_SelectedIndexChanged_1); + comboBox1.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + comboBox1.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + comboBox1.FormattingEnabled = true; + comboBox1.Location = new System.Drawing.Point(3, 3); + comboBox1.Name = "comboBox1"; + comboBox1.Size = new System.Drawing.Size(316, 21); + comboBox1.TabIndex = 3; + comboBox1.SelectedIndexChanged += comboBox1_SelectedIndexChanged_1; + comboBox1.SelectionChangeCommitted += comboBox1_SelectionChangeCommitted; + // + // btn_resetstats + // + btn_resetstats.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + btn_resetstats.Location = new System.Drawing.Point(2, 29); + btn_resetstats.Margin = new System.Windows.Forms.Padding(2); + btn_resetstats.Name = "btn_resetstats"; + btn_resetstats.Size = new System.Drawing.Size(318, 21); + btn_resetstats.TabIndex = 4; + btn_resetstats.Text = "Reset Stats"; + btn_resetstats.UseVisualStyleBackColor = true; + btn_resetstats.Click += btn_resetstats_Click; // // tabHistory // - this.tabHistory.Controls.Add(this.tableLayoutPanel1); - this.tabHistory.Location = new System.Drawing.Point(4, 22); - this.tabHistory.Name = "tabHistory"; - this.tabHistory.Padding = new System.Windows.Forms.Padding(3); - this.tabHistory.Size = new System.Drawing.Size(946, 436); - this.tabHistory.TabIndex = 0; - this.tabHistory.Text = "History"; - this.tabHistory.UseVisualStyleBackColor = true; - // - // tableLayoutPanel1 - // - this.tableLayoutPanel1.ColumnCount = 2; - this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 30F)); - this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 70F)); - this.tableLayoutPanel1.Controls.Add(this.tableLayoutPanel21, 1, 0); - this.tableLayoutPanel1.Controls.Add(this.splitContainer1, 0, 0); - this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill; - this.tableLayoutPanel1.Location = new System.Drawing.Point(3, 3); - this.tableLayoutPanel1.Name = "tableLayoutPanel1"; - this.tableLayoutPanel1.RowCount = 1; - this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 90F)); - this.tableLayoutPanel1.Size = new System.Drawing.Size(940, 430); - this.tableLayoutPanel1.TabIndex = 3; - // - // tableLayoutPanel21 - // - this.tableLayoutPanel21.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.tableLayoutPanel21.ColumnCount = 1; - this.tableLayoutPanel21.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.tableLayoutPanel21.Controls.Add(this.pictureBox1, 0, 1); - this.tableLayoutPanel21.Controls.Add(this.tableLayoutPanel22, 0, 0); - this.tableLayoutPanel21.Location = new System.Drawing.Point(285, 3); - this.tableLayoutPanel21.Name = "tableLayoutPanel21"; - this.tableLayoutPanel21.RowCount = 2; - this.tableLayoutPanel21.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 10F)); - this.tableLayoutPanel21.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 80F)); - this.tableLayoutPanel21.Size = new System.Drawing.Size(652, 424); - this.tableLayoutPanel21.TabIndex = 5; + tabHistory.Controls.Add(toolStrip1); + tabHistory.Controls.Add(splitContainer2); + tabHistory.Controls.Add(toolStripContainer1); + tabHistory.Location = new System.Drawing.Point(4, 22); + tabHistory.Name = "tabHistory"; + tabHistory.Padding = new System.Windows.Forms.Padding(3); + tabHistory.Size = new System.Drawing.Size(1099, 494); + tabHistory.TabIndex = 0; + tabHistory.Text = "History"; + tabHistory.UseVisualStyleBackColor = true; + // + // toolStrip1 + // + toolStrip1.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + toolStrip1.AutoSize = false; + toolStrip1.Dock = System.Windows.Forms.DockStyle.None; + toolStrip1.ImageScalingSize = new System.Drawing.Size(28, 28); + toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { comboBox_filter_camera, toolStripSeparator1, toolStripDropDownButtonFilters, toolStripDropDownButtonOptions, toolStripButtonDetails, toolStripButtonMaskDetails, toolStripButtonEditImageMask, toolStripSeparator9, toolStripButtonEditURL, toolStripButtonAdjustAnno }); + toolStrip1.Location = new System.Drawing.Point(6, 3); + toolStrip1.Name = "toolStrip1"; + toolStrip1.RenderMode = System.Windows.Forms.ToolStripRenderMode.System; + toolStrip1.Size = new System.Drawing.Size(1090, 37); + toolStrip1.TabIndex = 5; + toolStrip1.Text = "toolStrip1"; // - // pictureBox1 + // comboBox_filter_camera // - this.pictureBox1.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Zoom; - this.pictureBox1.Dock = System.Windows.Forms.DockStyle.Fill; - this.pictureBox1.Location = new System.Drawing.Point(3, 50); - this.pictureBox1.Name = "pictureBox1"; - this.pictureBox1.Size = new System.Drawing.Size(646, 371); - this.pictureBox1.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; - this.pictureBox1.TabIndex = 6; - this.pictureBox1.TabStop = false; - this.pictureBox1.Paint += new System.Windows.Forms.PaintEventHandler(this.pictureBox1_Paint); - // - // tableLayoutPanel22 - // - this.tableLayoutPanel22.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.tableLayoutPanel22.ColumnCount = 3; - this.tableLayoutPanel22.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 20F)); - this.tableLayoutPanel22.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 20F)); - this.tableLayoutPanel22.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 60F)); - this.tableLayoutPanel22.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20F)); - this.tableLayoutPanel22.Controls.Add(this.cb_showObjects, 0, 0); - this.tableLayoutPanel22.Controls.Add(this.cb_showMask, 0, 0); - this.tableLayoutPanel22.Controls.Add(this.lbl_objects, 2, 0); - this.tableLayoutPanel22.Location = new System.Drawing.Point(3, 3); - this.tableLayoutPanel22.Name = "tableLayoutPanel22"; - this.tableLayoutPanel22.RowCount = 1; - this.tableLayoutPanel22.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.tableLayoutPanel22.Size = new System.Drawing.Size(646, 41); - this.tableLayoutPanel22.TabIndex = 9; + comboBox_filter_camera.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + comboBox_filter_camera.FlatStyle = System.Windows.Forms.FlatStyle.Standard; + comboBox_filter_camera.Name = "comboBox_filter_camera"; + comboBox_filter_camera.Size = new System.Drawing.Size(173, 37); + comboBox_filter_camera.DropDownClosed += comboBox_filter_camera_DropDownClosed; // - // cb_showObjects + // toolStripSeparator1 + // + toolStripSeparator1.Name = "toolStripSeparator1"; + toolStripSeparator1.Size = new System.Drawing.Size(6, 37); + // + // toolStripDropDownButtonFilters + // + toolStripDropDownButtonFilters.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { cb_filter_success, cb_filter_nosuccess, cb_filter_person, cb_filter_animal, cb_filter_vehicle, cb_filter_skipped, cb_filter_masked }); + toolStripDropDownButtonFilters.Image = (System.Drawing.Image)resources.GetObject("toolStripDropDownButtonFilters.Image"); + toolStripDropDownButtonFilters.ImageTransparentColor = System.Drawing.Color.Magenta; + toolStripDropDownButtonFilters.Name = "toolStripDropDownButtonFilters"; + toolStripDropDownButtonFilters.Size = new System.Drawing.Size(120, 34); + toolStripDropDownButtonFilters.Text = "History Filters"; + // + // cb_filter_success + // + cb_filter_success.CheckOnClick = true; + cb_filter_success.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + cb_filter_success.Name = "cb_filter_success"; + cb_filter_success.Size = new System.Drawing.Size(188, 22); + cb_filter_success.Text = "Only Relevant"; + cb_filter_success.Click += cb_filter_success_Click; + // + // cb_filter_nosuccess + // + cb_filter_nosuccess.CheckOnClick = true; + cb_filter_nosuccess.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + cb_filter_nosuccess.Name = "cb_filter_nosuccess"; + cb_filter_nosuccess.Size = new System.Drawing.Size(188, 22); + cb_filter_nosuccess.Text = "Only False / Irrelevant"; + cb_filter_nosuccess.Click += cb_filter_nosuccess_Click; + // + // cb_filter_person + // + cb_filter_person.CheckOnClick = true; + cb_filter_person.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + cb_filter_person.Name = "cb_filter_person"; + cb_filter_person.Size = new System.Drawing.Size(188, 22); + cb_filter_person.Text = "Only People"; + cb_filter_person.Click += cb_filter_person_Click; + // + // cb_filter_animal + // + cb_filter_animal.CheckOnClick = true; + cb_filter_animal.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + cb_filter_animal.Name = "cb_filter_animal"; + cb_filter_animal.Size = new System.Drawing.Size(188, 22); + cb_filter_animal.Text = "Only Animals"; + cb_filter_animal.Click += cb_filter_animal_Click; + // + // cb_filter_vehicle // - this.cb_showObjects.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); - this.cb_showObjects.Appearance = System.Windows.Forms.Appearance.Button; - this.cb_showObjects.AutoSize = true; - this.cb_showObjects.Checked = true; - this.cb_showObjects.CheckState = System.Windows.Forms.CheckState.Checked; - this.cb_showObjects.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.cb_showObjects.Location = new System.Drawing.Point(132, 7); - this.cb_showObjects.Name = "cb_showObjects"; - this.cb_showObjects.Size = new System.Drawing.Size(123, 27); - this.cb_showObjects.TabIndex = 12; - this.cb_showObjects.Text = "Show Objects"; - this.cb_showObjects.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - this.cb_showObjects.UseVisualStyleBackColor = true; - this.cb_showObjects.MouseUp += new System.Windows.Forms.MouseEventHandler(this.cb_showObjects_MouseUp); + cb_filter_vehicle.CheckOnClick = true; + cb_filter_vehicle.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + cb_filter_vehicle.Name = "cb_filter_vehicle"; + cb_filter_vehicle.Size = new System.Drawing.Size(188, 22); + cb_filter_vehicle.Text = "Only Vehicles"; + cb_filter_vehicle.Click += cb_filter_vehicle_Click; + // + // cb_filter_skipped + // + cb_filter_skipped.CheckOnClick = true; + cb_filter_skipped.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + cb_filter_skipped.Name = "cb_filter_skipped"; + cb_filter_skipped.Size = new System.Drawing.Size(188, 22); + cb_filter_skipped.Text = "Only Skipped"; + cb_filter_skipped.Click += cb_filter_skipped_Click; + // + // cb_filter_masked + // + cb_filter_masked.CheckOnClick = true; + cb_filter_masked.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + cb_filter_masked.Name = "cb_filter_masked"; + cb_filter_masked.Size = new System.Drawing.Size(188, 22); + cb_filter_masked.Text = "Only Masked"; + cb_filter_masked.Click += cb_filter_masked_Click; + // + // toolStripDropDownButtonOptions + // + toolStripDropDownButtonOptions.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { cb_showMask, cb_showObjects, showOnlyRelevantObjectsToolStripMenuItem, cb_follow, automaticallyRefreshToolStripMenuItem, storeFalseAlertsToolStripMenuItem, storeMaskedAlertsToolStripMenuItem, restrictThresholdAtSourceToolStripMenuItem, mergeDuplicatePredictionsToolStripMenuItem }); + toolStripDropDownButtonOptions.Image = (System.Drawing.Image)resources.GetObject("toolStripDropDownButtonOptions.Image"); + toolStripDropDownButtonOptions.ImageTransparentColor = System.Drawing.Color.Magenta; + toolStripDropDownButtonOptions.Name = "toolStripDropDownButtonOptions"; + toolStripDropDownButtonOptions.Size = new System.Drawing.Size(131, 34); + toolStripDropDownButtonOptions.Text = "History Settings"; // // cb_showMask // - this.cb_showMask.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); - this.cb_showMask.Appearance = System.Windows.Forms.Appearance.Button; - this.cb_showMask.AutoSize = true; - this.cb_showMask.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.cb_showMask.Location = new System.Drawing.Point(3, 7); - this.cb_showMask.Name = "cb_showMask"; - this.cb_showMask.Size = new System.Drawing.Size(123, 27); - this.cb_showMask.TabIndex = 11; - this.cb_showMask.Text = "Show Mask"; - this.cb_showMask.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - this.cb_showMask.UseVisualStyleBackColor = true; - this.cb_showMask.CheckedChanged += new System.EventHandler(this.cb_showMask_CheckedChanged); + cb_showMask.CheckOnClick = true; + cb_showMask.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + cb_showMask.Name = "cb_showMask"; + cb_showMask.Size = new System.Drawing.Size(223, 22); + cb_showMask.Text = "Show Mask"; + cb_showMask.Click += cb_showMask_Click; + // + // cb_showObjects + // + cb_showObjects.Checked = true; + cb_showObjects.CheckOnClick = true; + cb_showObjects.CheckState = System.Windows.Forms.CheckState.Checked; + cb_showObjects.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + cb_showObjects.Name = "cb_showObjects"; + cb_showObjects.Size = new System.Drawing.Size(223, 22); + cb_showObjects.Text = "Show Objects"; + cb_showObjects.CheckedChanged += cb_showObjects_CheckedChanged; + cb_showObjects.Click += cb_showObjects_Click; + // + // showOnlyRelevantObjectsToolStripMenuItem + // + showOnlyRelevantObjectsToolStripMenuItem.CheckOnClick = true; + showOnlyRelevantObjectsToolStripMenuItem.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + showOnlyRelevantObjectsToolStripMenuItem.Name = "showOnlyRelevantObjectsToolStripMenuItem"; + showOnlyRelevantObjectsToolStripMenuItem.Size = new System.Drawing.Size(223, 22); + showOnlyRelevantObjectsToolStripMenuItem.Text = "Show Only Relevant Objects"; + showOnlyRelevantObjectsToolStripMenuItem.ToolTipText = resources.GetString("showOnlyRelevantObjectsToolStripMenuItem.ToolTipText"); + showOnlyRelevantObjectsToolStripMenuItem.Click += showOnlyRelevantObjectsToolStripMenuItem_Click; + // + // cb_follow + // + cb_follow.CheckOnClick = true; + cb_follow.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + cb_follow.Name = "cb_follow"; + cb_follow.Size = new System.Drawing.Size(223, 22); + cb_follow.Text = "Follow History List"; + cb_follow.ToolTipText = "Automatically select the latest history item in the list for every update"; + cb_follow.Click += cb_follow_Click; + // + // automaticallyRefreshToolStripMenuItem + // + automaticallyRefreshToolStripMenuItem.Checked = true; + automaticallyRefreshToolStripMenuItem.CheckOnClick = true; + automaticallyRefreshToolStripMenuItem.CheckState = System.Windows.Forms.CheckState.Checked; + automaticallyRefreshToolStripMenuItem.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + automaticallyRefreshToolStripMenuItem.Name = "automaticallyRefreshToolStripMenuItem"; + automaticallyRefreshToolStripMenuItem.Size = new System.Drawing.Size(223, 22); + automaticallyRefreshToolStripMenuItem.Text = "Automatically Refresh"; + automaticallyRefreshToolStripMenuItem.Click += automaticallyRefreshToolStripMenuItem_Click; + // + // storeFalseAlertsToolStripMenuItem + // + storeFalseAlertsToolStripMenuItem.CheckOnClick = true; + storeFalseAlertsToolStripMenuItem.DoubleClickEnabled = true; + storeFalseAlertsToolStripMenuItem.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + storeFalseAlertsToolStripMenuItem.Name = "storeFalseAlertsToolStripMenuItem"; + storeFalseAlertsToolStripMenuItem.Size = new System.Drawing.Size(223, 22); + storeFalseAlertsToolStripMenuItem.Text = "Store False Alerts"; + storeFalseAlertsToolStripMenuItem.ToolTipText = resources.GetString("storeFalseAlertsToolStripMenuItem.ToolTipText"); + storeFalseAlertsToolStripMenuItem.Click += storeFalseAlertsToolStripMenuItem_Click; + // + // storeMaskedAlertsToolStripMenuItem + // + storeMaskedAlertsToolStripMenuItem.CheckOnClick = true; + storeMaskedAlertsToolStripMenuItem.DoubleClickEnabled = true; + storeMaskedAlertsToolStripMenuItem.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + storeMaskedAlertsToolStripMenuItem.Name = "storeMaskedAlertsToolStripMenuItem"; + storeMaskedAlertsToolStripMenuItem.Size = new System.Drawing.Size(223, 22); + storeMaskedAlertsToolStripMenuItem.Text = "Store Masked Alerts"; + storeMaskedAlertsToolStripMenuItem.ToolTipText = "If disabled the database will be smaller, leave enabled for better troubleshooting"; + storeMaskedAlertsToolStripMenuItem.Click += storeMaskedAlertsToolStripMenuItem_Click; + // + // restrictThresholdAtSourceToolStripMenuItem + // + restrictThresholdAtSourceToolStripMenuItem.CheckOnClick = true; + restrictThresholdAtSourceToolStripMenuItem.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + restrictThresholdAtSourceToolStripMenuItem.Name = "restrictThresholdAtSourceToolStripMenuItem"; + restrictThresholdAtSourceToolStripMenuItem.Size = new System.Drawing.Size(223, 22); + restrictThresholdAtSourceToolStripMenuItem.Text = "Restrict Threshold at Source"; + restrictThresholdAtSourceToolStripMenuItem.ToolTipText = resources.GetString("restrictThresholdAtSourceToolStripMenuItem.ToolTipText"); + restrictThresholdAtSourceToolStripMenuItem.Click += restrictThresholdAtSourceToolStripMenuItem_Click; + // + // mergeDuplicatePredictionsToolStripMenuItem + // + mergeDuplicatePredictionsToolStripMenuItem.Name = "mergeDuplicatePredictionsToolStripMenuItem"; + mergeDuplicatePredictionsToolStripMenuItem.Size = new System.Drawing.Size(223, 22); + mergeDuplicatePredictionsToolStripMenuItem.Text = "Merge Duplicate Predictions"; + mergeDuplicatePredictionsToolStripMenuItem.Click += mergeDuplicatePredictionsToolStripMenuItem_Click; + // + // toolStripButtonDetails + // + toolStripButtonDetails.Enabled = false; + toolStripButtonDetails.Image = (System.Drawing.Image)resources.GetObject("toolStripButtonDetails.Image"); + toolStripButtonDetails.ImageTransparentColor = System.Drawing.Color.Magenta; + toolStripButtonDetails.Name = "toolStripButtonDetails"; + toolStripButtonDetails.Size = new System.Drawing.Size(131, 34); + toolStripButtonDetails.Text = "Prediction Details"; + toolStripButtonDetails.Click += toolStripButtonDetails_Click; + // + // toolStripButtonMaskDetails + // + toolStripButtonMaskDetails.Image = (System.Drawing.Image)resources.GetObject("toolStripButtonMaskDetails.Image"); + toolStripButtonMaskDetails.ImageTransparentColor = System.Drawing.Color.Magenta; + toolStripButtonMaskDetails.Name = "toolStripButtonMaskDetails"; + toolStripButtonMaskDetails.Size = new System.Drawing.Size(155, 34); + toolStripButtonMaskDetails.Text = "Dynamic Mask Details"; + toolStripButtonMaskDetails.Click += toolStripButtonMaskDetails_Click; + // + // toolStripButtonEditImageMask + // + toolStripButtonEditImageMask.Image = (System.Drawing.Image)resources.GetObject("toolStripButtonEditImageMask.Image"); + toolStripButtonEditImageMask.ImageTransparentColor = System.Drawing.Color.Magenta; + toolStripButtonEditImageMask.Name = "toolStripButtonEditImageMask"; + toolStripButtonEditImageMask.Size = new System.Drawing.Size(126, 34); + toolStripButtonEditImageMask.Text = "Edit Image Mask"; + toolStripButtonEditImageMask.Click += toolStripButtonEditImageMask_Click; + // + // toolStripSeparator9 + // + toolStripSeparator9.Name = "toolStripSeparator9"; + toolStripSeparator9.Size = new System.Drawing.Size(6, 37); + // + // toolStripButtonEditURL + // + toolStripButtonEditURL.Image = (System.Drawing.Image)resources.GetObject("toolStripButtonEditURL.Image"); + toolStripButtonEditURL.ImageTransparentColor = System.Drawing.Color.Magenta; + toolStripButtonEditURL.Name = "toolStripButtonEditURL"; + toolStripButtonEditURL.Size = new System.Drawing.Size(108, 34); + toolStripButtonEditURL.Text = "Edit AI Server"; + toolStripButtonEditURL.Click += toolStripButtonEditURL_Click; + // + // toolStripButtonAdjustAnno + // + toolStripButtonAdjustAnno.Image = (System.Drawing.Image)resources.GetObject("toolStripButtonAdjustAnno.Image"); + toolStripButtonAdjustAnno.ImageTransparentColor = System.Drawing.Color.Magenta; + toolStripButtonAdjustAnno.Name = "toolStripButtonAdjustAnno"; + toolStripButtonAdjustAnno.Size = new System.Drawing.Size(141, 32); + toolStripButtonAdjustAnno.Text = "Adjust Annotations"; + toolStripButtonAdjustAnno.Click += toolStripButtonAdjustAnno_Click; + // + // splitContainer2 + // + splitContainer2.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + splitContainer2.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; + splitContainer2.FixedPanel = System.Windows.Forms.FixedPanel.Panel1; + splitContainer2.Location = new System.Drawing.Point(2, 43); + splitContainer2.Margin = new System.Windows.Forms.Padding(2); + splitContainer2.Name = "splitContainer2"; + // + // splitContainer2.Panel1 + // + splitContainer2.Panel1.Controls.Add(groupBox8); + // + // splitContainer2.Panel2 + // + splitContainer2.Panel2.Controls.Add(lbl_objects); + splitContainer2.Panel2.Controls.Add(pictureBox1); + splitContainer2.Panel2MinSize = 300; + splitContainer2.Size = new System.Drawing.Size(1097, 461); + splitContainer2.SplitterDistance = 284; + splitContainer2.SplitterWidth = 2; + splitContainer2.TabIndex = 4; + // + // groupBox8 + // + groupBox8.Controls.Add(folv_history); + groupBox8.Dock = System.Windows.Forms.DockStyle.Fill; + groupBox8.Location = new System.Drawing.Point(0, 0); + groupBox8.Margin = new System.Windows.Forms.Padding(2); + groupBox8.Name = "groupBox8"; + groupBox8.Padding = new System.Windows.Forms.Padding(2); + groupBox8.Size = new System.Drawing.Size(280, 457); + groupBox8.TabIndex = 11; + groupBox8.TabStop = false; + groupBox8.Text = "History"; + // + // folv_history + // + folv_history.ContextMenuStrip = contextMenuStripHistory; + folv_history.Dock = System.Windows.Forms.DockStyle.Fill; + folv_history.Location = new System.Drawing.Point(2, 17); + folv_history.Margin = new System.Windows.Forms.Padding(2); + folv_history.Name = "folv_history"; + folv_history.ShowGroups = false; + folv_history.Size = new System.Drawing.Size(276, 438); + folv_history.TabIndex = 10; + folv_history.UseCellFormatEvents = true; + folv_history.UseCompatibleStateImageBehavior = false; + folv_history.UseFiltering = true; + folv_history.View = System.Windows.Forms.View.Details; + folv_history.VirtualMode = true; + folv_history.FormatRow += folv_history_FormatRow; + folv_history.SelectionChanged += folv_history_SelectionChanged; + folv_history.SelectedIndexChanged += folv_history_SelectedIndexChanged; + folv_history.MouseDoubleClick += folv_history_MouseDoubleClick; + // + // contextMenuStripHistory + // + contextMenuStripHistory.ImageScalingSize = new System.Drawing.Size(24, 24); + contextMenuStripHistory.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { testDetectionAgainToolStripMenuItem, detailsToolStripMenuItem, refreshToolStripMenuItem, dynamicMaskDetailsToolStripMenuItem, locateInLogToolStripMenuItem, manuallyAddImagesToolStripMenuItem, viewImageToolStripMenuItem, jumpToImageToolStripMenuItem }); + contextMenuStripHistory.Name = "contextMenuStripHistory"; + contextMenuStripHistory.Size = new System.Drawing.Size(191, 180); + // + // testDetectionAgainToolStripMenuItem + // + testDetectionAgainToolStripMenuItem.Name = "testDetectionAgainToolStripMenuItem"; + testDetectionAgainToolStripMenuItem.Size = new System.Drawing.Size(190, 22); + testDetectionAgainToolStripMenuItem.Text = "Test Detection Again"; + testDetectionAgainToolStripMenuItem.Click += testDetectionAgainToolStripMenuItem_Click; + // + // detailsToolStripMenuItem + // + detailsToolStripMenuItem.Name = "detailsToolStripMenuItem"; + detailsToolStripMenuItem.Size = new System.Drawing.Size(190, 22); + detailsToolStripMenuItem.Text = "Prediction Details"; + detailsToolStripMenuItem.Click += detailsToolStripMenuItem_Click; + // + // refreshToolStripMenuItem + // + refreshToolStripMenuItem.Name = "refreshToolStripMenuItem"; + refreshToolStripMenuItem.Size = new System.Drawing.Size(190, 22); + refreshToolStripMenuItem.Text = "Refresh"; + refreshToolStripMenuItem.Click += refreshToolStripMenuItem_Click; + // + // dynamicMaskDetailsToolStripMenuItem + // + dynamicMaskDetailsToolStripMenuItem.Name = "dynamicMaskDetailsToolStripMenuItem"; + dynamicMaskDetailsToolStripMenuItem.Size = new System.Drawing.Size(190, 22); + dynamicMaskDetailsToolStripMenuItem.Text = "Dynamic Mask Details"; + dynamicMaskDetailsToolStripMenuItem.Click += dynamicMaskDetailsToolStripMenuItem_Click; + // + // locateInLogToolStripMenuItem + // + locateInLogToolStripMenuItem.Name = "locateInLogToolStripMenuItem"; + locateInLogToolStripMenuItem.Size = new System.Drawing.Size(190, 22); + locateInLogToolStripMenuItem.Text = "Locate in log"; + locateInLogToolStripMenuItem.Click += locateInLogToolStripMenuItem_Click; + // + // manuallyAddImagesToolStripMenuItem + // + manuallyAddImagesToolStripMenuItem.Name = "manuallyAddImagesToolStripMenuItem"; + manuallyAddImagesToolStripMenuItem.Size = new System.Drawing.Size(190, 22); + manuallyAddImagesToolStripMenuItem.Text = "Manually Add Images"; + manuallyAddImagesToolStripMenuItem.Click += manuallyAddImagesToolStripMenuItem_Click; + // + // viewImageToolStripMenuItem + // + viewImageToolStripMenuItem.Name = "viewImageToolStripMenuItem"; + viewImageToolStripMenuItem.Size = new System.Drawing.Size(190, 22); + viewImageToolStripMenuItem.Text = "View Image"; + viewImageToolStripMenuItem.Click += viewImageToolStripMenuItem_Click; + // + // jumpToImageToolStripMenuItem + // + jumpToImageToolStripMenuItem.Name = "jumpToImageToolStripMenuItem"; + jumpToImageToolStripMenuItem.Size = new System.Drawing.Size(190, 22); + jumpToImageToolStripMenuItem.Text = "Jump To Image"; + jumpToImageToolStripMenuItem.Click += jumpToImageToolStripMenuItem_Click; // // lbl_objects // - this.lbl_objects.AutoSize = true; - this.lbl_objects.Dock = System.Windows.Forms.DockStyle.Fill; - this.lbl_objects.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.lbl_objects.Location = new System.Drawing.Point(261, 0); - this.lbl_objects.Name = "lbl_objects"; - this.lbl_objects.Padding = new System.Windows.Forms.Padding(5, 0, 0, 0); - this.lbl_objects.Size = new System.Drawing.Size(382, 41); - this.lbl_objects.TabIndex = 14; - this.lbl_objects.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + lbl_objects.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + lbl_objects.BackColor = System.Drawing.SystemColors.Info; + lbl_objects.Location = new System.Drawing.Point(1, 0); + lbl_objects.Margin = new System.Windows.Forms.Padding(6, 0, 3, 0); + lbl_objects.Name = "lbl_objects"; + lbl_objects.Padding = new System.Windows.Forms.Padding(6, 0, 0, 0); + lbl_objects.Size = new System.Drawing.Size(883, 20); + lbl_objects.TabIndex = 14; + lbl_objects.Text = "No selection"; + lbl_objects.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // pictureBox1 + // + pictureBox1.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + pictureBox1.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Zoom; + pictureBox1.Location = new System.Drawing.Point(4, 23); + pictureBox1.Name = "pictureBox1"; + pictureBox1.Size = new System.Drawing.Size(879, 431); + pictureBox1.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + pictureBox1.TabIndex = 6; + pictureBox1.TabStop = false; + pictureBox1.Click += pictureBox1_Click; + pictureBox1.Paint += pictureBox1_Paint; + // + // toolStripContainer1 + // + toolStripContainer1.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + toolStripContainer1.BottomToolStripPanelVisible = false; + // + // toolStripContainer1.ContentPanel + // + toolStripContainer1.ContentPanel.Size = new System.Drawing.Size(1095, 11); + toolStripContainer1.LeftToolStripPanelVisible = false; + toolStripContainer1.Location = new System.Drawing.Point(4, 4); + toolStripContainer1.Name = "toolStripContainer1"; + toolStripContainer1.RightToolStripPanelVisible = false; + toolStripContainer1.Size = new System.Drawing.Size(1095, 36); + toolStripContainer1.TabIndex = 6; + toolStripContainer1.Text = "toolStripContainer1"; + // + // tabCameras + // + tabCameras.BackColor = System.Drawing.Color.WhiteSmoke; + tabCameras.Controls.Add(splitContainer1); + tabCameras.Location = new System.Drawing.Point(4, 24); + tabCameras.Name = "tabCameras"; + tabCameras.Padding = new System.Windows.Forms.Padding(3); + tabCameras.Size = new System.Drawing.Size(1099, 492); + tabCameras.TabIndex = 2; + tabCameras.Text = "Cameras"; // // splitContainer1 // - this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill; - this.splitContainer1.Location = new System.Drawing.Point(3, 3); - this.splitContainer1.Name = "splitContainer1"; - this.splitContainer1.Orientation = System.Windows.Forms.Orientation.Horizontal; + splitContainer1.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; + splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill; + splitContainer1.FixedPanel = System.Windows.Forms.FixedPanel.Panel1; + splitContainer1.Location = new System.Drawing.Point(3, 3); + splitContainer1.Name = "splitContainer1"; // // splitContainer1.Panel1 // - this.splitContainer1.Panel1.Controls.Add(this.tableLayoutPanel19); + splitContainer1.Panel1.Controls.Add(splitContainer3); // // splitContainer1.Panel2 // - this.splitContainer1.Panel2.BackColor = System.Drawing.Color.Transparent; - this.splitContainer1.Panel2.Controls.Add(this.panel1); - this.splitContainer1.Panel2.Padding = new System.Windows.Forms.Padding(3); - this.splitContainer1.Size = new System.Drawing.Size(276, 424); - this.splitContainer1.SplitterDistance = 240; - this.splitContainer1.TabIndex = 6; - // - // tableLayoutPanel19 - // - this.tableLayoutPanel19.ColumnCount = 1; - this.tableLayoutPanel19.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.tableLayoutPanel19.Controls.Add(this.cb_showFilters, 0, 1); - this.tableLayoutPanel19.Controls.Add(this.list1, 0, 0); - this.tableLayoutPanel19.Dock = System.Windows.Forms.DockStyle.Fill; - this.tableLayoutPanel19.Location = new System.Drawing.Point(0, 0); - this.tableLayoutPanel19.Name = "tableLayoutPanel19"; - this.tableLayoutPanel19.RowCount = 2; - this.tableLayoutPanel19.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.tableLayoutPanel19.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 30F)); - this.tableLayoutPanel19.Size = new System.Drawing.Size(276, 240); - this.tableLayoutPanel19.TabIndex = 0; - // - // cb_showFilters - // - this.cb_showFilters.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); - this.cb_showFilters.Appearance = System.Windows.Forms.Appearance.Button; - this.cb_showFilters.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.cb_showFilters.Location = new System.Drawing.Point(3, 213); - this.cb_showFilters.MinimumSize = new System.Drawing.Size(0, 27); - this.cb_showFilters.Name = "cb_showFilters"; - this.cb_showFilters.Size = new System.Drawing.Size(270, 27); - this.cb_showFilters.TabIndex = 9; - this.cb_showFilters.Text = "˄ Filter"; - this.cb_showFilters.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; - this.cb_showFilters.UseVisualStyleBackColor = true; - this.cb_showFilters.CheckedChanged += new System.EventHandler(this.cb_showFilters_CheckedChanged); - // - // list1 - // - this.list1.AllowColumnReorder = true; - this.list1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.list1.Dock = System.Windows.Forms.DockStyle.Fill; - this.list1.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.list1.FullRowSelect = true; - this.list1.GridLines = true; - this.list1.HideSelection = false; - this.list1.Location = new System.Drawing.Point(3, 3); - this.list1.Name = "list1"; - this.list1.Size = new System.Drawing.Size(270, 204); - this.list1.TabIndex = 3; - this.list1.UseCompatibleStateImageBehavior = false; - this.list1.View = System.Windows.Forms.View.Details; - this.list1.SelectedIndexChanged += new System.EventHandler(this.list1_SelectedIndexChanged); - // - // panel1 - // - this.panel1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.panel1.Controls.Add(this.comboBox_filter_camera); - this.panel1.Controls.Add(this.cb_filter_nosuccess); - this.panel1.Controls.Add(this.cb_filter_success); - this.panel1.Controls.Add(this.cb_filter_person); - this.panel1.Controls.Add(this.cb_filter_vehicle); - this.panel1.Controls.Add(this.cb_filter_animal); - this.panel1.Dock = System.Windows.Forms.DockStyle.Fill; - this.panel1.Location = new System.Drawing.Point(3, 3); - this.panel1.Name = "panel1"; - this.panel1.Size = new System.Drawing.Size(270, 174); - this.panel1.TabIndex = 2; + splitContainer1.Panel2.Controls.Add(tableLayoutPanel6); + splitContainer1.Size = new System.Drawing.Size(1093, 486); + splitContainer1.SplitterDistance = 153; + splitContainer1.TabIndex = 1; // - // comboBox_filter_camera + // splitContainer3 // - this.comboBox_filter_camera.Dock = System.Windows.Forms.DockStyle.Top; - this.comboBox_filter_camera.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.comboBox_filter_camera.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.comboBox_filter_camera.FormattingEnabled = true; - this.comboBox_filter_camera.Location = new System.Drawing.Point(0, 0); - this.comboBox_filter_camera.Name = "comboBox_filter_camera"; - this.comboBox_filter_camera.Size = new System.Drawing.Size(268, 25); - this.comboBox_filter_camera.TabIndex = 2; - this.comboBox_filter_camera.SelectedIndexChanged += new System.EventHandler(this.comboBox_filter_camera_SelectedIndexChanged); + splitContainer3.Dock = System.Windows.Forms.DockStyle.Fill; + splitContainer3.Location = new System.Drawing.Point(0, 0); + splitContainer3.Name = "splitContainer3"; + splitContainer3.Orientation = System.Windows.Forms.Orientation.Horizontal; // - // cb_filter_nosuccess + // splitContainer3.Panel1 // - this.cb_filter_nosuccess.AutoSize = true; - this.cb_filter_nosuccess.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.cb_filter_nosuccess.Location = new System.Drawing.Point(6, 64); - this.cb_filter_nosuccess.Name = "cb_filter_nosuccess"; - this.cb_filter_nosuccess.Size = new System.Drawing.Size(185, 21); - this.cb_filter_nosuccess.TabIndex = 1; - this.cb_filter_nosuccess.Text = "only false / irrelevant alerts"; - this.cb_filter_nosuccess.UseVisualStyleBackColor = true; - this.cb_filter_nosuccess.CheckedChanged += new System.EventHandler(this.cb_filter_nosuccess_CheckedChanged); + splitContainer3.Panel1.Controls.Add(FOLV_Cameras); // - // cb_filter_success + // splitContainer3.Panel2 // - this.cb_filter_success.AutoSize = true; - this.cb_filter_success.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.cb_filter_success.Location = new System.Drawing.Point(6, 37); - this.cb_filter_success.Name = "cb_filter_success"; - this.cb_filter_success.Size = new System.Drawing.Size(137, 21); - this.cb_filter_success.TabIndex = 0; - this.cb_filter_success.Text = "only relevant alerts"; - this.cb_filter_success.UseVisualStyleBackColor = true; - this.cb_filter_success.CheckedChanged += new System.EventHandler(this.cb_filter_success_CheckedChanged); + splitContainer3.Panel2.Controls.Add(pictureBoxCamera); + splitContainer3.Size = new System.Drawing.Size(149, 482); + splitContainer3.SplitterDistance = 287; + splitContainer3.TabIndex = 1; // - // cb_filter_person + // FOLV_Cameras // - this.cb_filter_person.AutoSize = true; - this.cb_filter_person.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.cb_filter_person.Location = new System.Drawing.Point(6, 91); - this.cb_filter_person.Name = "cb_filter_person"; - this.cb_filter_person.Size = new System.Drawing.Size(159, 21); - this.cb_filter_person.TabIndex = 0; - this.cb_filter_person.Text = "only alerts with people"; - this.cb_filter_person.UseVisualStyleBackColor = true; - this.cb_filter_person.CheckedChanged += new System.EventHandler(this.cb_filter_person_CheckedChanged); + FOLV_Cameras.Dock = System.Windows.Forms.DockStyle.Fill; + FOLV_Cameras.LargeImageList = CameraImageList; + FOLV_Cameras.Location = new System.Drawing.Point(0, 0); + FOLV_Cameras.Name = "FOLV_Cameras"; + FOLV_Cameras.ShowGroups = false; + FOLV_Cameras.Size = new System.Drawing.Size(149, 287); + FOLV_Cameras.TabIndex = 0; + FOLV_Cameras.UseCompatibleStateImageBehavior = false; + FOLV_Cameras.View = System.Windows.Forms.View.Details; + FOLV_Cameras.VirtualMode = true; + FOLV_Cameras.FormatRow += FOLV_Cameras_FormatRow; + FOLV_Cameras.SelectionChanged += FOLV_Cameras_SelectionChanged; // - // cb_filter_vehicle + // CameraImageList // - this.cb_filter_vehicle.AutoSize = true; - this.cb_filter_vehicle.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.cb_filter_vehicle.Location = new System.Drawing.Point(6, 118); - this.cb_filter_vehicle.Name = "cb_filter_vehicle"; - this.cb_filter_vehicle.Size = new System.Drawing.Size(163, 21); - this.cb_filter_vehicle.TabIndex = 0; - this.cb_filter_vehicle.Text = "only alerts with vehicles"; - this.cb_filter_vehicle.UseVisualStyleBackColor = true; - this.cb_filter_vehicle.CheckedChanged += new System.EventHandler(this.cb_filter_vehicle_CheckedChanged); + CameraImageList.ColorDepth = System.Windows.Forms.ColorDepth.Depth8Bit; + CameraImageList.ImageStream = (System.Windows.Forms.ImageListStreamer)resources.GetObject("CameraImageList.ImageStream"); + CameraImageList.TransparentColor = System.Drawing.Color.Transparent; + CameraImageList.Images.SetKeyName(0, "camera-webcam.ico"); + CameraImageList.Images.SetKeyName(1, "camera-webcam_add.ico"); + CameraImageList.Images.SetKeyName(2, "camera-webcam_delete.ico"); // - // cb_filter_animal + // pictureBoxCamera // - this.cb_filter_animal.AutoSize = true; - this.cb_filter_animal.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.cb_filter_animal.Location = new System.Drawing.Point(6, 145); - this.cb_filter_animal.Name = "cb_filter_animal"; - this.cb_filter_animal.Size = new System.Drawing.Size(162, 21); - this.cb_filter_animal.TabIndex = 0; - this.cb_filter_animal.Text = "only alerts with animals"; - this.cb_filter_animal.UseVisualStyleBackColor = true; - this.cb_filter_animal.CheckedChanged += new System.EventHandler(this.cb_filter_animal_CheckedChanged); + pictureBoxCamera.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Zoom; + pictureBoxCamera.Dock = System.Windows.Forms.DockStyle.Fill; + pictureBoxCamera.Location = new System.Drawing.Point(0, 0); + pictureBoxCamera.Name = "pictureBoxCamera"; + pictureBoxCamera.Size = new System.Drawing.Size(149, 191); + pictureBoxCamera.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + pictureBoxCamera.TabIndex = 0; + pictureBoxCamera.TabStop = false; // - // tabCameras + // tableLayoutPanel6 + // + tableLayoutPanel6.BackColor = System.Drawing.Color.WhiteSmoke; + tableLayoutPanel6.ColumnCount = 1; + tableLayoutPanel6.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + tableLayoutPanel6.Controls.Add(tableLayoutPanel11, 0, 2); + tableLayoutPanel6.Controls.Add(lbl_camstats, 0, 0); + tableLayoutPanel6.Controls.Add(tableLayoutPanel7, 0, 1); + tableLayoutPanel6.Dock = System.Windows.Forms.DockStyle.Fill; + tableLayoutPanel6.Location = new System.Drawing.Point(0, 0); + tableLayoutPanel6.Name = "tableLayoutPanel6"; + tableLayoutPanel6.RowCount = 3; + tableLayoutPanel6.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 6.82557F)); + tableLayoutPanel6.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 93.17443F)); + tableLayoutPanel6.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 40F)); + tableLayoutPanel6.Size = new System.Drawing.Size(932, 482); + tableLayoutPanel6.TabIndex = 1; // - this.tabCameras.Controls.Add(this.tableLayoutPanel2); - this.tabCameras.Location = new System.Drawing.Point(4, 22); - this.tabCameras.Name = "tabCameras"; - this.tabCameras.Padding = new System.Windows.Forms.Padding(3); - this.tabCameras.Size = new System.Drawing.Size(946, 436); - this.tabCameras.TabIndex = 2; - this.tabCameras.Text = "Cameras"; - this.tabCameras.UseVisualStyleBackColor = true; - // - // tableLayoutPanel2 - // - this.tableLayoutPanel2.ColumnCount = 2; - this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 15.0505F)); - this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 84.94949F)); - this.tableLayoutPanel2.Controls.Add(this.tableLayoutPanel3, 0, 0); - this.tableLayoutPanel2.Controls.Add(this.tableLayoutPanel6, 1, 0); - this.tableLayoutPanel2.Dock = System.Windows.Forms.DockStyle.Fill; - this.tableLayoutPanel2.Location = new System.Drawing.Point(3, 3); - this.tableLayoutPanel2.Name = "tableLayoutPanel2"; - this.tableLayoutPanel2.RowCount = 1; - this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.tableLayoutPanel2.Size = new System.Drawing.Size(940, 430); - this.tableLayoutPanel2.TabIndex = 0; - // - // tableLayoutPanel3 - // - this.tableLayoutPanel3.ColumnCount = 1; - this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.tableLayoutPanel3.Controls.Add(this.list2, 0, 0); - this.tableLayoutPanel3.Controls.Add(this.btnCameraAdd, 0, 1); - this.tableLayoutPanel3.Dock = System.Windows.Forms.DockStyle.Fill; - this.tableLayoutPanel3.Location = new System.Drawing.Point(3, 3); - this.tableLayoutPanel3.Name = "tableLayoutPanel3"; - this.tableLayoutPanel3.RowCount = 2; - this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 80F)); - this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 10F)); - this.tableLayoutPanel3.Size = new System.Drawing.Size(135, 424); - this.tableLayoutPanel3.TabIndex = 0; - // - // list2 - // - this.list2.Dock = System.Windows.Forms.DockStyle.Fill; - this.list2.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.list2.GridLines = true; - this.list2.HideSelection = false; - this.list2.Location = new System.Drawing.Point(3, 3); - this.list2.Name = "list2"; - this.list2.Size = new System.Drawing.Size(129, 370); - this.list2.TabIndex = 1; - this.list2.UseCompatibleStateImageBehavior = false; - this.list2.View = System.Windows.Forms.View.Details; - this.list2.SelectedIndexChanged += new System.EventHandler(this.list2_SelectedIndexChanged); - this.list2.KeyDown += new System.Windows.Forms.KeyEventHandler(this.list2_KeyDown); + // tableLayoutPanel11 + // + tableLayoutPanel11.BackColor = System.Drawing.Color.Transparent; + tableLayoutPanel11.ColumnCount = 5; + tableLayoutPanel11.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 14.15411F)); + tableLayoutPanel11.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 21.46066F)); + tableLayoutPanel11.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 21.46066F)); + tableLayoutPanel11.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 21.46066F)); + tableLayoutPanel11.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 21.46391F)); + tableLayoutPanel11.Controls.Add(btnCameraAdd, 0, 0); + tableLayoutPanel11.Controls.Add(btnCameraDel, 1, 0); + tableLayoutPanel11.Controls.Add(btnSaveTo, 2, 0); + tableLayoutPanel11.Controls.Add(btnCameraSave, 4, 0); + tableLayoutPanel11.Controls.Add(btnPause, 3, 0); + tableLayoutPanel11.Dock = System.Windows.Forms.DockStyle.Fill; + tableLayoutPanel11.Location = new System.Drawing.Point(3, 444); + tableLayoutPanel11.Name = "tableLayoutPanel11"; + tableLayoutPanel11.RowCount = 1; + tableLayoutPanel11.RowStyles.Add(new System.Windows.Forms.RowStyle()); + tableLayoutPanel11.Size = new System.Drawing.Size(926, 35); + tableLayoutPanel11.TabIndex = 3; // // btnCameraAdd // - this.btnCameraAdd.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom))); - this.btnCameraAdd.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.btnCameraAdd.Location = new System.Drawing.Point(20, 379); - this.btnCameraAdd.Name = "btnCameraAdd"; - this.btnCameraAdd.Size = new System.Drawing.Size(94, 42); - this.btnCameraAdd.TabIndex = 4; - this.btnCameraAdd.Text = "Add Camera"; - this.btnCameraAdd.UseVisualStyleBackColor = true; - this.btnCameraAdd.Click += new System.EventHandler(this.btnCameraAdd_Click); + btnCameraAdd.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom; + btnCameraAdd.Image = Properties.Resources.camera_webcam_add; + btnCameraAdd.ImageAlign = System.Drawing.ContentAlignment.MiddleLeft; + btnCameraAdd.Location = new System.Drawing.Point(30, 3); + btnCameraAdd.Name = "btnCameraAdd"; + btnCameraAdd.Size = new System.Drawing.Size(70, 30); + btnCameraAdd.TabIndex = 31; + btnCameraAdd.Text = "Add"; + btnCameraAdd.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + btnCameraAdd.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageBeforeText; + btnCameraAdd.UseVisualStyleBackColor = true; + btnCameraAdd.Click += btnCameraAdd_Click; // - // tableLayoutPanel6 + // btnCameraDel // - this.tableLayoutPanel6.ColumnCount = 1; - this.tableLayoutPanel6.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.tableLayoutPanel6.Controls.Add(this.tableLayoutPanel7, 0, 1); - this.tableLayoutPanel6.Controls.Add(this.tableLayoutPanel11, 0, 2); - this.tableLayoutPanel6.Controls.Add(this.lbl_camstats, 0, 0); - this.tableLayoutPanel6.Dock = System.Windows.Forms.DockStyle.Fill; - this.tableLayoutPanel6.Location = new System.Drawing.Point(144, 3); - this.tableLayoutPanel6.Name = "tableLayoutPanel6"; - this.tableLayoutPanel6.RowCount = 3; - this.tableLayoutPanel6.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 10F)); - this.tableLayoutPanel6.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 80F)); - this.tableLayoutPanel6.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 10F)); - this.tableLayoutPanel6.Size = new System.Drawing.Size(793, 424); - this.tableLayoutPanel6.TabIndex = 1; + btnCameraDel.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom; + btnCameraDel.Image = Properties.Resources.camera_webcam_delete; + btnCameraDel.ImageAlign = System.Drawing.ContentAlignment.MiddleLeft; + btnCameraDel.Location = new System.Drawing.Point(195, 3); + btnCameraDel.Name = "btnCameraDel"; + btnCameraDel.Size = new System.Drawing.Size(70, 30); + btnCameraDel.TabIndex = 32; + btnCameraDel.Text = "Delete"; + btnCameraDel.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + btnCameraDel.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageBeforeText; + btnCameraDel.UseVisualStyleBackColor = true; + btnCameraDel.Click += btnCameraDel_Click; + // + // btnSaveTo + // + btnSaveTo.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom; + btnSaveTo.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Zoom; + btnSaveTo.Location = new System.Drawing.Point(393, 3); + btnSaveTo.Name = "btnSaveTo"; + btnSaveTo.Size = new System.Drawing.Size(70, 30); + btnSaveTo.TabIndex = 33; + btnSaveTo.Text = "Apply to"; + btnSaveTo.UseVisualStyleBackColor = false; + btnSaveTo.Click += btnSaveTo_Click; // - // tableLayoutPanel7 + // btnCameraSave // - this.tableLayoutPanel7.CellBorderStyle = System.Windows.Forms.TableLayoutPanelCellBorderStyle.Single; - this.tableLayoutPanel7.ColumnCount = 2; - this.tableLayoutPanel7.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 18F)); - this.tableLayoutPanel7.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 82F)); - this.tableLayoutPanel7.Controls.Add(this.tableLayoutPanel8, 1, 2); - this.tableLayoutPanel7.Controls.Add(this.label1, 0, 4); - this.tableLayoutPanel7.Controls.Add(this.tableLayoutPanel9, 1, 4); - this.tableLayoutPanel7.Controls.Add(this.lblPrefix, 0, 1); - this.tableLayoutPanel7.Controls.Add(this.lblName, 0, 0); - this.tableLayoutPanel7.Controls.Add(this.tableLayoutPanel12, 1, 1); - this.tableLayoutPanel7.Controls.Add(this.tableLayoutPanel13, 1, 0); - this.tableLayoutPanel7.Controls.Add(this.lblRelevantObjects, 0, 2); - this.tableLayoutPanel7.Controls.Add(this.lbl_threshold, 0, 3); - this.tableLayoutPanel7.Controls.Add(this.tableLayoutPanel24, 1, 3); - this.tableLayoutPanel7.Dock = System.Windows.Forms.DockStyle.Fill; - this.tableLayoutPanel7.Location = new System.Drawing.Point(3, 45); - this.tableLayoutPanel7.Name = "tableLayoutPanel7"; - this.tableLayoutPanel7.RowCount = 5; - this.tableLayoutPanel7.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 11F)); - this.tableLayoutPanel7.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 11F)); - this.tableLayoutPanel7.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 32F)); - this.tableLayoutPanel7.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 11F)); - this.tableLayoutPanel7.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 35F)); - this.tableLayoutPanel7.Size = new System.Drawing.Size(787, 333); - this.tableLayoutPanel7.TabIndex = 2; - // - // tableLayoutPanel8 - // - this.tableLayoutPanel8.ColumnCount = 5; - this.tableLayoutPanel8.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 20F)); - this.tableLayoutPanel8.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 20F)); - this.tableLayoutPanel8.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 20F)); - this.tableLayoutPanel8.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 20F)); - this.tableLayoutPanel8.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 20F)); - this.tableLayoutPanel8.Controls.Add(this.cb_person, 0, 0); - this.tableLayoutPanel8.Controls.Add(this.cb_bicycle, 0, 1); - this.tableLayoutPanel8.Controls.Add(this.cb_motorcycle, 0, 2); - this.tableLayoutPanel8.Controls.Add(this.cb_bear, 4, 2); - this.tableLayoutPanel8.Controls.Add(this.cb_cow, 4, 1); - this.tableLayoutPanel8.Controls.Add(this.cb_sheep, 4, 0); - this.tableLayoutPanel8.Controls.Add(this.cb_horse, 3, 2); - this.tableLayoutPanel8.Controls.Add(this.cb_bird, 3, 1); - this.tableLayoutPanel8.Controls.Add(this.cb_dog, 3, 0); - this.tableLayoutPanel8.Controls.Add(this.cb_cat, 2, 2); - this.tableLayoutPanel8.Controls.Add(this.cb_airplane, 2, 1); - this.tableLayoutPanel8.Controls.Add(this.cb_boat, 2, 0); - this.tableLayoutPanel8.Controls.Add(this.cb_bus, 1, 2); - this.tableLayoutPanel8.Controls.Add(this.cb_truck, 1, 1); - this.tableLayoutPanel8.Controls.Add(this.cb_car, 1, 0); - this.tableLayoutPanel8.Dock = System.Windows.Forms.DockStyle.Fill; - this.tableLayoutPanel8.Location = new System.Drawing.Point(146, 76); - this.tableLayoutPanel8.Name = "tableLayoutPanel8"; - this.tableLayoutPanel8.RowCount = 3; - this.tableLayoutPanel8.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 33.33333F)); - this.tableLayoutPanel8.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 33.33333F)); - this.tableLayoutPanel8.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 33.33333F)); - this.tableLayoutPanel8.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F)); - this.tableLayoutPanel8.Size = new System.Drawing.Size(637, 98); - this.tableLayoutPanel8.TabIndex = 14; - // - // cb_person - // - this.cb_person.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.cb_person.AutoSize = true; - this.cb_person.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.cb_person.Location = new System.Drawing.Point(20, 5); - this.cb_person.Margin = new System.Windows.Forms.Padding(20, 3, 3, 3); - this.cb_person.Name = "cb_person"; - this.cb_person.Size = new System.Drawing.Size(67, 21); - this.cb_person.TabIndex = 0; - this.cb_person.Text = "Person"; - this.cb_person.UseVisualStyleBackColor = true; - // - // cb_bicycle - // - this.cb_bicycle.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.cb_bicycle.AutoSize = true; - this.cb_bicycle.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.cb_bicycle.Location = new System.Drawing.Point(20, 37); - this.cb_bicycle.Margin = new System.Windows.Forms.Padding(20, 3, 3, 3); - this.cb_bicycle.Name = "cb_bicycle"; - this.cb_bicycle.Size = new System.Drawing.Size(65, 21); - this.cb_bicycle.TabIndex = 8; - this.cb_bicycle.Text = "Bicycle"; - this.cb_bicycle.UseVisualStyleBackColor = true; - // - // cb_motorcycle - // - this.cb_motorcycle.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.cb_motorcycle.AutoSize = true; - this.cb_motorcycle.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.cb_motorcycle.Location = new System.Drawing.Point(20, 70); - this.cb_motorcycle.Margin = new System.Windows.Forms.Padding(20, 3, 3, 3); - this.cb_motorcycle.Name = "cb_motorcycle"; - this.cb_motorcycle.Size = new System.Drawing.Size(92, 21); - this.cb_motorcycle.TabIndex = 10; - this.cb_motorcycle.Text = "Motorcycle"; - this.cb_motorcycle.UseVisualStyleBackColor = true; - // - // cb_bear - // - this.cb_bear.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.cb_bear.AutoSize = true; - this.cb_bear.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.cb_bear.Location = new System.Drawing.Point(528, 70); - this.cb_bear.Margin = new System.Windows.Forms.Padding(20, 3, 3, 3); - this.cb_bear.Name = "cb_bear"; - this.cb_bear.Size = new System.Drawing.Size(53, 21); - this.cb_bear.TabIndex = 13; - this.cb_bear.Text = "Bear"; - this.cb_bear.UseVisualStyleBackColor = true; - // - // cb_cow - // - this.cb_cow.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.cb_cow.AutoSize = true; - this.cb_cow.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.cb_cow.Location = new System.Drawing.Point(528, 37); - this.cb_cow.Margin = new System.Windows.Forms.Padding(20, 3, 3, 3); - this.cb_cow.Name = "cb_cow"; - this.cb_cow.Size = new System.Drawing.Size(52, 21); - this.cb_cow.TabIndex = 11; - this.cb_cow.Text = "Cow"; - this.cb_cow.UseVisualStyleBackColor = true; - // - // cb_sheep - // - this.cb_sheep.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.cb_sheep.AutoSize = true; - this.cb_sheep.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.cb_sheep.Location = new System.Drawing.Point(528, 5); - this.cb_sheep.Margin = new System.Windows.Forms.Padding(20, 3, 3, 3); - this.cb_sheep.Name = "cb_sheep"; - this.cb_sheep.Size = new System.Drawing.Size(63, 21); - this.cb_sheep.TabIndex = 9; - this.cb_sheep.Text = "Sheep"; - this.cb_sheep.UseVisualStyleBackColor = true; - // - // cb_horse - // - this.cb_horse.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.cb_horse.AutoSize = true; - this.cb_horse.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.cb_horse.Location = new System.Drawing.Point(401, 70); - this.cb_horse.Margin = new System.Windows.Forms.Padding(20, 3, 3, 3); - this.cb_horse.Name = "cb_horse"; - this.cb_horse.Size = new System.Drawing.Size(62, 21); - this.cb_horse.TabIndex = 7; - this.cb_horse.Text = "Horse"; - this.cb_horse.UseVisualStyleBackColor = true; - // - // cb_bird - // - this.cb_bird.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.cb_bird.AutoSize = true; - this.cb_bird.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.cb_bird.Location = new System.Drawing.Point(401, 37); - this.cb_bird.Margin = new System.Windows.Forms.Padding(20, 3, 3, 3); - this.cb_bird.Name = "cb_bird"; - this.cb_bird.Size = new System.Drawing.Size(50, 21); - this.cb_bird.TabIndex = 5; - this.cb_bird.Text = "Bird"; - this.cb_bird.UseVisualStyleBackColor = true; - // - // cb_dog - // - this.cb_dog.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.cb_dog.AutoSize = true; - this.cb_dog.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.cb_dog.Location = new System.Drawing.Point(401, 5); - this.cb_dog.Margin = new System.Windows.Forms.Padding(20, 3, 3, 3); - this.cb_dog.Name = "cb_dog"; - this.cb_dog.Size = new System.Drawing.Size(52, 21); - this.cb_dog.TabIndex = 3; - this.cb_dog.Text = "Dog"; - this.cb_dog.UseVisualStyleBackColor = true; - // - // cb_cat - // - this.cb_cat.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.cb_cat.AutoSize = true; - this.cb_cat.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.cb_cat.Location = new System.Drawing.Point(274, 70); - this.cb_cat.Margin = new System.Windows.Forms.Padding(20, 3, 3, 3); - this.cb_cat.Name = "cb_cat"; - this.cb_cat.Size = new System.Drawing.Size(46, 21); - this.cb_cat.TabIndex = 1; - this.cb_cat.Text = "Cat"; - this.cb_cat.UseVisualStyleBackColor = true; - // - // cb_airplane - // - this.cb_airplane.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.cb_airplane.AutoSize = true; - this.cb_airplane.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.cb_airplane.Location = new System.Drawing.Point(274, 37); - this.cb_airplane.Margin = new System.Windows.Forms.Padding(20, 3, 3, 3); - this.cb_airplane.Name = "cb_airplane"; - this.cb_airplane.Size = new System.Drawing.Size(75, 21); - this.cb_airplane.TabIndex = 14; - this.cb_airplane.Text = "Airplane"; - this.cb_airplane.UseVisualStyleBackColor = true; - // - // cb_boat - // - this.cb_boat.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.cb_boat.AutoSize = true; - this.cb_boat.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.cb_boat.Location = new System.Drawing.Point(274, 5); - this.cb_boat.Margin = new System.Windows.Forms.Padding(20, 3, 3, 3); - this.cb_boat.Name = "cb_boat"; - this.cb_boat.Size = new System.Drawing.Size(53, 21); - this.cb_boat.TabIndex = 12; - this.cb_boat.Text = "Boat"; - this.cb_boat.UseVisualStyleBackColor = true; - // - // cb_bus - // - this.cb_bus.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.cb_bus.AutoSize = true; - this.cb_bus.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.cb_bus.Location = new System.Drawing.Point(147, 70); - this.cb_bus.Margin = new System.Windows.Forms.Padding(20, 3, 3, 3); - this.cb_bus.Name = "cb_bus"; - this.cb_bus.Size = new System.Drawing.Size(47, 21); - this.cb_bus.TabIndex = 4; - this.cb_bus.Text = "Bus"; - this.cb_bus.UseVisualStyleBackColor = true; - // - // cb_truck - // - this.cb_truck.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.cb_truck.AutoSize = true; - this.cb_truck.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.cb_truck.Location = new System.Drawing.Point(147, 37); - this.cb_truck.Margin = new System.Windows.Forms.Padding(20, 3, 3, 3); - this.cb_truck.Name = "cb_truck"; - this.cb_truck.Size = new System.Drawing.Size(57, 21); - this.cb_truck.TabIndex = 6; - this.cb_truck.Text = "Truck"; - this.cb_truck.UseVisualStyleBackColor = true; - // - // cb_car - // - this.cb_car.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.cb_car.AutoSize = true; - this.cb_car.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.cb_car.Location = new System.Drawing.Point(147, 5); - this.cb_car.Margin = new System.Windows.Forms.Padding(20, 3, 3, 3); - this.cb_car.Name = "cb_car"; - this.cb_car.Size = new System.Drawing.Size(47, 21); - this.cb_car.TabIndex = 2; - this.cb_car.Text = "Car"; - this.cb_car.UseVisualStyleBackColor = true; + btnCameraSave.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom; + btnCameraSave.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Zoom; + btnCameraSave.Location = new System.Drawing.Point(790, 3); + btnCameraSave.Name = "btnCameraSave"; + btnCameraSave.Size = new System.Drawing.Size(70, 30); + btnCameraSave.TabIndex = 34; + btnCameraSave.Text = "Save"; + btnCameraSave.UseVisualStyleBackColor = false; + btnCameraSave.Click += btnCameraSave_Click_1; + // + // btnPause + // + btnPause.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom; + btnPause.Location = new System.Drawing.Point(591, 3); + btnPause.Name = "btnPause"; + btnPause.Size = new System.Drawing.Size(70, 30); + btnPause.TabIndex = 35; + btnPause.Text = "Pause..."; + btnPause.UseVisualStyleBackColor = true; + btnPause.Click += btnPause_Click; // - // label1 + // lbl_camstats // - this.label1.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.label1.AutoSize = true; - this.label1.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.label1.Location = new System.Drawing.Point(4, 264); - this.label1.Name = "label1"; - this.label1.Size = new System.Drawing.Size(50, 17); - this.label1.TabIndex = 9; - this.label1.Text = "Actions"; - // - // tableLayoutPanel9 - // - this.tableLayoutPanel9.ColumnCount = 1; - this.tableLayoutPanel9.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.tableLayoutPanel9.Controls.Add(this.tableLayoutPanel10, 0, 1); - this.tableLayoutPanel9.Controls.Add(this.cb_telegram, 0, 2); - this.tableLayoutPanel9.Controls.Add(this.tableLayoutPanel20, 0, 0); - this.tableLayoutPanel9.Dock = System.Windows.Forms.DockStyle.Fill; - this.tableLayoutPanel9.Location = new System.Drawing.Point(146, 217); - this.tableLayoutPanel9.Name = "tableLayoutPanel9"; - this.tableLayoutPanel9.RowCount = 3; - this.tableLayoutPanel9.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 33.33333F)); - this.tableLayoutPanel9.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 33.33333F)); - this.tableLayoutPanel9.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 33.33333F)); - this.tableLayoutPanel9.Size = new System.Drawing.Size(637, 112); - this.tableLayoutPanel9.TabIndex = 8; - // - // tableLayoutPanel10 - // - this.tableLayoutPanel10.ColumnCount = 2; - this.tableLayoutPanel10.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 20F)); - this.tableLayoutPanel10.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 80F)); - this.tableLayoutPanel10.Controls.Add(this.lblTriggerUrl, 0, 0); - this.tableLayoutPanel10.Controls.Add(this.tbTriggerUrl, 1, 0); - this.tableLayoutPanel10.Dock = System.Windows.Forms.DockStyle.Fill; - this.tableLayoutPanel10.Location = new System.Drawing.Point(3, 40); - this.tableLayoutPanel10.Name = "tableLayoutPanel10"; - this.tableLayoutPanel10.RowCount = 1; - this.tableLayoutPanel10.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.tableLayoutPanel10.Size = new System.Drawing.Size(631, 31); - this.tableLayoutPanel10.TabIndex = 0; - // - // lblTriggerUrl - // - this.lblTriggerUrl.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.lblTriggerUrl.AutoSize = true; - this.lblTriggerUrl.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.lblTriggerUrl.Location = new System.Drawing.Point(20, 7); - this.lblTriggerUrl.Margin = new System.Windows.Forms.Padding(20, 0, 3, 0); - this.lblTriggerUrl.MinimumSize = new System.Drawing.Size(80, 0); - this.lblTriggerUrl.Name = "lblTriggerUrl"; - this.lblTriggerUrl.Size = new System.Drawing.Size(91, 17); - this.lblTriggerUrl.TabIndex = 1; - this.lblTriggerUrl.Text = "Trigger URL(s)"; - // - // tbTriggerUrl - // - this.tbTriggerUrl.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); - this.tbTriggerUrl.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.tbTriggerUrl.Location = new System.Drawing.Point(126, 3); - this.tbTriggerUrl.Margin = new System.Windows.Forms.Padding(0, 3, 20, 3); - this.tbTriggerUrl.Name = "tbTriggerUrl"; - this.tbTriggerUrl.Size = new System.Drawing.Size(485, 25); - this.tbTriggerUrl.TabIndex = 0; - this.tbTriggerUrl.KeyDown += new System.Windows.Forms.KeyEventHandler(this.tbTriggerUrl_KeyDown); - this.tbTriggerUrl.KeyUp += new System.Windows.Forms.KeyEventHandler(this.tbTriggerUrl_KeyUp); - // - // cb_telegram - // - this.cb_telegram.AutoSize = true; - this.cb_telegram.Dock = System.Windows.Forms.DockStyle.Fill; - this.cb_telegram.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.cb_telegram.Location = new System.Drawing.Point(20, 77); - this.cb_telegram.Margin = new System.Windows.Forms.Padding(20, 3, 3, 3); - this.cb_telegram.Name = "cb_telegram"; - this.cb_telegram.Size = new System.Drawing.Size(614, 32); - this.cb_telegram.TabIndex = 7; - this.cb_telegram.Text = "Send alert images to Telegram"; - this.cb_telegram.UseVisualStyleBackColor = false; - // - // tableLayoutPanel20 - // - this.tableLayoutPanel20.ColumnCount = 3; - this.tableLayoutPanel20.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 20F)); - this.tableLayoutPanel20.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 15F)); - this.tableLayoutPanel20.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 65F)); - this.tableLayoutPanel20.Controls.Add(this.label5, 0, 0); - this.tableLayoutPanel20.Controls.Add(this.tb_cooldown, 1, 0); - this.tableLayoutPanel20.Controls.Add(this.label6, 2, 0); - this.tableLayoutPanel20.Dock = System.Windows.Forms.DockStyle.Fill; - this.tableLayoutPanel20.Location = new System.Drawing.Point(3, 3); - this.tableLayoutPanel20.Name = "tableLayoutPanel20"; - this.tableLayoutPanel20.RowCount = 1; - this.tableLayoutPanel20.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.tableLayoutPanel20.Size = new System.Drawing.Size(631, 31); - this.tableLayoutPanel20.TabIndex = 8; + lbl_camstats.Anchor = System.Windows.Forms.AnchorStyles.Left; + lbl_camstats.AutoSize = true; + lbl_camstats.Location = new System.Drawing.Point(3, 8); + lbl_camstats.Name = "lbl_camstats"; + lbl_camstats.Size = new System.Drawing.Size(32, 13); + lbl_camstats.TabIndex = 4; + lbl_camstats.Text = "Stats"; // - // label5 + // tableLayoutPanel7 // - this.label5.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.label5.AutoSize = true; - this.label5.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.label5.Location = new System.Drawing.Point(20, 7); - this.label5.Margin = new System.Windows.Forms.Padding(20, 0, 3, 0); - this.label5.Name = "label5"; - this.label5.Size = new System.Drawing.Size(99, 17); - this.label5.TabIndex = 0; - this.label5.Text = "Cooldown Time"; - // - // tb_cooldown - // - this.tb_cooldown.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); - this.tb_cooldown.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.tb_cooldown.Location = new System.Drawing.Point(126, 3); - this.tb_cooldown.Margin = new System.Windows.Forms.Padding(0, 3, 3, 3); - this.tb_cooldown.Name = "tb_cooldown"; - this.tb_cooldown.Size = new System.Drawing.Size(91, 25); - this.tb_cooldown.TabIndex = 1; + tableLayoutPanel7.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + tableLayoutPanel7.BackColor = System.Drawing.Color.White; + tableLayoutPanel7.CellBorderStyle = System.Windows.Forms.TableLayoutPanelCellBorderStyle.Single; + tableLayoutPanel7.ColumnCount = 2; + tableLayoutPanel7.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 17.60259F)); + tableLayoutPanel7.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 82.39741F)); + tableLayoutPanel7.Controls.Add(label26, 0, 8); + tableLayoutPanel7.Controls.Add(label14, 0, 3); + tableLayoutPanel7.Controls.Add(label1, 0, 6); + tableLayoutPanel7.Controls.Add(lblPrefix, 0, 2); + tableLayoutPanel7.Controls.Add(lblName, 0, 0); + tableLayoutPanel7.Controls.Add(tableLayoutPanel12, 1, 2); + tableLayoutPanel7.Controls.Add(tableLayoutPanel13, 1, 0); + tableLayoutPanel7.Controls.Add(lblRelevantObjects, 0, 4); + tableLayoutPanel7.Controls.Add(tableLayoutPanel26, 1, 3); + tableLayoutPanel7.Controls.Add(tableLayoutPanel27, 1, 7); + tableLayoutPanel7.Controls.Add(label25, 0, 1); + tableLayoutPanel7.Controls.Add(dbLayoutPanel6, 1, 1); + tableLayoutPanel7.Controls.Add(dbLayoutPanel7, 1, 8); + tableLayoutPanel7.Controls.Add(label39, 0, 5); + tableLayoutPanel7.Controls.Add(dbLayoutPanel11, 1, 4); + tableLayoutPanel7.Controls.Add(label15, 0, 7); + tableLayoutPanel7.Controls.Add(dbLayoutPanel12, 1, 5); + tableLayoutPanel7.Controls.Add(dbLayoutPanel13, 1, 6); + tableLayoutPanel7.Location = new System.Drawing.Point(3, 33); + tableLayoutPanel7.Name = "tableLayoutPanel7"; + tableLayoutPanel7.RowCount = 10; + tableLayoutPanel7.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 11.10862F)); + tableLayoutPanel7.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 11.11363F)); + tableLayoutPanel7.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 11.10915F)); + tableLayoutPanel7.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 11.10915F)); + tableLayoutPanel7.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 11.10536F)); + tableLayoutPanel7.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 11.11724F)); + tableLayoutPanel7.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 11.1134F)); + tableLayoutPanel7.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 11.11097F)); + tableLayoutPanel7.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 11.1125F)); + tableLayoutPanel7.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F)); + tableLayoutPanel7.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F)); + tableLayoutPanel7.Size = new System.Drawing.Size(926, 405); + tableLayoutPanel7.TabIndex = 2; + tableLayoutPanel7.Paint += tableLayoutPanel7_Paint; + // + // label26 + // + label26.Anchor = System.Windows.Forms.AnchorStyles.Right; + label26.AutoSize = true; + label26.Location = new System.Drawing.Point(64, 351); + label26.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + label26.Name = "label26"; + label26.Size = new System.Drawing.Size(97, 13); + label26.TabIndex = 19; + label26.Text = "Custom Mask File"; + label26.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // label14 + // + label14.Anchor = System.Windows.Forms.AnchorStyles.Right; + label14.AutoSize = true; + label14.Location = new System.Drawing.Point(89, 141); + label14.Name = "label14"; + label14.Size = new System.Drawing.Size(71, 13); + label14.TabIndex = 17; + label14.Text = "Input Folder"; // - // label6 + // label1 // - this.label6.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.label6.AutoSize = true; - this.label6.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.label6.Location = new System.Drawing.Point(223, 7); - this.label6.Name = "label6"; - this.label6.Size = new System.Drawing.Size(54, 17); - this.label6.TabIndex = 2; - this.label6.Text = "Minutes"; + label1.Anchor = System.Windows.Forms.AnchorStyles.Right; + label1.AutoSize = true; + label1.Location = new System.Drawing.Point(115, 267); + label1.Name = "label1"; + label1.Size = new System.Drawing.Size(45, 13); + label1.TabIndex = 9; + label1.Text = "Actions"; // // lblPrefix // - this.lblPrefix.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.lblPrefix.AutoSize = true; - this.lblPrefix.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.lblPrefix.Location = new System.Drawing.Point(4, 46); - this.lblPrefix.Name = "lblPrefix"; - this.lblPrefix.Size = new System.Drawing.Size(128, 17); - this.lblPrefix.TabIndex = 2; - this.lblPrefix.Text = "Input file begins with"; + lblPrefix.Anchor = System.Windows.Forms.AnchorStyles.Right; + lblPrefix.AutoSize = true; + lblPrefix.Location = new System.Drawing.Point(42, 99); + lblPrefix.Name = "lblPrefix"; + lblPrefix.Size = new System.Drawing.Size(118, 13); + lblPrefix.TabIndex = 2; + lblPrefix.Text = "Input file begins with"; + lblPrefix.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; // // lblName // - this.lblName.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.lblName.AutoSize = true; - this.lblName.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.lblName.Location = new System.Drawing.Point(4, 10); - this.lblName.Name = "lblName"; - this.lblName.Size = new System.Drawing.Size(43, 17); - this.lblName.TabIndex = 10; - this.lblName.Text = "Name"; + lblName.Anchor = System.Windows.Forms.AnchorStyles.Right; + lblName.AutoSize = true; + lblName.Location = new System.Drawing.Point(45, 15); + lblName.Name = "lblName"; + lblName.Size = new System.Drawing.Size(115, 13); + lblName.TabIndex = 10; + lblName.Text = "AI Tool Camera Name"; // // tableLayoutPanel12 // - this.tableLayoutPanel12.ColumnCount = 2; - this.tableLayoutPanel12.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); - this.tableLayoutPanel12.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); - this.tableLayoutPanel12.Controls.Add(this.tbPrefix, 0, 0); - this.tableLayoutPanel12.Controls.Add(this.lbl_prefix, 1, 0); - this.tableLayoutPanel12.Dock = System.Windows.Forms.DockStyle.Fill; - this.tableLayoutPanel12.Location = new System.Drawing.Point(146, 40); - this.tableLayoutPanel12.Name = "tableLayoutPanel12"; - this.tableLayoutPanel12.RowCount = 1; - this.tableLayoutPanel12.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F)); - this.tableLayoutPanel12.Size = new System.Drawing.Size(637, 29); - this.tableLayoutPanel12.TabIndex = 12; + tableLayoutPanel12.ColumnCount = 2; + tableLayoutPanel12.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); + tableLayoutPanel12.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); + tableLayoutPanel12.Controls.Add(lbl_prefix, 1, 0); + tableLayoutPanel12.Controls.Add(tbPrefix, 0, 0); + tableLayoutPanel12.Dock = System.Windows.Forms.DockStyle.Fill; + tableLayoutPanel12.Location = new System.Drawing.Point(166, 86); + tableLayoutPanel12.Margin = new System.Windows.Forms.Padding(2, 1, 2, 1); + tableLayoutPanel12.Name = "tableLayoutPanel12"; + tableLayoutPanel12.RowCount = 1; + tableLayoutPanel12.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F)); + tableLayoutPanel12.Size = new System.Drawing.Size(757, 39); + tableLayoutPanel12.TabIndex = 12; // - // tbPrefix + // lbl_prefix // - this.tbPrefix.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); - this.tbPrefix.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.tbPrefix.Location = new System.Drawing.Point(20, 3); - this.tbPrefix.Margin = new System.Windows.Forms.Padding(20, 3, 20, 3); - this.tbPrefix.Name = "tbPrefix"; - this.tbPrefix.Size = new System.Drawing.Size(278, 25); - this.tbPrefix.TabIndex = 5; - this.tbPrefix.TextChanged += new System.EventHandler(this.tbPrefix_TextChanged); + lbl_prefix.Anchor = System.Windows.Forms.AnchorStyles.None; + lbl_prefix.AutoSize = true; + lbl_prefix.Location = new System.Drawing.Point(567, 13); + lbl_prefix.Name = "lbl_prefix"; + lbl_prefix.Size = new System.Drawing.Size(0, 13); + lbl_prefix.TabIndex = 6; // - // lbl_prefix + // tbPrefix // - this.lbl_prefix.Anchor = System.Windows.Forms.AnchorStyles.None; - this.lbl_prefix.AutoSize = true; - this.lbl_prefix.Location = new System.Drawing.Point(477, 8); - this.lbl_prefix.Name = "lbl_prefix"; - this.lbl_prefix.Size = new System.Drawing.Size(0, 13); - this.lbl_prefix.TabIndex = 6; + tbPrefix.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + tbPrefix.Location = new System.Drawing.Point(21, 8); + tbPrefix.Margin = new System.Windows.Forms.Padding(21, 3, 21, 3); + tbPrefix.Name = "tbPrefix"; + tbPrefix.Size = new System.Drawing.Size(336, 22); + tbPrefix.TabIndex = 3; + tbPrefix.TextChanged += tbPrefix_TextChanged; // // tableLayoutPanel13 // - this.tableLayoutPanel13.ColumnCount = 2; - this.tableLayoutPanel13.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); - this.tableLayoutPanel13.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); - this.tableLayoutPanel13.Controls.Add(this.tbName, 0, 0); - this.tableLayoutPanel13.Controls.Add(this.cb_enabled, 1, 0); - this.tableLayoutPanel13.Dock = System.Windows.Forms.DockStyle.Fill; - this.tableLayoutPanel13.Location = new System.Drawing.Point(146, 4); - this.tableLayoutPanel13.Name = "tableLayoutPanel13"; - this.tableLayoutPanel13.RowCount = 1; - this.tableLayoutPanel13.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F)); - this.tableLayoutPanel13.Size = new System.Drawing.Size(637, 29); - this.tableLayoutPanel13.TabIndex = 13; + tableLayoutPanel13.ColumnCount = 2; + tableLayoutPanel13.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); + tableLayoutPanel13.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); + tableLayoutPanel13.Controls.Add(cb_enabled, 1, 0); + tableLayoutPanel13.Controls.Add(tbName, 0, 0); + tableLayoutPanel13.Dock = System.Windows.Forms.DockStyle.Fill; + tableLayoutPanel13.Location = new System.Drawing.Point(167, 2); + tableLayoutPanel13.Margin = new System.Windows.Forms.Padding(3, 1, 3, 1); + tableLayoutPanel13.Name = "tableLayoutPanel13"; + tableLayoutPanel13.RowCount = 1; + tableLayoutPanel13.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F)); + tableLayoutPanel13.Size = new System.Drawing.Size(755, 39); + tableLayoutPanel13.TabIndex = 13; // - // tbName + // cb_enabled // - this.tbName.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); - this.tbName.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.tbName.Location = new System.Drawing.Point(20, 3); - this.tbName.Margin = new System.Windows.Forms.Padding(20, 3, 20, 3); - this.tbName.Name = "tbName"; - this.tbName.Size = new System.Drawing.Size(278, 25); - this.tbName.TabIndex = 12; + cb_enabled.Anchor = System.Windows.Forms.AnchorStyles.Left; + cb_enabled.AutoSize = true; + cb_enabled.Location = new System.Drawing.Point(398, 11); + cb_enabled.Margin = new System.Windows.Forms.Padding(21, 3, 3, 3); + cb_enabled.Name = "cb_enabled"; + cb_enabled.Size = new System.Drawing.Size(206, 17); + cb_enabled.TabIndex = 1; + cb_enabled.Text = "Enable AI Detection for this camera"; + cb_enabled.UseVisualStyleBackColor = true; // - // cb_enabled + // tbName // - this.cb_enabled.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.cb_enabled.AutoSize = true; - this.cb_enabled.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.cb_enabled.Location = new System.Drawing.Point(338, 4); - this.cb_enabled.Margin = new System.Windows.Forms.Padding(20, 3, 3, 3); - this.cb_enabled.Name = "cb_enabled"; - this.cb_enabled.Size = new System.Drawing.Size(232, 21); - this.cb_enabled.TabIndex = 13; - this.cb_enabled.Text = "Enable AI Detection for this camera"; - this.cb_enabled.UseVisualStyleBackColor = true; + tbName.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + tbName.Location = new System.Drawing.Point(21, 8); + tbName.Margin = new System.Windows.Forms.Padding(21, 3, 21, 3); + tbName.Name = "tbName"; + tbName.Size = new System.Drawing.Size(335, 22); + tbName.TabIndex = 0; // // lblRelevantObjects // - this.lblRelevantObjects.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.lblRelevantObjects.AutoSize = true; - this.lblRelevantObjects.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.lblRelevantObjects.Location = new System.Drawing.Point(4, 116); - this.lblRelevantObjects.Name = "lblRelevantObjects"; - this.lblRelevantObjects.Size = new System.Drawing.Size(105, 17); - this.lblRelevantObjects.TabIndex = 1; - this.lblRelevantObjects.Text = "Relevant Objects"; - // - // lbl_threshold - // - this.lbl_threshold.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.lbl_threshold.AutoSize = true; - this.lbl_threshold.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.lbl_threshold.Location = new System.Drawing.Point(4, 187); - this.lbl_threshold.Name = "lbl_threshold"; - this.lbl_threshold.Size = new System.Drawing.Size(107, 17); - this.lbl_threshold.TabIndex = 15; - this.lbl_threshold.Text = "Confidence limits"; - // - // tableLayoutPanel24 - // - this.tableLayoutPanel24.ColumnCount = 6; - this.tableLayoutPanel24.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 20F)); - this.tableLayoutPanel24.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 10F)); - this.tableLayoutPanel24.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 20F)); - this.tableLayoutPanel24.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 20F)); - this.tableLayoutPanel24.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 10F)); - this.tableLayoutPanel24.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 20F)); - this.tableLayoutPanel24.Controls.Add(this.lbl_threshold_lower, 0, 0); - this.tableLayoutPanel24.Controls.Add(this.tb_threshold_upper, 4, 0); - this.tableLayoutPanel24.Controls.Add(this.lbl_threshold_upper, 3, 0); - this.tableLayoutPanel24.Controls.Add(this.tb_threshold_lower, 1, 0); - this.tableLayoutPanel24.Controls.Add(this.label9, 5, 0); - this.tableLayoutPanel24.Controls.Add(this.label10, 2, 0); - this.tableLayoutPanel24.Dock = System.Windows.Forms.DockStyle.Fill; - this.tableLayoutPanel24.Location = new System.Drawing.Point(146, 181); - this.tableLayoutPanel24.Name = "tableLayoutPanel24"; - this.tableLayoutPanel24.RowCount = 1; - this.tableLayoutPanel24.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.tableLayoutPanel24.Size = new System.Drawing.Size(637, 29); - this.tableLayoutPanel24.TabIndex = 16; - // - // lbl_threshold_lower - // - this.lbl_threshold_lower.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.lbl_threshold_lower.AutoSize = true; - this.lbl_threshold_lower.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.lbl_threshold_lower.Location = new System.Drawing.Point(3, 6); - this.lbl_threshold_lower.Name = "lbl_threshold_lower"; - this.lbl_threshold_lower.Size = new System.Drawing.Size(71, 17); - this.lbl_threshold_lower.TabIndex = 19; - this.lbl_threshold_lower.Text = "Lower limit"; - // - // tb_threshold_upper - // - this.tb_threshold_upper.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); - this.tb_threshold_upper.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.tb_threshold_upper.Location = new System.Drawing.Point(444, 3); - this.tb_threshold_upper.Margin = new System.Windows.Forms.Padding(0, 3, 3, 3); - this.tb_threshold_upper.MaxLength = 3; - this.tb_threshold_upper.Name = "tb_threshold_upper"; - this.tb_threshold_upper.Size = new System.Drawing.Size(60, 25); - this.tb_threshold_upper.TabIndex = 20; - this.tb_threshold_upper.Leave += new System.EventHandler(this.tb_threshold_upper_Leave); - // - // lbl_threshold_upper - // - this.lbl_threshold_upper.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.lbl_threshold_upper.AutoSize = true; - this.lbl_threshold_upper.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.lbl_threshold_upper.Location = new System.Drawing.Point(320, 6); - this.lbl_threshold_upper.Name = "lbl_threshold_upper"; - this.lbl_threshold_upper.Size = new System.Drawing.Size(73, 17); - this.lbl_threshold_upper.TabIndex = 21; - this.lbl_threshold_upper.Text = "Upper limit"; - // - // tb_threshold_lower - // - this.tb_threshold_lower.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); - this.tb_threshold_lower.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.tb_threshold_lower.Location = new System.Drawing.Point(127, 3); - this.tb_threshold_lower.Margin = new System.Windows.Forms.Padding(0, 3, 3, 3); - this.tb_threshold_lower.MaxLength = 3; - this.tb_threshold_lower.Name = "tb_threshold_lower"; - this.tb_threshold_lower.Size = new System.Drawing.Size(60, 25); - this.tb_threshold_lower.TabIndex = 18; - this.tb_threshold_lower.Leave += new System.EventHandler(this.tb_threshold_lower_Leave); + lblRelevantObjects.Anchor = System.Windows.Forms.AnchorStyles.Right; + lblRelevantObjects.AutoSize = true; + lblRelevantObjects.Location = new System.Drawing.Point(67, 183); + lblRelevantObjects.Name = "lblRelevantObjects"; + lblRelevantObjects.Size = new System.Drawing.Size(93, 13); + lblRelevantObjects.TabIndex = 1; + lblRelevantObjects.Text = "Relevant Objects"; + // + // tableLayoutPanel26 + // + tableLayoutPanel26.ColumnCount = 3; + tableLayoutPanel26.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 62.5F)); + tableLayoutPanel26.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 25F)); + tableLayoutPanel26.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 12.5F)); + tableLayoutPanel26.Controls.Add(cmbcaminput, 0, 0); + tableLayoutPanel26.Controls.Add(cb_monitorCamInputfolder, 1, 0); + tableLayoutPanel26.Controls.Add(button2, 2, 0); + tableLayoutPanel26.Dock = System.Windows.Forms.DockStyle.Fill; + tableLayoutPanel26.Location = new System.Drawing.Point(166, 128); + tableLayoutPanel26.Margin = new System.Windows.Forms.Padding(2, 1, 2, 1); + tableLayoutPanel26.Name = "tableLayoutPanel26"; + tableLayoutPanel26.RowCount = 1; + tableLayoutPanel26.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + tableLayoutPanel26.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 39F)); + tableLayoutPanel26.Size = new System.Drawing.Size(757, 39); + tableLayoutPanel26.TabIndex = 18; + // + // cmbcaminput + // + cmbcaminput.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + cmbcaminput.AutoCompleteMode = System.Windows.Forms.AutoCompleteMode.SuggestAppend; + cmbcaminput.AutoCompleteSource = System.Windows.Forms.AutoCompleteSource.ListItems; + cmbcaminput.FormattingEnabled = true; + cmbcaminput.Location = new System.Drawing.Point(21, 8); + cmbcaminput.Margin = new System.Windows.Forms.Padding(21, 2, 21, 2); + cmbcaminput.Name = "cmbcaminput"; + cmbcaminput.Size = new System.Drawing.Size(431, 21); + cmbcaminput.TabIndex = 4; + // + // cb_monitorCamInputfolder + // + cb_monitorCamInputfolder.Anchor = System.Windows.Forms.AnchorStyles.None; + cb_monitorCamInputfolder.AutoSize = true; + cb_monitorCamInputfolder.Location = new System.Drawing.Point(504, 11); + cb_monitorCamInputfolder.Margin = new System.Windows.Forms.Padding(2); + cb_monitorCamInputfolder.Name = "cb_monitorCamInputfolder"; + cb_monitorCamInputfolder.Size = new System.Drawing.Size(127, 17); + cb_monitorCamInputfolder.TabIndex = 5; + cb_monitorCamInputfolder.Text = "Monitor Subfolders"; + cb_monitorCamInputfolder.UseVisualStyleBackColor = true; + // + // button2 + // + button2.Anchor = System.Windows.Forms.AnchorStyles.None; + button2.Location = new System.Drawing.Point(674, 9); + button2.Name = "button2"; + button2.Size = new System.Drawing.Size(70, 21); + button2.TabIndex = 6; + button2.Text = "Select..."; + button2.UseVisualStyleBackColor = true; + button2.Click += button2_Click; + // + // tableLayoutPanel27 + // + tableLayoutPanel27.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + tableLayoutPanel27.ColumnCount = 5; + tableLayoutPanel27.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 29.63504F)); + tableLayoutPanel27.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 13.54895F)); + tableLayoutPanel27.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 12.23776F)); + tableLayoutPanel27.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 13.8986F)); + tableLayoutPanel27.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 30.94406F)); + tableLayoutPanel27.Controls.Add(cb_masking_enabled, 0, 0); + tableLayoutPanel27.Controls.Add(BtnDynamicMaskingSettings, 1, 0); + tableLayoutPanel27.Controls.Add(btnDetails, 2, 0); + tableLayoutPanel27.Controls.Add(btnCustomMask, 4, 0); + tableLayoutPanel27.Controls.Add(lblDrawMask, 3, 0); + tableLayoutPanel27.Location = new System.Drawing.Point(166, 296); + tableLayoutPanel27.Margin = new System.Windows.Forms.Padding(2, 1, 2, 1); + tableLayoutPanel27.Name = "tableLayoutPanel27"; + tableLayoutPanel27.RowCount = 1; + tableLayoutPanel27.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + tableLayoutPanel27.Size = new System.Drawing.Size(757, 39); + tableLayoutPanel27.TabIndex = 20; + // + // cb_masking_enabled + // + cb_masking_enabled.Anchor = System.Windows.Forms.AnchorStyles.Left; + cb_masking_enabled.AutoSize = true; + cb_masking_enabled.Location = new System.Drawing.Point(21, 12); + cb_masking_enabled.Margin = new System.Windows.Forms.Padding(21, 3, 5, 0); + cb_masking_enabled.Name = "cb_masking_enabled"; + cb_masking_enabled.Size = new System.Drawing.Size(152, 17); + cb_masking_enabled.TabIndex = 25; + cb_masking_enabled.Text = "Enable dynamic masking"; + cb_masking_enabled.UseVisualStyleBackColor = true; + // + // BtnDynamicMaskingSettings + // + BtnDynamicMaskingSettings.Anchor = System.Windows.Forms.AnchorStyles.Left; + BtnDynamicMaskingSettings.Location = new System.Drawing.Point(228, 7); + BtnDynamicMaskingSettings.Margin = new System.Windows.Forms.Padding(5, 1, 5, 1); + BtnDynamicMaskingSettings.Name = "BtnDynamicMaskingSettings"; + BtnDynamicMaskingSettings.Size = new System.Drawing.Size(70, 25); + BtnDynamicMaskingSettings.TabIndex = 26; + BtnDynamicMaskingSettings.Text = "Settings"; + BtnDynamicMaskingSettings.UseVisualStyleBackColor = true; + BtnDynamicMaskingSettings.Click += BtnDynamicMaskingSettings_Click; + // + // btnDetails + // + btnDetails.Anchor = System.Windows.Forms.AnchorStyles.Left; + btnDetails.Location = new System.Drawing.Point(330, 8); + btnDetails.Margin = new System.Windows.Forms.Padding(5, 2, 5, 2); + btnDetails.Name = "btnDetails"; + btnDetails.Size = new System.Drawing.Size(70, 23); + btnDetails.TabIndex = 27; + btnDetails.Text = "Details"; + btnDetails.UseVisualStyleBackColor = true; + btnDetails.Click += btnDetails_Click; + // + // btnCustomMask + // + btnCustomMask.Anchor = System.Windows.Forms.AnchorStyles.Left; + btnCustomMask.Location = new System.Drawing.Point(522, 8); + btnCustomMask.Margin = new System.Windows.Forms.Padding(1, 2, 5, 2); + btnCustomMask.Name = "btnCustomMask"; + btnCustomMask.Size = new System.Drawing.Size(70, 23); + btnCustomMask.TabIndex = 28; + btnCustomMask.Text = "Custom"; + btnCustomMask.UseVisualStyleBackColor = true; + btnCustomMask.Click += btnCustomMask_Click; + // + // lblDrawMask + // + lblDrawMask.Anchor = System.Windows.Forms.AnchorStyles.Right; + lblDrawMask.Location = new System.Drawing.Point(454, 11); + lblDrawMask.Margin = new System.Windows.Forms.Padding(0); + lblDrawMask.Name = "lblDrawMask"; + lblDrawMask.Size = new System.Drawing.Size(67, 16); + lblDrawMask.TabIndex = 25; + lblDrawMask.Text = "Draw Mask"; + lblDrawMask.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // label25 + // + label25.Anchor = System.Windows.Forms.AnchorStyles.Right; + label25.AutoSize = true; + label25.Location = new System.Drawing.Point(71, 57); + label25.Name = "label25"; + label25.Size = new System.Drawing.Size(89, 13); + label25.TabIndex = 10; + label25.Text = "BI Camera Name"; + // + // dbLayoutPanel6 + // + dbLayoutPanel6.ColumnCount = 2; + dbLayoutPanel6.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); + dbLayoutPanel6.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); + dbLayoutPanel6.Controls.Add(tbBiCamName, 0, 0); + dbLayoutPanel6.Dock = System.Windows.Forms.DockStyle.Fill; + dbLayoutPanel6.Location = new System.Drawing.Point(167, 44); + dbLayoutPanel6.Margin = new System.Windows.Forms.Padding(3, 1, 3, 1); + dbLayoutPanel6.Name = "dbLayoutPanel6"; + dbLayoutPanel6.RowCount = 1; + dbLayoutPanel6.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F)); + dbLayoutPanel6.Size = new System.Drawing.Size(755, 39); + dbLayoutPanel6.TabIndex = 24; + // + // tbBiCamName + // + tbBiCamName.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + tbBiCamName.Location = new System.Drawing.Point(21, 8); + tbBiCamName.Margin = new System.Windows.Forms.Padding(21, 3, 21, 3); + tbBiCamName.Name = "tbBiCamName"; + tbBiCamName.Size = new System.Drawing.Size(335, 22); + tbBiCamName.TabIndex = 2; + // + // dbLayoutPanel7 + // + dbLayoutPanel7.ColumnCount = 3; + dbLayoutPanel7.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 42.62048F)); + dbLayoutPanel7.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 16.86747F)); + dbLayoutPanel7.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 40.51205F)); + dbLayoutPanel7.Controls.Add(tb_camera_telegram_chatid, 2, 0); + dbLayoutPanel7.Controls.Add(tbCustomMaskFile, 0, 0); + dbLayoutPanel7.Controls.Add(label21, 1, 0); + dbLayoutPanel7.Dock = System.Windows.Forms.DockStyle.Fill; + dbLayoutPanel7.Location = new System.Drawing.Point(167, 338); + dbLayoutPanel7.Margin = new System.Windows.Forms.Padding(3, 1, 3, 1); + dbLayoutPanel7.Name = "dbLayoutPanel7"; + dbLayoutPanel7.RowCount = 1; + dbLayoutPanel7.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + dbLayoutPanel7.Size = new System.Drawing.Size(755, 39); + dbLayoutPanel7.TabIndex = 25; + // + // tb_camera_telegram_chatid + // + tb_camera_telegram_chatid.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + tb_camera_telegram_chatid.Location = new System.Drawing.Point(451, 8); + tb_camera_telegram_chatid.Name = "tb_camera_telegram_chatid"; + tb_camera_telegram_chatid.Size = new System.Drawing.Size(301, 22); + tb_camera_telegram_chatid.TabIndex = 30; + toolTip1.SetToolTip(tb_camera_telegram_chatid, "This overrides the chatid in the settings tab."); + // + // tbCustomMaskFile + // + tbCustomMaskFile.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + tbCustomMaskFile.Location = new System.Drawing.Point(3, 8); + tbCustomMaskFile.Name = "tbCustomMaskFile"; + tbCustomMaskFile.Size = new System.Drawing.Size(315, 22); + tbCustomMaskFile.TabIndex = 29; + // + // label21 + // + label21.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + label21.Location = new System.Drawing.Point(323, 10); + label21.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + label21.Name = "label21"; + label21.Size = new System.Drawing.Size(123, 19); + label21.TabIndex = 19; + label21.Text = "Telegram Chat ID Override"; + label21.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // label39 + // + label39.Anchor = System.Windows.Forms.AnchorStyles.Right; + label39.AutoSize = true; + label39.Location = new System.Drawing.Point(41, 225); + label39.Name = "label39"; + label39.Size = new System.Drawing.Size(119, 13); + label39.TabIndex = 15; + label39.Text = "Prediction Tolerances:"; + // + // dbLayoutPanel11 + // + dbLayoutPanel11.ColumnCount = 2; + dbLayoutPanel11.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 15.50725F)); + dbLayoutPanel11.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 84.49275F)); + dbLayoutPanel11.Controls.Add(BtnRelevantObjects, 0, 0); + dbLayoutPanel11.Controls.Add(lbl_RelevantObjects, 1, 0); + dbLayoutPanel11.Dock = System.Windows.Forms.DockStyle.Fill; + dbLayoutPanel11.Location = new System.Drawing.Point(167, 172); + dbLayoutPanel11.Name = "dbLayoutPanel11"; + dbLayoutPanel11.RowCount = 1; + dbLayoutPanel11.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F)); + dbLayoutPanel11.Size = new System.Drawing.Size(755, 35); + dbLayoutPanel11.TabIndex = 26; + // + // BtnRelevantObjects + // + BtnRelevantObjects.Anchor = System.Windows.Forms.AnchorStyles.Left; + BtnRelevantObjects.Location = new System.Drawing.Point(21, 5); + BtnRelevantObjects.Margin = new System.Windows.Forms.Padding(21, 2, 2, 2); + BtnRelevantObjects.Name = "BtnRelevantObjects"; + BtnRelevantObjects.Size = new System.Drawing.Size(70, 25); + BtnRelevantObjects.TabIndex = 23; + BtnRelevantObjects.Text = "Settings"; + BtnRelevantObjects.UseVisualStyleBackColor = true; + BtnRelevantObjects.Click += BtnRelevantObjects_Click; + // + // lbl_RelevantObjects + // + lbl_RelevantObjects.Anchor = System.Windows.Forms.AnchorStyles.Left; + lbl_RelevantObjects.AutoSize = true; + lbl_RelevantObjects.Location = new System.Drawing.Point(120, 4); + lbl_RelevantObjects.Name = "lbl_RelevantObjects"; + lbl_RelevantObjects.Size = new System.Drawing.Size(632, 26); + lbl_RelevantObjects.TabIndex = 24; + lbl_RelevantObjects.Text = "person, face, bear, elephant, car, truck, pickup truck, SUV, van, bicycle, motorcycle, bus, dog, horse, kangaroo, boat, train, airplane, zebra, giraffe, cow, sheep, cat, bird"; + // + // label15 + // + label15.Anchor = System.Windows.Forms.AnchorStyles.Right; + label15.AutoSize = true; + label15.Location = new System.Drawing.Point(110, 309); + label15.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + label15.Name = "label15"; + label15.Size = new System.Drawing.Size(51, 13); + label15.TabIndex = 19; + label15.Text = "Masking"; + label15.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // dbLayoutPanel12 + // + dbLayoutPanel12.ColumnCount = 2; + dbLayoutPanel12.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 15.36232F)); + dbLayoutPanel12.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 84.63768F)); + dbLayoutPanel12.Controls.Add(Lbl_PredictionTolerances, 1, 0); + dbLayoutPanel12.Controls.Add(BtnPredictionSize, 0, 0); + dbLayoutPanel12.Dock = System.Windows.Forms.DockStyle.Fill; + dbLayoutPanel12.Location = new System.Drawing.Point(167, 214); + dbLayoutPanel12.Name = "dbLayoutPanel12"; + dbLayoutPanel12.RowCount = 1; + dbLayoutPanel12.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F)); + dbLayoutPanel12.Size = new System.Drawing.Size(755, 35); + dbLayoutPanel12.TabIndex = 27; + // + // Lbl_PredictionTolerances + // + Lbl_PredictionTolerances.Anchor = System.Windows.Forms.AnchorStyles.Left; + Lbl_PredictionTolerances.AutoSize = true; + Lbl_PredictionTolerances.Location = new System.Drawing.Point(118, 11); + Lbl_PredictionTolerances.Name = "Lbl_PredictionTolerances"; + Lbl_PredictionTolerances.Size = new System.Drawing.Size(116, 13); + Lbl_PredictionTolerances.TabIndex = 24; + Lbl_PredictionTolerances.Text = "high low percent, etc"; + // + // BtnPredictionSize + // + BtnPredictionSize.Anchor = System.Windows.Forms.AnchorStyles.Left; + BtnPredictionSize.Location = new System.Drawing.Point(21, 5); + BtnPredictionSize.Margin = new System.Windows.Forms.Padding(21, 2, 2, 2); + BtnPredictionSize.Name = "BtnPredictionSize"; + BtnPredictionSize.Size = new System.Drawing.Size(70, 25); + BtnPredictionSize.TabIndex = 23; + BtnPredictionSize.Text = "Settings"; + BtnPredictionSize.UseVisualStyleBackColor = true; + BtnPredictionSize.Click += BtnPredictionSize_Click; + // + // dbLayoutPanel13 + // + dbLayoutPanel13.ColumnCount = 2; + dbLayoutPanel13.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 15.50725F)); + dbLayoutPanel13.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 84.49275F)); + dbLayoutPanel13.Controls.Add(Lbl_Actions, 1, 0); + dbLayoutPanel13.Controls.Add(btnActions, 0, 0); + dbLayoutPanel13.Dock = System.Windows.Forms.DockStyle.Fill; + dbLayoutPanel13.Location = new System.Drawing.Point(167, 256); + dbLayoutPanel13.Name = "dbLayoutPanel13"; + dbLayoutPanel13.RowCount = 1; + dbLayoutPanel13.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F)); + dbLayoutPanel13.Size = new System.Drawing.Size(755, 35); + dbLayoutPanel13.TabIndex = 28; + // + // Lbl_Actions + // + Lbl_Actions.Anchor = System.Windows.Forms.AnchorStyles.Left; + Lbl_Actions.AutoSize = true; + Lbl_Actions.Location = new System.Drawing.Point(120, 11); + Lbl_Actions.Name = "Lbl_Actions"; + Lbl_Actions.Size = new System.Drawing.Size(153, 13); + Lbl_Actions.TabIndex = 24; + Lbl_Actions.Text = "Telegram, Pushover, URL, etc"; + // + // btnActions + // + btnActions.Anchor = System.Windows.Forms.AnchorStyles.Left; + btnActions.Location = new System.Drawing.Point(21, 5); + btnActions.Margin = new System.Windows.Forms.Padding(21, 2, 2, 2); + btnActions.Name = "btnActions"; + btnActions.Size = new System.Drawing.Size(70, 25); + btnActions.TabIndex = 24; + btnActions.Text = "Settings"; + btnActions.UseVisualStyleBackColor = true; + btnActions.Click += btnActions_Click_1; // - // label9 + // tabSettings // - this.label9.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.label9.AutoSize = true; - this.label9.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.label9.Location = new System.Drawing.Point(510, 6); - this.label9.Name = "label9"; - this.label9.Size = new System.Drawing.Size(19, 17); - this.label9.TabIndex = 22; - this.label9.Text = "%"; - // - // label10 - // - this.label10.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.label10.AutoSize = true; - this.label10.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.label10.Location = new System.Drawing.Point(193, 6); - this.label10.Name = "label10"; - this.label10.Size = new System.Drawing.Size(19, 17); - this.label10.TabIndex = 23; - this.label10.Text = "%"; + tabSettings.Controls.Add(tableLayoutPanel4); + tabSettings.Location = new System.Drawing.Point(4, 24); + tabSettings.Name = "tabSettings"; + tabSettings.Size = new System.Drawing.Size(1099, 492); + tabSettings.TabIndex = 3; + tabSettings.Text = "Settings"; + tabSettings.UseVisualStyleBackColor = true; // - // tableLayoutPanel11 + // tableLayoutPanel4 // - this.tableLayoutPanel11.ColumnCount = 2; - this.tableLayoutPanel11.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); - this.tableLayoutPanel11.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); - this.tableLayoutPanel11.Controls.Add(this.btnCameraSave, 0, 0); - this.tableLayoutPanel11.Controls.Add(this.btnCameraDel, 1, 0); - this.tableLayoutPanel11.Dock = System.Windows.Forms.DockStyle.Fill; - this.tableLayoutPanel11.Location = new System.Drawing.Point(3, 384); - this.tableLayoutPanel11.Name = "tableLayoutPanel11"; - this.tableLayoutPanel11.RowCount = 1; - this.tableLayoutPanel11.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F)); - this.tableLayoutPanel11.Size = new System.Drawing.Size(787, 37); - this.tableLayoutPanel11.TabIndex = 3; + tableLayoutPanel4.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + tableLayoutPanel4.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + tableLayoutPanel4.BackColor = System.Drawing.Color.WhiteSmoke; + tableLayoutPanel4.ColumnCount = 1; + tableLayoutPanel4.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + tableLayoutPanel4.Controls.Add(tableLayoutPanel5, 0, 0); + tableLayoutPanel4.Controls.Add(dbLayoutPanel10, 0, 1); + tableLayoutPanel4.Location = new System.Drawing.Point(3, 3); + tableLayoutPanel4.Name = "tableLayoutPanel4"; + tableLayoutPanel4.RowCount = 2; + tableLayoutPanel4.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + tableLayoutPanel4.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 40F)); + tableLayoutPanel4.Size = new System.Drawing.Size(1093, 473); + tableLayoutPanel4.TabIndex = 5; // - // btnCameraSave + // tableLayoutPanel5 // - this.btnCameraSave.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom))); - this.btnCameraSave.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Zoom; - this.btnCameraSave.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.btnCameraSave.Location = new System.Drawing.Point(153, 3); - this.btnCameraSave.Name = "btnCameraSave"; - this.btnCameraSave.Size = new System.Drawing.Size(86, 31); - this.btnCameraSave.TabIndex = 4; - this.btnCameraSave.Text = "Save"; - this.btnCameraSave.UseVisualStyleBackColor = false; - this.btnCameraSave.Click += new System.EventHandler(this.btnCameraSave_Click_1); + tableLayoutPanel5.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + tableLayoutPanel5.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + tableLayoutPanel5.BackColor = System.Drawing.Color.White; + tableLayoutPanel5.CellBorderStyle = System.Windows.Forms.TableLayoutPanelCellBorderStyle.Single; + tableLayoutPanel5.ColumnCount = 2; + tableLayoutPanel5.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 15F)); + tableLayoutPanel5.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 85F)); + tableLayoutPanel5.Controls.Add(lbl_input, 0, 0); + tableLayoutPanel5.Controls.Add(lbl_telegram_token, 0, 2); + tableLayoutPanel5.Controls.Add(tableLayoutPanel18, 1, 0); + tableLayoutPanel5.Controls.Add(lbl_deepstackurl, 0, 1); + tableLayoutPanel5.Controls.Add(dbLayoutPanel1, 1, 1); + tableLayoutPanel5.Controls.Add(dbLayoutPanel2, 1, 2); + tableLayoutPanel5.Controls.Add(label12, 0, 4); + tableLayoutPanel5.Controls.Add(dbLayoutPanel3, 1, 4); + tableLayoutPanel5.Controls.Add(label4, 0, 6); + tableLayoutPanel5.Controls.Add(dbLayoutPanel4, 1, 6); + tableLayoutPanel5.Controls.Add(label18, 0, 7); + tableLayoutPanel5.Controls.Add(dbLayoutPanel5, 1, 7); + tableLayoutPanel5.Controls.Add(label29, 0, 3); + tableLayoutPanel5.Controls.Add(dbLayoutPanel8, 1, 3); + tableLayoutPanel5.Controls.Add(dbLayoutPanel9, 1, 5); + tableLayoutPanel5.Controls.Add(label13, 0, 5); + tableLayoutPanel5.Location = new System.Drawing.Point(3, 3); + tableLayoutPanel5.Name = "tableLayoutPanel5"; + tableLayoutPanel5.RowCount = 8; + tableLayoutPanel5.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 12.42959F)); + tableLayoutPanel5.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 12.29149F)); + tableLayoutPanel5.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 12.54469F)); + tableLayoutPanel5.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 12.5499F)); + tableLayoutPanel5.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 12.54591F)); + tableLayoutPanel5.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 12.54572F)); + tableLayoutPanel5.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 12.54632F)); + tableLayoutPanel5.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 12.54638F)); + tableLayoutPanel5.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F)); + tableLayoutPanel5.Size = new System.Drawing.Size(1087, 427); + tableLayoutPanel5.TabIndex = 3; // - // btnCameraDel + // lbl_input // - this.btnCameraDel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom))); - this.btnCameraDel.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.btnCameraDel.Location = new System.Drawing.Point(532, 3); - this.btnCameraDel.Name = "btnCameraDel"; - this.btnCameraDel.Size = new System.Drawing.Size(116, 31); - this.btnCameraDel.TabIndex = 5; - this.btnCameraDel.Text = " Delete Camera "; - this.btnCameraDel.UseVisualStyleBackColor = true; - this.btnCameraDel.Click += new System.EventHandler(this.btnCameraDel_Click); + lbl_input.Anchor = System.Windows.Forms.AnchorStyles.Right; + lbl_input.AutoSize = true; + lbl_input.Location = new System.Drawing.Point(58, 20); + lbl_input.Name = "lbl_input"; + lbl_input.Size = new System.Drawing.Size(102, 13); + lbl_input.TabIndex = 1; + lbl_input.Text = "Default Input Path"; // - // lbl_camstats + // lbl_telegram_token // - this.lbl_camstats.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.lbl_camstats.AutoSize = true; - this.lbl_camstats.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.lbl_camstats.Location = new System.Drawing.Point(3, 12); - this.lbl_camstats.Name = "lbl_camstats"; - this.lbl_camstats.Size = new System.Drawing.Size(36, 17); - this.lbl_camstats.TabIndex = 4; - this.lbl_camstats.Text = "Stats"; + lbl_telegram_token.Anchor = System.Windows.Forms.AnchorStyles.Right; + lbl_telegram_token.AutoSize = true; + lbl_telegram_token.Location = new System.Drawing.Point(73, 124); + lbl_telegram_token.Name = "lbl_telegram_token"; + lbl_telegram_token.Size = new System.Drawing.Size(87, 13); + lbl_telegram_token.TabIndex = 6; + lbl_telegram_token.Text = "Telegram Token"; // - // tabSettings + // tableLayoutPanel18 // - this.tabSettings.Controls.Add(this.tableLayoutPanel4); - this.tabSettings.Location = new System.Drawing.Point(4, 22); - this.tabSettings.Name = "tabSettings"; - this.tabSettings.Size = new System.Drawing.Size(946, 436); - this.tabSettings.TabIndex = 3; - this.tabSettings.Text = "Settings"; - this.tabSettings.UseVisualStyleBackColor = true; + tableLayoutPanel18.ColumnCount = 3; + tableLayoutPanel18.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 73.09702F)); + tableLayoutPanel18.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 13.45378F)); + tableLayoutPanel18.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 13.4492F)); + tableLayoutPanel18.Controls.Add(btn_input_path, 2, 0); + tableLayoutPanel18.Controls.Add(cmbInput, 0, 0); + tableLayoutPanel18.Controls.Add(cb_inputpathsubfolders, 1, 0); + tableLayoutPanel18.Dock = System.Windows.Forms.DockStyle.Fill; + tableLayoutPanel18.Location = new System.Drawing.Point(167, 4); + tableLayoutPanel18.Name = "tableLayoutPanel18"; + tableLayoutPanel18.RowCount = 1; + tableLayoutPanel18.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + tableLayoutPanel18.Size = new System.Drawing.Size(916, 45); + tableLayoutPanel18.TabIndex = 12; // - // tableLayoutPanel4 + // btn_input_path // - this.tableLayoutPanel4.ColumnCount = 1; - this.tableLayoutPanel4.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); - this.tableLayoutPanel4.Controls.Add(this.tableLayoutPanel5, 0, 0); - this.tableLayoutPanel4.Controls.Add(this.BtnSettingsSave, 0, 1); - this.tableLayoutPanel4.Dock = System.Windows.Forms.DockStyle.Fill; - this.tableLayoutPanel4.Location = new System.Drawing.Point(0, 0); - this.tableLayoutPanel4.Name = "tableLayoutPanel4"; - this.tableLayoutPanel4.RowCount = 2; - this.tableLayoutPanel4.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 85.65022F)); - this.tableLayoutPanel4.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 14.34978F)); - this.tableLayoutPanel4.Size = new System.Drawing.Size(946, 436); - this.tableLayoutPanel4.TabIndex = 5; + btn_input_path.Anchor = System.Windows.Forms.AnchorStyles.None; + btn_input_path.Location = new System.Drawing.Point(819, 7); + btn_input_path.Name = "btn_input_path"; + btn_input_path.Size = new System.Drawing.Size(70, 30); + btn_input_path.TabIndex = 2; + btn_input_path.Text = "Select..."; + btn_input_path.UseVisualStyleBackColor = true; + btn_input_path.Click += btn_input_path_Click; + // + // cmbInput + // + cmbInput.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + cmbInput.AutoCompleteMode = System.Windows.Forms.AutoCompleteMode.SuggestAppend; + cmbInput.AutoCompleteSource = System.Windows.Forms.AutoCompleteSource.ListItems; + cmbInput.FormattingEnabled = true; + cmbInput.Location = new System.Drawing.Point(3, 11); + cmbInput.Margin = new System.Windows.Forms.Padding(3, 2, 2, 2); + cmbInput.Name = "cmbInput"; + cmbInput.Size = new System.Drawing.Size(664, 21); + cmbInput.TabIndex = 0; + // + // cb_inputpathsubfolders + // + cb_inputpathsubfolders.Anchor = System.Windows.Forms.AnchorStyles.Left; + cb_inputpathsubfolders.AutoSize = true; + cb_inputpathsubfolders.Location = new System.Drawing.Point(680, 14); + cb_inputpathsubfolders.Margin = new System.Windows.Forms.Padding(11, 2, 2, 2); + cb_inputpathsubfolders.Name = "cb_inputpathsubfolders"; + cb_inputpathsubfolders.Size = new System.Drawing.Size(82, 17); + cb_inputpathsubfolders.TabIndex = 1; + cb_inputpathsubfolders.Text = "Subfolders"; + cb_inputpathsubfolders.UseVisualStyleBackColor = true; // - // tableLayoutPanel5 + // lbl_deepstackurl // - this.tableLayoutPanel5.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; - this.tableLayoutPanel5.CellBorderStyle = System.Windows.Forms.TableLayoutPanelCellBorderStyle.Single; - this.tableLayoutPanel5.ColumnCount = 2; - this.tableLayoutPanel5.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 15F)); - this.tableLayoutPanel5.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 85F)); - this.tableLayoutPanel5.Controls.Add(this.label4, 0, 4); - this.tableLayoutPanel5.Controls.Add(this.tableLayoutPanel25, 1, 4); - this.tableLayoutPanel5.Controls.Add(this.label12, 0, 5); - this.tableLayoutPanel5.Controls.Add(this.cb_send_errors, 1, 5); - this.tableLayoutPanel5.Controls.Add(this.lbl_deepstackurl, 0, 1); - this.tableLayoutPanel5.Controls.Add(this.lbl_input, 0, 0); - this.tableLayoutPanel5.Controls.Add(this.tbDeepstackUrl, 1, 1); - this.tableLayoutPanel5.Controls.Add(this.lbl_telegram_token, 0, 2); - this.tableLayoutPanel5.Controls.Add(this.lbl_telegram_chatid, 0, 3); - this.tableLayoutPanel5.Controls.Add(this.tb_telegram_token, 1, 2); - this.tableLayoutPanel5.Controls.Add(this.tb_telegram_chatid, 1, 3); - this.tableLayoutPanel5.Controls.Add(this.tableLayoutPanel18, 1, 0); - this.tableLayoutPanel5.Dock = System.Windows.Forms.DockStyle.Fill; - this.tableLayoutPanel5.Location = new System.Drawing.Point(3, 3); - this.tableLayoutPanel5.Name = "tableLayoutPanel5"; - this.tableLayoutPanel5.RowCount = 6; - this.tableLayoutPanel5.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 16.66667F)); - this.tableLayoutPanel5.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 16.66667F)); - this.tableLayoutPanel5.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 16.66667F)); - this.tableLayoutPanel5.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 16.66667F)); - this.tableLayoutPanel5.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 16.66667F)); - this.tableLayoutPanel5.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 16.66667F)); - this.tableLayoutPanel5.Size = new System.Drawing.Size(940, 367); - this.tableLayoutPanel5.TabIndex = 3; + lbl_deepstackurl.Anchor = System.Windows.Forms.AnchorStyles.Right; + lbl_deepstackurl.AutoSize = true; + lbl_deepstackurl.Location = new System.Drawing.Point(75, 72); + lbl_deepstackurl.Name = "lbl_deepstackurl"; + lbl_deepstackurl.Size = new System.Drawing.Size(85, 13); + lbl_deepstackurl.TabIndex = 4; + lbl_deepstackurl.Text = "AI Server URL(s)"; + // + // dbLayoutPanel1 + // + dbLayoutPanel1.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + dbLayoutPanel1.ColumnCount = 3; + dbLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 72.96037F)); + dbLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 13.51981F)); + dbLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 13.45F)); + dbLayoutPanel1.Controls.Add(cb_DeepStackURLsQueued, 1, 0); + dbLayoutPanel1.Controls.Add(button1, 0, 0); + dbLayoutPanel1.Location = new System.Drawing.Point(166, 55); + dbLayoutPanel1.Margin = new System.Windows.Forms.Padding(2); + dbLayoutPanel1.Name = "dbLayoutPanel1"; + dbLayoutPanel1.RowCount = 1; + dbLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + dbLayoutPanel1.Size = new System.Drawing.Size(918, 47); + dbLayoutPanel1.TabIndex = 18; + // + // cb_DeepStackURLsQueued + // + cb_DeepStackURLsQueued.Anchor = System.Windows.Forms.AnchorStyles.Left; + cb_DeepStackURLsQueued.AutoSize = true; + cb_DeepStackURLsQueued.Location = new System.Drawing.Point(681, 15); + cb_DeepStackURLsQueued.Margin = new System.Windows.Forms.Padding(11, 2, 2, 2); + cb_DeepStackURLsQueued.Name = "cb_DeepStackURLsQueued"; + cb_DeepStackURLsQueued.Size = new System.Drawing.Size(67, 17); + cb_DeepStackURLsQueued.TabIndex = 4; + cb_DeepStackURLsQueued.Text = "Queued"; + toolTip1.SetToolTip(cb_DeepStackURLsQueued, "When checked, all urls will take turns processing the images.\r\nWhen unchecked, the original order will always be used."); + cb_DeepStackURLsQueued.UseVisualStyleBackColor = true; + // + // button1 + // + button1.Anchor = System.Windows.Forms.AnchorStyles.Left; + button1.Location = new System.Drawing.Point(3, 8); + button1.Name = "button1"; + button1.Size = new System.Drawing.Size(70, 30); + button1.TabIndex = 3; + button1.Text = "Edit"; + button1.UseVisualStyleBackColor = true; + button1.Click += button1_Click_1; + // + // dbLayoutPanel2 + // + dbLayoutPanel2.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + dbLayoutPanel2.ColumnCount = 5; + dbLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 32.75058F)); + dbLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 8.275059F)); + dbLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 32.05128F)); + dbLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 13.63636F)); + dbLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 13.63636F)); + dbLayoutPanel2.Controls.Add(tb_telegram_cooldown, 4, 0); + dbLayoutPanel2.Controls.Add(label5, 3, 0); + dbLayoutPanel2.Controls.Add(tb_telegram_chatid, 2, 0); + dbLayoutPanel2.Controls.Add(lbl_telegram_chatid, 1, 0); + dbLayoutPanel2.Controls.Add(tb_telegram_token, 0, 0); + dbLayoutPanel2.Location = new System.Drawing.Point(166, 107); + dbLayoutPanel2.Margin = new System.Windows.Forms.Padding(2); + dbLayoutPanel2.Name = "dbLayoutPanel2"; + dbLayoutPanel2.RowCount = 1; + dbLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + dbLayoutPanel2.Size = new System.Drawing.Size(918, 48); + dbLayoutPanel2.TabIndex = 19; + // + // tb_telegram_cooldown + // + tb_telegram_cooldown.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + tb_telegram_cooldown.Location = new System.Drawing.Point(794, 13); + tb_telegram_cooldown.Name = "tb_telegram_cooldown"; + tb_telegram_cooldown.Size = new System.Drawing.Size(121, 22); + tb_telegram_cooldown.TabIndex = 7; // - // label4 + // label5 // - this.label4.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.label4.AutoSize = true; - this.label4.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.label4.Location = new System.Drawing.Point(4, 262); - this.label4.Name = "label4"; - this.label4.Size = new System.Drawing.Size(30, 17); - this.label4.TabIndex = 15; - this.label4.Text = "Log"; - // - // tableLayoutPanel25 - // - this.tableLayoutPanel25.ColumnCount = 2; - this.tableLayoutPanel25.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 90F)); - this.tableLayoutPanel25.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 10F)); - this.tableLayoutPanel25.Controls.Add(this.btn_open_log, 1, 0); - this.tableLayoutPanel25.Controls.Add(this.cb_log, 0, 0); - this.tableLayoutPanel25.Dock = System.Windows.Forms.DockStyle.Fill; - this.tableLayoutPanel25.Location = new System.Drawing.Point(145, 244); - this.tableLayoutPanel25.Name = "tableLayoutPanel25"; - this.tableLayoutPanel25.RowCount = 1; - this.tableLayoutPanel25.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.tableLayoutPanel25.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 53F)); - this.tableLayoutPanel25.Size = new System.Drawing.Size(791, 53); - this.tableLayoutPanel25.TabIndex = 14; - // - // btn_open_log - // - this.btn_open_log.Anchor = System.Windows.Forms.AnchorStyles.None; - this.btn_open_log.Location = new System.Drawing.Point(714, 15); - this.btn_open_log.Name = "btn_open_log"; - this.btn_open_log.Size = new System.Drawing.Size(74, 23); - this.btn_open_log.TabIndex = 2; - this.btn_open_log.Text = "Open Log"; - this.btn_open_log.UseVisualStyleBackColor = true; - this.btn_open_log.Click += new System.EventHandler(this.btn_open_log_Click); - // - // cb_log - // - this.cb_log.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.cb_log.Location = new System.Drawing.Point(3, 3); - this.cb_log.Name = "cb_log"; - this.cb_log.Size = new System.Drawing.Size(705, 47); - this.cb_log.TabIndex = 11; - this.cb_log.Text = "Log everything"; - this.cb_log.UseVisualStyleBackColor = true; + label5.Anchor = System.Windows.Forms.AnchorStyles.Right; + label5.AutoSize = true; + label5.Location = new System.Drawing.Point(702, 17); + label5.Name = "label5"; + label5.Size = new System.Drawing.Size(86, 13); + label5.TabIndex = 11; + label5.Text = "Cooldown Secs"; // - // label12 + // tb_telegram_chatid // - this.label12.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.label12.AutoSize = true; - this.label12.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.label12.Location = new System.Drawing.Point(4, 325); - this.label12.Name = "label12"; - this.label12.Size = new System.Drawing.Size(77, 17); - this.label12.TabIndex = 13; - this.label12.Text = "Send Errors"; - // - // cb_send_errors - // - this.cb_send_errors.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.cb_send_errors.Location = new System.Drawing.Point(145, 304); - this.cb_send_errors.Name = "cb_send_errors"; - this.cb_send_errors.Size = new System.Drawing.Size(791, 59); - this.cb_send_errors.TabIndex = 12; - this.cb_send_errors.Text = "Send Errors and Warnings to Telegram"; - this.cb_send_errors.UseVisualStyleBackColor = true; + tb_telegram_chatid.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + tb_telegram_chatid.Location = new System.Drawing.Point(377, 13); + tb_telegram_chatid.Name = "tb_telegram_chatid"; + tb_telegram_chatid.Size = new System.Drawing.Size(287, 22); + tb_telegram_chatid.TabIndex = 6; // - // lbl_deepstackurl + // lbl_telegram_chatid // - this.lbl_deepstackurl.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.lbl_deepstackurl.AutoSize = true; - this.lbl_deepstackurl.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.lbl_deepstackurl.Location = new System.Drawing.Point(4, 82); - this.lbl_deepstackurl.Name = "lbl_deepstackurl"; - this.lbl_deepstackurl.Size = new System.Drawing.Size(95, 17); - this.lbl_deepstackurl.TabIndex = 4; - this.lbl_deepstackurl.Text = "Deepstack URL"; + lbl_telegram_chatid.Anchor = System.Windows.Forms.AnchorStyles.Right; + lbl_telegram_chatid.AutoSize = true; + lbl_telegram_chatid.Location = new System.Drawing.Point(326, 17); + lbl_telegram_chatid.Name = "lbl_telegram_chatid"; + lbl_telegram_chatid.Size = new System.Drawing.Size(45, 13); + lbl_telegram_chatid.TabIndex = 7; + lbl_telegram_chatid.Text = "Chat ID"; // - // lbl_input + // tb_telegram_token // - this.lbl_input.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.lbl_input.AutoSize = true; - this.lbl_input.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.lbl_input.Location = new System.Drawing.Point(4, 22); - this.lbl_input.Name = "lbl_input"; - this.lbl_input.Size = new System.Drawing.Size(66, 17); - this.lbl_input.TabIndex = 1; - this.lbl_input.Text = "Input Path"; + tb_telegram_token.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + tb_telegram_token.Location = new System.Drawing.Point(3, 13); + tb_telegram_token.Name = "tb_telegram_token"; + tb_telegram_token.Size = new System.Drawing.Size(293, 22); + tb_telegram_token.TabIndex = 5; // - // tbDeepstackUrl + // label12 // - this.tbDeepstackUrl.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); - this.tbDeepstackUrl.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.tbDeepstackUrl.Location = new System.Drawing.Point(145, 78); - this.tbDeepstackUrl.Name = "tbDeepstackUrl"; - this.tbDeepstackUrl.Size = new System.Drawing.Size(791, 25); - this.tbDeepstackUrl.TabIndex = 5; + label12.Anchor = System.Windows.Forms.AnchorStyles.Right; + label12.AutoSize = true; + label12.Location = new System.Drawing.Point(94, 230); + label12.Name = "label12"; + label12.Size = new System.Drawing.Size(66, 13); + label12.TabIndex = 13; + label12.Text = "Send Errors"; + // + // dbLayoutPanel3 + // + dbLayoutPanel3.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + dbLayoutPanel3.ColumnCount = 4; + dbLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 26.16822F)); + dbLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 53.85514F)); + dbLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 9.918678F)); + dbLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 9.9018F)); + dbLayoutPanel3.Controls.Add(cb_send_telegram_errors, 0, 0); + dbLayoutPanel3.Controls.Add(cb_send_pushover_errors, 1, 0); + dbLayoutPanel3.Location = new System.Drawing.Point(167, 214); + dbLayoutPanel3.Name = "dbLayoutPanel3"; + dbLayoutPanel3.RowCount = 1; + dbLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + dbLayoutPanel3.Size = new System.Drawing.Size(916, 46); + dbLayoutPanel3.TabIndex = 20; + // + // cb_send_telegram_errors + // + cb_send_telegram_errors.Anchor = System.Windows.Forms.AnchorStyles.Left; + cb_send_telegram_errors.AutoSize = true; + cb_send_telegram_errors.Location = new System.Drawing.Point(3, 14); + cb_send_telegram_errors.Name = "cb_send_telegram_errors"; + cb_send_telegram_errors.Size = new System.Drawing.Size(72, 17); + cb_send_telegram_errors.TabIndex = 11; + cb_send_telegram_errors.Text = "Telegram"; + cb_send_telegram_errors.UseVisualStyleBackColor = true; + // + // cb_send_pushover_errors + // + cb_send_pushover_errors.Anchor = System.Windows.Forms.AnchorStyles.Left; + cb_send_pushover_errors.AutoSize = true; + cb_send_pushover_errors.Location = new System.Drawing.Point(243, 14); + cb_send_pushover_errors.Name = "cb_send_pushover_errors"; + cb_send_pushover_errors.Size = new System.Drawing.Size(73, 17); + cb_send_pushover_errors.TabIndex = 12; + cb_send_pushover_errors.Text = "Pushover"; + cb_send_pushover_errors.UseVisualStyleBackColor = true; // - // lbl_telegram_token + // label4 // - this.lbl_telegram_token.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.lbl_telegram_token.AutoSize = true; - this.lbl_telegram_token.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.lbl_telegram_token.Location = new System.Drawing.Point(4, 142); - this.lbl_telegram_token.Name = "lbl_telegram_token"; - this.lbl_telegram_token.Size = new System.Drawing.Size(100, 17); - this.lbl_telegram_token.TabIndex = 6; - this.lbl_telegram_token.Text = "Telegram Token"; + label4.Anchor = System.Windows.Forms.AnchorStyles.Right; + label4.AutoSize = true; + label4.Location = new System.Drawing.Point(55, 336); + label4.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + label4.Name = "label4"; + label4.Size = new System.Drawing.Size(106, 13); + label4.TabIndex = 16; + label4.Text = "Default Credentials"; + // + // dbLayoutPanel4 + // + dbLayoutPanel4.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + dbLayoutPanel4.ColumnCount = 5; + dbLayoutPanel4.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 75F)); + dbLayoutPanel4.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 150F)); + dbLayoutPanel4.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 75F)); + dbLayoutPanel4.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 150F)); + dbLayoutPanel4.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 466F)); + dbLayoutPanel4.Controls.Add(label6, 0, 0); + dbLayoutPanel4.Controls.Add(tb_username, 1, 0); + dbLayoutPanel4.Controls.Add(tb_password, 3, 0); + dbLayoutPanel4.Controls.Add(label16, 2, 0); + dbLayoutPanel4.Controls.Add(label17, 4, 0); + dbLayoutPanel4.Location = new System.Drawing.Point(167, 320); + dbLayoutPanel4.Name = "dbLayoutPanel4"; + dbLayoutPanel4.RowCount = 1; + dbLayoutPanel4.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + dbLayoutPanel4.Size = new System.Drawing.Size(916, 46); + dbLayoutPanel4.TabIndex = 21; // - // lbl_telegram_chatid + // label6 // - this.lbl_telegram_chatid.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.lbl_telegram_chatid.AutoSize = true; - this.lbl_telegram_chatid.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.lbl_telegram_chatid.Location = new System.Drawing.Point(4, 202); - this.lbl_telegram_chatid.Name = "lbl_telegram_chatid"; - this.lbl_telegram_chatid.Size = new System.Drawing.Size(108, 17); - this.lbl_telegram_chatid.TabIndex = 7; - this.lbl_telegram_chatid.Text = "Telegram Chat ID"; + label6.Anchor = System.Windows.Forms.AnchorStyles.Right; + label6.AutoSize = true; + label6.Location = new System.Drawing.Point(11, 16); + label6.Name = "label6"; + label6.Size = new System.Drawing.Size(61, 13); + label6.TabIndex = 0; + label6.Text = "Username:"; + // + // tb_username + // + tb_username.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + tb_username.Location = new System.Drawing.Point(78, 12); + tb_username.Name = "tb_username"; + tb_username.Size = new System.Drawing.Size(144, 22); + tb_username.TabIndex = 17; + // + // tb_password + // + tb_password.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + tb_password.Location = new System.Drawing.Point(303, 12); + tb_password.Name = "tb_password"; + tb_password.Size = new System.Drawing.Size(144, 22); + tb_password.TabIndex = 18; + // + // label16 + // + label16.Anchor = System.Windows.Forms.AnchorStyles.Right; + label16.AutoSize = true; + label16.Location = new System.Drawing.Point(238, 16); + label16.Name = "label16"; + label16.Size = new System.Drawing.Size(59, 13); + label16.TabIndex = 0; + label16.Text = "Password:"; + // + // label17 + // + label17.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + label17.AutoSize = true; + label17.Location = new System.Drawing.Point(453, 16); + label17.Name = "label17"; + label17.Size = new System.Drawing.Size(460, 13); + label17.TabIndex = 3; + label17.Text = "These will be used with the [Username] and [Password] variables in Camera Actions."; + label17.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // label18 + // + label18.Anchor = System.Windows.Forms.AnchorStyles.Right; + label18.AutoSize = true; + label18.Location = new System.Drawing.Point(48, 391); + label18.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + label18.Name = "label18"; + label18.Size = new System.Drawing.Size(113, 13); + label18.TabIndex = 16; + label18.Text = "BlueIris Server Name:"; + // + // dbLayoutPanel5 + // + dbLayoutPanel5.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + dbLayoutPanel5.ColumnCount = 3; + dbLayoutPanel5.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 17.52337F)); + dbLayoutPanel5.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 25.76419F)); + dbLayoutPanel5.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 56.76856F)); + dbLayoutPanel5.Controls.Add(tb_BlueIrisServer, 0, 0); + dbLayoutPanel5.Controls.Add(label19, 2, 0); + dbLayoutPanel5.Controls.Add(lbl_blueirisserver, 1, 0); + dbLayoutPanel5.Location = new System.Drawing.Point(167, 373); + dbLayoutPanel5.Name = "dbLayoutPanel5"; + dbLayoutPanel5.RowCount = 1; + dbLayoutPanel5.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + dbLayoutPanel5.Size = new System.Drawing.Size(916, 50); + dbLayoutPanel5.TabIndex = 22; + // + // tb_BlueIrisServer + // + tb_BlueIrisServer.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + tb_BlueIrisServer.Location = new System.Drawing.Point(3, 14); + tb_BlueIrisServer.Name = "tb_BlueIrisServer"; + tb_BlueIrisServer.Size = new System.Drawing.Size(154, 22); + tb_BlueIrisServer.TabIndex = 19; + // + // label19 + // + label19.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + label19.AutoSize = true; + label19.Location = new System.Drawing.Point(398, 0); + label19.Name = "label19"; + label19.Size = new System.Drawing.Size(515, 50); + label19.TabIndex = 3; + label19.Text = resources.GetString("label19.Text"); + label19.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // lbl_blueirisserver + // + lbl_blueirisserver.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + lbl_blueirisserver.AutoSize = true; + lbl_blueirisserver.ForeColor = System.Drawing.Color.DodgerBlue; + lbl_blueirisserver.Location = new System.Drawing.Point(163, 0); + lbl_blueirisserver.Name = "lbl_blueirisserver"; + lbl_blueirisserver.Size = new System.Drawing.Size(229, 50); + lbl_blueirisserver.TabIndex = 20; + lbl_blueirisserver.Text = "BlueIris Web Server is configured to listen on 111.111.111.111:43"; + lbl_blueirisserver.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // label29 + // + label29.Anchor = System.Windows.Forms.AnchorStyles.Right; + label29.AutoSize = true; + label29.Location = new System.Drawing.Point(67, 177); + label29.Name = "label29"; + label29.Size = new System.Drawing.Size(93, 13); + label29.TabIndex = 6; + label29.Text = "Pushover API Key"; + // + // dbLayoutPanel8 + // + dbLayoutPanel8.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + dbLayoutPanel8.ColumnCount = 5; + dbLayoutPanel8.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 32.75058F)); + dbLayoutPanel8.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 8.391608F)); + dbLayoutPanel8.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 31.81818F)); + dbLayoutPanel8.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 13.28671F)); + dbLayoutPanel8.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 13.63636F)); + dbLayoutPanel8.Controls.Add(tb_Pushover_Cooldown, 4, 0); + dbLayoutPanel8.Controls.Add(tb_Pushover_APIKey, 0, 0); + dbLayoutPanel8.Controls.Add(label31, 3, 0); + dbLayoutPanel8.Controls.Add(label30, 1, 0); + dbLayoutPanel8.Controls.Add(tb_Pushover_UserKey, 2, 0); + dbLayoutPanel8.Location = new System.Drawing.Point(166, 160); + dbLayoutPanel8.Margin = new System.Windows.Forms.Padding(2); + dbLayoutPanel8.Name = "dbLayoutPanel8"; + dbLayoutPanel8.RowCount = 1; + dbLayoutPanel8.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + dbLayoutPanel8.Size = new System.Drawing.Size(918, 48); + dbLayoutPanel8.TabIndex = 23; + // + // tb_Pushover_Cooldown + // + tb_Pushover_Cooldown.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + tb_Pushover_Cooldown.Location = new System.Drawing.Point(795, 13); + tb_Pushover_Cooldown.Name = "tb_Pushover_Cooldown"; + tb_Pushover_Cooldown.Size = new System.Drawing.Size(120, 22); + tb_Pushover_Cooldown.TabIndex = 10; + // + // tb_Pushover_APIKey + // + tb_Pushover_APIKey.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + tb_Pushover_APIKey.Location = new System.Drawing.Point(3, 13); + tb_Pushover_APIKey.Name = "tb_Pushover_APIKey"; + tb_Pushover_APIKey.Size = new System.Drawing.Size(295, 22); + tb_Pushover_APIKey.TabIndex = 8; + // + // label31 + // + label31.Anchor = System.Windows.Forms.AnchorStyles.Right; + label31.AutoSize = true; + label31.Location = new System.Drawing.Point(703, 17); + label31.Name = "label31"; + label31.Size = new System.Drawing.Size(86, 13); + label31.TabIndex = 11; + label31.Text = "Cooldown Secs"; + // + // label30 + // + label30.Anchor = System.Windows.Forms.AnchorStyles.Right; + label30.AutoSize = true; + label30.Location = new System.Drawing.Point(322, 17); + label30.Name = "label30"; + label30.Size = new System.Drawing.Size(53, 13); + label30.TabIndex = 7; + label30.Text = "User Key:"; + // + // tb_Pushover_UserKey + // + tb_Pushover_UserKey.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + tb_Pushover_UserKey.Location = new System.Drawing.Point(381, 13); + tb_Pushover_UserKey.Name = "tb_Pushover_UserKey"; + tb_Pushover_UserKey.Size = new System.Drawing.Size(286, 22); + tb_Pushover_UserKey.TabIndex = 9; + // + // dbLayoutPanel9 + // + dbLayoutPanel9.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + dbLayoutPanel9.ColumnCount = 3; + dbLayoutPanel9.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 26.82635F)); + dbLayoutPanel9.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 73.17365F)); + dbLayoutPanel9.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 92F)); + dbLayoutPanel9.Controls.Add(cbStartWithWindows, 0, 0); + dbLayoutPanel9.Controls.Add(cbMinimizeToTray, 1, 0); + dbLayoutPanel9.Location = new System.Drawing.Point(167, 267); + dbLayoutPanel9.Name = "dbLayoutPanel9"; + dbLayoutPanel9.RowCount = 1; + dbLayoutPanel9.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F)); + dbLayoutPanel9.Size = new System.Drawing.Size(916, 46); + dbLayoutPanel9.TabIndex = 24; + // + // cbStartWithWindows + // + cbStartWithWindows.Anchor = System.Windows.Forms.AnchorStyles.Left; + cbStartWithWindows.AutoSize = true; + cbStartWithWindows.Location = new System.Drawing.Point(2, 14); + cbStartWithWindows.Margin = new System.Windows.Forms.Padding(2); + cbStartWithWindows.Name = "cbStartWithWindows"; + cbStartWithWindows.Size = new System.Drawing.Size(199, 17); + cbStartWithWindows.TabIndex = 15; + cbStartWithWindows.Text = "Start with user login (non-service)"; + cbStartWithWindows.UseVisualStyleBackColor = true; + // + // cbMinimizeToTray + // + cbMinimizeToTray.Anchor = System.Windows.Forms.AnchorStyles.Left; + cbMinimizeToTray.AutoSize = true; + cbMinimizeToTray.Location = new System.Drawing.Point(224, 14); + cbMinimizeToTray.Name = "cbMinimizeToTray"; + cbMinimizeToTray.Size = new System.Drawing.Size(109, 17); + cbMinimizeToTray.TabIndex = 16; + cbMinimizeToTray.Text = "Minimize to Tray"; + cbMinimizeToTray.UseVisualStyleBackColor = true; + // + // label13 + // + label13.Anchor = System.Windows.Forms.AnchorStyles.Right; + label13.AutoSize = true; + label13.Location = new System.Drawing.Point(85, 283); + label13.Name = "label13"; + label13.Size = new System.Drawing.Size(75, 13); + label13.TabIndex = 13; + label13.Text = "Misc Settings"; + // + // dbLayoutPanel10 + // + dbLayoutPanel10.ColumnCount = 3; + dbLayoutPanel10.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 33.33333F)); + dbLayoutPanel10.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 33.33333F)); + dbLayoutPanel10.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 33.33333F)); + dbLayoutPanel10.Controls.Add(button3, 0, 0); + dbLayoutPanel10.Controls.Add(BtnSettingsSave, 2, 0); + dbLayoutPanel10.Controls.Add(bt_CheckUpdates, 1, 0); + dbLayoutPanel10.Dock = System.Windows.Forms.DockStyle.Fill; + dbLayoutPanel10.Location = new System.Drawing.Point(3, 436); + dbLayoutPanel10.Name = "dbLayoutPanel10"; + dbLayoutPanel10.RowCount = 1; + dbLayoutPanel10.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + dbLayoutPanel10.Size = new System.Drawing.Size(1087, 34); + dbLayoutPanel10.TabIndex = 4; + // + // button3 + // + button3.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom; + button3.Location = new System.Drawing.Point(146, 3); + button3.Name = "button3"; + button3.Size = new System.Drawing.Size(69, 28); + button3.TabIndex = 20; + button3.Text = "Reset"; + button3.UseVisualStyleBackColor = true; + button3.Click += button3_Click; // - // tb_telegram_token + // BtnSettingsSave // - this.tb_telegram_token.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); - this.tb_telegram_token.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.tb_telegram_token.Location = new System.Drawing.Point(145, 138); - this.tb_telegram_token.Name = "tb_telegram_token"; - this.tb_telegram_token.Size = new System.Drawing.Size(791, 25); - this.tb_telegram_token.TabIndex = 8; + BtnSettingsSave.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom; + BtnSettingsSave.Location = new System.Drawing.Point(871, 3); + BtnSettingsSave.Name = "BtnSettingsSave"; + BtnSettingsSave.Size = new System.Drawing.Size(69, 28); + BtnSettingsSave.TabIndex = 21; + BtnSettingsSave.Text = "Save"; + BtnSettingsSave.UseVisualStyleBackColor = true; + BtnSettingsSave.Click += BtnSettingsSave_Click_1; + // + // bt_CheckUpdates + // + bt_CheckUpdates.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom; + bt_CheckUpdates.Location = new System.Drawing.Point(508, 3); + bt_CheckUpdates.Name = "bt_CheckUpdates"; + bt_CheckUpdates.Size = new System.Drawing.Size(69, 28); + bt_CheckUpdates.TabIndex = 21; + bt_CheckUpdates.Text = "Updates"; + toolTip1.SetToolTip(bt_CheckUpdates, "Check for AITOOL updates"); + bt_CheckUpdates.UseVisualStyleBackColor = true; + bt_CheckUpdates.Click += bt_CheckUpdates_Click; + // + // tabDeepStack + // + tabDeepStack.Controls.Add(chk_AutoAdd); + tabDeepStack.Controls.Add(label34); + tabDeepStack.Controls.Add(label33); + tabDeepStack.Controls.Add(label32); + tabDeepStack.Controls.Add(txt_DeepstackNoMoreOftenThanMins); + tabDeepStack.Controls.Add(txt_DeepstackRestartFailCount); + tabDeepStack.Controls.Add(Chk_AutoReStart); + tabDeepStack.Controls.Add(Btn_ViewLog); + tabDeepStack.Controls.Add(Btn_DeepstackReset); + tabDeepStack.Controls.Add(linkLabel1); + tabDeepStack.Controls.Add(groupBox11); + tabDeepStack.Controls.Add(groupBox10); + tabDeepStack.Controls.Add(lbl_DeepstackType); + tabDeepStack.Controls.Add(lbl_Deepstackversion); + tabDeepStack.Controls.Add(lbl_deepstackname); + tabDeepStack.Controls.Add(label28); + tabDeepStack.Controls.Add(label24); + tabDeepStack.Controls.Add(label23); + tabDeepStack.Controls.Add(label22); + tabDeepStack.Controls.Add(chk_stopbeforestart); + tabDeepStack.Controls.Add(chk_HighPriority); + tabDeepStack.Controls.Add(Chk_DSDebug); + tabDeepStack.Controls.Add(Lbl_BlueStackRunning); + tabDeepStack.Controls.Add(Btn_Save); + tabDeepStack.Controls.Add(label11); + tabDeepStack.Controls.Add(groupBox1); + tabDeepStack.Controls.Add(groupBox2); + tabDeepStack.Controls.Add(groupBox4); + tabDeepStack.Controls.Add(groupBox3); + tabDeepStack.Controls.Add(Chk_AutoStart); + tabDeepStack.Controls.Add(Btn_Start); + tabDeepStack.Controls.Add(Btn_Stop); + tabDeepStack.Controls.Add(groupBoxCustomModel); + tabDeepStack.Location = new System.Drawing.Point(4, 24); + tabDeepStack.Margin = new System.Windows.Forms.Padding(2); + tabDeepStack.Name = "tabDeepStack"; + tabDeepStack.Size = new System.Drawing.Size(1099, 492); + tabDeepStack.TabIndex = 6; + tabDeepStack.Text = "DeepStack"; + tabDeepStack.UseVisualStyleBackColor = true; + // + // chk_AutoAdd + // + chk_AutoAdd.AutoSize = true; + chk_AutoAdd.Location = new System.Drawing.Point(93, 361); + chk_AutoAdd.Name = "chk_AutoAdd"; + chk_AutoAdd.Size = new System.Drawing.Size(75, 17); + chk_AutoAdd.TabIndex = 27; + chk_AutoAdd.Text = "Auto Add"; + toolTip1.SetToolTip(chk_AutoAdd, "If the local Deepstack server URL is not already in Settings > AI Server URL list, it will be added automatically."); + chk_AutoAdd.UseVisualStyleBackColor = true; + // + // label34 + // + label34.AutoSize = true; + label34.Location = new System.Drawing.Point(459, 389); + label34.Name = "label34"; + label34.Size = new System.Drawing.Size(32, 13); + label34.TabIndex = 26; + label34.Text = "Mins"; + // + // label33 + // + label33.AutoSize = true; + label33.Location = new System.Drawing.Point(307, 390); + label33.Name = "label33"; + label33.Size = new System.Drawing.Size(109, 13); + label33.TabIndex = 25; + label33.Text = "No more often than"; + // + // label32 + // + label32.AutoSize = true; + label32.Location = new System.Drawing.Point(172, 390); + label32.Name = "label32"; + label32.Size = new System.Drawing.Size(118, 13); + label32.TabIndex = 24; + label32.Text = "URL Failures in a row."; + // + // txt_DeepstackNoMoreOftenThanMins + // + txt_DeepstackNoMoreOftenThanMins.Location = new System.Drawing.Point(411, 386); + txt_DeepstackNoMoreOftenThanMins.Name = "txt_DeepstackNoMoreOftenThanMins"; + txt_DeepstackNoMoreOftenThanMins.Size = new System.Drawing.Size(42, 22); + txt_DeepstackNoMoreOftenThanMins.TabIndex = 17; + // + // txt_DeepstackRestartFailCount + // + txt_DeepstackRestartFailCount.Location = new System.Drawing.Point(124, 386); + txt_DeepstackRestartFailCount.Name = "txt_DeepstackRestartFailCount"; + txt_DeepstackRestartFailCount.Size = new System.Drawing.Size(42, 22); + txt_DeepstackRestartFailCount.TabIndex = 16; + // + // Chk_AutoReStart + // + Chk_AutoReStart.AutoSize = true; + Chk_AutoReStart.Location = new System.Drawing.Point(9, 389); + Chk_AutoReStart.Name = "Chk_AutoReStart"; + Chk_AutoReStart.Size = new System.Drawing.Size(117, 17); + Chk_AutoReStart.TabIndex = 15; + Chk_AutoReStart.Text = "Auto Restart after"; + toolTip1.SetToolTip(Chk_AutoReStart, "Note this feature only works if AUTO START is also enabled"); + Chk_AutoReStart.UseVisualStyleBackColor = true; + // + // Btn_ViewLog + // + Btn_ViewLog.ForeColor = System.Drawing.Color.Maroon; + Btn_ViewLog.Location = new System.Drawing.Point(359, 416); + Btn_ViewLog.Name = "Btn_ViewLog"; + Btn_ViewLog.Size = new System.Drawing.Size(70, 30); + Btn_ViewLog.TabIndex = 22; + Btn_ViewLog.Text = "stderr.txt"; + toolTip1.SetToolTip(Btn_ViewLog, "Open STDERR.TXT which contains any errors deepstack may have had."); + Btn_ViewLog.UseVisualStyleBackColor = true; + Btn_ViewLog.Click += Btn_ViewLog_Click; + // + // Btn_DeepstackReset + // + Btn_DeepstackReset.Location = new System.Drawing.Point(271, 416); + Btn_DeepstackReset.Name = "Btn_DeepstackReset"; + Btn_DeepstackReset.Size = new System.Drawing.Size(70, 30); + Btn_DeepstackReset.TabIndex = 21; + Btn_DeepstackReset.Text = "Reset"; + toolTip1.SetToolTip(Btn_DeepstackReset, "Delete all Deepstack temp files"); + Btn_DeepstackReset.UseVisualStyleBackColor = true; + Btn_DeepstackReset.Click += bt_DeepstackReset_Click; + // + // linkLabel1 + // + linkLabel1.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + linkLabel1.Location = new System.Drawing.Point(0, 26); + linkLabel1.Name = "linkLabel1"; + linkLabel1.Size = new System.Drawing.Size(1097, 13); + linkLabel1.TabIndex = 19; + linkLabel1.TabStop = true; + linkLabel1.Text = "https://docs.deepstack.cc/windows/index.html"; + linkLabel1.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + linkLabel1.LinkClicked += linkLabel1_LinkClicked; + // + // groupBox11 + // + groupBox11.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + groupBox11.Controls.Add(tb_DeepStackURLs); + groupBox11.Location = new System.Drawing.Point(511, 300); + groupBox11.Name = "groupBox11"; + groupBox11.Size = new System.Drawing.Size(577, 118); + groupBox11.TabIndex = 18; + groupBox11.TabStop = false; + groupBox11.Text = "URL"; + // + // tb_DeepStackURLs + // + tb_DeepStackURLs.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + tb_DeepStackURLs.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, 0); + tb_DeepStackURLs.Location = new System.Drawing.Point(3, 22); + tb_DeepStackURLs.Multiline = true; + tb_DeepStackURLs.Name = "tb_DeepStackURLs"; + tb_DeepStackURLs.ReadOnly = true; + tb_DeepStackURLs.ScrollBars = System.Windows.Forms.ScrollBars.Both; + tb_DeepStackURLs.Size = new System.Drawing.Size(570, 93); + tb_DeepStackURLs.TabIndex = 0; + tb_DeepStackURLs.WordWrap = false; + // + // groupBox10 + // + groupBox10.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + groupBox10.Controls.Add(tb_DeepstackCommandLine); + groupBox10.Location = new System.Drawing.Point(510, 167); + groupBox10.Name = "groupBox10"; + groupBox10.Size = new System.Drawing.Size(581, 118); + groupBox10.TabIndex = 18; + groupBox10.TabStop = false; + groupBox10.Text = "Command line"; + // + // tb_DeepstackCommandLine + // + tb_DeepstackCommandLine.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + tb_DeepstackCommandLine.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, 0); + tb_DeepstackCommandLine.Location = new System.Drawing.Point(3, 22); + tb_DeepstackCommandLine.Multiline = true; + tb_DeepstackCommandLine.Name = "tb_DeepstackCommandLine"; + tb_DeepstackCommandLine.ReadOnly = true; + tb_DeepstackCommandLine.ScrollBars = System.Windows.Forms.ScrollBars.Both; + tb_DeepstackCommandLine.Size = new System.Drawing.Size(575, 93); + tb_DeepstackCommandLine.TabIndex = 0; + tb_DeepstackCommandLine.WordWrap = false; + // + // lbl_DeepstackType + // + lbl_DeepstackType.AutoSize = true; + lbl_DeepstackType.ForeColor = System.Drawing.Color.DodgerBlue; + lbl_DeepstackType.Location = new System.Drawing.Point(558, 96); + lbl_DeepstackType.Name = "lbl_DeepstackType"; + lbl_DeepstackType.Size = new System.Drawing.Size(10, 13); + lbl_DeepstackType.TabIndex = 16; + lbl_DeepstackType.Text = "."; + // + // lbl_Deepstackversion + // + lbl_Deepstackversion.AutoSize = true; + lbl_Deepstackversion.ForeColor = System.Drawing.Color.DodgerBlue; + lbl_Deepstackversion.Location = new System.Drawing.Point(558, 75); + lbl_Deepstackversion.Name = "lbl_Deepstackversion"; + lbl_Deepstackversion.Size = new System.Drawing.Size(10, 13); + lbl_Deepstackversion.TabIndex = 16; + lbl_Deepstackversion.Text = "."; + // + // lbl_deepstackname + // + lbl_deepstackname.AutoSize = true; + lbl_deepstackname.ForeColor = System.Drawing.Color.DodgerBlue; + lbl_deepstackname.Location = new System.Drawing.Point(558, 56); + lbl_deepstackname.Name = "lbl_deepstackname"; + lbl_deepstackname.Size = new System.Drawing.Size(10, 13); + lbl_deepstackname.TabIndex = 16; + lbl_deepstackname.Text = "."; + // + // label28 + // + label28.AutoSize = true; + label28.Location = new System.Drawing.Point(512, 115); + label28.Name = "label28"; + label28.Size = new System.Drawing.Size(42, 13); + label28.TabIndex = 16; + label28.Text = "Status:"; + // + // label24 + // + label24.AutoSize = true; + label24.Location = new System.Drawing.Point(518, 96); + label24.Name = "label24"; + label24.Size = new System.Drawing.Size(33, 13); + label24.TabIndex = 16; + label24.Text = "Type:"; + // + // label23 + // + label23.AutoSize = true; + label23.Location = new System.Drawing.Point(507, 75); + label23.Name = "label23"; + label23.Size = new System.Drawing.Size(48, 13); + label23.TabIndex = 16; + label23.Text = "Version:"; + // + // label22 + // + label22.AutoSize = true; + label22.Location = new System.Drawing.Point(514, 56); + label22.Name = "label22"; + label22.Size = new System.Drawing.Size(39, 13); + label22.TabIndex = 16; + label22.Text = "Name:"; + // + // chk_stopbeforestart + // + chk_stopbeforestart.AutoSize = true; + chk_stopbeforestart.Location = new System.Drawing.Point(356, 361); + chk_stopbeforestart.Margin = new System.Windows.Forms.Padding(2); + chk_stopbeforestart.Name = "chk_stopbeforestart"; + chk_stopbeforestart.Size = new System.Drawing.Size(150, 17); + chk_stopbeforestart.TabIndex = 14; + chk_stopbeforestart.Text = "Always stop before start"; + toolTip1.SetToolTip(chk_stopbeforestart, "If deepstack exe files are running when a START is requested, stop them first."); + chk_stopbeforestart.UseVisualStyleBackColor = true; + // + // chk_HighPriority + // + chk_HighPriority.AutoSize = true; + chk_HighPriority.Location = new System.Drawing.Point(243, 361); + chk_HighPriority.Margin = new System.Windows.Forms.Padding(2); + chk_HighPriority.Name = "chk_HighPriority"; + chk_HighPriority.Size = new System.Drawing.Size(114, 17); + chk_HighPriority.TabIndex = 13; + chk_HighPriority.Text = "Run high priority"; + toolTip1.SetToolTip(chk_HighPriority, "Increase the process priority of deepstack, python, etc. This may make deepstack slighly more responsive."); + chk_HighPriority.UseVisualStyleBackColor = true; + // + // Chk_DSDebug + // + Chk_DSDebug.AutoSize = true; + Chk_DSDebug.Location = new System.Drawing.Point(174, 361); + Chk_DSDebug.Margin = new System.Windows.Forms.Padding(2); + Chk_DSDebug.Name = "Chk_DSDebug"; + Chk_DSDebug.Size = new System.Drawing.Size(61, 17); + Chk_DSDebug.TabIndex = 12; + Chk_DSDebug.Text = "Debug"; + toolTip1.SetToolTip(Chk_DSDebug, "Show all output from Deepstack's python.exe, redis.exe and server.exe (Windows version, installed on same machine)"); + Chk_DSDebug.UseVisualStyleBackColor = true; + // + // Lbl_BlueStackRunning + // + Lbl_BlueStackRunning.AutoSize = true; + Lbl_BlueStackRunning.Location = new System.Drawing.Point(558, 115); + Lbl_BlueStackRunning.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + Lbl_BlueStackRunning.Name = "Lbl_BlueStackRunning"; + Lbl_BlueStackRunning.Size = new System.Drawing.Size(93, 13); + Lbl_BlueStackRunning.TabIndex = 13; + Lbl_BlueStackRunning.Text = "*NOT RUNNING*"; + // + // Btn_Save + // + Btn_Save.Location = new System.Drawing.Point(183, 416); + Btn_Save.Margin = new System.Windows.Forms.Padding(2); + Btn_Save.Name = "Btn_Save"; + Btn_Save.Size = new System.Drawing.Size(70, 30); + Btn_Save.TabIndex = 20; + Btn_Save.Text = "Save"; + Btn_Save.UseVisualStyleBackColor = true; + Btn_Save.Click += Btn_Save_Click; + // + // label11 + // + label11.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + label11.BackColor = System.Drawing.SystemColors.Info; + label11.ForeColor = System.Drawing.Color.RoyalBlue; + label11.Location = new System.Drawing.Point(0, 4); + label11.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + label11.Name = "label11"; + label11.Size = new System.Drawing.Size(1097, 22); + label11.TabIndex = 0; + label11.Text = "This tab is only for the WINDOWS version of Deepstack"; + label11.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // groupBox1 + // + groupBox1.Controls.Add(Chk_DetectionAPI); + groupBox1.Controls.Add(Chk_FaceAPI); + groupBox1.Controls.Add(Chk_SceneAPI); + groupBox1.Location = new System.Drawing.Point(7, 258); + groupBox1.Margin = new System.Windows.Forms.Padding(2); + groupBox1.Name = "groupBox1"; + groupBox1.Padding = new System.Windows.Forms.Padding(2); + groupBox1.Size = new System.Drawing.Size(162, 88); + groupBox1.TabIndex = 3; + groupBox1.TabStop = false; + groupBox1.Text = "API"; + // + // Chk_DetectionAPI + // + Chk_DetectionAPI.Location = new System.Drawing.Point(11, 63); + Chk_DetectionAPI.Margin = new System.Windows.Forms.Padding(2); + Chk_DetectionAPI.Name = "Chk_DetectionAPI"; + Chk_DetectionAPI.Size = new System.Drawing.Size(147, 19); + Chk_DetectionAPI.TabIndex = 6; + Chk_DetectionAPI.Text = "Detection API"; + Chk_DetectionAPI.UseVisualStyleBackColor = true; + // + // Chk_FaceAPI + // + Chk_FaceAPI.Location = new System.Drawing.Point(11, 40); + Chk_FaceAPI.Margin = new System.Windows.Forms.Padding(2); + Chk_FaceAPI.Name = "Chk_FaceAPI"; + Chk_FaceAPI.Size = new System.Drawing.Size(147, 19); + Chk_FaceAPI.TabIndex = 5; + Chk_FaceAPI.Text = "Face API"; + Chk_FaceAPI.UseVisualStyleBackColor = true; + // + // Chk_SceneAPI + // + Chk_SceneAPI.Location = new System.Drawing.Point(11, 17); + Chk_SceneAPI.Margin = new System.Windows.Forms.Padding(2); + Chk_SceneAPI.Name = "Chk_SceneAPI"; + Chk_SceneAPI.Size = new System.Drawing.Size(147, 19); + Chk_SceneAPI.TabIndex = 4; + Chk_SceneAPI.Text = "Scene API"; + Chk_SceneAPI.UseVisualStyleBackColor = true; + // + // groupBox2 + // + groupBox2.Controls.Add(RB_High); + groupBox2.Controls.Add(RB_Medium); + groupBox2.Controls.Add(RB_Low); + groupBox2.Location = new System.Drawing.Point(174, 258); + groupBox2.Margin = new System.Windows.Forms.Padding(2); + groupBox2.Name = "groupBox2"; + groupBox2.Padding = new System.Windows.Forms.Padding(2); + groupBox2.Size = new System.Drawing.Size(154, 88); + groupBox2.TabIndex = 4; + groupBox2.TabStop = false; + groupBox2.Text = "Mode"; + // + // RB_High + // + RB_High.Location = new System.Drawing.Point(11, 63); + RB_High.Margin = new System.Windows.Forms.Padding(2); + RB_High.Name = "RB_High"; + RB_High.Size = new System.Drawing.Size(139, 19); + RB_High.TabIndex = 9; + RB_High.TabStop = true; + RB_High.Text = "High"; + RB_High.UseVisualStyleBackColor = true; + // + // RB_Medium + // + RB_Medium.Location = new System.Drawing.Point(11, 40); + RB_Medium.Margin = new System.Windows.Forms.Padding(2); + RB_Medium.Name = "RB_Medium"; + RB_Medium.Size = new System.Drawing.Size(139, 19); + RB_Medium.TabIndex = 8; + RB_Medium.TabStop = true; + RB_Medium.Text = "Medium"; + RB_Medium.UseVisualStyleBackColor = true; + // + // RB_Low + // + RB_Low.Location = new System.Drawing.Point(11, 17); + RB_Low.Margin = new System.Windows.Forms.Padding(2); + RB_Low.Name = "RB_Low"; + RB_Low.Size = new System.Drawing.Size(139, 19); + RB_Low.TabIndex = 7; + RB_Low.TabStop = true; + RB_Low.Text = "Low"; + RB_Low.UseVisualStyleBackColor = true; + // + // groupBox4 + // + groupBox4.Controls.Add(Txt_DeepStackInstallFolder); + groupBox4.Location = new System.Drawing.Point(11, 47); + groupBox4.Margin = new System.Windows.Forms.Padding(2); + groupBox4.Name = "groupBox4"; + groupBox4.Padding = new System.Windows.Forms.Padding(2); + groupBox4.Size = new System.Drawing.Size(483, 45); + groupBox4.TabIndex = 9; + groupBox4.TabStop = false; + groupBox4.Text = "DeepStack Install Folder"; + // + // Txt_DeepStackInstallFolder + // + Txt_DeepStackInstallFolder.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + Txt_DeepStackInstallFolder.Location = new System.Drawing.Point(7, 17); + Txt_DeepStackInstallFolder.Margin = new System.Windows.Forms.Padding(2); + Txt_DeepStackInstallFolder.Name = "Txt_DeepStackInstallFolder"; + Txt_DeepStackInstallFolder.ReadOnly = true; + Txt_DeepStackInstallFolder.Size = new System.Drawing.Size(472, 22); + Txt_DeepStackInstallFolder.TabIndex = 2; + // + // groupBox3 + // + groupBox3.Controls.Add(label27); + groupBox3.Controls.Add(Txt_Port); + groupBox3.Location = new System.Drawing.Point(341, 258); + groupBox3.Margin = new System.Windows.Forms.Padding(2); + groupBox3.Name = "groupBox3"; + groupBox3.Padding = new System.Windows.Forms.Padding(2); + groupBox3.Size = new System.Drawing.Size(151, 88); + groupBox3.TabIndex = 5; + groupBox3.TabStop = false; + groupBox3.Text = "Port(s)"; + // + // label27 + // + label27.ForeColor = System.Drawing.Color.DarkGray; + label27.Location = new System.Drawing.Point(5, 41); + label27.Name = "label27"; + label27.Size = new System.Drawing.Size(142, 45); + label27.TabIndex = 1; + label27.Text = "Enter one or more ports with commas between to start more than one instance."; + // + // Txt_Port + // + Txt_Port.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + Txt_Port.Location = new System.Drawing.Point(10, 19); + Txt_Port.Margin = new System.Windows.Forms.Padding(2); + Txt_Port.Name = "Txt_Port"; + Txt_Port.Size = new System.Drawing.Size(127, 22); + Txt_Port.TabIndex = 10; + toolTip1.SetToolTip(Txt_Port, resources.GetString("Txt_Port.ToolTip")); + // + // Chk_AutoStart + // + Chk_AutoStart.AutoSize = true; + Chk_AutoStart.Location = new System.Drawing.Point(9, 361); + Chk_AutoStart.Margin = new System.Windows.Forms.Padding(2); + Chk_AutoStart.Name = "Chk_AutoStart"; + Chk_AutoStart.Size = new System.Drawing.Size(78, 17); + Chk_AutoStart.TabIndex = 11; + Chk_AutoStart.Text = "Auto Start"; + toolTip1.SetToolTip(Chk_AutoStart, "Automatically start Deepstack when AITOOL starts."); + Chk_AutoStart.UseVisualStyleBackColor = true; + // + // Btn_Start + // + Btn_Start.Location = new System.Drawing.Point(7, 416); + Btn_Start.Margin = new System.Windows.Forms.Padding(2); + Btn_Start.Name = "Btn_Start"; + Btn_Start.Size = new System.Drawing.Size(70, 30); + Btn_Start.TabIndex = 18; + Btn_Start.Text = "Start"; + Btn_Start.UseVisualStyleBackColor = true; + Btn_Start.Click += Btn_Start_Click; + // + // Btn_Stop + // + Btn_Stop.Location = new System.Drawing.Point(95, 416); + Btn_Stop.Margin = new System.Windows.Forms.Padding(2); + Btn_Stop.Name = "Btn_Stop"; + Btn_Stop.Size = new System.Drawing.Size(70, 30); + Btn_Stop.TabIndex = 19; + Btn_Stop.Text = "Stop"; + Btn_Stop.UseVisualStyleBackColor = true; + Btn_Stop.Click += Btn_Stop_Click; + // + // groupBoxCustomModel + // + groupBoxCustomModel.Controls.Add(Chk_CustomModelAPI); + groupBoxCustomModel.Controls.Add(label38); + groupBoxCustomModel.Controls.Add(label9); + groupBoxCustomModel.Controls.Add(label37); + groupBoxCustomModel.Controls.Add(label36); + groupBoxCustomModel.Controls.Add(label35); + groupBoxCustomModel.Controls.Add(Txt_CustomModelMode); + groupBoxCustomModel.Controls.Add(Txt_CustomModelPort); + groupBoxCustomModel.Controls.Add(Txt_CustomModelName); + groupBoxCustomModel.Controls.Add(Txt_CustomModelPath); + groupBoxCustomModel.Location = new System.Drawing.Point(11, 96); + groupBoxCustomModel.Margin = new System.Windows.Forms.Padding(2); + groupBoxCustomModel.Name = "groupBoxCustomModel"; + groupBoxCustomModel.Padding = new System.Windows.Forms.Padding(2); + groupBoxCustomModel.Size = new System.Drawing.Size(483, 158); + groupBoxCustomModel.TabIndex = 17; + groupBoxCustomModel.TabStop = false; + // + // Chk_CustomModelAPI + // + Chk_CustomModelAPI.AutoSize = true; + Chk_CustomModelAPI.BackColor = System.Drawing.SystemColors.Control; + Chk_CustomModelAPI.FlatStyle = System.Windows.Forms.FlatStyle.System; + Chk_CustomModelAPI.Location = new System.Drawing.Point(7, -2); + Chk_CustomModelAPI.Name = "Chk_CustomModelAPI"; + Chk_CustomModelAPI.Size = new System.Drawing.Size(107, 18); + Chk_CustomModelAPI.TabIndex = 0; + Chk_CustomModelAPI.Text = "Custom Model"; + Chk_CustomModelAPI.UseVisualStyleBackColor = false; + Chk_CustomModelAPI.CheckedChanged += Chk_CustomModelAPI_CheckedChanged; + // + // label38 + // + label38.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + label38.ForeColor = System.Drawing.Color.DarkGray; + label38.Location = new System.Drawing.Point(8, 113); + label38.Name = "label38"; + label38.Size = new System.Drawing.Size(469, 43); + label38.TabIndex = 2; + label38.Text = "If needed, specify more than one custom model instance by supplying an equal number of items for PATH/NAME/PORT. For example PATH=C:\\PTH1, C:\\PTH2, NAME=DOGS,CATS, PORT=82,83"; // - // tb_telegram_chatid + // label9 // - this.tb_telegram_chatid.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); - this.tb_telegram_chatid.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.tb_telegram_chatid.Location = new System.Drawing.Point(145, 198); - this.tb_telegram_chatid.Name = "tb_telegram_chatid"; - this.tb_telegram_chatid.Size = new System.Drawing.Size(791, 25); - this.tb_telegram_chatid.TabIndex = 9; + label9.AutoSize = true; + label9.Location = new System.Drawing.Point(3, 94); + label9.Name = "label9"; + label9.Size = new System.Drawing.Size(51, 13); + label9.TabIndex = 1; + label9.Text = "Mode(s):"; + // + // label37 + // + label37.AutoSize = true; + label37.Location = new System.Drawing.Point(11, 70); + label37.Name = "label37"; + label37.Size = new System.Drawing.Size(42, 13); + label37.TabIndex = 1; + label37.Text = "Port(s):"; + // + // label36 + // + label36.AutoSize = true; + label36.Location = new System.Drawing.Point(2, 46); + label36.Name = "label36"; + label36.Size = new System.Drawing.Size(50, 13); + label36.TabIndex = 1; + label36.Text = "Name(s):"; + // + // label35 + // + label35.AutoSize = true; + label35.Location = new System.Drawing.Point(8, 21); + label35.Name = "label35"; + label35.Size = new System.Drawing.Size(44, 13); + label35.TabIndex = 1; + label35.Text = "Path(s):"; + // + // Txt_CustomModelMode + // + Txt_CustomModelMode.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + Txt_CustomModelMode.Location = new System.Drawing.Point(56, 91); + Txt_CustomModelMode.Margin = new System.Windows.Forms.Padding(2); + Txt_CustomModelMode.Name = "Txt_CustomModelMode"; + Txt_CustomModelMode.Size = new System.Drawing.Size(423, 22); + Txt_CustomModelMode.TabIndex = 3; + Txt_CustomModelMode.TextChanged += Txt_CustomModelName_TextChanged; + // + // Txt_CustomModelPort + // + Txt_CustomModelPort.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + Txt_CustomModelPort.Location = new System.Drawing.Point(56, 67); + Txt_CustomModelPort.Margin = new System.Windows.Forms.Padding(2); + Txt_CustomModelPort.Name = "Txt_CustomModelPort"; + Txt_CustomModelPort.Size = new System.Drawing.Size(423, 22); + Txt_CustomModelPort.TabIndex = 3; + Txt_CustomModelPort.TextChanged += Txt_CustomModelName_TextChanged; + // + // Txt_CustomModelName + // + Txt_CustomModelName.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + Txt_CustomModelName.Location = new System.Drawing.Point(56, 43); + Txt_CustomModelName.Margin = new System.Windows.Forms.Padding(2); + Txt_CustomModelName.Name = "Txt_CustomModelName"; + Txt_CustomModelName.Size = new System.Drawing.Size(423, 22); + Txt_CustomModelName.TabIndex = 2; + toolTip1.SetToolTip(Txt_CustomModelName, "The custom model name"); + Txt_CustomModelName.TextChanged += Txt_CustomModelName_TextChanged; + // + // Txt_CustomModelPath + // + Txt_CustomModelPath.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + Txt_CustomModelPath.Location = new System.Drawing.Point(56, 18); + Txt_CustomModelPath.Margin = new System.Windows.Forms.Padding(2); + Txt_CustomModelPath.Name = "Txt_CustomModelPath"; + Txt_CustomModelPath.Size = new System.Drawing.Size(423, 22); + Txt_CustomModelPath.TabIndex = 1; + toolTip1.SetToolTip(Txt_CustomModelPath, "The custom model path not including filename"); + // + // tabLog + // + tabLog.Controls.Add(toolStrip2); + tabLog.Controls.Add(groupBox7); + tabLog.Location = new System.Drawing.Point(4, 24); + tabLog.Margin = new System.Windows.Forms.Padding(2); + tabLog.Name = "tabLog"; + tabLog.Size = new System.Drawing.Size(1099, 492); + tabLog.TabIndex = 7; + tabLog.Text = "Log"; + tabLog.UseVisualStyleBackColor = true; + tabLog.Click += tabLog_Click; + // + // toolStrip2 + // + toolStrip2.ImageScalingSize = new System.Drawing.Size(24, 24); + toolStrip2.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { toolStripLabel1, ToolStripComboBoxSearch, toolStripDropDownButton1, toolStripSeparator3, toolStripSeparator, toolStripDropDownButtonSettings, toolStripSeparator4, toolStripSeparator2, openToolStripButton, toolStripButtonLoad, toolStripSeparator7, toolStripButtonReload, toolStripSeparator5, toolStripButtonPauseLog, toolStripSeparator6, toolStripSeparator8, chk_filterErrors, chk_filterErrorsAll }); + toolStrip2.Location = new System.Drawing.Point(0, 0); + toolStrip2.Name = "toolStrip2"; + toolStrip2.Size = new System.Drawing.Size(1099, 31); + toolStrip2.TabIndex = 6; + toolStrip2.Text = "toolStrip2"; + // + // toolStripLabel1 + // + toolStripLabel1.Name = "toolStripLabel1"; + toolStripLabel1.Size = new System.Drawing.Size(45, 28); + toolStripLabel1.Text = "Search:"; + // + // ToolStripComboBoxSearch + // + ToolStripComboBoxSearch.BackColor = System.Drawing.SystemColors.Info; + ToolStripComboBoxSearch.ForeColor = System.Drawing.Color.FromArgb(0, 0, 192); + ToolStripComboBoxSearch.Items.AddRange(new object[] { "person", "error|warn|fatal|problem|exception", "this | orthat", "imagefilename.jpg | key=1234" }); + ToolStripComboBoxSearch.Name = "ToolStripComboBoxSearch"; + ToolStripComboBoxSearch.Size = new System.Drawing.Size(200, 31); + ToolStripComboBoxSearch.ToolTipText = "The search can be normal text OR a valid 'RegEx' statement.\r\n"; + ToolStripComboBoxSearch.Leave += ToolStripComboBoxSearch_Leave; + ToolStripComboBoxSearch.TextChanged += ToolStripComboBoxSearch_TextChanged; + // + // toolStripDropDownButton1 + // + toolStripDropDownButton1.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + toolStripDropDownButton1.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { mnu_Filter, mnu_Highlight }); + toolStripDropDownButton1.Image = (System.Drawing.Image)resources.GetObject("toolStripDropDownButton1.Image"); + toolStripDropDownButton1.ImageTransparentColor = System.Drawing.Color.Magenta; + toolStripDropDownButton1.Name = "toolStripDropDownButton1"; + toolStripDropDownButton1.Size = new System.Drawing.Size(37, 28); + toolStripDropDownButton1.Text = "Filter or highlight search box"; + // + // mnu_Filter + // + mnu_Filter.CheckOnClick = true; + mnu_Filter.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + mnu_Filter.Name = "mnu_Filter"; + mnu_Filter.Size = new System.Drawing.Size(124, 22); + mnu_Filter.Text = "Filter"; + mnu_Filter.CheckStateChanged += mnu_Filter_CheckStateChanged; + mnu_Filter.Click += mnu_Filter_Click; + // + // mnu_Highlight + // + mnu_Highlight.CheckOnClick = true; + mnu_Highlight.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + mnu_Highlight.Name = "mnu_Highlight"; + mnu_Highlight.Size = new System.Drawing.Size(124, 22); + mnu_Highlight.Text = "Highlight"; + mnu_Highlight.CheckStateChanged += mnu_highlight_CheckStateChanged; + // + // toolStripSeparator3 + // + toolStripSeparator3.Name = "toolStripSeparator3"; + toolStripSeparator3.Size = new System.Drawing.Size(6, 31); + // + // toolStripSeparator + // + toolStripSeparator.Name = "toolStripSeparator"; + toolStripSeparator.Size = new System.Drawing.Size(6, 31); + // + // toolStripDropDownButtonSettings + // + toolStripDropDownButtonSettings.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { Chk_AutoScroll, clearRecentErrorsToolStripMenuItem, toolStripMenuItemLogLevel }); + toolStripDropDownButtonSettings.Image = (System.Drawing.Image)resources.GetObject("toolStripDropDownButtonSettings.Image"); + toolStripDropDownButtonSettings.ImageTransparentColor = System.Drawing.Color.Magenta; + toolStripDropDownButtonSettings.Name = "toolStripDropDownButtonSettings"; + toolStripDropDownButtonSettings.Size = new System.Drawing.Size(109, 28); + toolStripDropDownButtonSettings.Text = "Log Settings"; + // + // Chk_AutoScroll + // + Chk_AutoScroll.CheckOnClick = true; + Chk_AutoScroll.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + Chk_AutoScroll.Name = "Chk_AutoScroll"; + Chk_AutoScroll.Size = new System.Drawing.Size(204, 22); + Chk_AutoScroll.Text = "Auto Scroll"; + Chk_AutoScroll.Click += Chk_AutoScroll_Click_1; + // + // clearRecentErrorsToolStripMenuItem + // + clearRecentErrorsToolStripMenuItem.Name = "clearRecentErrorsToolStripMenuItem"; + clearRecentErrorsToolStripMenuItem.Size = new System.Drawing.Size(204, 22); + clearRecentErrorsToolStripMenuItem.Text = "Clear Recent Error Count"; + clearRecentErrorsToolStripMenuItem.Click += clearRecentErrorsToolStripMenuItem_Click; + // + // toolStripMenuItemLogLevel + // + toolStripMenuItemLogLevel.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { mnu_log_filter_off, mnu_log_filter_fatal, mnu_log_filter_error, mnu_log_filter_warn, mnu_log_filter_info, mnu_log_filter_debug, mnu_log_filter_trace }); + toolStripMenuItemLogLevel.Name = "toolStripMenuItemLogLevel"; + toolStripMenuItemLogLevel.Size = new System.Drawing.Size(204, 22); + toolStripMenuItemLogLevel.Text = "Logging Level"; + // + // mnu_log_filter_off // - // tableLayoutPanel18 + mnu_log_filter_off.CheckOnClick = true; + mnu_log_filter_off.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + mnu_log_filter_off.Name = "mnu_log_filter_off"; + mnu_log_filter_off.Size = new System.Drawing.Size(109, 22); + mnu_log_filter_off.Text = "Off"; + mnu_log_filter_off.CheckStateChanged += mnu_log_filter_off_CheckStateChanged; + mnu_log_filter_off.Click += mnu_log_filter_off_Click; + // + // mnu_log_filter_fatal // - this.tableLayoutPanel18.ColumnCount = 2; - this.tableLayoutPanel18.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 90F)); - this.tableLayoutPanel18.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 10F)); - this.tableLayoutPanel18.Controls.Add(this.tbInput, 0, 0); - this.tableLayoutPanel18.Controls.Add(this.btn_input_path, 1, 0); - this.tableLayoutPanel18.Dock = System.Windows.Forms.DockStyle.Fill; - this.tableLayoutPanel18.Location = new System.Drawing.Point(145, 4); - this.tableLayoutPanel18.Name = "tableLayoutPanel18"; - this.tableLayoutPanel18.RowCount = 1; - this.tableLayoutPanel18.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.tableLayoutPanel18.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 53F)); - this.tableLayoutPanel18.Size = new System.Drawing.Size(791, 53); - this.tableLayoutPanel18.TabIndex = 12; - // - // tbInput - // - this.tbInput.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); - this.tbInput.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.tbInput.Location = new System.Drawing.Point(3, 14); - this.tbInput.Name = "tbInput"; - this.tbInput.Size = new System.Drawing.Size(705, 25); - this.tbInput.TabIndex = 1; + mnu_log_filter_fatal.CheckOnClick = true; + mnu_log_filter_fatal.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + mnu_log_filter_fatal.Name = "mnu_log_filter_fatal"; + mnu_log_filter_fatal.Size = new System.Drawing.Size(109, 22); + mnu_log_filter_fatal.Text = "Fatal"; + mnu_log_filter_fatal.CheckStateChanged += mnu_log_filter_fatal_CheckStateChanged; + // + // mnu_log_filter_error + // + mnu_log_filter_error.CheckOnClick = true; + mnu_log_filter_error.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + mnu_log_filter_error.Name = "mnu_log_filter_error"; + mnu_log_filter_error.Size = new System.Drawing.Size(109, 22); + mnu_log_filter_error.Text = "Error"; + mnu_log_filter_error.CheckStateChanged += mnu_log_filter_error_CheckStateChanged; // - // btn_input_path + // mnu_log_filter_warn // - this.btn_input_path.Anchor = System.Windows.Forms.AnchorStyles.None; - this.btn_input_path.Location = new System.Drawing.Point(721, 15); - this.btn_input_path.Name = "btn_input_path"; - this.btn_input_path.Size = new System.Drawing.Size(59, 23); - this.btn_input_path.TabIndex = 2; - this.btn_input_path.Text = "Select..."; - this.btn_input_path.UseVisualStyleBackColor = true; - this.btn_input_path.Click += new System.EventHandler(this.btn_input_path_Click); + mnu_log_filter_warn.CheckOnClick = true; + mnu_log_filter_warn.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + mnu_log_filter_warn.Name = "mnu_log_filter_warn"; + mnu_log_filter_warn.Size = new System.Drawing.Size(109, 22); + mnu_log_filter_warn.Text = "Warn"; + mnu_log_filter_warn.CheckStateChanged += mnu_log_filter_warn_CheckStateChanged; // - // BtnSettingsSave + // mnu_log_filter_info + // + mnu_log_filter_info.CheckOnClick = true; + mnu_log_filter_info.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + mnu_log_filter_info.Name = "mnu_log_filter_info"; + mnu_log_filter_info.Size = new System.Drawing.Size(109, 22); + mnu_log_filter_info.Text = "Info"; + mnu_log_filter_info.CheckStateChanged += mnu_log_filter_info_CheckStateChanged; + // + // mnu_log_filter_debug + // + mnu_log_filter_debug.CheckOnClick = true; + mnu_log_filter_debug.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + mnu_log_filter_debug.Name = "mnu_log_filter_debug"; + mnu_log_filter_debug.Size = new System.Drawing.Size(109, 22); + mnu_log_filter_debug.Text = "Debug"; + mnu_log_filter_debug.CheckStateChanged += mnu_log_filter_debug_CheckStateChanged; // - this.BtnSettingsSave.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom))); - this.BtnSettingsSave.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.BtnSettingsSave.Location = new System.Drawing.Point(405, 376); - this.BtnSettingsSave.Name = "BtnSettingsSave"; - this.BtnSettingsSave.Size = new System.Drawing.Size(136, 57); - this.BtnSettingsSave.TabIndex = 2; - this.BtnSettingsSave.Text = "Save"; - this.BtnSettingsSave.UseVisualStyleBackColor = true; - this.BtnSettingsSave.Click += new System.EventHandler(this.BtnSettingsSave_Click_1); + // mnu_log_filter_trace + // + mnu_log_filter_trace.CheckOnClick = true; + mnu_log_filter_trace.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None; + mnu_log_filter_trace.Name = "mnu_log_filter_trace"; + mnu_log_filter_trace.Size = new System.Drawing.Size(109, 22); + mnu_log_filter_trace.Text = "Trace"; + mnu_log_filter_trace.CheckStateChanged += mnu_log_filter_trace_CheckStateChanged; + // + // toolStripSeparator4 + // + toolStripSeparator4.Name = "toolStripSeparator4"; + toolStripSeparator4.Size = new System.Drawing.Size(6, 31); + // + // toolStripSeparator2 + // + toolStripSeparator2.Name = "toolStripSeparator2"; + toolStripSeparator2.Size = new System.Drawing.Size(6, 31); + // + // openToolStripButton + // + openToolStripButton.Image = (System.Drawing.Image)resources.GetObject("openToolStripButton.Image"); + openToolStripButton.ImageTransparentColor = System.Drawing.Color.Magenta; + openToolStripButton.Name = "openToolStripButton"; + openToolStripButton.Size = new System.Drawing.Size(64, 28); + openToolStripButton.Text = "Open"; + openToolStripButton.ToolTipText = "Open Log File in external editor"; + openToolStripButton.Click += openToolStripButton_Click; + // + // toolStripButtonLoad + // + toolStripButtonLoad.Image = (System.Drawing.Image)resources.GetObject("toolStripButtonLoad.Image"); + toolStripButtonLoad.ImageTransparentColor = System.Drawing.Color.Magenta; + toolStripButtonLoad.Name = "toolStripButtonLoad"; + toolStripButtonLoad.Size = new System.Drawing.Size(61, 28); + toolStripButtonLoad.Text = "Load"; + toolStripButtonLoad.ToolTipText = "Load a specific log file into the list"; + toolStripButtonLoad.Click += toolStripButtonLoad_Click; + // + // toolStripSeparator7 + // + toolStripSeparator7.Name = "toolStripSeparator7"; + toolStripSeparator7.Size = new System.Drawing.Size(6, 31); + // + // toolStripButtonReload + // + toolStripButtonReload.Image = (System.Drawing.Image)resources.GetObject("toolStripButtonReload.Image"); + toolStripButtonReload.ImageTransparentColor = System.Drawing.Color.Magenta; + toolStripButtonReload.Name = "toolStripButtonReload"; + toolStripButtonReload.Size = new System.Drawing.Size(71, 28); + toolStripButtonReload.Text = "Reload"; + toolStripButtonReload.ToolTipText = "Reloads the entire current log file from file without limiting the max number of lines. \r\nUse this after loading other files or filtering to reset view"; + toolStripButtonReload.Click += toolStripButtonReload_ClickAsync; + // + // toolStripSeparator5 + // + toolStripSeparator5.Name = "toolStripSeparator5"; + toolStripSeparator5.Size = new System.Drawing.Size(6, 31); + // + // toolStripButtonPauseLog + // + toolStripButtonPauseLog.CheckOnClick = true; + toolStripButtonPauseLog.Image = (System.Drawing.Image)resources.GetObject("toolStripButtonPauseLog.Image"); + toolStripButtonPauseLog.ImageTransparentColor = System.Drawing.Color.Magenta; + toolStripButtonPauseLog.Name = "toolStripButtonPauseLog"; + toolStripButtonPauseLog.Size = new System.Drawing.Size(66, 28); + toolStripButtonPauseLog.Text = "Pause"; + toolStripButtonPauseLog.ToolTipText = "Pause log tab auto refresh"; + toolStripButtonPauseLog.Click += toolStripButtonPauseLog_Click; + // + // toolStripSeparator6 + // + toolStripSeparator6.Name = "toolStripSeparator6"; + toolStripSeparator6.Size = new System.Drawing.Size(6, 31); + // + // toolStripSeparator8 + // + toolStripSeparator8.Name = "toolStripSeparator8"; + toolStripSeparator8.Size = new System.Drawing.Size(6, 31); + // + // chk_filterErrors + // + chk_filterErrors.CheckOnClick = true; + chk_filterErrors.Image = (System.Drawing.Image)resources.GetObject("chk_filterErrors.Image"); + chk_filterErrors.ImageTransparentColor = System.Drawing.Color.Magenta; + chk_filterErrors.Name = "chk_filterErrors"; + chk_filterErrors.Size = new System.Drawing.Size(65, 28); + chk_filterErrors.Text = "Errors"; + chk_filterErrors.ToolTipText = "Show errors from current loaded log"; + chk_filterErrors.Click += chk_filterErrors_Click_1; + // + // chk_filterErrorsAll + // + chk_filterErrorsAll.CheckOnClick = true; + chk_filterErrorsAll.Image = (System.Drawing.Image)resources.GetObject("chk_filterErrorsAll.Image"); + chk_filterErrorsAll.ImageTransparentColor = System.Drawing.Color.Magenta; + chk_filterErrorsAll.Name = "chk_filterErrorsAll"; + chk_filterErrorsAll.Size = new System.Drawing.Size(90, 28); + chk_filterErrorsAll.Text = "Errors (All)"; + chk_filterErrorsAll.ToolTipText = "Show errors from ALL logs"; + chk_filterErrorsAll.Click += chk_filterErrorsAll_Click; + // + // groupBox7 + // + groupBox7.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + groupBox7.Controls.Add(folv_log); + groupBox7.Location = new System.Drawing.Point(5, 36); + groupBox7.Margin = new System.Windows.Forms.Padding(2); + groupBox7.Name = "groupBox7"; + groupBox7.Padding = new System.Windows.Forms.Padding(2); + groupBox7.Size = new System.Drawing.Size(1092, 443); + groupBox7.TabIndex = 5; + groupBox7.TabStop = false; + groupBox7.Text = "Log"; + // + // folv_log + // + folv_log.BackColor = System.Drawing.Color.Navy; + folv_log.Dock = System.Windows.Forms.DockStyle.Fill; + folv_log.ForeColor = System.Drawing.Color.White; + folv_log.Location = new System.Drawing.Point(2, 17); + folv_log.Name = "folv_log"; + folv_log.ShowGroups = false; + folv_log.Size = new System.Drawing.Size(1088, 424); + folv_log.TabIndex = 0; + folv_log.UseCompatibleStateImageBehavior = false; + folv_log.View = System.Windows.Forms.View.Details; + folv_log.VirtualMode = true; + folv_log.FormatCell += folv_log_FormatCell; + folv_log.FormatRow += folv_log_FormatRow; + // + // HistoryImageList + // + HistoryImageList.ColorDepth = System.Windows.Forms.ColorDepth.Depth32Bit; + HistoryImageList.ImageStream = (System.Windows.Forms.ImageListStreamer)resources.GetObject("HistoryImageList.ImageStream"); + HistoryImageList.TransparentColor = System.Drawing.Color.Transparent; + HistoryImageList.Images.SetKeyName(0, "error16"); + HistoryImageList.Images.SetKeyName(1, "person16"); + HistoryImageList.Images.SetKeyName(2, "nothing16"); + HistoryImageList.Images.SetKeyName(3, "detection16"); + HistoryImageList.Images.SetKeyName(4, "success16"); + HistoryImageList.Images.SetKeyName(5, "bear16"); + HistoryImageList.Images.SetKeyName(6, "cat16"); + HistoryImageList.Images.SetKeyName(7, "dog16"); + HistoryImageList.Images.SetKeyName(8, "horse16"); + HistoryImageList.Images.SetKeyName(9, "bird16"); + HistoryImageList.Images.SetKeyName(10, "alien16"); + HistoryImageList.Images.SetKeyName(11, "cow16"); + HistoryImageList.Images.SetKeyName(12, "car16"); + HistoryImageList.Images.SetKeyName(13, "truck16"); + HistoryImageList.Images.SetKeyName(14, "motorcycle16"); + HistoryImageList.Images.SetKeyName(15, "bicycle32.png"); + HistoryImageList.Images.SetKeyName(16, "airplane.png"); + HistoryImageList.Images.SetKeyName(17, "bear.png"); + HistoryImageList.Images.SetKeyName(18, "bicycle.png"); + HistoryImageList.Images.SetKeyName(19, "bird.png"); + HistoryImageList.Images.SetKeyName(20, "boat.png"); + HistoryImageList.Images.SetKeyName(21, "bus.png"); + HistoryImageList.Images.SetKeyName(22, "car.png"); + HistoryImageList.Images.SetKeyName(23, "cat.png"); + HistoryImageList.Images.SetKeyName(24, "cow.png"); + HistoryImageList.Images.SetKeyName(25, "dog.png"); + HistoryImageList.Images.SetKeyName(26, "horse.png"); + HistoryImageList.Images.SetKeyName(27, "motorcycle.png"); + HistoryImageList.Images.SetKeyName(28, "person.png"); + HistoryImageList.Images.SetKeyName(29, "sheep.png"); + HistoryImageList.Images.SetKeyName(30, "truck.png"); + HistoryImageList.Images.SetKeyName(31, "error32.png"); + HistoryImageList.Images.SetKeyName(32, "person32.png"); + HistoryImageList.Images.SetKeyName(33, "nothing32.png"); + HistoryImageList.Images.SetKeyName(34, "success32.png"); + HistoryImageList.Images.SetKeyName(35, "detection32.png"); + HistoryImageList.Images.SetKeyName(36, "bear32.png"); + HistoryImageList.Images.SetKeyName(37, "cat32.png"); + HistoryImageList.Images.SetKeyName(38, "dog32.png"); + HistoryImageList.Images.SetKeyName(39, "horse32.png"); + HistoryImageList.Images.SetKeyName(40, "bird32.png"); + HistoryImageList.Images.SetKeyName(41, "alien32.png"); + HistoryImageList.Images.SetKeyName(42, "cow32.png"); + HistoryImageList.Images.SetKeyName(43, "car32.png"); + HistoryImageList.Images.SetKeyName(44, "truck32.png"); + HistoryImageList.Images.SetKeyName(45, "motorcycle32.png"); + // + // HistoryUpdateListTimer + // + HistoryUpdateListTimer.Interval = 3000; + HistoryUpdateListTimer.Tick += HistoryUpdateListTimer_Tick; + // + // statusStrip1 + // + statusStrip1.ImageScalingSize = new System.Drawing.Size(28, 28); + statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { toolStripProgressBar1, toolStripStatusLabelHistoryItems, toolStripStatusErrors, toolStripStatusLabel1, toolStripStatusLabelInfo }); + statusStrip1.Location = new System.Drawing.Point(0, 527); + statusStrip1.Name = "statusStrip1"; + statusStrip1.Padding = new System.Windows.Forms.Padding(1, 0, 8, 0); + statusStrip1.Size = new System.Drawing.Size(1107, 22); + statusStrip1.TabIndex = 5; + statusStrip1.Text = "statusStrip1"; + // + // toolStripProgressBar1 + // + toolStripProgressBar1.Name = "toolStripProgressBar1"; + toolStripProgressBar1.Size = new System.Drawing.Size(100, 29); + toolStripProgressBar1.Visible = false; + // + // toolStripStatusLabelHistoryItems + // + toolStripStatusLabelHistoryItems.ForeColor = System.Drawing.Color.DodgerBlue; + toolStripStatusLabelHistoryItems.Name = "toolStripStatusLabelHistoryItems"; + toolStripStatusLabelHistoryItems.Size = new System.Drawing.Size(86, 17); + toolStripStatusLabelHistoryItems.Text = "0 History Items"; + // + // toolStripStatusErrors + // + toolStripStatusErrors.Name = "toolStripStatusErrors"; + toolStripStatusErrors.Size = new System.Drawing.Size(10, 17); + toolStripStatusErrors.Text = "."; + toolStripStatusErrors.Click += toolStripStatusErrors_Click; + // + // toolStripStatusLabel1 + // + toolStripStatusLabel1.Name = "toolStripStatusLabel1"; + toolStripStatusLabel1.Size = new System.Drawing.Size(10, 17); + toolStripStatusLabel1.Text = "."; + // + // toolStripStatusLabelInfo + // + toolStripStatusLabelInfo.ForeColor = System.Drawing.Color.DarkOrange; + toolStripStatusLabelInfo.Name = "toolStripStatusLabelInfo"; + toolStripStatusLabelInfo.Size = new System.Drawing.Size(26, 17); + toolStripStatusLabelInfo.Text = "Idle"; + // + // LogUpdateListTimer + // + LogUpdateListTimer.Interval = 2000; + LogUpdateListTimer.Tick += LogUpdateListTimer_Tick; // // Shell // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.AutoSize = true; - this.ClientSize = new System.Drawing.Size(954, 462); - this.Controls.Add(this.tabControl1); - this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); - this.MinimumSize = new System.Drawing.Size(970, 500); - this.Name = "Shell"; - this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; - this.Text = "AI Tool"; - this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.Shell_FormClosing); - this.Resize += new System.EventHandler(this.Form1_Resize); - this.tabControl1.ResumeLayout(false); - this.tabOverview.ResumeLayout(false); - this.tableLayoutPanel14.ResumeLayout(false); - this.tableLayoutPanel15.ResumeLayout(false); - this.tableLayoutPanel15.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox2)).EndInit(); - this.tabStats.ResumeLayout(false); - this.tableLayoutPanel16.ResumeLayout(false); - this.tableLayoutPanel23.ResumeLayout(false); - this.tableLayoutPanel23.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)(this.chart_confidence)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.timeline)).EndInit(); - this.tableLayoutPanel17.ResumeLayout(false); - ((System.ComponentModel.ISupportInitialize)(this.chart1)).EndInit(); - this.tabHistory.ResumeLayout(false); - this.tableLayoutPanel1.ResumeLayout(false); - this.tableLayoutPanel21.ResumeLayout(false); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit(); - this.tableLayoutPanel22.ResumeLayout(false); - this.tableLayoutPanel22.PerformLayout(); - this.splitContainer1.Panel1.ResumeLayout(false); - this.splitContainer1.Panel2.ResumeLayout(false); - ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit(); - this.splitContainer1.ResumeLayout(false); - this.tableLayoutPanel19.ResumeLayout(false); - this.panel1.ResumeLayout(false); - this.panel1.PerformLayout(); - this.tabCameras.ResumeLayout(false); - this.tableLayoutPanel2.ResumeLayout(false); - this.tableLayoutPanel3.ResumeLayout(false); - this.tableLayoutPanel6.ResumeLayout(false); - this.tableLayoutPanel6.PerformLayout(); - this.tableLayoutPanel7.ResumeLayout(false); - this.tableLayoutPanel7.PerformLayout(); - this.tableLayoutPanel8.ResumeLayout(false); - this.tableLayoutPanel8.PerformLayout(); - this.tableLayoutPanel9.ResumeLayout(false); - this.tableLayoutPanel9.PerformLayout(); - this.tableLayoutPanel10.ResumeLayout(false); - this.tableLayoutPanel10.PerformLayout(); - this.tableLayoutPanel20.ResumeLayout(false); - this.tableLayoutPanel20.PerformLayout(); - this.tableLayoutPanel12.ResumeLayout(false); - this.tableLayoutPanel12.PerformLayout(); - this.tableLayoutPanel13.ResumeLayout(false); - this.tableLayoutPanel13.PerformLayout(); - this.tableLayoutPanel24.ResumeLayout(false); - this.tableLayoutPanel24.PerformLayout(); - this.tableLayoutPanel11.ResumeLayout(false); - this.tabSettings.ResumeLayout(false); - this.tableLayoutPanel4.ResumeLayout(false); - this.tableLayoutPanel5.ResumeLayout(false); - this.tableLayoutPanel5.PerformLayout(); - this.tableLayoutPanel25.ResumeLayout(false); - this.tableLayoutPanel18.ResumeLayout(false); - this.tableLayoutPanel18.PerformLayout(); - this.ResumeLayout(false); - + AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + ClientSize = new System.Drawing.Size(1107, 549); + Controls.Add(tabControl1); + Controls.Add(statusStrip1); + Font = new System.Drawing.Font("Segoe UI", 8.25F); + Icon = (System.Drawing.Icon)resources.GetObject("$this.Icon"); + MinimumSize = new System.Drawing.Size(755, 440); + Name = "Shell"; + StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; + Tag = "SAVE"; + Text = "AI Tool"; + Activated += Shell_Activated; + FormClosing += Shell_FormClosing; + Load += Shell_Load; + DpiChanged += Shell_DpiChanged; + Resize += Form1_Resize; + TraycontextMenuStrip.ResumeLayout(false); + tabControl1.ResumeLayout(false); + tabOverview.ResumeLayout(false); + tableLayoutPanel14.ResumeLayout(false); + tableLayoutPanel15.ResumeLayout(false); + tableLayoutPanel15.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)pictureBox2).EndInit(); + tabStats.ResumeLayout(false); + tableLayoutPanel16.ResumeLayout(false); + tableLayoutPanel23.ResumeLayout(false); + tableLayoutPanel23.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)chart_confidence).EndInit(); + ((System.ComponentModel.ISupportInitialize)timeline).EndInit(); + tableLayoutPanel17.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)chart1).EndInit(); + tabHistory.ResumeLayout(false); + toolStrip1.ResumeLayout(false); + toolStrip1.PerformLayout(); + splitContainer2.Panel1.ResumeLayout(false); + splitContainer2.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)splitContainer2).EndInit(); + splitContainer2.ResumeLayout(false); + groupBox8.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)folv_history).EndInit(); + contextMenuStripHistory.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)pictureBox1).EndInit(); + toolStripContainer1.ResumeLayout(false); + toolStripContainer1.PerformLayout(); + tabCameras.ResumeLayout(false); + splitContainer1.Panel1.ResumeLayout(false); + splitContainer1.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)splitContainer1).EndInit(); + splitContainer1.ResumeLayout(false); + splitContainer3.Panel1.ResumeLayout(false); + splitContainer3.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)splitContainer3).EndInit(); + splitContainer3.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)FOLV_Cameras).EndInit(); + ((System.ComponentModel.ISupportInitialize)pictureBoxCamera).EndInit(); + tableLayoutPanel6.ResumeLayout(false); + tableLayoutPanel6.PerformLayout(); + tableLayoutPanel11.ResumeLayout(false); + tableLayoutPanel7.ResumeLayout(false); + tableLayoutPanel7.PerformLayout(); + tableLayoutPanel12.ResumeLayout(false); + tableLayoutPanel12.PerformLayout(); + tableLayoutPanel13.ResumeLayout(false); + tableLayoutPanel13.PerformLayout(); + tableLayoutPanel26.ResumeLayout(false); + tableLayoutPanel26.PerformLayout(); + tableLayoutPanel27.ResumeLayout(false); + tableLayoutPanel27.PerformLayout(); + dbLayoutPanel6.ResumeLayout(false); + dbLayoutPanel6.PerformLayout(); + dbLayoutPanel7.ResumeLayout(false); + dbLayoutPanel7.PerformLayout(); + dbLayoutPanel11.ResumeLayout(false); + dbLayoutPanel11.PerformLayout(); + dbLayoutPanel12.ResumeLayout(false); + dbLayoutPanel12.PerformLayout(); + dbLayoutPanel13.ResumeLayout(false); + dbLayoutPanel13.PerformLayout(); + tabSettings.ResumeLayout(false); + tableLayoutPanel4.ResumeLayout(false); + tableLayoutPanel5.ResumeLayout(false); + tableLayoutPanel5.PerformLayout(); + tableLayoutPanel18.ResumeLayout(false); + tableLayoutPanel18.PerformLayout(); + dbLayoutPanel1.ResumeLayout(false); + dbLayoutPanel1.PerformLayout(); + dbLayoutPanel2.ResumeLayout(false); + dbLayoutPanel2.PerformLayout(); + dbLayoutPanel3.ResumeLayout(false); + dbLayoutPanel3.PerformLayout(); + dbLayoutPanel4.ResumeLayout(false); + dbLayoutPanel4.PerformLayout(); + dbLayoutPanel5.ResumeLayout(false); + dbLayoutPanel5.PerformLayout(); + dbLayoutPanel8.ResumeLayout(false); + dbLayoutPanel8.PerformLayout(); + dbLayoutPanel9.ResumeLayout(false); + dbLayoutPanel9.PerformLayout(); + dbLayoutPanel10.ResumeLayout(false); + tabDeepStack.ResumeLayout(false); + tabDeepStack.PerformLayout(); + groupBox11.ResumeLayout(false); + groupBox11.PerformLayout(); + groupBox10.ResumeLayout(false); + groupBox10.PerformLayout(); + groupBox1.ResumeLayout(false); + groupBox2.ResumeLayout(false); + groupBox4.ResumeLayout(false); + groupBox4.PerformLayout(); + groupBox3.ResumeLayout(false); + groupBox3.PerformLayout(); + groupBoxCustomModel.ResumeLayout(false); + groupBoxCustomModel.PerformLayout(); + tabLog.ResumeLayout(false); + tabLog.PerformLayout(); + toolStrip2.ResumeLayout(false); + toolStrip2.PerformLayout(); + groupBox7.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)folv_log).EndInit(); + statusStrip1.ResumeLayout(false); + statusStrip1.PerformLayout(); + ResumeLayout(false); + PerformLayout(); } #endregion private System.Windows.Forms.NotifyIcon notifyIcon; - private DBLayoutPanel tableLayoutPanel1; private System.Windows.Forms.TabControl tabControl1; private System.Windows.Forms.TabPage tabHistory; private System.Windows.Forms.TabPage tabCameras; @@ -1927,19 +3776,12 @@ private void InitializeComponent() private System.Windows.Forms.Label lbl_deepstackurl; private System.Windows.Forms.Label lbl_input; private System.Windows.Forms.Button BtnSettingsSave; - private DBLayoutPanel tableLayoutPanel2; - private DBLayoutPanel tableLayoutPanel3; private System.Windows.Forms.Button btnCameraAdd; private DBLayoutPanel tableLayoutPanel7; private System.Windows.Forms.Label label1; private System.Windows.Forms.Label lblPrefix; private System.Windows.Forms.Label lblRelevantObjects; - private DBLayoutPanel tableLayoutPanel9; - private DBLayoutPanel tableLayoutPanel10; - private System.Windows.Forms.TextBox tbTriggerUrl; - private System.Windows.Forms.CheckBox cb_telegram; private System.Windows.Forms.Label lblName; - private System.Windows.Forms.ListView list2; private DBLayoutPanel tableLayoutPanel11; private System.Windows.Forms.Button btnCameraSave; private System.Windows.Forms.Button btnCameraDel; @@ -1951,10 +3793,7 @@ private void InitializeComponent() private System.Windows.Forms.CheckBox cb_enabled; private System.Windows.Forms.Label lbl_telegram_token; private System.Windows.Forms.Label lbl_telegram_chatid; - private System.Windows.Forms.TextBox tb_telegram_token; - private System.Windows.Forms.TextBox tb_telegram_chatid; private System.Windows.Forms.TabPage tabOverview; - private System.Windows.Forms.Label lblTriggerUrl; private System.Windows.Forms.Label lbl_camstats; private DBLayoutPanel tableLayoutPanel15; private System.Windows.Forms.PictureBox pictureBox2; @@ -1967,69 +3806,242 @@ private void InitializeComponent() private System.Windows.Forms.ComboBox comboBox1; private System.Windows.Forms.Label lbl_version; private System.Windows.Forms.Label lbl_errors; - private System.Windows.Forms.CheckBox cb_log; - private System.Windows.Forms.TextBox tbDeepstackUrl; private DBLayoutPanel tableLayoutPanel18; - private System.Windows.Forms.TextBox tbInput; private System.Windows.Forms.Button btn_input_path; - private DBLayoutPanel tableLayoutPanel20; - private System.Windows.Forms.Label label5; - private System.Windows.Forms.TextBox tb_cooldown; - private System.Windows.Forms.Label label6; - private DBLayoutPanel tableLayoutPanel21; private System.Windows.Forms.PictureBox pictureBox1; private System.Windows.Forms.Label lbl_info; - private System.Windows.Forms.SplitContainer splitContainer1; - private DBLayoutPanel tableLayoutPanel19; - private System.Windows.Forms.CheckBox cb_showFilters; - private System.Windows.Forms.ListView list1; - private System.Windows.Forms.Panel panel1; - private System.Windows.Forms.CheckBox cb_filter_nosuccess; - private System.Windows.Forms.CheckBox cb_filter_success; - private System.Windows.Forms.CheckBox cb_filter_person; - private System.Windows.Forms.CheckBox cb_filter_vehicle; - private System.Windows.Forms.CheckBox cb_filter_animal; - private System.Windows.Forms.ComboBox comboBox_filter_camera; private DBLayoutPanel tableLayoutPanel23; private System.Windows.Forms.Label label8; private System.Windows.Forms.DataVisualization.Charting.Chart chart_confidence; private System.Windows.Forms.DataVisualization.Charting.Chart timeline; private System.Windows.Forms.Label label7; - private DBLayoutPanel tableLayoutPanel8; - private System.Windows.Forms.CheckBox cb_person; - private System.Windows.Forms.CheckBox cb_bicycle; - private System.Windows.Forms.CheckBox cb_motorcycle; - private System.Windows.Forms.CheckBox cb_bear; - private System.Windows.Forms.CheckBox cb_cow; - private System.Windows.Forms.CheckBox cb_sheep; - private System.Windows.Forms.CheckBox cb_horse; - private System.Windows.Forms.CheckBox cb_bird; - private System.Windows.Forms.CheckBox cb_dog; - private System.Windows.Forms.CheckBox cb_cat; - private System.Windows.Forms.CheckBox cb_airplane; - private System.Windows.Forms.CheckBox cb_boat; - private System.Windows.Forms.CheckBox cb_bus; - private System.Windows.Forms.CheckBox cb_truck; - private System.Windows.Forms.CheckBox cb_car; - private System.Windows.Forms.Label lbl_threshold; - private DBLayoutPanel tableLayoutPanel24; - private System.Windows.Forms.Label lbl_threshold_lower; - private System.Windows.Forms.TextBox tb_threshold_upper; - private System.Windows.Forms.Label lbl_threshold_upper; - private System.Windows.Forms.TextBox tb_threshold_lower; - private System.Windows.Forms.Label label9; - private System.Windows.Forms.Label label10; private System.Windows.Forms.Label label12; - private System.Windows.Forms.CheckBox cb_send_errors; - private System.Windows.Forms.Label label4; - private DBLayoutPanel tableLayoutPanel25; - private System.Windows.Forms.Button btn_open_log; - private DBLayoutPanel tableLayoutPanel22; - private System.Windows.Forms.CheckBox cb_showObjects; - private System.Windows.Forms.CheckBox cb_showMask; + private System.Windows.Forms.CheckBox cb_send_telegram_errors; private System.Windows.Forms.Label lbl_objects; private DBLayoutPanel tableLayoutPanel6; private DBLayoutPanel tableLayoutPanel14; + private System.Windows.Forms.TabPage tabDeepStack; + private System.Windows.Forms.CheckBox Chk_AutoStart; + private System.Windows.Forms.Button Btn_Stop; + private System.Windows.Forms.Button Btn_Start; + private System.Windows.Forms.GroupBox groupBox3; + private System.Windows.Forms.TextBox Txt_Port; + private System.Windows.Forms.GroupBox groupBox2; + private System.Windows.Forms.RadioButton RB_High; + private System.Windows.Forms.RadioButton RB_Medium; + private System.Windows.Forms.RadioButton RB_Low; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.CheckBox Chk_DetectionAPI; + private System.Windows.Forms.CheckBox Chk_FaceAPI; + private System.Windows.Forms.CheckBox Chk_SceneAPI; + private System.Windows.Forms.TextBox Txt_DeepStackInstallFolder; + private System.Windows.Forms.Label label11; + private System.Windows.Forms.GroupBox groupBox4; + private System.Windows.Forms.Label Lbl_BlueStackRunning; + private System.Windows.Forms.Button Btn_Save; + private System.Windows.Forms.TabPage tabLog; + private System.Windows.Forms.ComboBox cmbInput; + private System.Windows.Forms.CheckBox cbStartWithWindows; + private System.Windows.Forms.CheckBox cb_inputpathsubfolders; + private System.Windows.Forms.Label label14; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel26; + private System.Windows.Forms.ComboBox cmbcaminput; + private System.Windows.Forms.CheckBox cb_monitorCamInputfolder; + private System.Windows.Forms.Button button2; + private System.Windows.Forms.CheckBox Chk_DSDebug; + private System.Windows.Forms.ToolTip toolTip1; + private System.Windows.Forms.CheckBox chk_HighPriority; + private System.Windows.Forms.Label label15; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel27; + protected internal System.Windows.Forms.CheckBox cb_masking_enabled; + private System.Windows.Forms.Button BtnDynamicMaskingSettings; + private System.Windows.Forms.Button btnDetails; + private System.Windows.Forms.Label lblQueue; + private System.Windows.Forms.GroupBox groupBox7; + private System.Windows.Forms.Button btnCustomMask; + private System.Windows.Forms.Label lblDrawMask; + private System.Windows.Forms.Button btnActions; + private DBLayoutPanel dbLayoutPanel1; + private System.Windows.Forms.CheckBox cb_DeepStackURLsQueued; + private DBLayoutPanel dbLayoutPanel2; + private System.Windows.Forms.TextBox tb_telegram_cooldown; + private System.Windows.Forms.Label label5; + private System.Windows.Forms.TextBox tb_telegram_chatid; + private System.Windows.Forms.TextBox tb_telegram_token; + private System.Windows.Forms.SplitContainer splitContainer2; + private BrightIdeasSoftware.FastObjectListView folv_history; + private System.Windows.Forms.Button btn_resetstats; + private System.Windows.Forms.ImageList HistoryImageList; + private System.Windows.Forms.Timer HistoryUpdateListTimer; + private System.Windows.Forms.StatusStrip statusStrip1; + private System.Windows.Forms.ToolStripStatusLabel toolStripStatusLabelHistoryItems; + private System.Windows.Forms.ToolStripStatusLabel toolStripStatusLabel1; + private System.Windows.Forms.ToolStripStatusLabel toolStripStatusErrors; + private System.Windows.Forms.ToolStrip toolStrip1; + private System.Windows.Forms.ToolStripComboBox comboBox_filter_camera; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator1; + private System.Windows.Forms.ToolStripDropDownButton toolStripDropDownButtonFilters; + private System.Windows.Forms.ToolStripMenuItem cb_filter_success; + private System.Windows.Forms.ToolStripMenuItem cb_filter_nosuccess; + private System.Windows.Forms.ToolStripMenuItem cb_filter_person; + private System.Windows.Forms.ToolStripMenuItem cb_filter_animal; + private System.Windows.Forms.ToolStripMenuItem cb_filter_vehicle; + private System.Windows.Forms.ToolStripMenuItem cb_filter_skipped; + private System.Windows.Forms.ToolStripMenuItem cb_filter_masked; + private System.Windows.Forms.ToolStripDropDownButton toolStripDropDownButtonOptions; + private System.Windows.Forms.ToolStripMenuItem cb_showMask; + private System.Windows.Forms.ToolStripMenuItem cb_showObjects; + private System.Windows.Forms.ToolStripMenuItem cb_follow; + private System.Windows.Forms.ToolStripMenuItem automaticallyRefreshToolStripMenuItem; + private System.Windows.Forms.ToolStripStatusLabel toolStripStatusLabelInfo; + private System.Windows.Forms.ToolStripButton toolStripButtonDetails; + private System.Windows.Forms.GroupBox groupBox8; + private System.Windows.Forms.ContextMenuStrip contextMenuStripHistory; + private System.Windows.Forms.ToolStripMenuItem testDetectionAgainToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem detailsToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem refreshToolStripMenuItem; + private System.Windows.Forms.ToolStripButton toolStripButtonMaskDetails; + private System.Windows.Forms.ToolStripMenuItem dynamicMaskDetailsToolStripMenuItem; + private System.Windows.Forms.ToolStripButton toolStripButtonEditImageMask; + private DBLayoutPanel dbLayoutPanel3; + private System.Windows.Forms.ToolStripMenuItem storeFalseAlertsToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem storeMaskedAlertsToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem showOnlyRelevantObjectsToolStripMenuItem; + private System.Windows.Forms.Button btnSaveTo; + private BrightIdeasSoftware.FastObjectListView folv_log; + private System.Windows.Forms.Timer LogUpdateListTimer; + private System.Windows.Forms.ToolStripMenuItem locateInLogToolStripMenuItem; + private System.Windows.Forms.ToolStrip toolStrip2; + private System.Windows.Forms.ToolStripLabel toolStripLabel1; + private System.Windows.Forms.ToolStripComboBox ToolStripComboBoxSearch; + private System.Windows.Forms.ToolStripDropDownButton toolStripDropDownButton1; + private System.Windows.Forms.ToolStripMenuItem mnu_Filter; + private System.Windows.Forms.ToolStripMenuItem mnu_Highlight; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator3; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator; + private System.Windows.Forms.ToolStripDropDownButton toolStripDropDownButtonSettings; + private System.Windows.Forms.ToolStripMenuItem Chk_AutoScroll; + private System.Windows.Forms.ToolStripMenuItem clearRecentErrorsToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem toolStripMenuItemLogLevel; + private System.Windows.Forms.ToolStripMenuItem mnu_log_filter_off; + private System.Windows.Forms.ToolStripMenuItem mnu_log_filter_fatal; + private System.Windows.Forms.ToolStripMenuItem mnu_log_filter_error; + private System.Windows.Forms.ToolStripMenuItem mnu_log_filter_warn; + private System.Windows.Forms.ToolStripMenuItem mnu_log_filter_info; + private System.Windows.Forms.ToolStripMenuItem mnu_log_filter_debug; + private System.Windows.Forms.ToolStripMenuItem mnu_log_filter_trace; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator4; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator2; + private System.Windows.Forms.ToolStripButton openToolStripButton; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator7; + private System.Windows.Forms.ToolStripButton toolStripButtonReload; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator5; + private System.Windows.Forms.ToolStripButton toolStripButtonPauseLog; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator6; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator8; + private System.Windows.Forms.ToolStripButton toolStripButtonLoad; + private System.Windows.Forms.ToolStripButton chk_filterErrors; + private System.Windows.Forms.ToolStripButton chk_filterErrorsAll; + private System.Windows.Forms.ToolStripProgressBar toolStripProgressBar1; + private System.Windows.Forms.Label label4; + private DBLayoutPanel dbLayoutPanel4; + private System.Windows.Forms.Label label6; + private System.Windows.Forms.TextBox tb_username; + private System.Windows.Forms.TextBox tb_password; + private System.Windows.Forms.Label label16; + private System.Windows.Forms.Label label17; + private System.Windows.Forms.Label label18; + private DBLayoutPanel dbLayoutPanel5; + private System.Windows.Forms.TextBox tb_BlueIrisServer; + private System.Windows.Forms.Label label19; + private System.Windows.Forms.ToolStripMenuItem manuallyAddImagesToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem restrictThresholdAtSourceToolStripMenuItem; + private System.Windows.Forms.Button button1; + private System.Windows.Forms.Label label21; + private System.Windows.Forms.TextBox tb_camera_telegram_chatid; + private System.Windows.Forms.Label lbl_Deepstackversion; + private System.Windows.Forms.Label lbl_deepstackname; + private System.Windows.Forms.Label label23; + private System.Windows.Forms.Label label22; + private System.Windows.Forms.GroupBox groupBoxCustomModel; + private System.Windows.Forms.TextBox Txt_CustomModelPath; + private System.Windows.Forms.Label lbl_DeepstackType; + private System.Windows.Forms.Label label24; + private System.Windows.Forms.Label label25; + private DBLayoutPanel dbLayoutPanel6; + private System.Windows.Forms.TextBox tbBiCamName; + private System.Windows.Forms.Label label26; + private DBLayoutPanel dbLayoutPanel7; + private System.Windows.Forms.TextBox tbCustomMaskFile; + private System.Windows.Forms.Label label27; + private System.Windows.Forms.GroupBox groupBox10; + private System.Windows.Forms.TextBox tb_DeepstackCommandLine; + private System.Windows.Forms.LinkLabel linkLabel1; + private System.Windows.Forms.GroupBox groupBox11; + private System.Windows.Forms.TextBox tb_DeepStackURLs; + private System.Windows.Forms.Button Btn_DeepstackReset; + private System.Windows.Forms.Button Btn_ViewLog; + private System.Windows.Forms.Label label28; + private System.Windows.Forms.Label label29; + private DBLayoutPanel dbLayoutPanel8; + private System.Windows.Forms.TextBox tb_Pushover_Cooldown; + private System.Windows.Forms.TextBox tb_Pushover_APIKey; + private System.Windows.Forms.Label label31; + private System.Windows.Forms.Label label30; + private System.Windows.Forms.TextBox tb_Pushover_UserKey; + private System.Windows.Forms.CheckBox chk_stopbeforestart; + private System.Windows.Forms.CheckBox cb_send_pushover_errors; + private DBLayoutPanel dbLayoutPanel9; + private System.Windows.Forms.CheckBox cbMinimizeToTray; + private System.Windows.Forms.Label label13; + private System.Windows.Forms.Label label32; + private System.Windows.Forms.TextBox txt_DeepstackRestartFailCount; + private System.Windows.Forms.CheckBox Chk_AutoReStart; + private System.Windows.Forms.Label label34; + private System.Windows.Forms.Label label33; + private System.Windows.Forms.TextBox txt_DeepstackNoMoreOftenThanMins; + private System.Windows.Forms.TextBox Txt_CustomModelName; + private System.Windows.Forms.Label label36; + private System.Windows.Forms.Label label35; + private System.Windows.Forms.Label label37; + private System.Windows.Forms.TextBox Txt_CustomModelPort; + private System.Windows.Forms.Label label38; + private System.Windows.Forms.CheckBox Chk_CustomModelAPI; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator9; + private System.Windows.Forms.ToolStripButton toolStripButtonEditURL; + private System.Windows.Forms.ColorDialog colorDialog1; + private DBLayoutPanel dbLayoutPanel10; + private System.Windows.Forms.Button button3; + private System.Windows.Forms.ToolStripMenuItem viewImageToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem jumpToImageToolStripMenuItem; + private System.Windows.Forms.Label label39; + private System.Windows.Forms.Button BtnPredictionSize; + private System.Windows.Forms.SplitContainer splitContainer1; + private BrightIdeasSoftware.FastObjectListView FOLV_Cameras; + private System.Windows.Forms.ImageList CameraImageList; + private DBLayoutPanel dbLayoutPanel11; + private System.Windows.Forms.Button BtnRelevantObjects; + private System.Windows.Forms.Label lbl_RelevantObjects; + private DBLayoutPanel dbLayoutPanel12; + private System.Windows.Forms.Label Lbl_PredictionTolerances; + private DBLayoutPanel dbLayoutPanel13; + private System.Windows.Forms.Label Lbl_Actions; + private System.Windows.Forms.ToolStripContainer toolStripContainer1; + private System.Windows.Forms.ToolStripButton toolStripButtonAdjustAnno; + private System.Windows.Forms.Button btnPause; + private System.Windows.Forms.SplitContainer splitContainer3; + private System.Windows.Forms.PictureBox pictureBoxCamera; + private System.Windows.Forms.Label lbl_blueirisserver; + private System.Windows.Forms.Button bt_CheckUpdates; + private System.Windows.Forms.CheckBox chk_AutoAdd; + private System.Windows.Forms.ToolStripMenuItem mergeDuplicatePredictionsToolStripMenuItem; + private System.Windows.Forms.Label label9; + private System.Windows.Forms.TextBox Txt_CustomModelMode; + private System.Windows.Forms.ContextMenuStrip TraycontextMenuStrip; + private System.Windows.Forms.ToolStripMenuItem exitToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem pauseToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem pauseAllToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem resumeAllToolStripMenuItem; } } diff --git a/src/UI/Shell.Designer.cs.new b/src/UI/Shell.Designer.cs.new new file mode 100644 index 00000000..ce6689ae --- /dev/null +++ b/src/UI/Shell.Designer.cs.new @@ -0,0 +1,1918 @@ +using System.IO; +namespace AITool +{ + partial class Shell + { + /// + /// Erforderliche Designervariable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Verwendete Ressourcen bereinigen. + /// + /// True, wenn verwaltete Ressourcen gelöscht werden sollen; andernfalls False. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Vom Windows Form-Designer generierter Code + + /// + /// Erforderliche Methode für die Designerunterstützung. + /// Der Inhalt der Methode darf nicht mit dem Code-Editor geändert werden. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Shell)); + System.Windows.Forms.DataVisualization.Charting.ChartArea chartArea1 = new System.Windows.Forms.DataVisualization.Charting.ChartArea(); + System.Windows.Forms.DataVisualization.Charting.Series series1 = new System.Windows.Forms.DataVisualization.Charting.Series(); + System.Windows.Forms.DataVisualization.Charting.Series series2 = new System.Windows.Forms.DataVisualization.Charting.Series(); + System.Windows.Forms.DataVisualization.Charting.ChartArea chartArea2 = new System.Windows.Forms.DataVisualization.Charting.ChartArea(); + System.Windows.Forms.DataVisualization.Charting.Series series3 = new System.Windows.Forms.DataVisualization.Charting.Series(); + System.Windows.Forms.DataVisualization.Charting.Series series4 = new System.Windows.Forms.DataVisualization.Charting.Series(); + System.Windows.Forms.DataVisualization.Charting.Series series5 = new System.Windows.Forms.DataVisualization.Charting.Series(); + System.Windows.Forms.DataVisualization.Charting.Series series6 = new System.Windows.Forms.DataVisualization.Charting.Series(); + System.Windows.Forms.DataVisualization.Charting.ChartArea chartArea3 = new System.Windows.Forms.DataVisualization.Charting.ChartArea(); + System.Windows.Forms.DataVisualization.Charting.Legend legend1 = new System.Windows.Forms.DataVisualization.Charting.Legend(); + System.Windows.Forms.DataVisualization.Charting.Series series7 = new System.Windows.Forms.DataVisualization.Charting.Series(); + System.Windows.Forms.DataVisualization.Charting.DataPoint dataPoint1 = new System.Windows.Forms.DataVisualization.Charting.DataPoint(1D, 30D); + System.Windows.Forms.DataVisualization.Charting.DataPoint dataPoint2 = new System.Windows.Forms.DataVisualization.Charting.DataPoint(1D, 30D); + System.Windows.Forms.DataVisualization.Charting.DataPoint dataPoint3 = new System.Windows.Forms.DataVisualization.Charting.DataPoint(1D, 30D); + System.Windows.Forms.DataVisualization.Charting.Title title1 = new System.Windows.Forms.DataVisualization.Charting.Title(); + this.notifyIcon = new System.Windows.Forms.NotifyIcon(this.components); + this.tabControl1 = new System.Windows.Forms.TabControl(); + this.tabOverview = new System.Windows.Forms.TabPage(); + this.tabStats = new System.Windows.Forms.TabPage(); + this.tabHistory = new System.Windows.Forms.TabPage(); + this.tabCameras = new System.Windows.Forms.TabPage(); + this.tabSettings = new System.Windows.Forms.TabPage(); + this.tabDeepStack = new System.Windows.Forms.TabPage(); + this.Lbl_BlueStackRunning = new System.Windows.Forms.Label(); + this.Btn_Save = new System.Windows.Forms.Button(); + this.label11 = new System.Windows.Forms.Label(); + this.groupBox6 = new System.Windows.Forms.GroupBox(); + this.Txt_APIKey = new System.Windows.Forms.TextBox(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.Chk_DetectionAPI = new System.Windows.Forms.CheckBox(); + this.Chk_FaceAPI = new System.Windows.Forms.CheckBox(); + this.Chk_SceneAPI = new System.Windows.Forms.CheckBox(); + this.groupBox5 = new System.Windows.Forms.GroupBox(); + this.Txt_AdminKey = new System.Windows.Forms.TextBox(); + this.groupBox2 = new System.Windows.Forms.GroupBox(); + this.RB_High = new System.Windows.Forms.RadioButton(); + this.RB_Medium = new System.Windows.Forms.RadioButton(); + this.RB_Low = new System.Windows.Forms.RadioButton(); + this.groupBox4 = new System.Windows.Forms.GroupBox(); + this.Txt_DeepStackInstallFolder = new System.Windows.Forms.TextBox(); + this.groupBox3 = new System.Windows.Forms.GroupBox(); + this.Txt_Port = new System.Windows.Forms.TextBox(); + this.Chk_AutoStart = new System.Windows.Forms.CheckBox(); + this.Btn_Start = new System.Windows.Forms.Button(); + this.Btn_Stop = new System.Windows.Forms.Button(); + this.tabLog = new System.Windows.Forms.TabPage(); + this.btnStopscroll = new System.Windows.Forms.Button(); + this.btnViewLog = new System.Windows.Forms.Button(); + this.RTF_Log = new System.Windows.Forms.RichTextBox(); + this.pictureBox2 = new System.Windows.Forms.PictureBox(); + this.label2 = new System.Windows.Forms.Label(); + this.label3 = new System.Windows.Forms.Label(); + this.lbl_version = new System.Windows.Forms.Label(); + this.lbl_errors = new System.Windows.Forms.Label(); + this.lbl_info = new System.Windows.Forms.Label(); + this.label8 = new System.Windows.Forms.Label(); + this.chart_confidence = new System.Windows.Forms.DataVisualization.Charting.Chart(); + this.timeline = new System.Windows.Forms.DataVisualization.Charting.Chart(); + this.label7 = new System.Windows.Forms.Label(); + this.chart1 = new System.Windows.Forms.DataVisualization.Charting.Chart(); + this.comboBox1 = new System.Windows.Forms.ComboBox(); + this.pictureBox1 = new System.Windows.Forms.PictureBox(); + this.cb_showObjects = new System.Windows.Forms.CheckBox(); + this.cb_showMask = new System.Windows.Forms.CheckBox(); + this.lbl_objects = new System.Windows.Forms.Label(); + this.splitContainer1 = new System.Windows.Forms.SplitContainer(); + this.panel1 = new System.Windows.Forms.Panel(); + this.comboBox_filter_camera = new System.Windows.Forms.ComboBox(); + this.cb_filter_nosuccess = new System.Windows.Forms.CheckBox(); + this.cb_filter_success = new System.Windows.Forms.CheckBox(); + this.cb_filter_person = new System.Windows.Forms.CheckBox(); + this.cb_filter_vehicle = new System.Windows.Forms.CheckBox(); + this.cb_filter_animal = new System.Windows.Forms.CheckBox(); + this.cb_showFilters = new System.Windows.Forms.CheckBox(); + this.list1 = new System.Windows.Forms.ListView(); + this.list2 = new System.Windows.Forms.ListView(); + this.cb_person = new System.Windows.Forms.CheckBox(); + this.cb_bicycle = new System.Windows.Forms.CheckBox(); + this.cb_motorcycle = new System.Windows.Forms.CheckBox(); + this.cb_bear = new System.Windows.Forms.CheckBox(); + this.cb_cow = new System.Windows.Forms.CheckBox(); + this.cb_sheep = new System.Windows.Forms.CheckBox(); + this.cb_horse = new System.Windows.Forms.CheckBox(); + this.cb_bird = new System.Windows.Forms.CheckBox(); + this.cb_dog = new System.Windows.Forms.CheckBox(); + this.cb_cat = new System.Windows.Forms.CheckBox(); + this.cb_airplane = new System.Windows.Forms.CheckBox(); + this.cb_boat = new System.Windows.Forms.CheckBox(); + this.cb_bus = new System.Windows.Forms.CheckBox(); + this.cb_truck = new System.Windows.Forms.CheckBox(); + this.cb_car = new System.Windows.Forms.CheckBox(); + this.label1 = new System.Windows.Forms.Label(); + this.lblTriggerUrl = new System.Windows.Forms.Label(); + this.tbTriggerUrl = new System.Windows.Forms.TextBox(); + this.cb_telegram = new System.Windows.Forms.CheckBox(); + this.label5 = new System.Windows.Forms.Label(); + this.tb_cooldown = new System.Windows.Forms.TextBox(); + this.label6 = new System.Windows.Forms.Label(); + this.lblPrefix = new System.Windows.Forms.Label(); + this.lblName = new System.Windows.Forms.Label(); + this.tbPrefix = new System.Windows.Forms.TextBox(); + this.lbl_prefix = new System.Windows.Forms.Label(); + this.tbName = new System.Windows.Forms.TextBox(); + this.cb_enabled = new System.Windows.Forms.CheckBox(); + this.lblRelevantObjects = new System.Windows.Forms.Label(); + this.lbl_threshold = new System.Windows.Forms.Label(); + this.lbl_threshold_lower = new System.Windows.Forms.Label(); + this.tb_threshold_upper = new System.Windows.Forms.TextBox(); + this.lbl_threshold_upper = new System.Windows.Forms.Label(); + this.tb_threshold_lower = new System.Windows.Forms.TextBox(); + this.label9 = new System.Windows.Forms.Label(); + this.label10 = new System.Windows.Forms.Label(); + this.btnCameraAdd = new System.Windows.Forms.Button(); + this.btnCameraDel = new System.Windows.Forms.Button(); + this.btnCameraSave = new System.Windows.Forms.Button(); + this.lbl_camstats = new System.Windows.Forms.Label(); + this.label4 = new System.Windows.Forms.Label(); + this.btn_open_log = new System.Windows.Forms.Button(); + this.cb_log = new System.Windows.Forms.CheckBox(); + this.label12 = new System.Windows.Forms.Label(); + this.cb_send_errors = new System.Windows.Forms.CheckBox(); + this.lbl_deepstackurl = new System.Windows.Forms.Label(); + this.lbl_input = new System.Windows.Forms.Label(); + this.tbDeepstackUrl = new System.Windows.Forms.TextBox(); + this.lbl_telegram_token = new System.Windows.Forms.Label(); + this.lbl_telegram_chatid = new System.Windows.Forms.Label(); + this.tb_telegram_token = new System.Windows.Forms.TextBox(); + this.tb_telegram_chatid = new System.Windows.Forms.TextBox(); + this.btn_input_path = new System.Windows.Forms.Button(); + this.cmbInput = new System.Windows.Forms.ComboBox(); + this.label13 = new System.Windows.Forms.Label(); + this.cbStartWithWindows = new System.Windows.Forms.CheckBox(); + this.BtnSettingsSave = new System.Windows.Forms.Button(); + this.tabControl1.SuspendLayout(); + this.tabDeepStack.SuspendLayout(); + this.groupBox6.SuspendLayout(); + this.groupBox1.SuspendLayout(); + this.groupBox5.SuspendLayout(); + this.groupBox2.SuspendLayout(); + this.groupBox4.SuspendLayout(); + this.groupBox3.SuspendLayout(); + this.tabLog.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox2)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.chart_confidence)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.timeline)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.chart1)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); + this.splitContainer1.Panel2.SuspendLayout(); + this.splitContainer1.SuspendLayout(); + this.panel1.SuspendLayout(); + this.SuspendLayout(); + // + // notifyIcon + // + this.notifyIcon.BalloonTipText = "Running in the background"; + this.notifyIcon.BalloonTipTitle = "AI Tool"; + this.notifyIcon.Icon = ((System.Drawing.Icon)(resources.GetObject("notifyIcon.Icon"))); + this.notifyIcon.Text = "AI Tool"; + this.notifyIcon.MouseDoubleClick += new System.Windows.Forms.MouseEventHandler(this.notifyIcon_MouseDoubleClick); + // + // tabControl1 + // + this.tabControl1.Controls.Add(this.tabOverview); + this.tabControl1.Controls.Add(this.tabStats); + this.tabControl1.Controls.Add(this.tabHistory); + this.tabControl1.Controls.Add(this.tabCameras); + this.tabControl1.Controls.Add(this.tabSettings); + this.tabControl1.Controls.Add(this.tabDeepStack); + this.tabControl1.Controls.Add(this.tabLog); + this.tabControl1.Dock = System.Windows.Forms.DockStyle.Fill; + this.tabControl1.Location = new System.Drawing.Point(0, 0); + this.tabControl1.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.tabControl1.Name = "tabControl1"; + this.tabControl1.SelectedIndex = 0; + this.tabControl1.Size = new System.Drawing.Size(1422, 683); + this.tabControl1.TabIndex = 4; + this.tabControl1.SelectedIndexChanged += new System.EventHandler(this.tabControl1_SelectedIndexChanged); + this.tabControl1.MouseDown += new System.Windows.Forms.MouseEventHandler(this.tabControl1_MouseDown); + // + // tabOverview + // + this.tabOverview.Location = new System.Drawing.Point(4, 29); + this.tabOverview.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.tabOverview.Name = "tabOverview"; + this.tabOverview.Size = new System.Drawing.Size(1414, 650); + this.tabOverview.TabIndex = 4; + this.tabOverview.Text = "Overview"; + this.tabOverview.UseVisualStyleBackColor = true; + // + // tabStats + // + this.tabStats.Location = new System.Drawing.Point(4, 29); + this.tabStats.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.tabStats.Name = "tabStats"; + this.tabStats.Size = new System.Drawing.Size(1414, 650); + this.tabStats.TabIndex = 5; + this.tabStats.Text = "Stats"; + this.tabStats.UseVisualStyleBackColor = true; + // + // tabHistory + // + this.tabHistory.Location = new System.Drawing.Point(4, 29); + this.tabHistory.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.tabHistory.Name = "tabHistory"; + this.tabHistory.Padding = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.tabHistory.Size = new System.Drawing.Size(1414, 650); + this.tabHistory.TabIndex = 0; + this.tabHistory.Text = "History"; + this.tabHistory.UseVisualStyleBackColor = true; + // + // tabCameras + // + this.tabCameras.Location = new System.Drawing.Point(4, 29); + this.tabCameras.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.tabCameras.Name = "tabCameras"; + this.tabCameras.Padding = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.tabCameras.Size = new System.Drawing.Size(1414, 650); + this.tabCameras.TabIndex = 2; + this.tabCameras.Text = "Cameras"; + this.tabCameras.UseVisualStyleBackColor = true; + // + // tabSettings + // + this.tabSettings.Location = new System.Drawing.Point(4, 29); + this.tabSettings.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.tabSettings.Name = "tabSettings"; + this.tabSettings.Size = new System.Drawing.Size(1414, 650); + this.tabSettings.TabIndex = 3; + this.tabSettings.Text = "Settings"; + this.tabSettings.UseVisualStyleBackColor = true; + // + // tabDeepStack + // + this.tabDeepStack.Controls.Add(this.Lbl_BlueStackRunning); + this.tabDeepStack.Controls.Add(this.Btn_Save); + this.tabDeepStack.Controls.Add(this.label11); + this.tabDeepStack.Controls.Add(this.groupBox6); + this.tabDeepStack.Controls.Add(this.groupBox1); + this.tabDeepStack.Controls.Add(this.groupBox5); + this.tabDeepStack.Controls.Add(this.groupBox2); + this.tabDeepStack.Controls.Add(this.groupBox4); + this.tabDeepStack.Controls.Add(this.groupBox3); + this.tabDeepStack.Controls.Add(this.Chk_AutoStart); + this.tabDeepStack.Controls.Add(this.Btn_Start); + this.tabDeepStack.Controls.Add(this.Btn_Stop); + this.tabDeepStack.Location = new System.Drawing.Point(4, 29); + this.tabDeepStack.Name = "tabDeepStack"; + this.tabDeepStack.Size = new System.Drawing.Size(1414, 650); + this.tabDeepStack.TabIndex = 6; + this.tabDeepStack.Text = "DeepStack"; + this.tabDeepStack.UseVisualStyleBackColor = true; + // + // Lbl_BlueStackRunning + // + this.Lbl_BlueStackRunning.AutoSize = true; + this.Lbl_BlueStackRunning.Font = new System.Drawing.Font("Microsoft Sans Serif", 8F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.Lbl_BlueStackRunning.Location = new System.Drawing.Point(404, 507); + this.Lbl_BlueStackRunning.Name = "Lbl_BlueStackRunning"; + this.Lbl_BlueStackRunning.Size = new System.Drawing.Size(145, 20); + this.Lbl_BlueStackRunning.TabIndex = 13; + this.Lbl_BlueStackRunning.Text = "*NOT RUNNING*"; + // + // Btn_Save + // + this.Btn_Save.Location = new System.Drawing.Point(274, 493); + this.Btn_Save.Name = "Btn_Save"; + this.Btn_Save.Size = new System.Drawing.Size(109, 48); + this.Btn_Save.TabIndex = 12; + this.Btn_Save.Text = "Save"; + this.Btn_Save.UseVisualStyleBackColor = true; + this.Btn_Save.Click += new System.EventHandler(this.Btn_Save_Click); + // + // label11 + // + this.label11.ForeColor = System.Drawing.Color.RoyalBlue; + this.label11.Location = new System.Drawing.Point(8, 15); + this.label11.Name = "label11"; + this.label11.Size = new System.Drawing.Size(699, 51); + this.label11.TabIndex = 0; + this.label11.Text = "When the WINDOWS version of DeepStack will be running on the same machine as AI T" + + "ool, this tab can replace the DeepStack UI. (Deepstack.exe) - No control over Do" + + "cker version yet."; + // + // groupBox6 + // + this.groupBox6.Controls.Add(this.Txt_APIKey); + this.groupBox6.Location = new System.Drawing.Point(15, 213); + this.groupBox6.Name = "groupBox6"; + this.groupBox6.Size = new System.Drawing.Size(692, 65); + this.groupBox6.TabIndex = 11; + this.groupBox6.TabStop = false; + this.groupBox6.Text = "API Key (Optional)"; + // + // Txt_APIKey + // + this.Txt_APIKey.Location = new System.Drawing.Point(10, 25); + this.Txt_APIKey.Name = "Txt_APIKey"; + this.Txt_APIKey.Size = new System.Drawing.Size(608, 26); + this.Txt_APIKey.TabIndex = 0; + // + // groupBox1 + // + this.groupBox1.Controls.Add(this.Chk_DetectionAPI); + this.groupBox1.Controls.Add(this.Chk_FaceAPI); + this.groupBox1.Controls.Add(this.Chk_SceneAPI); + this.groupBox1.Location = new System.Drawing.Point(12, 296); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.Size = new System.Drawing.Size(223, 120); + this.groupBox1.TabIndex = 3; + this.groupBox1.TabStop = false; + this.groupBox1.Text = "API"; + // + // Chk_DetectionAPI + // + this.Chk_DetectionAPI.AutoSize = true; + this.Chk_DetectionAPI.Font = new System.Drawing.Font("Microsoft Sans Serif", 8F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.Chk_DetectionAPI.Location = new System.Drawing.Point(15, 81); + this.Chk_DetectionAPI.Name = "Chk_DetectionAPI"; + this.Chk_DetectionAPI.Size = new System.Drawing.Size(147, 24); + this.Chk_DetectionAPI.TabIndex = 2; + this.Chk_DetectionAPI.Text = "Detection API"; + this.Chk_DetectionAPI.UseVisualStyleBackColor = true; + // + // Chk_FaceAPI + // + this.Chk_FaceAPI.AutoSize = true; + this.Chk_FaceAPI.Location = new System.Drawing.Point(15, 53); + this.Chk_FaceAPI.Name = "Chk_FaceAPI"; + this.Chk_FaceAPI.Size = new System.Drawing.Size(101, 24); + this.Chk_FaceAPI.TabIndex = 1; + this.Chk_FaceAPI.Text = "Face API"; + this.Chk_FaceAPI.UseVisualStyleBackColor = true; + // + // Chk_SceneAPI + // + this.Chk_SceneAPI.AutoSize = true; + this.Chk_SceneAPI.Location = new System.Drawing.Point(15, 25); + this.Chk_SceneAPI.Name = "Chk_SceneAPI"; + this.Chk_SceneAPI.Size = new System.Drawing.Size(111, 24); + this.Chk_SceneAPI.TabIndex = 0; + this.Chk_SceneAPI.Text = "Scene API"; + this.Chk_SceneAPI.UseVisualStyleBackColor = true; + // + // groupBox5 + // + this.groupBox5.Controls.Add(this.Txt_AdminKey); + this.groupBox5.Location = new System.Drawing.Point(15, 140); + this.groupBox5.Name = "groupBox5"; + this.groupBox5.Size = new System.Drawing.Size(692, 65); + this.groupBox5.TabIndex = 10; + this.groupBox5.TabStop = false; + this.groupBox5.Text = "Admin Key (Optional)"; + // + // Txt_AdminKey + // + this.Txt_AdminKey.Location = new System.Drawing.Point(10, 23); + this.Txt_AdminKey.Name = "Txt_AdminKey"; + this.Txt_AdminKey.Size = new System.Drawing.Size(608, 26); + this.Txt_AdminKey.TabIndex = 0; + // + // groupBox2 + // + this.groupBox2.Controls.Add(this.RB_High); + this.groupBox2.Controls.Add(this.RB_Medium); + this.groupBox2.Controls.Add(this.RB_Low); + this.groupBox2.Location = new System.Drawing.Point(251, 296); + this.groupBox2.Name = "groupBox2"; + this.groupBox2.Size = new System.Drawing.Size(221, 120); + this.groupBox2.TabIndex = 4; + this.groupBox2.TabStop = false; + this.groupBox2.Text = "Mode"; + // + // RB_High + // + this.RB_High.AutoSize = true; + this.RB_High.Location = new System.Drawing.Point(16, 83); + this.RB_High.Name = "RB_High"; + this.RB_High.Size = new System.Drawing.Size(67, 24); + this.RB_High.TabIndex = 3; + this.RB_High.TabStop = true; + this.RB_High.Text = "High"; + this.RB_High.UseVisualStyleBackColor = true; + // + // RB_Medium + // + this.RB_Medium.AutoSize = true; + this.RB_Medium.Location = new System.Drawing.Point(16, 54); + this.RB_Medium.Name = "RB_Medium"; + this.RB_Medium.Size = new System.Drawing.Size(90, 24); + this.RB_Medium.TabIndex = 2; + this.RB_Medium.TabStop = true; + this.RB_Medium.Text = "Medium"; + this.RB_Medium.UseVisualStyleBackColor = true; + // + // RB_Low + // + this.RB_Low.AutoSize = true; + this.RB_Low.Location = new System.Drawing.Point(16, 25); + this.RB_Low.Name = "RB_Low"; + this.RB_Low.Size = new System.Drawing.Size(63, 24); + this.RB_Low.TabIndex = 1; + this.RB_Low.TabStop = true; + this.RB_Low.Text = "Low"; + this.RB_Low.UseVisualStyleBackColor = true; + // + // groupBox4 + // + this.groupBox4.Controls.Add(this.Txt_DeepStackInstallFolder); + this.groupBox4.Location = new System.Drawing.Point(15, 69); + this.groupBox4.Name = "groupBox4"; + this.groupBox4.Size = new System.Drawing.Size(692, 65); + this.groupBox4.TabIndex = 9; + this.groupBox4.TabStop = false; + this.groupBox4.Text = "DeepStack Install Folder"; + // + // Txt_DeepStackInstallFolder + // + this.Txt_DeepStackInstallFolder.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.Txt_DeepStackInstallFolder.Location = new System.Drawing.Point(10, 25); + this.Txt_DeepStackInstallFolder.Name = "Txt_DeepStackInstallFolder"; + this.Txt_DeepStackInstallFolder.ReadOnly = true; + this.Txt_DeepStackInstallFolder.Size = new System.Drawing.Size(674, 26); + this.Txt_DeepStackInstallFolder.TabIndex = 2; + // + // groupBox3 + // + this.groupBox3.Controls.Add(this.Txt_Port); + this.groupBox3.Location = new System.Drawing.Point(490, 296); + this.groupBox3.Name = "groupBox3"; + this.groupBox3.Size = new System.Drawing.Size(217, 120); + this.groupBox3.TabIndex = 5; + this.groupBox3.TabStop = false; + this.groupBox3.Text = "Port"; + // + // Txt_Port + // + this.Txt_Port.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.Txt_Port.Location = new System.Drawing.Point(14, 28); + this.Txt_Port.Name = "Txt_Port"; + this.Txt_Port.Size = new System.Drawing.Size(197, 26); + this.Txt_Port.TabIndex = 0; + // + // Chk_AutoStart + // + this.Chk_AutoStart.AutoSize = true; + this.Chk_AutoStart.Location = new System.Drawing.Point(18, 439); + this.Chk_AutoStart.Name = "Chk_AutoStart"; + this.Chk_AutoStart.Size = new System.Drawing.Size(252, 24); + this.Chk_AutoStart.TabIndex = 8; + this.Chk_AutoStart.Text = "Automatically Start DeepStack"; + this.Chk_AutoStart.UseVisualStyleBackColor = true; + // + // Btn_Start + // + this.Btn_Start.Location = new System.Drawing.Point(12, 493); + this.Btn_Start.Name = "Btn_Start"; + this.Btn_Start.Size = new System.Drawing.Size(109, 48); + this.Btn_Start.TabIndex = 6; + this.Btn_Start.Text = "Start"; + this.Btn_Start.UseVisualStyleBackColor = true; + this.Btn_Start.Click += new System.EventHandler(this.Btn_Start_Click); + // + // Btn_Stop + // + this.Btn_Stop.Location = new System.Drawing.Point(141, 493); + this.Btn_Stop.Name = "Btn_Stop"; + this.Btn_Stop.Size = new System.Drawing.Size(109, 48); + this.Btn_Stop.TabIndex = 7; + this.Btn_Stop.Text = "Stop"; + this.Btn_Stop.UseVisualStyleBackColor = true; + this.Btn_Stop.Click += new System.EventHandler(this.Btn_Stop_Click); + // + // tabLog + // + this.tabLog.Controls.Add(this.btnStopscroll); + this.tabLog.Controls.Add(this.btnViewLog); + this.tabLog.Controls.Add(this.RTF_Log); + this.tabLog.Location = new System.Drawing.Point(4, 29); + this.tabLog.Name = "tabLog"; + this.tabLog.Size = new System.Drawing.Size(1414, 650); + this.tabLog.TabIndex = 7; + this.tabLog.Text = "Log"; + this.tabLog.UseVisualStyleBackColor = true; + // + // btnStopscroll + // + this.btnStopscroll.Location = new System.Drawing.Point(114, 5); + this.btnStopscroll.Name = "btnStopscroll"; + this.btnStopscroll.Size = new System.Drawing.Size(125, 35); + this.btnStopscroll.TabIndex = 2; + this.btnStopscroll.Text = "Stop scrolling"; + this.btnStopscroll.UseVisualStyleBackColor = true; + this.btnStopscroll.Click += new System.EventHandler(this.btnStopscroll_Click); + // + // btnViewLog + // + this.btnViewLog.Location = new System.Drawing.Point(8, 5); + this.btnViewLog.Name = "btnViewLog"; + this.btnViewLog.Size = new System.Drawing.Size(100, 35); + this.btnViewLog.TabIndex = 1; + this.btnViewLog.Text = "Open log"; + this.btnViewLog.UseVisualStyleBackColor = true; + this.btnViewLog.Click += new System.EventHandler(this.btnViewLog_Click); + // + // RTF_Log + // + this.RTF_Log.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.RTF_Log.BackColor = System.Drawing.Color.RoyalBlue; + this.RTF_Log.Font = new System.Drawing.Font("Consolas", 8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.RTF_Log.ForeColor = System.Drawing.Color.White; + this.RTF_Log.Location = new System.Drawing.Point(0, 46); + this.RTF_Log.Name = "RTF_Log"; + this.RTF_Log.Size = new System.Drawing.Size(1414, 604); + this.RTF_Log.TabIndex = 0; + this.RTF_Log.Text = ""; + // + // pictureBox2 + // + this.pictureBox2.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.pictureBox2.BackgroundImageLayout = System.Windows.Forms.ImageLayout.None; + this.pictureBox2.Image = ((System.Drawing.Image)(resources.GetObject("pictureBox2.Image"))); + this.pictureBox2.Location = new System.Drawing.Point(4, 104); + this.pictureBox2.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.pictureBox2.Name = "pictureBox2"; + this.pictureBox2.Size = new System.Drawing.Size(1396, 191); + this.pictureBox2.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.pictureBox2.TabIndex = 4; + this.pictureBox2.TabStop = false; + // + // label2 + // + this.label2.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.label2.Font = new System.Drawing.Font("Segoe UI", 48F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.label2.ForeColor = System.Drawing.Color.Green; + this.label2.Location = new System.Drawing.Point(4, 306); + this.label2.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(1396, 142); + this.label2.TabIndex = 3; + this.label2.Text = "Running"; + this.label2.TextAlign = System.Drawing.ContentAlignment.TopCenter; + // + // label3 + // + this.label3.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.label3.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; + this.label3.Location = new System.Drawing.Point(90, 300); + this.label3.Margin = new System.Windows.Forms.Padding(90, 0, 90, 0); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(1224, 3); + this.label3.TabIndex = 5; + this.label3.Text = "label3"; + // + // lbl_version + // + this.lbl_version.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.lbl_version.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.lbl_version.Location = new System.Drawing.Point(4, 606); + this.lbl_version.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.lbl_version.Name = "lbl_version"; + this.lbl_version.Size = new System.Drawing.Size(1396, 32); + this.lbl_version.TabIndex = 6; + this.lbl_version.Text = "Version 1.67 preview 7"; + this.lbl_version.TextAlign = System.Drawing.ContentAlignment.BottomCenter; + // + // lbl_errors + // + this.lbl_errors.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.lbl_errors.Font = new System.Drawing.Font("Segoe UI Semibold", 20.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.lbl_errors.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(192)))), ((int)(((byte)(0)))), ((int)(((byte)(0))))); + this.lbl_errors.Location = new System.Drawing.Point(4, 488); + this.lbl_errors.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.lbl_errors.Name = "lbl_errors"; + this.lbl_errors.Size = new System.Drawing.Size(1396, 88); + this.lbl_errors.TabIndex = 7; + this.lbl_errors.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + this.lbl_errors.Visible = false; + this.lbl_errors.Click += new System.EventHandler(this.lbl_errors_Click); + // + // lbl_info + // + this.lbl_info.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.lbl_info.AutoSize = true; + this.lbl_info.Location = new System.Drawing.Point(4, 576); + this.lbl_info.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.lbl_info.Name = "lbl_info"; + this.lbl_info.Size = new System.Drawing.Size(1396, 30); + this.lbl_info.TabIndex = 8; + this.lbl_info.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // label8 + // + this.label8.AutoSize = true; + this.label8.Dock = System.Windows.Forms.DockStyle.Top; + this.label8.Font = new System.Drawing.Font("Segoe UI", 15.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.label8.Location = new System.Drawing.Point(4, 325); + this.label8.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.label8.Name = "label8"; + this.label8.Size = new System.Drawing.Size(974, 41); + this.label8.TabIndex = 9; + this.label8.Text = "Frequencies of alert result confidences"; + this.label8.TextAlign = System.Drawing.ContentAlignment.TopCenter; + // + // chart_confidence + // + this.chart_confidence.BackColor = System.Drawing.Color.Transparent; + this.chart_confidence.BorderlineColor = System.Drawing.Color.Transparent; + chartArea1.AxisX.Interval = 10D; + chartArea1.AxisX.MajorGrid.Interval = 6D; + chartArea1.AxisX.MajorGrid.LineColor = System.Drawing.Color.LightGray; + chartArea1.AxisX.MajorGrid.LineDashStyle = System.Windows.Forms.DataVisualization.Charting.ChartDashStyle.Dash; + chartArea1.AxisX.MajorTickMark.Interval = 1D; + chartArea1.AxisX.Maximum = 100D; + chartArea1.AxisX.Minimum = 0D; + chartArea1.AxisX.Title = "Alert confidence"; + chartArea1.AxisX.TitleFont = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + chartArea1.AxisY.MajorGrid.LineColor = System.Drawing.Color.LightGray; + chartArea1.AxisY.MajorGrid.LineDashStyle = System.Windows.Forms.DataVisualization.Charting.ChartDashStyle.Dot; + chartArea1.AxisY.Title = "Frequency"; + chartArea1.AxisY.TitleFont = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + chartArea1.BackColor = System.Drawing.Color.Transparent; + chartArea1.Name = "ChartArea1"; + this.chart_confidence.ChartAreas.Add(chartArea1); + this.chart_confidence.Dock = System.Windows.Forms.DockStyle.Fill; + this.chart_confidence.Location = new System.Drawing.Point(4, 376); + this.chart_confidence.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.chart_confidence.Name = "chart_confidence"; + series1.BorderWidth = 4; + series1.ChartArea = "ChartArea1"; + series1.ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.Spline; + series1.Color = System.Drawing.Color.Orange; + series1.Name = "no alert"; + series2.BorderWidth = 3; + series2.ChartArea = "ChartArea1"; + series2.ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.Spline; + series2.Color = System.Drawing.Color.Green; + series2.Legend = "Legend1"; + series2.Name = "alert"; + this.chart_confidence.Series.Add(series1); + this.chart_confidence.Series.Add(series2); + this.chart_confidence.Size = new System.Drawing.Size(974, 259); + this.chart_confidence.TabIndex = 8; + // + // timeline + // + this.timeline.BackColor = System.Drawing.Color.Transparent; + this.timeline.BorderlineColor = System.Drawing.Color.Transparent; + chartArea2.AxisX.Interval = 3D; + chartArea2.AxisX.MajorGrid.Interval = 6D; + chartArea2.AxisX.MajorGrid.LineColor = System.Drawing.Color.LightGray; + chartArea2.AxisX.MajorGrid.LineDashStyle = System.Windows.Forms.DataVisualization.Charting.ChartDashStyle.Dash; + chartArea2.AxisX.MajorTickMark.Interval = 1D; + chartArea2.AxisX.Maximum = 24D; + chartArea2.AxisX.Minimum = 0D; + chartArea2.AxisX.TitleFont = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + chartArea2.AxisY.MajorGrid.LineColor = System.Drawing.Color.LightGray; + chartArea2.AxisY.MajorGrid.LineDashStyle = System.Windows.Forms.DataVisualization.Charting.ChartDashStyle.Dot; + chartArea2.AxisY.Title = "Number"; + chartArea2.AxisY.TitleFont = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + chartArea2.BackColor = System.Drawing.Color.Transparent; + chartArea2.Name = "ChartArea1"; + this.timeline.ChartAreas.Add(chartArea2); + this.timeline.Dock = System.Windows.Forms.DockStyle.Fill; + this.timeline.Location = new System.Drawing.Point(4, 56); + this.timeline.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.timeline.Name = "timeline"; + series3.ChartArea = "ChartArea1"; + series3.ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.SplineArea; + series3.Color = System.Drawing.Color.Silver; + series3.Legend = "Legend1"; + series3.Name = "all"; + series4.BorderWidth = 3; + series4.ChartArea = "ChartArea1"; + series4.ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.Spline; + series4.Color = System.Drawing.Color.OrangeRed; + series4.Legend = "Legend1"; + series4.Name = "falses"; + series5.BorderWidth = 3; + series5.ChartArea = "ChartArea1"; + series5.ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.Spline; + series5.Color = System.Drawing.Color.Orange; + series5.Legend = "Legend1"; + series5.Name = "irrelevant"; + series6.BorderWidth = 4; + series6.ChartArea = "ChartArea1"; + series6.ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.Spline; + series6.Color = System.Drawing.Color.Green; + series6.Legend = "Legend1"; + series6.Name = "relevant"; + this.timeline.Series.Add(series3); + this.timeline.Series.Add(series4); + this.timeline.Series.Add(series5); + this.timeline.Series.Add(series6); + this.timeline.Size = new System.Drawing.Size(974, 259); + this.timeline.TabIndex = 6; + // + // label7 + // + this.label7.AutoSize = true; + this.label7.Dock = System.Windows.Forms.DockStyle.Top; + this.label7.Font = new System.Drawing.Font("Segoe UI", 15.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.label7.Location = new System.Drawing.Point(4, 5); + this.label7.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.label7.Name = "label7"; + this.label7.Size = new System.Drawing.Size(974, 41); + this.label7.TabIndex = 0; + this.label7.Text = "Timeline"; + this.label7.TextAlign = System.Drawing.ContentAlignment.TopCenter; + // + // chart1 + // + this.chart1.BackColor = System.Drawing.Color.Transparent; + this.chart1.BorderlineColor = System.Drawing.Color.Transparent; + chartArea3.Area3DStyle.Enable3D = true; + chartArea3.Area3DStyle.Inclination = 35; + chartArea3.Area3DStyle.LightStyle = System.Windows.Forms.DataVisualization.Charting.LightStyle.None; + chartArea3.BackColor = System.Drawing.Color.Transparent; + chartArea3.Name = "ChartArea1"; + this.chart1.ChartAreas.Add(chartArea3); + this.chart1.Dock = System.Windows.Forms.DockStyle.Fill; + legend1.Alignment = System.Drawing.StringAlignment.Center; + legend1.BackColor = System.Drawing.Color.Transparent; + legend1.Docking = System.Windows.Forms.DataVisualization.Charting.Docking.Bottom; + legend1.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + legend1.IsTextAutoFit = false; + legend1.LegendStyle = System.Windows.Forms.DataVisualization.Charting.LegendStyle.Column; + legend1.Name = "Legend1"; + this.chart1.Legends.Add(legend1); + this.chart1.Location = new System.Drawing.Point(4, 56); + this.chart1.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.chart1.Name = "chart1"; + series7.ChartArea = "ChartArea1"; + series7.ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.Pie; + series7.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + series7.IsValueShownAsLabel = true; + series7.Legend = "Legend1"; + series7.Name = "s1"; + dataPoint1.IsVisibleInLegend = true; + series7.Points.Add(dataPoint1); + series7.Points.Add(dataPoint2); + series7.Points.Add(dataPoint3); + this.chart1.Series.Add(series7); + this.chart1.Size = new System.Drawing.Size(408, 579); + this.chart1.TabIndex = 2; + this.chart1.Text = "chart1"; + title1.Font = new System.Drawing.Font("Segoe UI", 15.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + title1.Name = "Title1"; + title1.Text = "Input Rates"; + this.chart1.Titles.Add(title1); + // + // comboBox1 + // + this.comboBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.comboBox1.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.comboBox1.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.comboBox1.FormattingEnabled = true; + this.comboBox1.Location = new System.Drawing.Point(4, 5); + this.comboBox1.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.comboBox1.Name = "comboBox1"; + this.comboBox1.Size = new System.Drawing.Size(408, 36); + this.comboBox1.TabIndex = 3; + this.comboBox1.SelectedIndexChanged += new System.EventHandler(this.comboBox1_SelectedIndexChanged_1); + // + // pictureBox1 + // + this.pictureBox1.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Zoom; + this.pictureBox1.Dock = System.Windows.Forms.DockStyle.Fill; + this.pictureBox1.Location = new System.Drawing.Point(4, 75); + this.pictureBox1.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.pictureBox1.Name = "pictureBox1"; + this.pictureBox1.Size = new System.Drawing.Size(969, 550); + this.pictureBox1.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.pictureBox1.TabIndex = 6; + this.pictureBox1.TabStop = false; + this.pictureBox1.Paint += new System.Windows.Forms.PaintEventHandler(this.pictureBox1_Paint); + // + // cb_showObjects + // + this.cb_showObjects.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.cb_showObjects.Appearance = System.Windows.Forms.Appearance.Button; + this.cb_showObjects.AutoSize = true; + this.cb_showObjects.Checked = true; + this.cb_showObjects.CheckState = System.Windows.Forms.CheckState.Checked; + this.cb_showObjects.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.cb_showObjects.Location = new System.Drawing.Point(197, 11); + this.cb_showObjects.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.cb_showObjects.Name = "cb_showObjects"; + this.cb_showObjects.Size = new System.Drawing.Size(185, 38); + this.cb_showObjects.TabIndex = 12; + this.cb_showObjects.Text = "Show Objects"; + this.cb_showObjects.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + this.cb_showObjects.UseVisualStyleBackColor = true; + this.cb_showObjects.MouseUp += new System.Windows.Forms.MouseEventHandler(this.cb_showObjects_MouseUp); + // + // cb_showMask + // + this.cb_showMask.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.cb_showMask.Appearance = System.Windows.Forms.Appearance.Button; + this.cb_showMask.AutoSize = true; + this.cb_showMask.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.cb_showMask.Location = new System.Drawing.Point(4, 11); + this.cb_showMask.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.cb_showMask.Name = "cb_showMask"; + this.cb_showMask.Size = new System.Drawing.Size(185, 38); + this.cb_showMask.TabIndex = 11; + this.cb_showMask.Text = "Show Mask"; + this.cb_showMask.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + this.cb_showMask.UseVisualStyleBackColor = true; + this.cb_showMask.CheckedChanged += new System.EventHandler(this.cb_showMask_CheckedChanged); + // + // lbl_objects + // + this.lbl_objects.AutoSize = true; + this.lbl_objects.Dock = System.Windows.Forms.DockStyle.Fill; + this.lbl_objects.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.lbl_objects.Location = new System.Drawing.Point(390, 0); + this.lbl_objects.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.lbl_objects.Name = "lbl_objects"; + this.lbl_objects.Padding = new System.Windows.Forms.Padding(8, 0, 0, 0); + this.lbl_objects.Size = new System.Drawing.Size(575, 60); + this.lbl_objects.TabIndex = 14; + this.lbl_objects.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // splitContainer1 + // + this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill; + this.splitContainer1.Location = new System.Drawing.Point(4, 5); + this.splitContainer1.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.splitContainer1.Name = "splitContainer1"; + this.splitContainer1.Orientation = System.Windows.Forms.Orientation.Horizontal; + // + // splitContainer1.Panel2 + // + this.splitContainer1.Panel2.BackColor = System.Drawing.Color.Transparent; + this.splitContainer1.Panel2.Controls.Add(this.panel1); + this.splitContainer1.Panel2.Padding = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.splitContainer1.Size = new System.Drawing.Size(413, 630); + this.splitContainer1.SplitterDistance = 356; + this.splitContainer1.SplitterWidth = 6; + this.splitContainer1.TabIndex = 6; + // + // panel1 + // + this.panel1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this.panel1.Controls.Add(this.comboBox_filter_camera); + this.panel1.Controls.Add(this.cb_filter_nosuccess); + this.panel1.Controls.Add(this.cb_filter_success); + this.panel1.Controls.Add(this.cb_filter_person); + this.panel1.Controls.Add(this.cb_filter_vehicle); + this.panel1.Controls.Add(this.cb_filter_animal); + this.panel1.Dock = System.Windows.Forms.DockStyle.Fill; + this.panel1.Location = new System.Drawing.Point(4, 5); + this.panel1.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.panel1.Name = "panel1"; + this.panel1.Size = new System.Drawing.Size(405, 258); + this.panel1.TabIndex = 2; + // + // comboBox_filter_camera + // + this.comboBox_filter_camera.Dock = System.Windows.Forms.DockStyle.Top; + this.comboBox_filter_camera.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.comboBox_filter_camera.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.comboBox_filter_camera.FormattingEnabled = true; + this.comboBox_filter_camera.Location = new System.Drawing.Point(0, 0); + this.comboBox_filter_camera.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.comboBox_filter_camera.Name = "comboBox_filter_camera"; + this.comboBox_filter_camera.Size = new System.Drawing.Size(403, 36); + this.comboBox_filter_camera.TabIndex = 2; + this.comboBox_filter_camera.SelectedIndexChanged += new System.EventHandler(this.comboBox_filter_camera_SelectedIndexChanged); + // + // cb_filter_nosuccess + // + this.cb_filter_nosuccess.AutoSize = true; + this.cb_filter_nosuccess.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.cb_filter_nosuccess.Location = new System.Drawing.Point(9, 98); + this.cb_filter_nosuccess.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.cb_filter_nosuccess.Name = "cb_filter_nosuccess"; + this.cb_filter_nosuccess.Size = new System.Drawing.Size(272, 32); + this.cb_filter_nosuccess.TabIndex = 1; + this.cb_filter_nosuccess.Text = "only false / irrelevant alerts"; + this.cb_filter_nosuccess.UseVisualStyleBackColor = true; + this.cb_filter_nosuccess.CheckedChanged += new System.EventHandler(this.cb_filter_nosuccess_CheckedChanged); + // + // cb_filter_success + // + this.cb_filter_success.AutoSize = true; + this.cb_filter_success.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.cb_filter_success.Location = new System.Drawing.Point(9, 57); + this.cb_filter_success.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.cb_filter_success.Name = "cb_filter_success"; + this.cb_filter_success.Size = new System.Drawing.Size(203, 32); + this.cb_filter_success.TabIndex = 0; + this.cb_filter_success.Text = "only relevant alerts"; + this.cb_filter_success.UseVisualStyleBackColor = true; + this.cb_filter_success.CheckedChanged += new System.EventHandler(this.cb_filter_success_CheckedChanged); + // + // cb_filter_person + // + this.cb_filter_person.AutoSize = true; + this.cb_filter_person.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.cb_filter_person.Location = new System.Drawing.Point(9, 140); + this.cb_filter_person.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.cb_filter_person.Name = "cb_filter_person"; + this.cb_filter_person.Size = new System.Drawing.Size(236, 32); + this.cb_filter_person.TabIndex = 0; + this.cb_filter_person.Text = "only alerts with people"; + this.cb_filter_person.UseVisualStyleBackColor = true; + this.cb_filter_person.CheckedChanged += new System.EventHandler(this.cb_filter_person_CheckedChanged); + // + // cb_filter_vehicle + // + this.cb_filter_vehicle.AutoSize = true; + this.cb_filter_vehicle.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.cb_filter_vehicle.Location = new System.Drawing.Point(9, 182); + this.cb_filter_vehicle.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.cb_filter_vehicle.Name = "cb_filter_vehicle"; + this.cb_filter_vehicle.Size = new System.Drawing.Size(243, 32); + this.cb_filter_vehicle.TabIndex = 0; + this.cb_filter_vehicle.Text = "only alerts with vehicles"; + this.cb_filter_vehicle.UseVisualStyleBackColor = true; + this.cb_filter_vehicle.CheckedChanged += new System.EventHandler(this.cb_filter_vehicle_CheckedChanged); + // + // cb_filter_animal + // + this.cb_filter_animal.AutoSize = true; + this.cb_filter_animal.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.cb_filter_animal.Location = new System.Drawing.Point(9, 223); + this.cb_filter_animal.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.cb_filter_animal.Name = "cb_filter_animal"; + this.cb_filter_animal.Size = new System.Drawing.Size(241, 32); + this.cb_filter_animal.TabIndex = 0; + this.cb_filter_animal.Text = "only alerts with animals"; + this.cb_filter_animal.UseVisualStyleBackColor = true; + this.cb_filter_animal.CheckedChanged += new System.EventHandler(this.cb_filter_animal_CheckedChanged); + // + // cb_showFilters + // + this.cb_showFilters.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.cb_showFilters.Appearance = System.Windows.Forms.Appearance.Button; + this.cb_showFilters.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.cb_showFilters.Location = new System.Drawing.Point(4, 315); + this.cb_showFilters.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.cb_showFilters.MinimumSize = new System.Drawing.Size(0, 42); + this.cb_showFilters.Name = "cb_showFilters"; + this.cb_showFilters.Size = new System.Drawing.Size(405, 42); + this.cb_showFilters.TabIndex = 9; + this.cb_showFilters.Text = "˄ Filter"; + this.cb_showFilters.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + this.cb_showFilters.UseVisualStyleBackColor = true; + this.cb_showFilters.CheckedChanged += new System.EventHandler(this.cb_showFilters_CheckedChanged); + // + // list1 + // + this.list1.AllowColumnReorder = true; + this.list1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this.list1.Dock = System.Windows.Forms.DockStyle.Fill; + this.list1.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.list1.FullRowSelect = true; + this.list1.GridLines = true; + this.list1.HideSelection = false; + this.list1.Location = new System.Drawing.Point(4, 5); + this.list1.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.list1.Name = "list1"; + this.list1.Size = new System.Drawing.Size(405, 300); + this.list1.TabIndex = 3; + this.list1.UseCompatibleStateImageBehavior = false; + this.list1.View = System.Windows.Forms.View.Details; + this.list1.SelectedIndexChanged += new System.EventHandler(this.list1_SelectedIndexChanged); + // + // list2 + // + this.list2.Dock = System.Windows.Forms.DockStyle.Fill; + this.list2.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.list2.GridLines = true; + this.list2.HideSelection = false; + this.list2.Location = new System.Drawing.Point(4, 5); + this.list2.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.list2.Name = "list2"; + this.list2.Size = new System.Drawing.Size(251, 620); + this.list2.TabIndex = 1; + this.list2.UseCompatibleStateImageBehavior = false; + this.list2.View = System.Windows.Forms.View.Details; + this.list2.SelectedIndexChanged += new System.EventHandler(this.list2_SelectedIndexChanged); + this.list2.KeyDown += new System.Windows.Forms.KeyEventHandler(this.list2_KeyDown); + // + // cb_person + // + this.cb_person.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.cb_person.AutoSize = true; + this.cb_person.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.cb_person.Location = new System.Drawing.Point(30, 8); + this.cb_person.Margin = new System.Windows.Forms.Padding(30, 5, 4, 5); + this.cb_person.Name = "cb_person"; + this.cb_person.Size = new System.Drawing.Size(96, 32); + this.cb_person.TabIndex = 4; + this.cb_person.Text = "Person"; + this.cb_person.UseVisualStyleBackColor = true; + // + // cb_bicycle + // + this.cb_bicycle.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.cb_bicycle.AutoSize = true; + this.cb_bicycle.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.cb_bicycle.Location = new System.Drawing.Point(30, 56); + this.cb_bicycle.Margin = new System.Windows.Forms.Padding(30, 5, 4, 5); + this.cb_bicycle.Name = "cb_bicycle"; + this.cb_bicycle.Size = new System.Drawing.Size(97, 32); + this.cb_bicycle.TabIndex = 9; + this.cb_bicycle.Text = "Bicycle"; + this.cb_bicycle.UseVisualStyleBackColor = true; + // + // cb_motorcycle + // + this.cb_motorcycle.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.cb_motorcycle.AutoSize = true; + this.cb_motorcycle.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.cb_motorcycle.Location = new System.Drawing.Point(30, 105); + this.cb_motorcycle.Margin = new System.Windows.Forms.Padding(30, 5, 4, 5); + this.cb_motorcycle.Name = "cb_motorcycle"; + this.cb_motorcycle.Size = new System.Drawing.Size(137, 32); + this.cb_motorcycle.TabIndex = 14; + this.cb_motorcycle.Text = "Motorcycle"; + this.cb_motorcycle.UseVisualStyleBackColor = true; + // + // cb_bear + // + this.cb_bear.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.cb_bear.AutoSize = true; + this.cb_bear.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.cb_bear.Location = new System.Drawing.Point(758, 105); + this.cb_bear.Margin = new System.Windows.Forms.Padding(30, 5, 4, 5); + this.cb_bear.Name = "cb_bear"; + this.cb_bear.Size = new System.Drawing.Size(76, 32); + this.cb_bear.TabIndex = 18; + this.cb_bear.Text = "Bear"; + this.cb_bear.UseVisualStyleBackColor = true; + // + // cb_cow + // + this.cb_cow.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.cb_cow.AutoSize = true; + this.cb_cow.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.cb_cow.Location = new System.Drawing.Point(758, 56); + this.cb_cow.Margin = new System.Windows.Forms.Padding(30, 5, 4, 5); + this.cb_cow.Name = "cb_cow"; + this.cb_cow.Size = new System.Drawing.Size(76, 32); + this.cb_cow.TabIndex = 13; + this.cb_cow.Text = "Cow"; + this.cb_cow.UseVisualStyleBackColor = true; + // + // cb_sheep + // + this.cb_sheep.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.cb_sheep.AutoSize = true; + this.cb_sheep.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.cb_sheep.Location = new System.Drawing.Point(758, 8); + this.cb_sheep.Margin = new System.Windows.Forms.Padding(30, 5, 4, 5); + this.cb_sheep.Name = "cb_sheep"; + this.cb_sheep.Size = new System.Drawing.Size(92, 32); + this.cb_sheep.TabIndex = 8; + this.cb_sheep.Text = "Sheep"; + this.cb_sheep.UseVisualStyleBackColor = true; + // + // cb_horse + // + this.cb_horse.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.cb_horse.AutoSize = true; + this.cb_horse.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.cb_horse.Location = new System.Drawing.Point(576, 105); + this.cb_horse.Margin = new System.Windows.Forms.Padding(30, 5, 4, 5); + this.cb_horse.Name = "cb_horse"; + this.cb_horse.Size = new System.Drawing.Size(89, 32); + this.cb_horse.TabIndex = 17; + this.cb_horse.Text = "Horse"; + this.cb_horse.UseVisualStyleBackColor = true; + // + // cb_bird + // + this.cb_bird.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.cb_bird.AutoSize = true; + this.cb_bird.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.cb_bird.Location = new System.Drawing.Point(576, 56); + this.cb_bird.Margin = new System.Windows.Forms.Padding(30, 5, 4, 5); + this.cb_bird.Name = "cb_bird"; + this.cb_bird.Size = new System.Drawing.Size(73, 32); + this.cb_bird.TabIndex = 12; + this.cb_bird.Text = "Bird"; + this.cb_bird.UseVisualStyleBackColor = true; + // + // cb_dog + // + this.cb_dog.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.cb_dog.AutoSize = true; + this.cb_dog.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.cb_dog.Location = new System.Drawing.Point(576, 8); + this.cb_dog.Margin = new System.Windows.Forms.Padding(30, 5, 4, 5); + this.cb_dog.Name = "cb_dog"; + this.cb_dog.Size = new System.Drawing.Size(76, 32); + this.cb_dog.TabIndex = 7; + this.cb_dog.Text = "Dog"; + this.cb_dog.UseVisualStyleBackColor = true; + // + // cb_cat + // + this.cb_cat.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.cb_cat.AutoSize = true; + this.cb_cat.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.cb_cat.Location = new System.Drawing.Point(394, 105); + this.cb_cat.Margin = new System.Windows.Forms.Padding(30, 5, 4, 5); + this.cb_cat.Name = "cb_cat"; + this.cb_cat.Size = new System.Drawing.Size(67, 32); + this.cb_cat.TabIndex = 16; + this.cb_cat.Text = "Cat"; + this.cb_cat.UseVisualStyleBackColor = true; + // + // cb_airplane + // + this.cb_airplane.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.cb_airplane.AutoSize = true; + this.cb_airplane.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.cb_airplane.Location = new System.Drawing.Point(394, 56); + this.cb_airplane.Margin = new System.Windows.Forms.Padding(30, 5, 4, 5); + this.cb_airplane.Name = "cb_airplane"; + this.cb_airplane.Size = new System.Drawing.Size(111, 32); + this.cb_airplane.TabIndex = 11; + this.cb_airplane.Text = "Airplane"; + this.cb_airplane.UseVisualStyleBackColor = true; + // + // cb_boat + // + this.cb_boat.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.cb_boat.AutoSize = true; + this.cb_boat.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.cb_boat.Location = new System.Drawing.Point(394, 8); + this.cb_boat.Margin = new System.Windows.Forms.Padding(30, 5, 4, 5); + this.cb_boat.Name = "cb_boat"; + this.cb_boat.Size = new System.Drawing.Size(78, 32); + this.cb_boat.TabIndex = 6; + this.cb_boat.Text = "Boat"; + this.cb_boat.UseVisualStyleBackColor = true; + // + // cb_bus + // + this.cb_bus.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.cb_bus.AutoSize = true; + this.cb_bus.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.cb_bus.Location = new System.Drawing.Point(212, 105); + this.cb_bus.Margin = new System.Windows.Forms.Padding(30, 5, 4, 5); + this.cb_bus.Name = "cb_bus"; + this.cb_bus.Size = new System.Drawing.Size(68, 32); + this.cb_bus.TabIndex = 15; + this.cb_bus.Text = "Bus"; + this.cb_bus.UseVisualStyleBackColor = true; + // + // cb_truck + // + this.cb_truck.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.cb_truck.AutoSize = true; + this.cb_truck.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.cb_truck.Location = new System.Drawing.Point(212, 56); + this.cb_truck.Margin = new System.Windows.Forms.Padding(30, 5, 4, 5); + this.cb_truck.Name = "cb_truck"; + this.cb_truck.Size = new System.Drawing.Size(83, 32); + this.cb_truck.TabIndex = 10; + this.cb_truck.Text = "Truck"; + this.cb_truck.UseVisualStyleBackColor = true; + // + // cb_car + // + this.cb_car.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.cb_car.AutoSize = true; + this.cb_car.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.cb_car.Location = new System.Drawing.Point(212, 8); + this.cb_car.Margin = new System.Windows.Forms.Padding(30, 5, 4, 5); + this.cb_car.Name = "cb_car"; + this.cb_car.Size = new System.Drawing.Size(67, 32); + this.cb_car.TabIndex = 5; + this.cb_car.Text = "Car"; + this.cb_car.UseVisualStyleBackColor = true; + // + // label1 + // + this.label1.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.label1.AutoSize = true; + this.label1.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.label1.Location = new System.Drawing.Point(5, 392); + this.label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(77, 28); + this.label1.TabIndex = 9; + this.label1.Text = "Actions"; + // + // lblTriggerUrl + // + this.lblTriggerUrl.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.lblTriggerUrl.AutoSize = true; + this.lblTriggerUrl.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.lblTriggerUrl.Location = new System.Drawing.Point(30, 8); + this.lblTriggerUrl.Margin = new System.Windows.Forms.Padding(30, 0, 4, 0); + this.lblTriggerUrl.MinimumSize = new System.Drawing.Size(120, 0); + this.lblTriggerUrl.Name = "lblTriggerUrl"; + this.lblTriggerUrl.Size = new System.Drawing.Size(133, 28); + this.lblTriggerUrl.TabIndex = 1; + this.lblTriggerUrl.Text = "Trigger URL(s)"; + // + // tbTriggerUrl + // + this.tbTriggerUrl.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.tbTriggerUrl.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.tbTriggerUrl.Location = new System.Drawing.Point(180, 5); + this.tbTriggerUrl.Margin = new System.Windows.Forms.Padding(0, 5, 30, 5); + this.tbTriggerUrl.Name = "tbTriggerUrl"; + this.tbTriggerUrl.Size = new System.Drawing.Size(693, 33); + this.tbTriggerUrl.TabIndex = 22; + this.tbTriggerUrl.KeyDown += new System.Windows.Forms.KeyEventHandler(this.tbTriggerUrl_KeyDown); + this.tbTriggerUrl.KeyUp += new System.Windows.Forms.KeyEventHandler(this.tbTriggerUrl_KeyUp); + // + // cb_telegram + // + this.cb_telegram.AutoSize = true; + this.cb_telegram.Dock = System.Windows.Forms.DockStyle.Fill; + this.cb_telegram.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.cb_telegram.Location = new System.Drawing.Point(30, 113); + this.cb_telegram.Margin = new System.Windows.Forms.Padding(30, 5, 4, 5); + this.cb_telegram.Name = "cb_telegram"; + this.cb_telegram.Size = new System.Drawing.Size(877, 45); + this.cb_telegram.TabIndex = 23; + this.cb_telegram.Text = "Send alert images to Telegram"; + this.cb_telegram.UseVisualStyleBackColor = false; + // + // label5 + // + this.label5.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.label5.AutoSize = true; + this.label5.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.label5.Location = new System.Drawing.Point(30, 0); + this.label5.Margin = new System.Windows.Forms.Padding(30, 0, 4, 0); + this.label5.Name = "label5"; + this.label5.Size = new System.Drawing.Size(107, 44); + this.label5.TabIndex = 0; + this.label5.Text = "Cooldown Time"; + // + // tb_cooldown + // + this.tb_cooldown.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.tb_cooldown.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.tb_cooldown.Location = new System.Drawing.Point(180, 5); + this.tb_cooldown.Margin = new System.Windows.Forms.Padding(0, 5, 4, 5); + this.tb_cooldown.Name = "tb_cooldown"; + this.tb_cooldown.Size = new System.Drawing.Size(131, 33); + this.tb_cooldown.TabIndex = 21; + // + // label6 + // + this.label6.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.label6.AutoSize = true; + this.label6.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.label6.Location = new System.Drawing.Point(319, 8); + this.label6.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.label6.Name = "label6"; + this.label6.Size = new System.Drawing.Size(82, 28); + this.label6.TabIndex = 2; + this.label6.Text = "Minutes"; + // + // lblPrefix + // + this.lblPrefix.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.lblPrefix.AutoSize = true; + this.lblPrefix.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.lblPrefix.Location = new System.Drawing.Point(5, 55); + this.lblPrefix.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.lblPrefix.Name = "lblPrefix"; + this.lblPrefix.Size = new System.Drawing.Size(157, 53); + this.lblPrefix.TabIndex = 2; + this.lblPrefix.Text = "Input file begins with"; + // + // lblName + // + this.lblName.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.lblName.AutoSize = true; + this.lblName.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.lblName.Location = new System.Drawing.Point(5, 13); + this.lblName.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.lblName.Name = "lblName"; + this.lblName.Size = new System.Drawing.Size(64, 28); + this.lblName.TabIndex = 10; + this.lblName.Text = "Name"; + // + // tbPrefix + // + this.tbPrefix.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.tbPrefix.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.tbPrefix.Location = new System.Drawing.Point(30, 5); + this.tbPrefix.Margin = new System.Windows.Forms.Padding(30, 5, 30, 5); + this.tbPrefix.Name = "tbPrefix"; + this.tbPrefix.Size = new System.Drawing.Size(395, 33); + this.tbPrefix.TabIndex = 3; + this.tbPrefix.TextChanged += new System.EventHandler(this.tbPrefix_TextChanged); + // + // lbl_prefix + // + this.lbl_prefix.Anchor = System.Windows.Forms.AnchorStyles.None; + this.lbl_prefix.AutoSize = true; + this.lbl_prefix.Location = new System.Drawing.Point(683, 11); + this.lbl_prefix.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.lbl_prefix.Name = "lbl_prefix"; + this.lbl_prefix.Size = new System.Drawing.Size(0, 20); + this.lbl_prefix.TabIndex = 6; + // + // tbName + // + this.tbName.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.tbName.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.tbName.Location = new System.Drawing.Point(30, 5); + this.tbName.Margin = new System.Windows.Forms.Padding(30, 5, 30, 5); + this.tbName.Name = "tbName"; + this.tbName.Size = new System.Drawing.Size(395, 33); + this.tbName.TabIndex = 1; + // + // cb_enabled + // + this.cb_enabled.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.cb_enabled.AutoSize = true; + this.cb_enabled.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.cb_enabled.Location = new System.Drawing.Point(485, 5); + this.cb_enabled.Margin = new System.Windows.Forms.Padding(30, 5, 4, 5); + this.cb_enabled.Name = "cb_enabled"; + this.cb_enabled.Size = new System.Drawing.Size(343, 32); + this.cb_enabled.TabIndex = 2; + this.cb_enabled.Text = "Enable AI Detection for this camera"; + this.cb_enabled.UseVisualStyleBackColor = true; + // + // lblRelevantObjects + // + this.lblRelevantObjects.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.lblRelevantObjects.AutoSize = true; + this.lblRelevantObjects.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.lblRelevantObjects.Location = new System.Drawing.Point(5, 173); + this.lblRelevantObjects.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.lblRelevantObjects.Name = "lblRelevantObjects"; + this.lblRelevantObjects.Size = new System.Drawing.Size(157, 28); + this.lblRelevantObjects.TabIndex = 1; + this.lblRelevantObjects.Text = "Relevant Objects"; + // + // lbl_threshold + // + this.lbl_threshold.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.lbl_threshold.AutoSize = true; + this.lbl_threshold.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.lbl_threshold.Location = new System.Drawing.Point(5, 278); + this.lbl_threshold.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.lbl_threshold.Name = "lbl_threshold"; + this.lbl_threshold.Size = new System.Drawing.Size(162, 28); + this.lbl_threshold.TabIndex = 15; + this.lbl_threshold.Text = "Confidence limits"; + // + // lbl_threshold_lower + // + this.lbl_threshold_lower.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.lbl_threshold_lower.AutoSize = true; + this.lbl_threshold_lower.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.lbl_threshold_lower.Location = new System.Drawing.Point(4, 7); + this.lbl_threshold_lower.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.lbl_threshold_lower.Name = "lbl_threshold_lower"; + this.lbl_threshold_lower.Size = new System.Drawing.Size(108, 28); + this.lbl_threshold_lower.TabIndex = 19; + this.lbl_threshold_lower.Text = "Lower limit"; + // + // tb_threshold_upper + // + this.tb_threshold_upper.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.tb_threshold_upper.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.tb_threshold_upper.Location = new System.Drawing.Point(637, 5); + this.tb_threshold_upper.Margin = new System.Windows.Forms.Padding(0, 5, 4, 5); + this.tb_threshold_upper.MaxLength = 3; + this.tb_threshold_upper.Name = "tb_threshold_upper"; + this.tb_threshold_upper.Size = new System.Drawing.Size(87, 33); + this.tb_threshold_upper.TabIndex = 20; + this.tb_threshold_upper.Leave += new System.EventHandler(this.tb_threshold_upper_Leave); + // + // lbl_threshold_upper + // + this.lbl_threshold_upper.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.lbl_threshold_upper.AutoSize = true; + this.lbl_threshold_upper.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.lbl_threshold_upper.Location = new System.Drawing.Point(459, 7); + this.lbl_threshold_upper.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.lbl_threshold_upper.Name = "lbl_threshold_upper"; + this.lbl_threshold_upper.Size = new System.Drawing.Size(111, 28); + this.lbl_threshold_upper.TabIndex = 21; + this.lbl_threshold_upper.Text = "Upper limit"; + // + // tb_threshold_lower + // + this.tb_threshold_lower.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.tb_threshold_lower.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.tb_threshold_lower.Location = new System.Drawing.Point(182, 5); + this.tb_threshold_lower.Margin = new System.Windows.Forms.Padding(0, 5, 4, 5); + this.tb_threshold_lower.MaxLength = 3; + this.tb_threshold_lower.Name = "tb_threshold_lower"; + this.tb_threshold_lower.Size = new System.Drawing.Size(87, 33); + this.tb_threshold_lower.TabIndex = 19; + this.tb_threshold_lower.Leave += new System.EventHandler(this.tb_threshold_lower_Leave); + // + // label9 + // + this.label9.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.label9.AutoSize = true; + this.label9.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.label9.Location = new System.Drawing.Point(732, 7); + this.label9.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.label9.Name = "label9"; + this.label9.Size = new System.Drawing.Size(28, 28); + this.label9.TabIndex = 22; + this.label9.Text = "%"; + // + // label10 + // + this.label10.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.label10.AutoSize = true; + this.label10.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.label10.Location = new System.Drawing.Point(277, 7); + this.label10.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.label10.Name = "label10"; + this.label10.Size = new System.Drawing.Size(28, 28); + this.label10.TabIndex = 23; + this.label10.Text = "%"; + // + // btnCameraAdd + // + this.btnCameraAdd.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom))); + this.btnCameraAdd.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.btnCameraAdd.Location = new System.Drawing.Point(478, 5); + this.btnCameraAdd.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.btnCameraAdd.Name = "btnCameraAdd"; + this.btnCameraAdd.Size = new System.Drawing.Size(165, 43); + this.btnCameraAdd.TabIndex = 24; + this.btnCameraAdd.Text = "Add Camera"; + this.btnCameraAdd.UseVisualStyleBackColor = true; + this.btnCameraAdd.Click += new System.EventHandler(this.btnCameraAdd_Click); + // + // btnCameraDel + // + this.btnCameraDel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom))); + this.btnCameraDel.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.btnCameraDel.Location = new System.Drawing.Point(702, 5); + this.btnCameraDel.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.btnCameraDel.Name = "btnCameraDel"; + this.btnCameraDel.Size = new System.Drawing.Size(165, 43); + this.btnCameraDel.TabIndex = 25; + this.btnCameraDel.Text = " Delete Camera "; + this.btnCameraDel.UseVisualStyleBackColor = true; + this.btnCameraDel.Click += new System.EventHandler(this.btnCameraDel_Click); + // + // btnCameraSave + // + this.btnCameraSave.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom))); + this.btnCameraSave.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Zoom; + this.btnCameraSave.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.btnCameraSave.Location = new System.Drawing.Point(927, 5); + this.btnCameraSave.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.btnCameraSave.Name = "btnCameraSave"; + this.btnCameraSave.Size = new System.Drawing.Size(165, 43); + this.btnCameraSave.TabIndex = 26; + this.btnCameraSave.Text = "Save"; + this.btnCameraSave.UseVisualStyleBackColor = false; + this.btnCameraSave.Click += new System.EventHandler(this.btnCameraSave_Click_1); + // + // lbl_camstats + // + this.lbl_camstats.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.lbl_camstats.AutoSize = true; + this.lbl_camstats.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.lbl_camstats.Location = new System.Drawing.Point(4, 17); + this.lbl_camstats.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.lbl_camstats.Name = "lbl_camstats"; + this.lbl_camstats.Size = new System.Drawing.Size(54, 28); + this.lbl_camstats.TabIndex = 4; + this.lbl_camstats.Text = "Stats"; + // + // label4 + // + this.label4.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.label4.AutoSize = true; + this.label4.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.label4.Location = new System.Drawing.Point(5, 334); + this.label4.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(45, 28); + this.label4.TabIndex = 15; + this.label4.Text = "Log"; + // + // btn_open_log + // + this.btn_open_log.Anchor = System.Windows.Forms.AnchorStyles.None; + this.btn_open_log.Location = new System.Drawing.Point(1081, 16); + this.btn_open_log.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.btn_open_log.Name = "btn_open_log"; + this.btn_open_log.Size = new System.Drawing.Size(88, 35); + this.btn_open_log.TabIndex = 2; + this.btn_open_log.Text = "Open Log"; + this.btn_open_log.UseVisualStyleBackColor = true; + this.btn_open_log.Click += new System.EventHandler(this.btn_open_log_Click); + // + // cb_log + // + this.cb_log.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.cb_log.Location = new System.Drawing.Point(4, 5); + this.cb_log.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.cb_log.Name = "cb_log"; + this.cb_log.Size = new System.Drawing.Size(1058, 57); + this.cb_log.TabIndex = 11; + this.cb_log.Text = "Log everything"; + this.cb_log.UseVisualStyleBackColor = true; + // + // label12 + // + this.label12.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.label12.AutoSize = true; + this.label12.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.label12.Location = new System.Drawing.Point(5, 412); + this.label12.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.label12.Name = "label12"; + this.label12.Size = new System.Drawing.Size(112, 28); + this.label12.TabIndex = 13; + this.label12.Text = "Send Errors"; + // + // cb_send_errors + // + this.cb_send_errors.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.cb_send_errors.Location = new System.Drawing.Point(216, 393); + this.cb_send_errors.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.cb_send_errors.Name = "cb_send_errors"; + this.cb_send_errors.Size = new System.Drawing.Size(1185, 67); + this.cb_send_errors.TabIndex = 12; + this.cb_send_errors.Text = "Send Errors and Warnings to Telegram"; + this.cb_send_errors.UseVisualStyleBackColor = true; + // + // lbl_deepstackurl + // + this.lbl_deepstackurl.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.lbl_deepstackurl.AutoSize = true; + this.lbl_deepstackurl.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.lbl_deepstackurl.Location = new System.Drawing.Point(5, 101); + this.lbl_deepstackurl.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.lbl_deepstackurl.Name = "lbl_deepstackurl"; + this.lbl_deepstackurl.Size = new System.Drawing.Size(142, 28); + this.lbl_deepstackurl.TabIndex = 4; + this.lbl_deepstackurl.Text = "Deepstack URL"; + // + // lbl_input + // + this.lbl_input.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.lbl_input.AutoSize = true; + this.lbl_input.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.lbl_input.Location = new System.Drawing.Point(5, 25); + this.lbl_input.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.lbl_input.Name = "lbl_input"; + this.lbl_input.Size = new System.Drawing.Size(101, 28); + this.lbl_input.TabIndex = 1; + this.lbl_input.Text = "Input Path"; + // + // tbDeepstackUrl + // + this.tbDeepstackUrl.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.tbDeepstackUrl.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.tbDeepstackUrl.Location = new System.Drawing.Point(216, 99); + this.tbDeepstackUrl.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.tbDeepstackUrl.Name = "tbDeepstackUrl"; + this.tbDeepstackUrl.Size = new System.Drawing.Size(1185, 33); + this.tbDeepstackUrl.TabIndex = 5; + // + // lbl_telegram_token + // + this.lbl_telegram_token.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.lbl_telegram_token.AutoSize = true; + this.lbl_telegram_token.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.lbl_telegram_token.Location = new System.Drawing.Point(5, 178); + this.lbl_telegram_token.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.lbl_telegram_token.Name = "lbl_telegram_token"; + this.lbl_telegram_token.Size = new System.Drawing.Size(147, 28); + this.lbl_telegram_token.TabIndex = 6; + this.lbl_telegram_token.Text = "Telegram Token"; + // + // lbl_telegram_chatid + // + this.lbl_telegram_chatid.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.lbl_telegram_chatid.AutoSize = true; + this.lbl_telegram_chatid.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.lbl_telegram_chatid.Location = new System.Drawing.Point(5, 256); + this.lbl_telegram_chatid.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.lbl_telegram_chatid.Name = "lbl_telegram_chatid"; + this.lbl_telegram_chatid.Size = new System.Drawing.Size(160, 28); + this.lbl_telegram_chatid.TabIndex = 7; + this.lbl_telegram_chatid.Text = "Telegram Chat ID"; + // + // tb_telegram_token + // + this.tb_telegram_token.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.tb_telegram_token.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.tb_telegram_token.Location = new System.Drawing.Point(216, 176); + this.tb_telegram_token.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.tb_telegram_token.Name = "tb_telegram_token"; + this.tb_telegram_token.Size = new System.Drawing.Size(1185, 33); + this.tb_telegram_token.TabIndex = 8; + // + // tb_telegram_chatid + // + this.tb_telegram_chatid.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.tb_telegram_chatid.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.tb_telegram_chatid.Location = new System.Drawing.Point(216, 254); + this.tb_telegram_chatid.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.tb_telegram_chatid.Name = "tb_telegram_chatid"; + this.tb_telegram_chatid.Size = new System.Drawing.Size(1185, 33); + this.tb_telegram_chatid.TabIndex = 9; + // + // btn_input_path + // + this.btn_input_path.Anchor = System.Windows.Forms.AnchorStyles.None; + this.btn_input_path.Location = new System.Drawing.Point(1081, 15); + this.btn_input_path.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.btn_input_path.Name = "btn_input_path"; + this.btn_input_path.Size = new System.Drawing.Size(88, 35); + this.btn_input_path.TabIndex = 2; + this.btn_input_path.Text = "Select..."; + this.btn_input_path.UseVisualStyleBackColor = true; + this.btn_input_path.Click += new System.EventHandler(this.btn_input_path_Click); + // + // cmbInput + // + this.cmbInput.Anchor = System.Windows.Forms.AnchorStyles.None; + this.cmbInput.FormattingEnabled = true; + this.cmbInput.Location = new System.Drawing.Point(3, 19); + this.cmbInput.Name = "cmbInput"; + this.cmbInput.Size = new System.Drawing.Size(1059, 28); + this.cmbInput.TabIndex = 3; + // + // label13 + // + this.label13.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.label13.AutoSize = true; + this.label13.Font = new System.Drawing.Font("Segoe UI", 9.75F); + this.label13.Location = new System.Drawing.Point(4, 491); + this.label13.Name = "label13"; + this.label13.Size = new System.Drawing.Size(53, 28); + this.label13.TabIndex = 16; + this.label13.Text = "Start"; + // + // cbStartWithWindows + // + this.cbStartWithWindows.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.cbStartWithWindows.AutoSize = true; + this.cbStartWithWindows.Location = new System.Drawing.Point(215, 493); + this.cbStartWithWindows.Name = "cbStartWithWindows"; + this.cbStartWithWindows.Size = new System.Drawing.Size(269, 24); + this.cbStartWithWindows.TabIndex = 17; + this.cbStartWithWindows.Text = "Start with user login (non-service)"; + this.cbStartWithWindows.UseVisualStyleBackColor = true; + // + // BtnSettingsSave + // + this.BtnSettingsSave.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom))); + this.BtnSettingsSave.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.BtnSettingsSave.Location = new System.Drawing.Point(605, 561); + this.BtnSettingsSave.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.BtnSettingsSave.Name = "BtnSettingsSave"; + this.BtnSettingsSave.Size = new System.Drawing.Size(204, 84); + this.BtnSettingsSave.TabIndex = 2; + this.BtnSettingsSave.Text = "Save"; + this.BtnSettingsSave.UseVisualStyleBackColor = true; + this.BtnSettingsSave.Click += new System.EventHandler(this.BtnSettingsSave_Click_1); + // + // Shell + // + this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 20F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoSize = true; + this.ClientSize = new System.Drawing.Size(1422, 683); + this.Controls.Add(this.tabControl1); + this.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.MinimumSize = new System.Drawing.Size(1444, 739); + this.Name = "Shell"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; + this.Text = "AI Tool"; + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.Shell_FormClosing); + this.Load += new System.EventHandler(this.Shell_Load); + this.Resize += new System.EventHandler(this.Form1_Resize); + this.tabControl1.ResumeLayout(false); + this.tabDeepStack.ResumeLayout(false); + this.tabDeepStack.PerformLayout(); + this.groupBox6.ResumeLayout(false); + this.groupBox6.PerformLayout(); + this.groupBox1.ResumeLayout(false); + this.groupBox1.PerformLayout(); + this.groupBox5.ResumeLayout(false); + this.groupBox5.PerformLayout(); + this.groupBox2.ResumeLayout(false); + this.groupBox2.PerformLayout(); + this.groupBox4.ResumeLayout(false); + this.groupBox4.PerformLayout(); + this.groupBox3.ResumeLayout(false); + this.groupBox3.PerformLayout(); + this.tabLog.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox2)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.chart_confidence)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.timeline)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.chart1)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit(); + this.splitContainer1.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit(); + this.splitContainer1.ResumeLayout(false); + this.panel1.ResumeLayout(false); + this.panel1.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.NotifyIcon notifyIcon; + private DBLayoutPanel tableLayoutPanel1; + private System.Windows.Forms.TabControl tabControl1; + private System.Windows.Forms.TabPage tabHistory; + private System.Windows.Forms.TabPage tabCameras; + private System.Windows.Forms.TabPage tabSettings; + private DBLayoutPanel tableLayoutPanel4; + private DBLayoutPanel tableLayoutPanel5; + private System.Windows.Forms.Label lbl_deepstackurl; + private System.Windows.Forms.Label lbl_input; + private System.Windows.Forms.Button BtnSettingsSave; + private DBLayoutPanel tableLayoutPanel2; + private DBLayoutPanel tableLayoutPanel3; + private System.Windows.Forms.Button btnCameraAdd; + private DBLayoutPanel tableLayoutPanel7; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Label lblPrefix; + private System.Windows.Forms.Label lblRelevantObjects; + private DBLayoutPanel tableLayoutPanel9; + private DBLayoutPanel tableLayoutPanel10; + private System.Windows.Forms.TextBox tbTriggerUrl; + private System.Windows.Forms.CheckBox cb_telegram; + private System.Windows.Forms.Label lblName; + private System.Windows.Forms.ListView list2; + private DBLayoutPanel tableLayoutPanel11; + private System.Windows.Forms.Button btnCameraSave; + private System.Windows.Forms.Button btnCameraDel; + private DBLayoutPanel tableLayoutPanel12; + private System.Windows.Forms.TextBox tbPrefix; + private System.Windows.Forms.Label lbl_prefix; + private DBLayoutPanel tableLayoutPanel13; + private System.Windows.Forms.TextBox tbName; + private System.Windows.Forms.CheckBox cb_enabled; + private System.Windows.Forms.Label lbl_telegram_token; + private System.Windows.Forms.Label lbl_telegram_chatid; + private System.Windows.Forms.TextBox tb_telegram_token; + private System.Windows.Forms.TextBox tb_telegram_chatid; + private System.Windows.Forms.TabPage tabOverview; + private System.Windows.Forms.Label lblTriggerUrl; + private System.Windows.Forms.Label lbl_camstats; + private DBLayoutPanel tableLayoutPanel15; + private System.Windows.Forms.PictureBox pictureBox2; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.TabPage tabStats; + private DBLayoutPanel tableLayoutPanel16; + private DBLayoutPanel tableLayoutPanel17; + private System.Windows.Forms.DataVisualization.Charting.Chart chart1; + private System.Windows.Forms.ComboBox comboBox1; + private System.Windows.Forms.Label lbl_version; + private System.Windows.Forms.Label lbl_errors; + private System.Windows.Forms.CheckBox cb_log; + private System.Windows.Forms.TextBox tbDeepstackUrl; + private DBLayoutPanel tableLayoutPanel18; + private System.Windows.Forms.Button btn_input_path; + private DBLayoutPanel tableLayoutPanel20; + private System.Windows.Forms.Label label5; + private System.Windows.Forms.TextBox tb_cooldown; + private System.Windows.Forms.Label label6; + private DBLayoutPanel tableLayoutPanel21; + private System.Windows.Forms.PictureBox pictureBox1; + private System.Windows.Forms.Label lbl_info; + private System.Windows.Forms.SplitContainer splitContainer1; + private DBLayoutPanel tableLayoutPanel19; + private System.Windows.Forms.CheckBox cb_showFilters; + private System.Windows.Forms.ListView list1; + private System.Windows.Forms.Panel panel1; + private System.Windows.Forms.CheckBox cb_filter_nosuccess; + private System.Windows.Forms.CheckBox cb_filter_success; + private System.Windows.Forms.CheckBox cb_filter_person; + private System.Windows.Forms.CheckBox cb_filter_vehicle; + private System.Windows.Forms.CheckBox cb_filter_animal; + private System.Windows.Forms.ComboBox comboBox_filter_camera; + private DBLayoutPanel tableLayoutPanel23; + private System.Windows.Forms.Label label8; + private System.Windows.Forms.DataVisualization.Charting.Chart chart_confidence; + private System.Windows.Forms.DataVisualization.Charting.Chart timeline; + private System.Windows.Forms.Label label7; + private DBLayoutPanel tableLayoutPanel8; + private System.Windows.Forms.CheckBox cb_person; + private System.Windows.Forms.CheckBox cb_bicycle; + private System.Windows.Forms.CheckBox cb_motorcycle; + private System.Windows.Forms.CheckBox cb_bear; + private System.Windows.Forms.CheckBox cb_cow; + private System.Windows.Forms.CheckBox cb_sheep; + private System.Windows.Forms.CheckBox cb_horse; + private System.Windows.Forms.CheckBox cb_bird; + private System.Windows.Forms.CheckBox cb_dog; + private System.Windows.Forms.CheckBox cb_cat; + private System.Windows.Forms.CheckBox cb_airplane; + private System.Windows.Forms.CheckBox cb_boat; + private System.Windows.Forms.CheckBox cb_bus; + private System.Windows.Forms.CheckBox cb_truck; + private System.Windows.Forms.CheckBox cb_car; + private System.Windows.Forms.Label lbl_threshold; + private DBLayoutPanel tableLayoutPanel24; + private System.Windows.Forms.Label lbl_threshold_lower; + private System.Windows.Forms.TextBox tb_threshold_upper; + private System.Windows.Forms.Label lbl_threshold_upper; + private System.Windows.Forms.TextBox tb_threshold_lower; + private System.Windows.Forms.Label label9; + private System.Windows.Forms.Label label10; + private System.Windows.Forms.Label label12; + private System.Windows.Forms.CheckBox cb_send_errors; + private System.Windows.Forms.Label label4; + private DBLayoutPanel tableLayoutPanel25; + private System.Windows.Forms.Button btn_open_log; + private DBLayoutPanel tableLayoutPanel22; + private System.Windows.Forms.CheckBox cb_showObjects; + private System.Windows.Forms.CheckBox cb_showMask; + private System.Windows.Forms.Label lbl_objects; + private DBLayoutPanel tableLayoutPanel6; + private DBLayoutPanel tableLayoutPanel14; + private System.Windows.Forms.TabPage tabDeepStack; + private System.Windows.Forms.CheckBox Chk_AutoStart; + private System.Windows.Forms.Button Btn_Stop; + private System.Windows.Forms.Button Btn_Start; + private System.Windows.Forms.GroupBox groupBox3; + private System.Windows.Forms.TextBox Txt_Port; + private System.Windows.Forms.GroupBox groupBox2; + private System.Windows.Forms.RadioButton RB_High; + private System.Windows.Forms.RadioButton RB_Medium; + private System.Windows.Forms.RadioButton RB_Low; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.CheckBox Chk_DetectionAPI; + private System.Windows.Forms.CheckBox Chk_FaceAPI; + private System.Windows.Forms.CheckBox Chk_SceneAPI; + private System.Windows.Forms.TextBox Txt_DeepStackInstallFolder; + private System.Windows.Forms.Label label11; + private System.Windows.Forms.GroupBox groupBox6; + private System.Windows.Forms.TextBox Txt_APIKey; + private System.Windows.Forms.GroupBox groupBox5; + private System.Windows.Forms.TextBox Txt_AdminKey; + private System.Windows.Forms.GroupBox groupBox4; + private System.Windows.Forms.Label Lbl_BlueStackRunning; + private System.Windows.Forms.Button Btn_Save; + private System.Windows.Forms.TabPage tabLog; + private System.Windows.Forms.RichTextBox RTF_Log; + private System.Windows.Forms.ComboBox cmbInput; + private System.Windows.Forms.Label label13; + private System.Windows.Forms.CheckBox cbStartWithWindows; + private System.Windows.Forms.Button btnStopscroll; + private System.Windows.Forms.Button btnViewLog; + } +} + diff --git a/src/UI/Shell.cs b/src/UI/Shell.cs index 3f398015..a9f70134 100644 --- a/src/UI/Shell.cs +++ b/src/UI/Shell.cs @@ -2,897 +2,984 @@ using System.Collections.Generic; using System.ComponentModel; using System.Data; +using System.Diagnostics; using System.Drawing; +using System.IO; using System.Linq; -using System.Text; +using System.Media; +using System.Net; +using System.Reflection; +using System.Threading; using System.Threading.Tasks; +using System.Timers; using System.Windows.Forms; -using System.Configuration; - -using System.IO; -using System.Net.Http; -using System.Net; +using System.Windows.Forms.DataVisualization.Charting; -using Newtonsoft.Json; //deserialize DeepquestAI response +using BrightIdeasSoftware; -//for image cutting -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Processing; -using SixLabors.Primitives; +using Microsoft.WindowsAPICodePack.Dialogs; -//for telegram -using Telegram.Bot; -using Telegram.Bot.Args; -using Telegram.Bot.Types; -using Telegram.Bot.Types.Enums; -using Telegram.Bot.Types.InputFiles; +using Newtonsoft.Json; //deserialize DeepquestAI response -using Microsoft.WindowsAPICodePack.Dialogs; -using Size = SixLabors.Primitives.Size; -using SizeF = SixLabors.Primitives.SizeF; //for file dialog +using NLog; +using static AITool.AITOOL; +using static AITool.Global; -namespace WindowsFormsApp2 +namespace AITool { - public partial class Shell : Form + public partial class Shell:Form { - public string input_path = Properties.Settings.Default.input_path; //image input path - public static string deepstack_url = Properties.Settings.Default.deepstack_url; //deepstack url - public static bool log_everything = Properties.Settings.Default.log_everything; //save every action sent to Log() into the log file? - public static bool send_errors = Properties.Settings.Default.send_errors; //send error messages to Telegram? - public static string telegram_chatid = Properties.Settings.Default.telegram_chatid; //telegram chat id - public static string[] telegram_chatids = telegram_chatid.Replace(" ", "").Split(','); //for multiple Telegram chats that receive alert images - public static string telegram_token = Properties.Settings.Default.telegram_token; //telegram bot token - public int errors = 0; //error counter - public bool detection_running = false; //is detection running right now or not - public int file_access_delay = 10; //delay before accessing new file in ms - public int retry_delay = 10; //delay for first file acess retry - will increase on each retry - List CameraList = new List(); //list containing all cameras - - static HttpClient client = new HttpClient(); - - FileSystemWatcher watcher = new FileSystemWatcher(); //fswatcher checking the input folder for new images + private ThreadSafe.DateTime LastListUpdate = new ThreadSafe.DateTime(DateTime.MinValue, AppSettings.Settings.DateFormat); - public Shell() - { - InitializeComponent(); + private ThreadSafe.Boolean DatabaseInitialized = new ThreadSafe.Boolean(false); - this.Resize += new System.EventHandler(this.Form1_Resize); //resize event to enable 'minimize to tray' + private ThreadSafe.Boolean IsHistoryListUpdating = new ThreadSafe.Boolean(false); + private ThreadSafe.Boolean IsLogListUpdating = new ThreadSafe.Boolean(false); + private ThreadSafe.Boolean IsStatsUpdating = new ThreadSafe.Boolean(false); - //if camera settings folder does not exist, create it - if (!Directory.Exists("./cameras/")) - { - //create folder - DirectoryInfo di = Directory.CreateDirectory("./cameras"); - Log("./cameras/" + " dir created."); - } + //Dictionary HistoryDic = new Dictionary(); + //private ThreadSafe.Boolean FilterChanged = new ThreadSafe.Boolean(true); - //--------------------------------------------------------------------------- - //CAMERAS TAB + //Instantiate a Singleton of the Semaphore with a value of 1. This means that only 1 thread can be granted access at a time. + public static SemaphoreSlim Semaphore_List_Updating = new SemaphoreSlim(1, 1); - //left list column setup - list2.Columns.Add("Camera"); + //public static ConcurrentQueue AddedHistoryItems = new ConcurrentQueue(); + //public static ConcurrentQueue DeletedHistoryItems = new ConcurrentQueue(); - //set left list column width segmentation (because of some bug -4 is necessary to achieve the correct width) - list2.Columns[0].Width = list2.Width - 4; - list2.FullRowSelect = true; //make all columns clickable + //for searching log tab: + System.Timers.Timer tmr; + DateTime TimeSinceType = DateTime.MinValue; - LoadCameras(); //load camera list + FrmSplash SplashScreen = null; - this.Opacity = 0; - this.Show(); + Stopwatch StartupSW = null; - //--------------------------------------------------------------------------- - //HISTORY TAB - - //left list column setup - list1.Columns.Add("Name"); - list1.Columns.Add("Date and Time"); - list1.Columns.Add("Camera"); - list1.Columns.Add("Detections"); - list1.Columns.Add("Positions"); - list1.Columns.Add("✓"); - - //set left list column width segmentation - list1.Columns[0].Width = list1.Width * 0 / 100; //filename - list1.Columns[1].Width = list1.Width * 47 / 100; //date - list1.Columns[2].Width = list1.Width * 43 / 100; //cam name - list1.Columns[3].Width = list1.Width * 0 / 100; //obj and confidences - list1.Columns[4].Width = list1.Width * 0 / 100; // object positions of all detected objects separated by ";" - list1.Columns[5].Width = list1.Width * 10 / 100; //checkmark if something relevant was detected or not - list1.FullRowSelect = true; //make all columns clickable - - //check if history.csv exists, if not then create it - if (!System.IO.File.Exists(@"cameras/history.csv")) - { - Log("ATTENTION: Creating database cameras/history.csv ."); - try - { - using (StreamWriter sw = new StreamWriter(AppDomain.CurrentDomain.BaseDirectory + "cameras/history.csv")) - { - sw.WriteLine("filename|date and time|camera|detections|positions of detections|success"); - } - } - catch - { - lbl_errors.Text = "Can't create cameras/history.csv database!"; - } - } - //this method is slow if the database is large, so it's usually only called on startup. During runtime, DeleteListImage() is used to remove obsolete images from the history list - CleanCSVList(); + public Shell() + { - //load entries from history.csv into history ListView - //LoadFromCSV(); not neccessary because below, comboBox_filter_camera.SelectedIndex will call LoadFromCSV() - splitContainer1.Panel2Collapsed = true; //collapse filter panel under left list - comboBox_filter_camera.Items.Add("All Cameras"); //add "all cameras" entry in filter dropdown combobox - comboBox_filter_camera.SelectedIndex = comboBox_filter_camera.FindStringExact("All Cameras"); //select all cameras entry + this.StartupSW = Stopwatch.StartNew(); + this.InitializeComponent(); - //configure fswatcher to checks input_path for new images, images deleted and renamed images - try - { - watcher.Path = input_path; - watcher.Filter = "*.jpg"; + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. - //fswatcher events - watcher.Created += new FileSystemEventHandler(OnCreatedAsync); - watcher.Renamed += new RenamedEventHandler(OnRenamed); - watcher.Deleted += new FileSystemEventHandler(OnDeleted); + //this is to log and status messages from other classes... + Global.progress = new Progress(this.EventMessage); - //enable fswatcher - watcher.EnableRaisingEvents = true; - } - catch - { - if (input_path == "") - { - Log("ATTENTION: No input folder defined."); - } - else - { - Log($"ERROR: Can't access input folder '{input_path}'."); - } + this.SplashScreen = new FrmSplash(); - } + if (Debugger.IsAttached) + this.SplashScreen.Hide(); + else + this.SplashScreen.Show(); + HideForm(); + Application.DoEvents(); + Debug.Print("Init tid=" + Thread.CurrentThread.ManagedThreadId); + } + private async void Shell_Load(object sender, EventArgs e) + { + //PlayOOG("D:\\TEMP\\AwACAgEAAxkBAAI7OGGhP0AhP-aBlubv3Vbq4OahTbkAAy4CAAJ59AlFMssC4x5Yy3wiBA.ogg"); + //using var synth = new SpeechSynthesizer(); + //foreach (InstalledVoice voice in synth.GetInstalledVoices()) + //{ + // VoiceInfo info = voice.VoiceInfo; + // Console.WriteLine(" Voice Name: " + info.Name); + //} + //string test = CompressToBase64String(" er 2 35464353 4t f fg wefg wer gwe gw t345 y4 t4 rtw w egfgkfkkkk"); + //bool isbase = IsBase64String(test); + //TimeSpan ts = TimeSpan.FromDays(1.2); + //Debug.Print(ts.FormatTS(true)); + //Debug.Print(ts); - //--------------------------------------------------------------------------- - //SETTINGS TAB + //Global.IsTimeBetween(new DateTime(2021, 6, 13, 06, 00, 00), "dusk-dawn"); + //Global.IsTimeBetween(new DateTime(2021, 6, 13, 23, 00, 00), "dusk-dawn"); + //Global.IsTimeBetween(new DateTime(2021, 6, 13, 07, 00, 00), "dusk-dawn"); + //Global.IsTimeBetween(new DateTime(2021, 6, 13, 21, 00, 00), "dusk-dawn"); - //fill settings tab with stored settings - tbInput.Text = input_path; - tbDeepstackUrl.Text = deepstack_url; - tb_telegram_chatid.Text = telegram_chatid; - tb_telegram_token.Text = telegram_token; - cb_log.Checked = log_everything; - cb_send_errors.Checked = send_errors; + //Global.IsTimeBetween(new DateTime(2021, 6, 13, 06, 00, 00), "dawn-dusk"); + //Global.IsTimeBetween(new DateTime(2021, 6, 13, 23, 00, 00), "dawn-dusk"); + //Global.IsTimeBetween(new DateTime(2021, 6, 13, 07, 00, 00), "dawn-dusk"); + //Global.IsTimeBetween(new DateTime(2021, 6, 13, 21, 00, 00), "dawn-dusk"); - //--------------------------------------------------------------------------- - //STATS TAB - comboBox1.Items.Add("All Cameras"); //add all cameras stats entry - comboBox1.SelectedIndex = comboBox1.FindStringExact("All Cameras"); //select all cameras entry + //Rectangle img = new Rectangle(0, 0, 3840, 2160); + //Rectangle prd = new Rectangle(0, 0, 155, 204); // .38% of the original image + //Rectangle prd = new Rectangle(0, 0, img.Width / 2, img.Height); //half of the original image = 50% + //Rectangle prd = new Rectangle(0, 0, img.Width, img.Height / 2); //half of the original image = 50% + //Rectangle prd = new Rectangle(0, 0, img.Width / 2, img.Height / 2); //a quarter of the original image = 25% + //double percent = img.PercentOfSize(prd); - this.Opacity = 1; + Debug.Print("load tid=" + Thread.CurrentThread.ManagedThreadId); - Log("APP START complete."); - } + //Uri pth = new Uri("\\\\[2600:6c64:6b7f:f8d8::1d4]\\c$"); + //ClsDoodsRequest cdr = new ClsDoodsRequest(); - //---------------------------------------------------------------------------------------------------------- - //CORE - //---------------------------------------------------------------------------------------------------------- + //cdr.Detect.MinPercentMatch = 50; - //analyze image with AI - public async Task DetectObjects(string image_path) - { + //string testjson = JsonConvert.SerializeObject(cdr); - string error = ""; //if code fails at some point, the last text of the error string will be posted in the log - Log(""); - Log($"Starting analysis of {image_path}"); - var fullDeepstackUrl = ""; - //allows both "http://ip:port" and "ip:port" - if (!deepstack_url.Contains("http://")) //"ip:port" - { - fullDeepstackUrl = "http://" + deepstack_url + "/v1/vision/detection"; - } - else //"http://ip:port" + using var cw = new Global_GUI.CursorWait(true); + + //--------------------------------------------------------------------------- + //INITIALIZE HISTORY DB, load settings, ETC + + if (AppSettings.AlreadyRunning && !Global.IsService) { - fullDeepstackUrl = deepstack_url + "/v1/vision/detection"; + if (MessageBox.Show("AITOOL.EXE is already running. If you need to modify settings:\r\n\r\n1) Close this copy.\r\n2) Stop the other service/instance. \r\n3) Restart AITOOL.\r\n\r\nCLOSE THIS COPY?", "Already running", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes) + { + this.SplashScreen.Close(); + this.SplashScreen = null; + CloseImmediately = true; + Application.Exit(); + } } + Stopwatch sw = Stopwatch.StartNew(); - // check if camera is still in the first half of the cooldown. If yes, don't analyze to minimize cpu load. + await AITOOL.InitializeBackend(); - string fileprefix = Path.GetFileNameWithoutExtension(image_path).Split('.')[0]; //get prefix of inputted file - int index = CameraList.FindIndex(x => x.prefix == fileprefix); //get index of camera with same prefix, is =-1 if no camera has the same prefix - //only analyze if 50% of the cameras cooldown time since last detection has passed - if (index == -1 || (DateTime.Now - CameraList[index].last_trigger_time).TotalMinutes >= (CameraList[index].cooldown_time / 2)) //it's important that the condition index == 1 comes first, because if index is -1 and the second condition is checked, it will try to acces the CameraList at position -1 => the program cr - { - var request = new MultipartFormDataContent(); - for (int attempts = 1; attempts < 10; attempts++) //retry if file is in use by another process. - { - try - { - error = "loading image failed"; - using (var image_data = System.IO.File.OpenRead(image_path)) - { - Log("(1/6) Uploading image to DeepQuestAI Server"); - error = $"Can't reach DeepQuestAI Server at {fullDeepstackUrl}."; - request.Add(new StreamContent(image_data), "image", Path.GetFileName(image_path)); - var output = await client.PostAsync(fullDeepstackUrl, request); - Log("(2/6) Waiting for results"); - var jsonString = await output.Content.ReadAsStringAsync(); - Response response = JsonConvert.DeserializeObject(jsonString); + //Camera testcam = GetCamera("c:\\test\\CAMNAME.1.123456.jpg"); - Log("(3/6) Processing results:"); - error = $"Failure in AI Tool processing the image."; + //ClsImageQueueItem cli = new ClsImageQueueItem("C:\\Downloads\\TestImage.jpg", 0); + //NPushover.RequestObjects.Message msg = new NPushover.RequestObjects.Message(); + //msg.Timestamp = DateTime.Now; + //msg.Title = "Test title"; + //msg.Body = "Some message"; - //print every detected object with the according confidence-level - string outputtext = " Detected objects:"; + //AITOOL.pushoverClient = new NPushover.Pushover(AppSettings.Settings.pushover_APIKey); + //NPushover.ResponseObjects.PushoverUserResponse usr = await AITOOL.pushoverClient.SendPushoverMessageAsync(msg, "", cli); - foreach (var user in response.predictions) - { - outputtext += $"{user.label.ToString()} ({Math.Round((user.confidence * 100), 2).ToString() }%), "; - } - Log(outputtext); + Log($"Debug: Back end initialization completed in {sw.ElapsedMilliseconds}ms."); - if (response.success == true) - { - //if there is no camera with the same prefix - if (index == -1) - { - Log(" No camera with the same prefix found: " + fileprefix); - //check if there is a default camera which accepts any prefix, select it - if (CameraList.Exists(x => x.prefix == "")) - { - index = CameraList.FindIndex(x => x.prefix == ""); - Log("( Found a default camera."); - } - else - { - Log("WARNING: No default camera found. Aborting."); - } - } - //index == -1 now means that no camera has the same prefix and no default camera exists. The alert therefore won't be used. + //since async stuff continues on another thread, we have to do this: + Global_GUI.InvokeIFRequired(this, () => + { + //--------------------------------------------------------------------------- + //HISTORY TAB + Global_GUI.ConfigureFOLV(this.folv_history, typeof(History), new Font("Segoe UI", (float)9.75, FontStyle.Regular), this.HistoryImageList, GridLines: false); + //this.folv_history.EmptyListMsg = "Initializing database"; + this.cb_showMask.Checked = AppSettings.Settings.HistoryShowMask; + this.cb_showObjects.Checked = AppSettings.Settings.HistoryShowObjects; + this.cb_follow.Checked = AppSettings.Settings.HistoryFollow; + this.automaticallyRefreshToolStripMenuItem.Checked = AppSettings.Settings.HistoryAutoRefresh; + this.storeFalseAlertsToolStripMenuItem.Checked = AppSettings.Settings.HistoryStoreFalseAlerts; + this.storeMaskedAlertsToolStripMenuItem.Checked = AppSettings.Settings.HistoryStoreMaskedAlerts; + this.showOnlyRelevantObjectsToolStripMenuItem.Checked = AppSettings.Settings.HistoryOnlyDisplayRelevantObjects; + this.restrictThresholdAtSourceToolStripMenuItem.Checked = AppSettings.Settings.HistoryRestrictMinThresholdAtSource; + this.mergeDuplicatePredictionsToolStripMenuItem.Checked = AppSettings.Settings.HistoryMergeDuplicatePredictions; + this.cb_filter_animal.Checked = AppSettings.Settings.HistoryFilterAnimals; + this.cb_filter_masked.Checked = AppSettings.Settings.HistoryFilterMasked; + this.cb_filter_nosuccess.Checked = AppSettings.Settings.HistoryFilterNoSuccess; + this.cb_filter_person.Checked = AppSettings.Settings.HistoryFilterPeople; + this.cb_filter_skipped.Checked = AppSettings.Settings.HistoryFilterSkipped; + this.cb_filter_success.Checked = AppSettings.Settings.HistoryFilterRelevant; + this.cb_filter_vehicle.Checked = AppSettings.Settings.HistoryFilterVehicles; + this.HistoryUpdateListTimer.Interval = AppSettings.Settings.TimeBetweenListRefreshsMS; - //if a camera finally is associated with the inputted alert image - if (index != -1) - { + //--------------------------------------------------------------------------- + //CAMERAS TAB - //if camera is enabled - if (CameraList[index].enabled == true) - { + Global_GUI.ConfigureFOLV(this.FOLV_Cameras, typeof(Camera), new Font("Segoe UI", (float)9.75, FontStyle.Regular), CameraImageList, GridLines: false); - //if something was detected - if (response.predictions.Length > 0) - { - List objects = new List(); //list that will be filled with all objects that were detected and are triggering_objects for the camera - List objects_confidence = new List(); //list containing ai confidence value of object at same position in List objects - List objects_position = new List(); //list containing object positions (xmin, ymin, xmax, ymax) - List irrelevant_objects = new List(); //list that will be filled with all irrelevant objects - List irrelevant_objects_confidence = new List(); //list containing ai confidence value of irrelevant object at same position in List objects - List irrelevant_objects_position = new List(); //list containing irrelevant object positions (xmin, ymin, xmax, ymax) + this.comboBox_filter_camera.SelectedIndex = this.comboBox_filter_camera.FindStringExact("All Cameras"); //select all cameras entry + //--------------------------------------------------------------------------- + //SETTINGS TAB - int masked_counter = 0; //this value is incremented if an object is in a masked area - int threshold_counter = 0; // this value is incremented if an object does not satisfy the confidence limit requirements - int irrelevant_counter = 0; // this value is incremented if an irrelevant (but not masked or out of range) object is detected + this.LoadSettingsTab(); - Log("(4/6) Checking if detected object is relevant and within confidence limits:"); - //add all triggering_objects of the specific camera into a list and the correlating confidence levels into a second list - foreach (var user in response.predictions) - { - Log($" {user.label.ToString()} ({Math.Round((user.confidence * 100), 2).ToString() }%):"); - - using (var img = new Bitmap(image_path)) - { - bool irrelevant_object = false; - - //if object detected is one of the objects that is relevant - if (CameraList[index].triggering_objects_as_string.Contains(user.label)) - { - // -> OBJECT IS RELEVANT - - //if confidence limits are satisfied - if (user.confidence * 100 >= CameraList[index].threshold_lower && user.confidence * 100 <= CameraList[index].threshold_upper) - { - // -> OBJECT IS WITHIN CONFIDENCE LIMITS - - //only if the object is outside of the masked area - if (Outsidemask(CameraList[index].name, user.x_min, user.x_max, user.y_min, user.y_max, img.Width, img.Height)) - { - // -> OBJECT IS OUTSIDE OF MASKED AREAS - - objects.Add(user.label); - objects_confidence.Add(user.confidence); - string position = $"{user.x_min},{user.y_min},{user.x_max},{user.y_max}"; - objects_position.Add(position); - Log($" { user.label.ToString()} ({ Math.Round((user.confidence * 100), 2).ToString() }%) confirmed."); - } - else //if the object is in a masked area - { - masked_counter++; - irrelevant_object = true; - } - } - else //if confidence limits are not satisfied - { - threshold_counter++; - irrelevant_object = true; - } - } - else //if object is not relevant - { - irrelevant_counter++; - irrelevant_object = true; - } - - if (irrelevant_object == true) - { - irrelevant_objects.Add(user.label); - irrelevant_objects_confidence.Add(user.confidence); - string position = $"{user.x_min},{user.y_min},{user.x_max},{user.y_max}"; - irrelevant_objects_position.Add(position); - Log($" { user.label.ToString()} ({ Math.Round((user.confidence * 100), 2).ToString() }%) is irrelevant."); - } - } + //--------------------------------------------------------------------------- + //STATS TAB + this.comboBox1.Items.Add("All Cameras"); //add all cameras stats entry + this.comboBox1.SelectedIndex = this.comboBox1.FindStringExact("All Cameras"); //select all cameras entry - } - //if one or more objects were detected, that are 1. relevant, 2. within confidence limits and 3. outside of masked areas - if (objects.Count() > 0) - { - //store these last detections for the specific camera - CameraList[index].last_detections = objects; - CameraList[index].last_confidences = objects_confidence; - CameraList[index].last_positions = objects_position; + //--------------------------------------------------------------------------- + //Deepstack server TAB - //create summary string for this detection - StringBuilder detectionsTextSb = new StringBuilder(); - for (int i = 0; i < objects.Count(); i++) - { - detectionsTextSb.Append(String.Format("{0} ({1}%) | ", objects[i], Math.Round((objects_confidence[i] * 100), 2))); - } - if (detectionsTextSb.Length >= 3) - { - detectionsTextSb.Remove(detectionsTextSb.Length - 3, 3); - } - CameraList[index].last_detections_summary = detectionsTextSb.ToString(); - Log("The summary:" + CameraList[index].last_detections_summary); + if (!DeepStackServerControl.IsInstalled) + { + //remove deepstack tab if not installed + //Log("Removing DeepStack tab since it not installed as a Windows app (No docker support yet)"); + //this.tabControl1.TabPages.Remove(this.tabControl1.TabPages[""]); + } + else + { + if (DeepStackServerControl.NeedsSaving) + { + //this may happen if the already running instance has a different port, etc, so we update the config + this.SaveDeepStackTabAsync(); + } + this.LoadDeepStackTab(); + } + //--------------------------------------------------------------------------- + //LOG TAB - //RELEVANT ALERT - Log("(5/6) Performing alert actions:"); - await Trigger(index, image_path); //make TRIGGER - CameraList[index].IncrementAlerts(); //stats update - Log($"(6/6) SUCCESS."); + Global_GUI.ConfigureFOLV(this.folv_log, typeof(ClsLogItm), null, null, GridLines: false); + this.UpdateLogAddedRemovedAsync(true); + this.LogUpdateListTimer.Interval = AppSettings.Settings.TimeBetweenListRefreshsMS; + if (this.toolStripButtonPauseLog.Checked) + { + this.LogUpdateListTimer.Enabled = false; + this.LogUpdateListTimer.Stop(); + } + else + { + this.LogUpdateListTimer.Enabled = true; + this.LogUpdateListTimer.Start(); + } + this.UpdateLogAddedRemovedAsync(true); + this.tmr = new System.Timers.Timer(); + this.tmr.Interval = 300; + this.tmr.Elapsed += new System.Timers.ElapsedEventHandler(this.tmr_Elapsed); + this.tmr.Stop(); - //create text string objects and confidences - string objects_and_confidences = ""; - string object_positions_as_string = ""; - for (int i = 0; i < objects.Count; i++) - { - objects_and_confidences += $"{objects[i]} ({Math.Round((objects_confidence[i] * 100), 0)}%); "; - object_positions_as_string += $"{objects_position[i]};"; - } + this.ToolStripComboBoxSearch.Text = Global.GetRegSetting("SearchText", ""); + this.mnu_Filter.Checked = AppSettings.Settings.log_mnu_Filter; + this.mnu_Highlight.Checked = AppSettings.Settings.log_mnu_Highlight; - //add to history list - Log("Adding detection to history list."); - CreateListItem(Path.GetFileName(image_path), DateTime.Now.ToString("dd.MM.yy, HH:mm:ss"), CameraList[index].name, objects_and_confidences, object_positions_as_string); + if (string.Equals(AppSettings.Settings.LogLevel, "off", StringComparison.OrdinalIgnoreCase)) + { + this.mnu_log_filter_off.Checked = true; + } + else if (string.Equals(AppSettings.Settings.LogLevel, "fatal", StringComparison.OrdinalIgnoreCase)) + { + this.mnu_log_filter_fatal.Checked = true; + } + else if (string.Equals(AppSettings.Settings.LogLevel, "error", StringComparison.OrdinalIgnoreCase)) + { + this.mnu_log_filter_error.Checked = true; + } + else if (string.Equals(AppSettings.Settings.LogLevel, "warn", StringComparison.OrdinalIgnoreCase)) + { + this.mnu_log_filter_warn.Checked = true; + } + else if (string.Equals(AppSettings.Settings.LogLevel, "info", StringComparison.OrdinalIgnoreCase)) + { + this.mnu_log_filter_info.Checked = true; + } + else if (string.Equals(AppSettings.Settings.LogLevel, "debug", StringComparison.OrdinalIgnoreCase)) + { + this.mnu_log_filter_debug.Checked = true; + } + else if (string.Equals(AppSettings.Settings.LogLevel, "trace", StringComparison.OrdinalIgnoreCase)) + { + this.mnu_log_filter_trace.Checked = true; + } - } - //if no object fulfills all 3 requirements but there are other objects: - else if (irrelevant_objects.Count() > 0) - { - //IRRELEVANT ALERT - - - CameraList[index].IncrementIrrelevantAlerts(); //stats update - Log($"(6/6) Camera {CameraList[index].name} caused an irrelevant alert."); - Log("Adding irrelevant detection to history list."); - - //retrieve confidences and positions - string objects_and_confidences = ""; - string object_positions_as_string = ""; - for (int i = 0; i < irrelevant_objects.Count; i++) - { - objects_and_confidences += $"{irrelevant_objects[i]} ({Math.Round((irrelevant_objects_confidence[i] * 100), 0)}%); "; - object_positions_as_string += $"{irrelevant_objects_position[i]};"; - } - - //string text contains what is written in the log and in the history list - string text = ""; - if (masked_counter > 0)//if masked objects, add them - { - text += $"{masked_counter}x masked; "; - } - if (threshold_counter > 0)//if objects out of confidence range, add them - { - text += $"{threshold_counter}x not in confidence range; "; - } - if (irrelevant_counter > 0) //if other irrelevant objects, add them - { - text += $"{irrelevant_counter}x irrelevant; "; - } - - if (text != "") //remove last ";" - { - text = text.Remove(text.Length - 2); - } - - Log($"{text}, so it's an irrelevant alert."); - //add to history list - CreateListItem(Path.GetFileName(image_path), DateTime.Now.ToString("dd.MM.yy, HH:mm:ss"), CameraList[index].name, $"{text} : {objects_and_confidences}", object_positions_as_string); - } - } - //if no object was detected - else if (response.predictions.Length == 0) - { - // FALSE ALERT + folv_log.MouseWheel += ListScrollZoom; + folv_history.MouseWheel += ListScrollZoom; - CameraList[index].IncrementFalseAlerts(); //stats update - Log($"(6/6) Camera {CameraList[index].name} caused a false alert, nothing detected."); + //--------------------------------------------------------------------------- + // finish up - //add to history list - Log("Adding false to history list."); - CreateListItem(Path.GetFileName(image_path), DateTime.Now.ToString("dd.MM.yy, HH:mm:ss"), CameraList[index].name, "false alert", ""); - } - } + string AssemVer = Assembly.GetExecutingAssembly().GetName().Version.ToString(); + this.lbl_version.Text = $"Version {AssemVer} built on {Global.RetrieveLinkerTimestamp()}"; - //if camera is disabled. - else if (CameraList[index].enabled == false) - { - Log("(6/6) Selected camera is disabled."); - } + //--------------------------------------------------------------------------------------------------------- - } + IsLoading = false; - } - else if (response.success == false) //if nothing was detected - { - Log("ERROR: no response from AI Server"); - } - } + this.Resize += new System.EventHandler(this.Form1_Resize); //resize event to enable 'minimize to tray' - //load updated camera stats info in camera tab if a camera is selected - MethodInvoker LabelUpdate = delegate - { - if (list2.SelectedItems.Count > 0) - { - //load only stats from Camera.cs object + Log($"{{yellow}}APP START complete. Initialized in {this.StartupSW.Elapsed.TotalSeconds.ToString("##0.0")} seconds ({this.StartupSW.ElapsedMilliseconds}ms)"); - //all camera objects are stored in the list CameraList, so firstly the position (stored in the second column for each entry) is gathered - int i = CameraList.FindIndex(x => x.name == list2.SelectedItems[0].Text); + this.SplashScreen.Close(); + this.SplashScreen = null; - //load cameras stats - string stats = $"Alerts: {CameraList[i].stats_alerts.ToString()} | Irrelevant Alerts: {CameraList[i].stats_irrelevant_alerts.ToString()} | False Alerts: {CameraList[i].stats_false_alerts.ToString()}"; - lbl_camstats.Text = stats; - } + Global_GUI.RestoreWindowState(this); + this.LoadCameras(); //load camera list - }; - Invoke(LabelUpdate); - break; //end retries if code was successful - } - catch (Exception ex) - { - Log($"{ex.GetType().ToString()} | {ex.Message.ToString()} (code: {ex.HResult} )"); + if (Environment.CommandLine.IndexOf("/min", StringComparison.OrdinalIgnoreCase) == -1) + { + this.ShowForm(); + } - if (error == "loading image failed") //this was a file exception error - retry file access - { - if (attempts != 9) //failure at attempt 1-8 - { - Log($"Could not access file - will retry after {attempts * retry_delay} ms delay"); - } - else //last attempt failed - { - Log($"ERROR: Could not access image '{image_path}'."); - } - } - else //all other exceptions - { - Log($"ERROR: Processing the following image '{image_path}' failed. {error}"); - //upload the alert image which could not be analyzed to Telegram - if (send_errors == true) - { - await TelegramUpload(image_path); - } - break; //end retries - this was not a file access error - } - } - System.Threading.Thread.Sleep(retry_delay * attempts); - Log($"Retrying image processing - retry {attempts}"); - } - } + }); - /* - try + + } + + private void ListScrollZoom(object sender, MouseEventArgs e) + { + if (Control.ModifierKeys != Keys.Control) + return; + float delta = (e.Delta > 0 ? 2f : -2f); + FastObjectListView folv = ((FastObjectListView)sender); + folv.Font = new Font(folv.Font.FontFamily, folv.Font.Size + delta); + folv.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent); + } + private void LoadSettingsTab() + { + + // fill settings tab with stored settings + + this.cmbInput.Text = AppSettings.Settings.input_path; + this.cb_inputpathsubfolders.Checked = AppSettings.Settings.input_path_includesubfolders; + this.cmbInput.Items.Clear(); + foreach (string pth in AITOOL.BlueIrisInfo.ClipPaths) { - System.IO.File.Delete(image_path); + this.cmbInput.Items.Add(pth); + } - catch - { - Console.WriteLine($"ERROR: Could not delete {image_path} ."); - }*/ + //this.tbDeepstackUrl.Text = AppSettings.Settings.deepstack_url; + this.cb_DeepStackURLsQueued.Checked = AppSettings.Settings.deepstack_urls_are_queued; - } + this.Chk_AutoScroll.Checked = AppSettings.Settings.Autoscroll_log; - //call trigger urls - public void CallTriggerURLs(string[] trigger_urls) - { + this.tb_telegram_chatid.Text = String.Join(",", AppSettings.Settings.telegram_chatids); + this.tb_telegram_token.Text = AppSettings.Settings.telegram_token; + this.tb_telegram_cooldown.Text = AppSettings.Settings.telegram_cooldown_seconds.ToString(); + + this.cb_send_telegram_errors.Checked = AppSettings.Settings.send_telegram_errors; + this.cb_send_pushover_errors.Checked = AppSettings.Settings.send_pushover_errors; + this.cbStartWithWindows.Checked = AppSettings.Settings.startwithwindows; + this.cbMinimizeToTray.Checked = AppSettings.Settings.MinimizeToTray; + + this.tb_username.Text = AppSettings.Settings.DefaultUserName; + this.tb_password.Text = AppSettings.Settings.DefaultPasswordEncrypted.Decrypt(); + + this.tb_BlueIrisServer.Text = AppSettings.Settings.BlueIrisServer; - var client = new WebClient(); - foreach (string x in trigger_urls) + + if (AITOOL.BlueIrisInfo.Result == BlueIrisResult.Valid) { - try - { - Log($" trigger url: {x}"); - var content = client.DownloadString(x); - } - catch (Exception ex) + lbl_blueirisserver.Text = "BI Config: " + AITOOL.BlueIrisInfo.Result; + lbl_blueirisserver.Text += $"; WebServer is configured for {AITOOL.BlueIrisInfo.URL}"; + lbl_blueirisserver.ForeColor = Color.DodgerBlue; + if (Global.IsValidIPAddress(AppSettings.Settings.BlueIrisServer, out IPAddress foundip) && !AppSettings.Settings.BlueIrisServer.EqualsIgnoreCase(AITOOL.BlueIrisInfo.ServerName) && AppSettings.Settings.BlueIrisServer != "127.0.0.1") { - Log(ex.Message); - Log($"ERROR: Could not trigger URL '{x}', please check if '{x}' is correct and reachable."); + Log($"Warning: BlueIris Settings > Web Server > Local IP address is set to a different IP: AITOOL={AppSettings.Settings.BlueIrisServer}, BI={AITOOL.BlueIrisInfo.ServerName}"); } - } - - if (trigger_urls.Length > 1) + else if (!AppSettings.Settings.BlueIrisServer.IsEmpty()) { - Log($" -> {trigger_urls.Length} trigger URLs called."); + lbl_blueirisserver.Text = "BI Config: Error - " + AITOOL.BlueIrisInfo.Result; + lbl_blueirisserver.ForeColor = Color.MediumPurple; } else { - Log(" -> Trigger URL called."); + lbl_blueirisserver.Text = ""; } - } - //send image to Telegram - public async Task TelegramUpload(string image_path) + this.tb_Pushover_APIKey.Text = AppSettings.Settings.pushover_APIKey; + this.tb_Pushover_UserKey.Text = AppSettings.Settings.pushover_UserKey; + this.tb_Pushover_Cooldown.Text = AppSettings.Settings.pushover_cooldown_seconds.ToString(); + + } + private void tmr_Elapsed(object sender, ElapsedEventArgs e) { - if (telegram_chatid != "" && telegram_token != "") + if ((DateTime.Now - this.TimeSinceType).Milliseconds >= 600) { - //telegram upload sometimes fails - try + Global_GUI.InvokeIFRequired(this.toolStripStatusLabelHistoryItems.GetCurrentParent(), () => { - using (var image_telegram = System.IO.File.OpenRead(image_path)) + this.tmr.Stop(); + this.tmr.Enabled = false; + if (Global.IsRegexPatternValid(this.ToolStripComboBoxSearch.Text) || this.ToolStripComboBoxSearch.Text.Length == 0) { - var bot = new TelegramBotClient(telegram_token); - - //upload image to Telegram servers and send to first chat - Log($" uploading image to chat \"{telegram_chatids[0]}\""); - var message = await bot.SendPhotoAsync(telegram_chatids[0], new InputOnlineFile(image_telegram, "image.jpg")); - string file_id = message.Photo[0].FileId; //get file_id of uploaded image + this.ToolStripComboBoxSearch.ForeColor = Color.Blue; + if (this.ToolStripComboBoxSearch.FindStringExact(this.ToolStripComboBoxSearch.Text) == -1) + this.ToolStripComboBoxSearch.Items.Add(this.ToolStripComboBoxSearch.Text); - //share uploaded image with all remaining telegram chats (if multiple chat_ids given) using file_id - foreach (string chatid in telegram_chatids.Skip(1)) - { - Log($" uploading image to chat \"{chatid}\""); - await bot.SendPhotoAsync(chatid, file_id); - } - } - } - catch - { - Log($"ERROR: Could not upload image {image_path} to Telegram."); - //store image that caused an error in ./errors/ - if (!Directory.Exists("./errors/")) //if folder does not exist, create the folder - { - //create folder - DirectoryInfo di = Directory.CreateDirectory("./errors"); - Log("./errors/" + " dir created."); + Global_GUI.FilterFOLV(this.folv_log, this.ToolStripComboBoxSearch.Text, this.mnu_Filter.Checked); } - //save error image - using (var image = SixLabors.ImageSharp.Image.Load(image_path)) + else { - image.Save("./errors/" + "TELEGRAM-ERROR-" + Path.GetFileName(image_path) + ".jpg"); + this.ToolStripComboBoxSearch.ForeColor = Color.Red; } - } - + }); } } - //send text to Telegram - public async Task TelegramText(string text) + private async Task UpdateLogAddedRemovedAsync(bool Follow = false, bool Force = false) { - if (telegram_chatid != "" && telegram_token != "") + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + if (IsLoading) + return; + + if (!this.IsLogListUpdating && Force || + !this.IsLogListUpdating && + (this.tabControl1.SelectedTab == this.tabControl1.TabPages["tabLog"]) && + this.Visible && + !(this.WindowState == FormWindowState.Minimized) && + LogMan != null) { - //telegram upload sometimes fails - try - { - var bot = new Telegram.Bot.TelegramBotClient(telegram_token); - foreach (string chatid in telegram_chatids) - { - await bot.SendTextMessageAsync(chatid, text); - } + //run in another thread so gui doesnt freeze + //await Task.Run(() => + //{ + Stopwatch sw = Stopwatch.StartNew(); - } - catch + this.IsLogListUpdating = true; + + Global_GUI.InvokeIFRequired(this.folv_log, async () => { - if (send_errors == true && text.Contains("ERROR") || text.Contains("WARNING")) //if Error message originating from Log() methods can't be uploaded - { - send_errors = false; //shortly disable send_errors to ensure that the Log() does not try to send the 'Telegram upload failed' message via Telegram again (causing a loop) - Log($"ERROR: Could not send text \"{text}\" to Telegram."); - send_errors = true; + if (this.folv_log.Items.Count == 0) + this.folv_log.EmptyListMsg = "Loading log..."; + + List added = LogMan.GetRecentlyAdded(); + List removed = LogMan.GetRecentlyDeleted(); - //inform on main tab that Telegram upload failed - MethodInvoker LabelUpdate = delegate { lbl_errors.Text = "Can't upload error message to Telegram!"; }; - Invoke(LabelUpdate); + if (added.Count > 2000 || this.folv_log.Items.Count == 0) + { + //do it all in one update so it is faster: + using var cw = new Global_GUI.CursorWait(); + // run in another thread so gui doesnt freeze + await Task.Run(() => + { + Global_GUI.UpdateFOLV(this.folv_log, LogMan.Values, (Follow || AppSettings.Settings.Autoscroll_log), FullRefresh: true); + }); } else { - Log($"ERROR: Could not send text \"{text}\" to Telegram."); + if (removed.Count > 0) + this.folv_log.RemoveObjects(removed); + + if (added.Count > 0) + Global_GUI.UpdateFOLV(this.folv_log, added, (Follow || AppSettings.Settings.Autoscroll_log)); } - } - } - } - //trigger actions - public async Task Trigger(int index, string image_path) - { - //only trigger if cameras cooldown time since last detection has passed - if ((DateTime.Now - CameraList[index].last_trigger_time).TotalMinutes >= CameraList[index].cooldown_time) - { - //call trigger urls - if (CameraList[index].trigger_urls.Length > 0) - { - //replace url paramters with according values - string[] urls = new string[CameraList[index].trigger_urls.Count()]; - int c = 0; - //call urls - foreach (string url in CameraList[index].trigger_urls) + if (sw.ElapsedMilliseconds > 5000 && sw.ElapsedMilliseconds < 10000) { - try - { - urls[c] = url.Replace("[camera]", CameraList[index].name) - .Replace("[detection]", CameraList[index].last_detections.ElementAt(0)) //only gives first detection (maybe not most relevant one) - .Replace("[position]", CameraList[index].last_positions.ElementAt(0)) - .Replace("[confidence]", CameraList[index].last_confidences.ElementAt(0).ToString()) - .Replace("[detections]", string.Join(",", CameraList[index].last_detections)) - .Replace("[confidences]", string.Join(",", CameraList[index].last_confidences.ToString())) - .Replace("[imagepath]", image_path) //gives the full path of the image that caused the trigger - .Replace("[imagefilename]", Path.GetFileName(image_path)) //gives the image name of the image that caused the trigger - .Replace("[summary]", Uri.EscapeUriString(CameraList[index].last_detections_summary)); //summary text including all detections and confidences, p.e."person (91,53%)" - } - catch (Exception ex) - { - Log($"{ex.GetType().ToString()} | {ex.Message.ToString()} (code: {ex.HResult} )"); - } - - c++; + Log($"Debug: ---- Log window update took {sw.ElapsedMilliseconds}ms to load. {added.Count} added, {removed.Count} removed, {this.folv_history.Items.Count} total. Consider lowering the '{nameof(AppSettings.Settings.MaxGUILogItems)}' setting in JSON file (Currently {AppSettings.Settings.MaxGUILogItems}) "); + } + else if (sw.ElapsedMilliseconds > 10000) + { + Log($"Warn: ---- Log window update took {sw.ElapsedMilliseconds}ms to load. {added.Count} added, {removed.Count} removed, {this.folv_history.Items.Count} total. Consider lowering the '{nameof(AppSettings.Settings.MaxGUILogItems)}' setting in JSON file (Currently {AppSettings.Settings.MaxGUILogItems}) "); } - CallTriggerURLs(urls); - } + this.UpdateStats(); - //upload to telegram - if (CameraList[index].telegram_enabled) - { - Log(" Uploading image to Telegram..."); - await TelegramUpload(image_path); - Log(" -> Sent image to Telegram."); - } + }); + + //}); + + this.IsLogListUpdating = false; + } else { - //log that nothing was done - Log($" Camera {CameraList[index].name} is still in cooldown. Trigger URL wasn't called and no image will be uploaded to Telegram."); - } - - CameraList[index].last_trigger_time = DateTime.Now; //reset cooldown time every time an image contains something, even if no trigger was called (still in cooldown time) - - Task ignoredAwaitableResult = this.LastTriggerInfo(index, CameraList[index].cooldown_time); //write info to label + } } - - - - //check if detected object is outside the mask for the specific camera - public bool Outsidemask(string cameraname, double xmin, double xmax, double ymin, double ymax, int width, int height) + async Task UpdateHistoryAddedRemoved() { - Log($" Checking if object is outside privacy mask of {cameraname}:"); - Log(" Loading mask file..."); - try + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + //Log("===Enter"); + //this should be a quicker full list update + if (AppSettings.Settings.HistoryAutoRefresh && + !this.IsHistoryListUpdating && + this.tabControl1.SelectedIndex == 2 && + this.Visible && + !(this.WindowState == FormWindowState.Minimized) && + (DateTime.Now - this.LastListUpdate).TotalMilliseconds >= AppSettings.Settings.TimeBetweenListRefreshsMS && + this.DatabaseInitialized && + !IsLoading && + await HistoryDB.HasUpdates()) { - if (System.IO.File.Exists("./cameras/" + cameraname + ".png")) //only check if mask image exists + // run in another thread so gui doesnt freeze + await Task.Run(() => { - //load mask file (in the image all places that have color (transparency > 9 [0-255 scale]) are masked) - using (var mask_img = new Bitmap($"./cameras/{cameraname}.png")) - { - //if any coordinates of the object are outside of the mask image, th mask image must be too small. - if (mask_img.Width != width || mask_img.Height != height) - { - Log($"ERROR: The resolution of the mask './camera/{cameraname}.png' does not equal the resolution of the processed image. Skipping privacy mask feature. Image: {width}x{height}, Mask: {mask_img.Width}x{mask_img.Height}"); - return true; - } - Log(" Checking if the object is in a masked area..."); + this.IsHistoryListUpdating = true; + + Global_GUI.InvokeIFRequired(this.folv_history, () => + { - //relative x and y locations of the 9 detection points - double[] x_factor = new double[] { 0.25, 0.5, 0.75, 0.25, 0.5, 0.75, 0.25, 0.5, 0.75 }; - double[] y_factor = new double[] { 0.25, 0.25, 0.25, 0.5, 0.5, 0.5, 0.75, 0.75, 0.75 }; + //Log($"Debug: Updating list...({AddedHistoryItems.Count} added, {DeletedHistoryItems.Count} deleted)"); - int result = 0; //counts how many of the 9 points are outside of masked area(s) + //UpdateToolstrip("Updating list..."); - //check the transparency of the mask image in all 9 detection points - for (int i = 0; i < 9; i++) - { - //get image point coordinates (and converting double to int) - int x = (int)(xmin + (xmax - xmin) * x_factor[i]); - int y = (int)(ymin + (ymax - ymin) * y_factor[i]); + List added = HistoryDB.GetRecentlyAdded(); + List removed = HistoryDB.GetRecentlyDeleted(); - // Get the color of the pixel - Color pixelColor = mask_img.GetPixel(x, y); + if (removed.Count > 0) + this.folv_history.RemoveObjects(removed); - //if the pixel is transparent (A refers to the alpha channel), the point is outside of masked area(s) - if (pixelColor.A < 10) + if (added.Count > 0) + { + //find the last item that is unfiltered: + History lasthist = null; + for (int i = added.Count - 1; i >= 0; i--) { - result++; + if (this.checkListFilters(added[i])) + { + lasthist = added[i]; + break; + } } - } - Log($" { result.ToString() } of 9 detection points are outside of masked areas."); //print how many of the 9 detection points are outside of masked areas. + Global_GUI.UpdateFOLV(this.folv_history, added, AppSettings.Settings.HistoryFollow, UseSelected: true, SelectObject: lasthist); - if (result > 4) //if 5 or more of the 9 detection points are outside of masked areas, the majority of the object is outside of masked area(s) - { - Log(" ->The object is OUTSIDE of masked area(s)."); - return true; } - else //if 4 or less of 9 detection points are outside, then 5 or more points are in masked areas and the majority of the object is so too + + if (AppSettings.Settings.HistoryFollow && this.folv_history.SelectedObject == null && this.folv_history.GetItemCount() > 0) { - Log(" ->The object is INSIDE a masked area."); - return false; + if (this.folv_history.IsFiltering) + { + //use the last filtered object as the selected object + this.folv_history.SelectedObject = this.folv_history.FilteredObjects.Cast().Last(); + } + else if (!this.folv_history.IsFiltering) + { + this.folv_history.SelectedObject = this.folv_history.Objects.Cast().Last(); + } + + if (this.folv_history.SelectedObject != null) + this.folv_history.EnsureModelVisible(this.folv_history.SelectedObject); + } - } - } - else //if mask image does not exist, object is outside the non-existing masked area - { - Log(" ->Camera has no mask, the object is OUTSIDE of the masked area."); - return true; - } + this.LastListUpdate = DateTime.Now; + + this.IsHistoryListUpdating = false; + + //UpdateToolstrip(""); + + }); + + }); } - catch + else { - Log($"ERROR while loading the mask file ./cameras/{cameraname}.png."); - return true; + //Log($"Debug: List not updated - Refresh={AppSettings.Settings.HistoryAutoRefresh}, Visible={tabControl1.SelectedIndex == 2 && this.Visible && !(this.WindowState == FormWindowState.Minimized)}, IsListUpdating={this.IsListUpdating}, LastListUpdateMS={(DateTime.Now - this.LastListUpdate).TotalMilliseconds}"); } + //Log("===Exit"); + } - //save how many times an error happened - public void IncrementErrorCounter() + private void HistoryStartStop() { - errors++; - MethodInvoker LabelUpdate = delegate + if (AppSettings.Settings.HistoryAutoRefresh) + { + this.HistoryUpdateListTimer.Enabled = true; + this.HistoryUpdateListTimer.Start(); + Log("Debug: History update timer started."); + } + else { - lbl_errors.Show(); - lbl_errors.Text = $"{errors.ToString()} error(s) occured. Click to open Log."; //update error counter label - }; - Invoke(LabelUpdate); + this.HistoryUpdateListTimer.Enabled = false; + this.HistoryUpdateListTimer.Stop(); + Log("Debug: History update timer stopped."); + } } - //add text to log - public async void Log(string text) + async void EventMessage(ClsMessage msg) { + if (IsClosing) + return; - //if log everything is disabled and the text is neighter an ERROR, nor a WARNING: write only to console and ABORT - if (log_everything == false && !text.Contains("ERROR") && !text.Contains("WARNING")) + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + //output messages from the deepstack, blueiris, etc class to the text log window and log file + if (msg.MessageType == MessageType.LogEntry) { - text += "Enabling \'Log everything\' might give more information."; - Console.WriteLine($"{text}"); - return; + Log(msg.Description, ""); } + else if (msg.MessageType == MessageType.DatabaseInitialized) + { + Log("debug: Database initialized."); - //get current date and time + this.folv_history.EmptyListMsg = "No History"; - string time = DateTime.Now.ToString("dd.MM.yyyy, HH:mm:ss"); + this.DatabaseInitialized = true; + await this.LoadHistoryAsync(true, true); + this.HistoryStartStop(); - if (log_everything == true) - { - time = DateTime.Now.ToString("dd.MM.yyyy, HH:mm:ss.fff"); } + else if (msg.MessageType == MessageType.UpdateDeepstackStatus) + { + + Log($"debug: Deepstack control message '{msg.Description}'"); + LoadDeepStackTab(); - //if log file does not exist, create it - if (!System.IO.File.Exists("./log.txt")) + } + else if (msg.MessageType == MessageType.CreateHistoryItem) { - Console.WriteLine("ATTENTION: Creating log file."); - try + JsonSerializerSettings jset = new JsonSerializerSettings { }; + jset.TypeNameHandling = TypeNameHandling.All; + jset.PreserveReferencesHandling = PreserveReferencesHandling.Objects; + jset.ContractResolver = Global.JSONContractResolver; + + History hist = JsonConvert.DeserializeObject(msg.JSONPayload, jset); + + + if (!HistoryDB.ReadOnly) { - using (StreamWriter sw = new StreamWriter(AppDomain.CurrentDomain.BaseDirectory + "log.txt")) + bool StoreMasked = hist.Success || !hist.WasMasked || (hist.WasMasked && AppSettings.Settings.HistoryStoreMaskedAlerts); + bool StoreFalse = hist.Success || !(hist.Detections.IndexOf("false alert", StringComparison.OrdinalIgnoreCase) >= 0) || (hist.Detections.IndexOf("false alert", StringComparison.OrdinalIgnoreCase) >= 0 && AppSettings.Settings.HistoryStoreFalseAlerts); + bool Save = true; + if (!StoreMasked || !StoreFalse) + Save = false; + + if (Save) { - sw.WriteLine("Log format: [dd.MM.yyyy, HH:mm:ss]: Log text."); + HistoryDB.InsertHistoryQueue(hist); + } + else + { + Log($"debug: Not storing item in db - StoreMasked={StoreMasked}, StoreFalse={StoreFalse}: {hist.Detections}"); } - } - catch - { - MethodInvoker LabelUpdate = delegate { lbl_errors.Text = "Can't create log.txt file!"; }; - Invoke(LabelUpdate); } - } + //UpdateHistoryAddedRemoved(); - //add text to log - try + this.UpdateStats(); + } + else if (msg.MessageType == MessageType.DeleteHistoryItem) { - using (StreamWriter sw = new StreamWriter(AppDomain.CurrentDomain.BaseDirectory + "log.txt", append: true)) + + if (!HistoryDB.ReadOnly) //assume service or other instance will be handling { - sw.WriteLine($"[{time}]: {text}"); + HistoryDB.DeleteHistoryQueue(msg.Description); } + + //UpdateHistoryAddedRemoved(); + + this.UpdateStats(); + } - catch + else if (msg.MessageType == MessageType.ImageAddedToQueue) { - MethodInvoker LabelUpdate = delegate { lbl_errors.Text = "Can't write to log.txt file!"; }; - Invoke(LabelUpdate); + this.UpdateStats(); } + else if (msg.MessageType == MessageType.UpdateStatus) + { + this.UpdateStats(); + } + else if (msg.MessageType == MessageType.UpdateProgressBar) + { + + this.UpdateProgressBar(msg.Description, msg.CurVal, msg.MinVal, msg.MaxVal); - if (send_errors == true && text.Contains("ERROR") || text.Contains("WARNING")) + } + else if (msg.MessageType == MessageType.BeginProcessImage) + { + this.BeginProcessImage(msg.Description); + } + else if (msg.MessageType == MessageType.EndProcessImage) { - await TelegramText($"[{time}]: {text}"); //upload text to Telegram + this.EndProcessImage(msg.Description); + this.UpdateStats(); } + else if (msg.MessageType == MessageType.UpdateLabel) + { + string lblcontrolname = (string)Global.SetJSONString(msg.JSONPayload); + + if (!string.IsNullOrWhiteSpace(lblcontrolname)) + { + Label lbl = null; + try + { + lbl = this.Controls.Find(lblcontrolname, true).FirstOrDefault() as Label; + } + catch (Exception ex) + { + + Log($"Error: Could not find label '{lblcontrolname}': {ex.Msg()}"); + } + + if (lbl != null) + { + Global_GUI.InvokeIFRequired(lbl, () => + { + lbl.Show(); + lbl.Text = msg.Description; + }); + + } + else + { + Log($"Error: Could not find label '{lblcontrolname}'?"); + } + } + else + { + Log($"Error: No label name passed - '{msg.Description}'"); - //add log text to console - Console.WriteLine($"[{time}]: {text}"); + } - //increment error counter - if (text.Contains("ERROR") || text.Contains("WARNING")) + this.UpdateStats(); + } + else { - IncrementErrorCounter(); + Log($"Error: Unhandled message type '{msg.MessageType}'"); } - } - //update input path for fswatcher - public void UpdateFSWatcher() + private void UpdateProgressBar(string Message, int CurVal = -1, int MinVal = -1, int MaxVal = -1) { - try + + if (IsClosing) + return; + + if (this.SplashScreen != null) { - watcher.Path = input_path; + Global_GUI.InvokeIFRequired(this.SplashScreen.progressBar, () => + { + + if (MaxVal != -1 && this.SplashScreen.progressBar.Maximum != MaxVal) + this.SplashScreen.progressBar.Maximum = MaxVal; + + if (MinVal != -1 && this.SplashScreen.progressBar.Minimum != MinVal) + this.SplashScreen.progressBar.Minimum = MinVal; + + if (this.SplashScreen.progressBar.Style != ProgressBarStyle.Continuous) + this.SplashScreen.progressBar.Style = ProgressBarStyle.Continuous; + + if (CurVal == 1 && MinVal == 1 && MaxVal == 1) + { + this.SplashScreen.progressBar.Maximum = 2; + this.SplashScreen.progressBar.Value = this.SplashScreen.progressBar.Maximum; + + } + else + { + + if (CurVal > -1) + { + if (CurVal >= this.SplashScreen.progressBar.Minimum && CurVal <= this.SplashScreen.progressBar.Maximum) + this.SplashScreen.progressBar.Value = CurVal; + if (CurVal < this.SplashScreen.progressBar.Minimum) + this.SplashScreen.progressBar.Value = this.SplashScreen.progressBar.Minimum; + if (CurVal > this.SplashScreen.progressBar.Maximum) + this.SplashScreen.progressBar.Value = this.SplashScreen.progressBar.Maximum; + } + } + + string msg = Global.ReplaceCaseInsensitive(Message, "debug:", "").Trim(); + if (msg != this.SplashScreen.lbl_status.Text) + { + this.SplashScreen.lbl_status.Text = msg; + //SplashScreen.Refresh(); + } + + //SplashScreen.progressBar.Refresh(); + //Application.DoEvents(); //causes all sorts of issues + + }); + } - catch + else { - if (input_path == "") - { - Log("ATTENTION: No input folder defined."); - } - else + Global_GUI.InvokeIFRequired(this.toolStripProgressBar1.GetCurrentParent(), () => { - Log($"ERROR: Can't access input folder '{input_path}'."); - } + + if (MaxVal != -1 && this.toolStripProgressBar1.Maximum != MaxVal) + this.toolStripProgressBar1.Maximum = MaxVal; + + if (MinVal != -1 && this.toolStripProgressBar1.Minimum != MinVal) + this.toolStripProgressBar1.Minimum = MinVal; + + if (this.toolStripProgressBar1.Style != ProgressBarStyle.Continuous) + this.toolStripProgressBar1.Style = ProgressBarStyle.Continuous; + + if (CurVal == 1 && MinVal == 1 && MaxVal == 1) + { + this.toolStripProgressBar1.Maximum = 2; + this.toolStripProgressBar1.Value = this.toolStripProgressBar1.Maximum; + + } + else + { + + if (CurVal > -1) + { + if (CurVal >= this.toolStripProgressBar1.Minimum && CurVal <= this.toolStripProgressBar1.Maximum) + this.toolStripProgressBar1.Value = CurVal; + if (CurVal < this.toolStripProgressBar1.Minimum) + this.toolStripProgressBar1.Value = this.toolStripProgressBar1.Minimum; + if (CurVal > this.toolStripProgressBar1.Maximum) + this.toolStripProgressBar1.Value = this.toolStripProgressBar1.Maximum; + } + } + + if (this.toolStripProgressBar1.Value > 0 && !this.Visible) + { + this.Visible = true; + } + else if (this.toolStripProgressBar1.Value == 0 && this.Visible) + { + this.Visible = false; + } + + this.toolStripProgressBar1.GetCurrentParent().Refresh(); + //Application.DoEvents(); //causes all sorts of issues + + }); + + + this.UpdateStats(Message); } + } + //---------------------------------------------------------------------------------------------------------- + //CORE + //---------------------------------------------------------------------------------------------------------- + + + //add text to log + //public async void Log(string text, [CallerMemberName] string memberName = null) + //{ + + // if (IsClosing) + // return; + + // try + // { + + // //get current date and time + + // string time = DateTime.Now.ToString("dd.MM.yyyy, HH:mm:ss"); + // string rtftime = DateTime.Now.ToString("dHH:mm:ss"); //no need for date in log tab + // string ModName = ""; + // if (memberName == ".ctor") + // memberName = "Constructor"; + + // if (AppSettings.Settings.log_everything == true || AppSettings.Settings.deepstack_debug) + // { + // time = DateTime.Now.ToString("dd.MM.yyyy, HH:mm:ss.fff"); + // rtftime = DateTime.Now.ToString("HH:mm:ss.fff"); + // if (memberName != null && !string.IsNullOrEmpty(memberName)) + // ModName = memberName.PadLeft(24) + "> "; + + // //when the global logger reports back to the progress logger we cant use CallerMemberName, so extract the member name from text + + // int gg = text.IndexOf(">> "); + + // if (gg > 0 && gg <= 24) + // { + // string modfromglobal = Global.GetWordBetween(text, "", ">> "); + // if (!string.IsNullOrEmpty(modfromglobal)) + // { + // ModName = modfromglobal.PadLeft(24) + "> "; + // text = Global.GetWordBetween(text, ">> ", ""); + // } + + // } + // } + + // //check for messages coming from deepstack processes and kill them if we didnt ask for debugging messages + // if (!AppSettings.Settings.deepstack_debug) + // { + // if (text.ToLower().Contains("redis-server.exe>") || text.ToLower().Contains("python.exe>")) + // { + // return; + // } + // } + + // //make the error and warning detection case insensitive: + // bool HasError = (text.IndexOf("error", StringComparison.InvariantCultureIgnoreCase) > -1) || (text.IndexOf("exception", StringComparison.InvariantCultureIgnoreCase) > -1); + // bool HasWarning = (text.IndexOf("warning:", StringComparison.InvariantCultureIgnoreCase) > -1); + // bool HasInfo = (text.IndexOf("info:", StringComparison.InvariantCultureIgnoreCase) > -1); + // bool HasDebug = (text.IndexOf("debug:", StringComparison.InvariantCultureIgnoreCase) > -1); + // bool IsDeepStackMsg = (memberName.IndexOf("deepstack", StringComparison.InvariantCultureIgnoreCase) > -1) || (text.IndexOf("deepstack", StringComparison.InvariantCultureIgnoreCase) > -1) || (ModName.IndexOf("deepstack", StringComparison.InvariantCultureIgnoreCase) > -1); + + // string RTFText = ""; + + // //set the color for RTF text window: + // if (HasError) + // { + // RTFText = $"{{gray}}[{rtftime}]: {ModName}{{red}}{text}"; + // } + // else if (HasWarning) + // { + // RTFText = $"{{gray}}[{rtftime}]: {ModName}{{mediumorchid}}{text}"; + // } + // else if (IsDeepStackMsg) + // { + // RTFText = $"{{gray}}[{rtftime}]: {ModName}{{lime}}{text}"; + // } + // else if (HasInfo) + // { + // RTFText = $"{{gray}}[{rtftime}]: {ModName}{{yellow}}{text}"; + // } + // else if (HasDebug) + // { + // RTFText = $"{{gray}}[{rtftime}]: {ModName}{text}"; + // } + // else + // { + // RTFText = $"{{gray}}[{rtftime}]: {ModName}{{white}}{text}"; + // } + + // if (!AppSettings.AlreadyRunning) + // { + // Global.SaveSetting("LastLogEntry", RTFText); + // Global.SaveSetting("LastShutdownState", $"checkpoint: GUI.Log: {DateTime.Now}"); + // } + + // //get rid of any common color coding before logging to file or console + // text = text.Replace("{yellow}", "").Replace("{red}", "").Replace("{white}", "").Replace("{orange}", "").Replace("{lime}", "").Replace("{orange}", "mediumorchid"); + + // //if log everything is disabled and the text is neither an ERROR, nor a WARNING: write only to console and ABORT + // if (AppSettings.Settings.log_everything == false && !HasError && !HasWarning) + // { + // //Creates a lot of extra text in immediate window while debugging, disabling -Vorlon + // //text += "Enabling \'Log everything\' might give more information."; + // Console.WriteLine($"[{rtftime}]: {ModName}{text}"); + + // return; + // } + + + + // RTFLogger.LogToRTF(RTFText); + // LogWriter.WriteToLog($"[{time}]: {ModName}{text}", HasError); + + + + // //add log text to console + + + // } + // catch (Exception ex) + // { + + // Console.WriteLine("Error: In LOG, got: " + ex.Message); + // } + + //} + + + //---------------------------------------------------------------------------------------------------------- //GUI //---------------------------------------------------------------------------------------------------------- @@ -900,106 +987,93 @@ public void UpdateFSWatcher() //minimize to tray private void Form1_Resize(object sender, EventArgs e) { + + if (IsLoading) + return; + + if (IsClosing) + return; + if (this.WindowState == FormWindowState.Minimized) { - this.Hide(); - notifyIcon.Visible = true; - } - else - { - ResizeListViews(); + HideForm(); } + + UpdateErrorIcon(); + } //open from tray private void notifyIcon_MouseDoubleClick(object sender, MouseEventArgs e) { - this.Show(); - this.WindowState = FormWindowState.Normal; - notifyIcon.Visible = false; + ShowForm(); } - //open Log when clicking or error message - private void lbl_errors_Click(object sender, EventArgs e) + //protected override void SetVisibleCore(bool value) + //{ + // base.SetVisibleCore(AllowShowDisplay ? value : AllowShowDisplay); + //} + + private async void ShowForm() { - if (System.IO.File.Exists("log.txt")) - { - System.Diagnostics.Process.Start("log.txt"); - lbl_errors.Text = ""; - } - else + + if (IsClosing) + return; + + try { - MessageBox.Show("log missing"); - } + //this.AllowShowDisplay = true; + this.Opacity = 100; + this.TopMost = true; + this.TopMost = false; + this.Show(); + this.WindowState = FormWindowState.Normal; + this.ShowInTaskbar = true; + this.notifyIcon.Visible = false; + this.LoadHistoryAsync(true, true); + this.UpdateLogAddedRemovedAsync(true, true); + this.Activate(); + Application.DoEvents(); + } + catch { } } - //adapt list views (history tab and cameras tab) to window size while considering scrollbar influence - private void ResizeListViews() + private async void HideForm() { - //suspend layout of most complex tablelayout elements (gives a few milliseconds) - tableLayoutPanel7.SuspendLayout(); - tableLayoutPanel8.SuspendLayout(); - tableLayoutPanel9.SuspendLayout(); - //variable storing list1 effective width - int width = list1.Width; + if (IsClosing) + return; - //subtract vertical scrollbar width if scrollbar is shown (scrollbar is shown when there are more items(including the header row) than fit in the visible space of the list) - try - { - if (list1.Items.Count > 0 && list1.Height <= (list1.GetItemRect(0).Height * (list1.Items.Count + 1))) - { - width -= SystemInformation.VerticalScrollBarWidth; - } - } - catch - { - Log("ERROR in ReziseListViews(), checking if scrollbar is shown and subtracting scrollbar width failed."); - } + if (!AppSettings.Settings.MinimizeToTray) + return; - if (width > 350) // if the list is wider than 350px, aditionally show the 'detections' column and mainly grow this column + try { - //set left list column width segmentation - list1.Columns[0].Width = width * 0 / 100; //filename - list1.Columns[1].Width = 120 + (width - 350) * 25 / 1000; //date - list1.Columns[2].Width = 120 + (width - 350) * 25 / 1000; //cam name - list1.Columns[3].Width = 80 + (width - 350) * 95 / 100; //obj and confidences - list1.Columns[4].Width = width * 0 / 100; // object positions of all detected objects separated by ";" - list1.Columns[5].Width = 30; //checkmark if something relevant detected or not + this.Hide(); + this.Opacity = 0; + this.notifyIcon.Visible = true; + //Log($"Debug: Hiding app. Visible={this.Visible}, state={this.WindowState}, tbicon={this.ShowInTaskbar}, trayicon={this.notifyIcon.Visible}"); + Application.DoEvents(); } - else //if the form is smaller than 350px in width, don't show the detections column - { - //set left list column width segmentation - list1.Columns[0].Width = width * 0 / 100; //filename - list1.Columns[1].Width = width * 47 / 100; //date - list1.Columns[2].Width = width * 43 / 100; //cam name - list1.Columns[3].Width = width * 0 / 100; //obj and confidences - list1.Columns[4].Width = width * 0 / 100; // object positions of all detected objects separated by ";" - list1.Columns[5].Width = width * 10 / 100; //checkmark if something relevant detected or not - } + catch { } - list2.Columns[0].Width = list2.Width - 4; //resize camera list column + } + //open Log when clicking or error message + private void lbl_errors_Click(object sender, EventArgs e) + { + this.ShowErrors(); - //resume layout again - tableLayoutPanel7.ResumeLayout(); - tableLayoutPanel8.ResumeLayout(); - tableLayoutPanel9.ResumeLayout(); } - //add last trigger time to label on Overview page - private async Task LastTriggerInfo(int index, double minutes) + private void ShowErrors() { - string text1 = $"{CameraList[index].name} last triggered at {CameraList[index].last_trigger_time}. Sleeping for {minutes / 2} minutes."; //write last trigger time to label on Overview page - lbl_info.Text = text1; - int time = 30 * Convert.ToInt32(1000 * minutes); - await Task.Delay(time); // wait while the analysis is sleeping for this camera - if (lbl_info.Text == text1) - { - lbl_info.Text = $"{CameraList[index].name} last triggered at {CameraList[index].last_trigger_time}."; //Remove "sleeping for ..." - } + //filter the main log list for errors + this.chk_filterErrors.Checked = true; + this.tabControl1.SelectedTab = this.tabLog; + this.FilterLogErrors(); } @@ -1008,20 +1082,55 @@ private async Task LastTriggerInfo(int index, double minutes) //event: mouse click on tab control private void tabControl1_MouseDown(object sender, MouseEventArgs e) { - ResizeListViews(); } //event: another tab selected (Only load certain things in tabs if they are actually open) - private void tabControl1_SelectedIndexChanged(object sender, EventArgs e) + private async void tabControl1_SelectedIndexChanged(object sender, EventArgs e) { - if (tabControl1.SelectedIndex == 1) + //Application.DoEvents(); + using var Trace = new Trace(); + + try { - UpdatePieChart(); UpdateTimeline(); UpdateConfidenceChart(); + if (this.tabControl1.SelectedIndex == 1) + { + this.UpdatePieChart(); this.UpdateTimeline(); this.UpdateConfidenceChart(); + } + else if (this.tabControl1.SelectedIndex == 2) + { + await this.LoadHistoryAsync(true, true); + } + else if (this.tabControl1.SelectedIndex == 3) + { + this.LoadCameras(); + } + else if (this.tabControl1.SelectedIndex == 4) + { + this.LoadSettingsTab(); + } + else if (this.tabControl1.SelectedTab == this.tabControl1.TabPages["tabDeepStack"]) + { + this.LoadDeepStackTab(); + } + else if (this.tabControl1.SelectedTab == this.tabControl1.TabPages["tabLog"]) + { + //scroll to bottom, only when tab is active for better performance + if (!this.toolStripButtonPauseLog.Checked) + { + await this.UpdateLogAddedRemovedAsync(true, true); + } + } + UpdateStats(); + } - else if (tabControl1.SelectedIndex == 2) + catch (Exception ex) { - //CleanCSVList(); //removed to load the history list faster + string err = $"Error: While changing to tab {this.tabControl1.SelectedTab.Name}, got error: {ex.Msg()}"; + Log(err); + MessageBox.Show(err); + } + } @@ -1032,196 +1141,246 @@ private void tabControl1_SelectedIndexChanged(object sender, EventArgs e) //other camera in combobox selected, display according PieChart private void comboBox1_SelectedIndexChanged_1(object sender, EventArgs e) { - if (tabControl1.SelectedIndex == 1) - { - UpdatePieChart(); UpdateTimeline(); UpdateConfidenceChart(); - } } //update pie chart public void UpdatePieChart() { + + if (this.tabControl1.SelectedIndex != 1 || !this.Visible || this.WindowState == FormWindowState.Minimized || string.IsNullOrEmpty(this.comboBox1.Text)) + return; + int alerts = 0; int irrelevantalerts = 0; int falsealerts = 0; + int skipped = 0; + Series ser = this.chart1.Series[0]; + - if (comboBox1.Text == "All Cameras") + if (this.comboBox1.Text == "All Cameras") { - foreach (Camera cam in CameraList) + foreach (Camera cam in AppSettings.Settings.CameraList) { alerts += cam.stats_alerts; irrelevantalerts += cam.stats_irrelevant_alerts; falsealerts += cam.stats_false_alerts; + skipped += cam.stats_skipped_images; } } else { - int i = CameraList.FindIndex(x => x.name == comboBox1.Text.Substring(3)); - alerts = CameraList[i].stats_alerts; - irrelevantalerts = CameraList[i].stats_irrelevant_alerts; - falsealerts = CameraList[i].stats_false_alerts; + + Camera cam = AITOOL.GetCamera(this.comboBox1.Text); //int i = AppSettings.Settings.CameraList.FindIndex(x => x.name.ToLower().Trim() == comboBox1.Text.ToLower().Trim()); + if (cam != null) + { + alerts = cam.stats_alerts; + irrelevantalerts = cam.stats_irrelevant_alerts; + falsealerts = cam.stats_false_alerts; + skipped = cam.stats_skipped_images; + } + else + { + alerts = 0; + irrelevantalerts = 0; + falsealerts = 0; + skipped = 0; + Log($"Error: Could not match combobox dropdown '{this.comboBox1.Text}' to a known camera name?"); + } } - chart1.Series[0].Points.Clear(); + ser.Points.Clear(); + ser.IsVisibleInLegend = true; - chart1.Series[0].LegendText = "#VALY #VALX"; - chart1.Series[0]["PieLabelStyle"] = "Disabled"; + + ser.LegendText = "#LABEL"; //"#VALY"; //"#VALY #VALX" + //ser["PieLabelStyle"] = "Disabled"; int index = -1; //show Alerts label - index = chart1.Series[0].Points.AddXY("Alerts", alerts); - chart1.Series[0].Points[index].Color = Color.Green; + index = ser.Points.AddXY("Alerts", alerts); + ser.Points[index].Color = System.Drawing.Color.Green; + ser.Points[index].LegendText = $"{alerts.ToString("00000")} - Alerts"; + ser.Points[index].Label = "Alerts"; //show irrelevant Alerts label - index = chart1.Series[0].Points.AddXY("irrelevant Alerts", irrelevantalerts); - chart1.Series[0].Points[index].Color = Color.Orange; + index = ser.Points.AddXY("Irrelevant Alerts", irrelevantalerts); + ser.Points[index].Color = System.Drawing.Color.Orange; + ser.Points[index].LegendText = $"{irrelevantalerts.ToString("00000")} - Irrelevant Alerts"; + ser.Points[index].Label = "Irrelevant"; //show false Alerts label - index = chart1.Series[0].Points.AddXY("false Alerts", falsealerts); - chart1.Series[0].Points[index].Color = Color.OrangeRed; + index = ser.Points.AddXY("False Alerts", falsealerts); + ser.Points[index].Color = System.Drawing.Color.OrangeRed; + ser.Points[index].LegendText = $"{falsealerts.ToString("00000")} - False Alerts"; + ser.Points[index].Label = "False"; + + //show skipped label + index = ser.Points.AddXY("Skipped Images", skipped); + ser.Points[index].Color = System.Drawing.Color.Purple; + ser.Points[index].LegendText = $"{skipped.ToString("00000")} - Skipped Images"; + ser.Points[index].Label = "Skipped"; + + + } //update timeline public void UpdateTimeline() { - Log("Loading time line from cameras/history.csv ..."); + + if (this.tabControl1.SelectedIndex != 1 || !this.Visible || this.WindowState == FormWindowState.Minimized || string.IsNullOrEmpty(this.comboBox1.Text)) + return; + + Log("Debug: Loading time line from history database ..."); //clear previous values - timeline.Series[0].Points.Clear(); - timeline.Series[1].Points.Clear(); - timeline.Series[2].Points.Clear(); - timeline.Series[3].Points.Clear(); + this.timeline.Series[0].Points.Clear(); + this.timeline.Series[1].Points.Clear(); + this.timeline.Series[2].Points.Clear(); + this.timeline.Series[3].Points.Clear(); + this.timeline.Series[4].Points.Clear(); - List result = new List(); //List that later on will be containing all lines of the csv file + Stopwatch SW = Stopwatch.StartNew(); - if (comboBox1.Text == "All Cameras") //all cameras selected + try { - //load all lines except the first line - foreach (string line in System.IO.File.ReadAllLines(@"cameras/history.csv").Skip(1)) + List result = HistoryDB.GetAllValues(); + + if (!string.Equals(this.comboBox1.Text.Trim(), "All Cameras", StringComparison.OrdinalIgnoreCase)) //all cameras selected { - result.Add(line); + result = result.Where(hist => hist.Camera.StartsWith(this.comboBox1.Text.Trim(), StringComparison.OrdinalIgnoreCase)).ToList(); } - } - else //camera selection - { - string cameraname = comboBox1.Text.Substring(3); - //load all lines from the history.csv except the first line into List (the first line is the table heading and not an alert entry) - foreach (string line in System.IO.File.ReadAllLines(@"cameras/history.csv").Skip(1)) - { - if (line.Split('|')[2] == cameraname) + //every int represents the number of ai calls in successive half hours (p.e. relevant[0] is 0:00-0:30 o'clock, relevant[1] is 0:30-1:00 o'clock) + int[] all = new int[48]; + int[] falses = new int[48]; + int[] irrelevant = new int[48]; + int[] relevant = new int[48]; + int[] skipped = new int[48]; + + //fill arrays with amount of calls/half hour + foreach (History hist in result) + { //example of time column entry: 23.08.19, 18:31:09 + //get hour + int hour = hist.Date.Hour; + + //get minute + int minute = hist.Date.Minute; + + int halfhour; //stores the half hour in which the alert occurred + + //add +1 to counter for corresponding half-hour + if (minute >= 30) //if alert occurred after half o clock { - result.Add(line); + halfhour = hour * 2 + 1; + } + else //if alert occured before half o clock + { + halfhour = hour * 2; + } + + //if detection was successful + if (hist.Success) + { + relevant[halfhour]++; + } + //if it was a false alert + else if (string.Equals(hist.Detections, "false alert", StringComparison.OrdinalIgnoreCase)) + { + falses[halfhour]++; + } + //if something irrelevant was detected + else + { + irrelevant[halfhour]++; } - } - } - //every int represents the number of ai calls in successive half hours (p.e. relevant[0] is 0:00-0:30 o'clock, relevant[1] is 0:30-1:00 o'clock) - int[] all = new int[48]; - int[] falses = new int[48]; - int[] irrelevant = new int[48]; - int[] relevant = new int[48]; + if (hist.WasSkipped) + skipped[halfhour]++; - //fill arrays with amount of calls/half hour - foreach (var val in result) - { //example of time column entry: 23.08.19, 18:31:09 - //get hour - string hourstring = val.Split('|')[1].Split(',')[1].Split(':')[0]; - int hour; - Int32.TryParse(hourstring, out hour); + all[halfhour]++; + } - //get minute - string minutestring = val.Split('|')[1].Split(',')[1].Split(':')[1]; - int minute; - Int32.TryParse(minutestring, out minute); + //add to graph "all": - int halfhour; //stores the half hour in which the alert occured + /*the graph will have a gap at the end and at the beginning if we don'f specify a value + * with an x value outside the visible area at the end and before the first visible point. + * So the first point is at -0.25 and has the value of the last visible point and the + * last point is at 24.25 and has the value of the first visible point. */ - //add +1 to counter for corresponding half-hour - if (minute > 30) //if alert occured after half o clock - { - halfhour = hour * 2 + 1; - } - else //if alert occured before half o clock - { - halfhour = hour * 2; - } + this.timeline.Series[0].Points.AddXY(-0.25, all[47]); // beginning point with value of last visible point - //if detection was successful - if (val.Split('|')[5] == "true") + //and now add all visible points + double x = 0.25; + foreach (int halfhour in all) { - relevant[halfhour]++; + int index = this.timeline.Series[0].Points.AddXY(x, halfhour); + x = x + 0.5; } - //if it was a false alert - else if (val.Split('|')[3] == "false alert") + + this.timeline.Series[0].Points.AddXY(24.25, all[0]); // finally add last point with value of first visible point + + //add to graph "falses": + + this.timeline.Series[1].Points.AddXY(-0.25, falses[47]); // beginning point with value of last visible point + //and now add all visible points + x = 0.25; + foreach (int halfhour in falses) { - falses[halfhour]++; + int index = this.timeline.Series[1].Points.AddXY(x, halfhour); + x = x + 0.5; } - //if something irrelevant was detected - else + this.timeline.Series[1].Points.AddXY(24.25, falses[0]); // finally add last point with value of first visible point + + //add to graph "irrelevant": + + this.timeline.Series[2].Points.AddXY(-0.25, irrelevant[47]); // beginning point with value of last visible point + //and now add all visible points + x = 0.25; + foreach (int halfhour in irrelevant) { - irrelevant[halfhour]++; + int index = this.timeline.Series[2].Points.AddXY(x, halfhour); + x = x + 0.5; } + this.timeline.Series[2].Points.AddXY(24.25, irrelevant[0]); // finally add last point with value of first visible point - all[halfhour]++; - } + //add to graph "relevant": - //add to graph "all": + this.timeline.Series[3].Points.AddXY(-0.25, relevant[47]); // beginning point with value of last visible point + //and now add all visible points + x = 0.25; + foreach (int halfhour in relevant) + { + int index = this.timeline.Series[3].Points.AddXY(x, halfhour); + x = x + 0.5; + } + this.timeline.Series[3].Points.AddXY(24.25, relevant[0]); // finally add last point with value of first visible point - /*the graph will have a gap at the end and at the beginning if we don'f specify a value - * with an x value outside the visible area at the end and before the first visible point. - * So the first point is at -0.25 and has the value of the last visible point and the - * last point is at 24.25 and has the value of the first visible point. */ - timeline.Series[0].Points.AddXY(-0.25, all[47]); // beginning point with value of last visible point + //add to graph "skipped": - //and now add all visible points - double x = 0.25; - foreach (int halfhour in all) - { - int index = timeline.Series[0].Points.AddXY(x, halfhour); - x = x + 0.5; - } + this.timeline.Series[4].Points.AddXY(-0.25, skipped[47]); // beginning point with value of last visible point + //and now add all visible points + x = 0.25; + foreach (int halfhour in skipped) + { + int index = this.timeline.Series[4].Points.AddXY(x, halfhour); + x = x + 0.5; + } + this.timeline.Series[4].Points.AddXY(24.25, skipped[0]); // finally add last point with value of first visible point - timeline.Series[0].Points.AddXY(24.25, all[0]); // finally add last point with value of first visible point - //add to graph "falses": - timeline.Series[1].Points.AddXY(-0.25, falses[47]); // beginning point with value of last visible point - //and now add all visible points - x = 0.25; - foreach (int halfhour in falses) - { - int index = timeline.Series[1].Points.AddXY(x, halfhour); - x = x + 0.5; } - timeline.Series[1].Points.AddXY(24.25, falses[0]); // finally add last point with value of first visible point - - //add to graph "irrelevant": - - timeline.Series[2].Points.AddXY(-0.25, irrelevant[47]); // beginning point with value of last visible point - //and now add all visible points - x = 0.25; - foreach (int halfhour in irrelevant) + catch (Exception ex) { - int index = timeline.Series[2].Points.AddXY(x, halfhour); - x = x + 0.5; + + Log("Error: " + ex.Msg()); } - timeline.Series[2].Points.AddXY(24.25, irrelevant[0]); // finally add last point with value of first visible point - //add to graph "relevant": - timeline.Series[3].Points.AddXY(-0.25, relevant[47]); // beginning point with value of last visible point - //and now add all visible points - x = 0.25; - foreach (int halfhour in relevant) - { - int index = timeline.Series[3].Points.AddXY(x, halfhour); - x = x + 0.5; - } - timeline.Series[3].Points.AddXY(24.25, relevant[0]); // finally add last point with value of first visible point } @@ -1229,1212 +1388,4251 @@ public void UpdateTimeline() //update confidence_frequency chart public void UpdateConfidenceChart() { - Log("Loading confidence-frequency chart from cameras/history.csv ..."); + + if (this.tabControl1.SelectedIndex != 1 || !this.Visible || this.WindowState == FormWindowState.Minimized || string.IsNullOrEmpty(this.comboBox1.Text)) + return; + + + Log("Debug: Loading confidence-frequency chart from history database ..."); //clear previous values - chart_confidence.Series[0].Points.Clear(); - chart_confidence.Series[1].Points.Clear(); + this.chart_confidence.Series[0].Points.Clear(); + this.chart_confidence.Series[1].Points.Clear(); - List result = new List(); //List that later on will be containing all lines of the csv file - if (comboBox1.Text == "All Cameras") //all cameras selected + Stopwatch SW = Stopwatch.StartNew(); + + try { - //load all lines except the first line - foreach (string line in System.IO.File.ReadAllLines(@"cameras/history.csv").Skip(1)) + List result = HistoryDB.GetAllValues(); + + if (!string.Equals(this.comboBox1.Text.Trim(), "All Cameras", StringComparison.OrdinalIgnoreCase)) //all cameras selected { - result.Add(line); + result = result.Where(hist => hist.Camera.StartsWith(this.comboBox1.Text.Trim(), StringComparison.OrdinalIgnoreCase)).ToList(); } - } - else //camera selection - { - string cameraname = comboBox1.Text.Substring(3); - //load all lines from the history.csv except the first line into List (the first line is the table heading and not an alert entry) - foreach (string line in System.IO.File.ReadAllLines(@"cameras/history.csv").Skip(1)) + //this array stores the Absolute frequencies of all possible confidence values (0%-100%) + int[] green_values = new int[101]; + int[] orange_values = new int[101]; + + //fill array with frequencies + foreach (History hist in result) { - if (line.Split('|')[2] == cameraname) + //example of detections column entry: "person (41%); person (97%);" or "masked: person (41%); person (97%);" + string detections_column = hist.Detections; + if (detections_column.Contains(':')) { - result.Add(line); + detections_column = detections_column.Split(':')[1]; - } - } - } + string[] detections = detections_column.Split(';'); - //this array stores the Absolute frequencies of all possible confidence values (0%-100%) - int[] green_values = new int[101]; - int[] orange_values = new int[101]; + //write the confidence of every detection into the green_values string + foreach (string detection in detections) + { + int x_value = Global.GetNumberInt(detection); // gets a number anywhere in the string - //fill array with frequencies - foreach (var line in result) + //fix temp bug from earlier where whole number percentages were multiplied by 100 when they shouldnt have been. + if (x_value > 500) + x_value = x_value / 100; + + if (x_value > 0 && x_value <= orange_values.Count() - 1) + { + //example: -> "person (41%)" + //Int32.TryParse(detection.Split('(')[1].Split('%')[0], out int x_value); //example: -> "41" + orange_values[x_value]++; + } + } + } + else + { + string[] detections = detections_column.Split(';'); + + //write the confidence of every detection into the green_values string + foreach (string detection in detections) + { + int x_value = Global.GetNumberInt(detection); // gets first number anywhere in the string - TODO: May not be correct + if (x_value > 0 && x_value <= green_values.Count() - 1) + { + //example: -> "person (41%)" + //Int32.TryParse(detection.Split('(')[1].Split('%')[0], out int x_value); //example: -> "41" + green_values[x_value]++; + } + } + } + } + + + //write green series in chart + int i = 0; + foreach (int y_value in green_values) + { + this.chart_confidence.Series[1].Points.AddXY(i, y_value); + i++; + } + + //write orange series in chart + i = 0; + foreach (int y_value in orange_values) + { + this.chart_confidence.Series[0].Points.AddXY(i, y_value); + i++; + } + + + + } + catch (Exception ex) + { + + Log("Error: " + ex.Msg()); + } + + + } + + + private void showHideMask() + { + if (this.cb_showMask.Checked == true) //show overlay { - //example of detections column entry: "person (41%); person (97%);" or "masked: person (41%); person (97%);" - string detections_column = line.Split('|')[3]; - if (detections_column.Contains(':')) + //Log("Show mask toggled."); + + if (this.folv_history.SelectedObjects != null && this.folv_history.SelectedObjects.Count > 0) { - detections_column = detections_column.Split(':')[1]; + History hist = (History)this.folv_history.SelectedObjects[0]; - string[] detections = detections_column.Split(';'); + string imagefile = AITOOL.GetMaskFile(hist.Camera); - //write the confidence of every detection into the green_values string - foreach (string detection in detections) + if (File.Exists(imagefile)) { - if (detection.Contains('%')) + using (var img = new Bitmap(imagefile)) { - //example: -> "person (41%)" - Int32.TryParse(detection.Split('(')[1].Split('%')[0], out int x_value); //example: -> "41" - orange_values[x_value]++; + this.pictureBox1.Image = new Bitmap(img); //load mask as overlay } } + else + { + this.pictureBox1.Image = null; //if file does not exist, empty mask overlay (from possible overlays of previous images) + } + } - else + + } + else //if showmask toggle-button is not checked, hide the mask overlay + { + this.pictureBox1.Image = null; + } + + } + + //show rectangle overlay + // private void showObject(PaintEventArgs e, double _xmin, double _ymin, double _xmax, double _ymax, string text, ResultType result) + // { + // try + // { + // if (this.folv_history.SelectedObjects != null && this.folv_history.SelectedObjects.Count > 0 && (this.pictureBox1 != null) && (this.pictureBox1.BackgroundImage != null)) + // { + + // System.Drawing.Color color = new System.Drawing.Color(); + // int BorderWidth = AppSettings.Settings.RectBorderWidth + //; + + // if (result == ResultType.Relevant) + // { + // color = System.Drawing.Color.FromArgb(AppSettings.Settings.RectRelevantColorAlpha, AppSettings.Settings.RectRelevantColor); + // } + // else if (result == ResultType.DynamicMasked || result == ResultType.ImageMasked || result == ResultType.StaticMasked) + // { + // color = System.Drawing.Color.FromArgb(AppSettings.Settings.RectMaskedColorAlpha, AppSettings.Settings.RectMaskedColor); + // } + // else + // { + // color = System.Drawing.Color.FromArgb(AppSettings.Settings.RectIrrelevantColorAlpha, AppSettings.Settings.RectIrrelevantColor); + // } + + // //1. get the padding between the image and the picturebox border + + // //get dimensions of the image and the picturebox + // double imgWidth = this.pictureBox1.BackgroundImage.Width; + // double imgHeight = this.pictureBox1.BackgroundImage.Height; + // double boxWidth = this.pictureBox1.Width; + // double boxHeight = this.pictureBox1.Height; + // //double clnWidth = this.pictureBox1.ClientSize.Width; + // //double clnHeight = this.pictureBox1.ClientSize.Height; + // //double rctWidth = this.pictureBox1.ClientRectangle.Width; + // //double rctHeight = this.pictureBox1.ClientRectangle.Height; + + // //these variables store the padding between image border and picturebox border + // double absX = 0; + // double absY = 0; + + // //because the sizemode of the picturebox is set to 'zoom', the image is scaled down + // double scale = 1; + + + // //Comparing the aspect ratio of both the control and the image itself. + // if (imgWidth / imgHeight > boxWidth / boxHeight) //if the image is p.e. 16:9 and the picturebox is 4:3 + // { + // scale = boxWidth / imgWidth; //get scale factor + // absY = (boxHeight - scale * imgHeight) / 2; //padding on top and below the image + // } + // else //if the image is p.e. 4:3 and the picturebox is widescreen 16:9 + // { + // scale = boxHeight / imgHeight; //get scale factor + // absX = (boxWidth - scale * imgWidth) / 2; //padding left and right of the image + // } + + // //2. inputted position values are for the original image size. As the image is probably smaller in the picturebox, the positions must be adapted. + // double xmin = (scale * _xmin) + absX; + // double xmax = (scale * _xmax) + absX; + // double ymin = (scale * _ymin) + absY; + // double ymax = (scale * _ymax) + absY; + + // double sclWidth = xmax - xmin; + // double sclHeight = ymax - ymin; + + // double sclxmax = boxWidth - (absX * 2); + // double sclymax = boxHeight - (absY * 2); + // double sclxmin = absX; + // double sclymin = absY; + + // //3. paint rectangle + // System.Drawing.Rectangle rect = new System.Drawing.Rectangle(xmin.ToInt(), + // ymin.ToInt(), + // sclWidth.ToInt(), + // sclHeight.ToInt()); + // using (Pen pen = new Pen(color, BorderWidth)) + // { + // e.Graphics.DrawRectangle(pen, rect); //draw rectangle + // } + + + // ///testing================================================= + // //3. paint rectangle + // //rect = new System.Drawing.Rectangle(absX + 5, + // // absY + 5, + // // sclxmax - 10, + // // sclymax - 10); + + // //using (Pen pen = new Pen(Color.Red, BorderWidth)) + // //{ + // // e.Graphics.DrawRectangle(pen, rect); //draw rectangle + // //} + // ///testing================================================= + + // //we need this since people can change the border width in the json file + // double halfbrd = BorderWidth / 2; + + + // System.Drawing.SizeF TextSize = e.Graphics.MeasureString(text, new Font(AppSettings.Settings.RectDetectionTextFont, AppSettings.Settings.RectDetectionTextSize)); //finds size of text to draw the background rectangle + + + // //object name text below rectangle + + // double x = xmin - halfbrd; + // double y = ymax + halfbrd; + + + // //adjust the x / width label so it doesnt go off screen + // double EndX = x + TextSize.Width; + // if (EndX > sclxmax) + // { + // //int diffx = x - sclxmax; + // x = xmax - TextSize.Width + halfbrd; + // } + + // if (x < sclxmin) + // x = sclxmin; + + // if (x < 0) + // x = 0; + + // //adjust the y / height label so it doesnt go off screen + // double EndY = y + TextSize.Height; + // if (EndY > sclymax) + // { + // //float diffy = EndY - sclymax; + // y = ymax - TextSize.Height - halfbrd; + // } + + // if (y < 0) + // y = 0; + + + // rect = new System.Drawing.Rectangle(x.ToInt(), + // y.ToInt(), + // boxWidth.ToInt(), + // boxHeight.ToInt()); //sets bounding box for drawn text + + + // Brush brush = new SolidBrush(color); //sets background rectangle color + // if (AppSettings.Settings.RectDetectionTextBackColor != Color.Gainsboro) + // brush = new SolidBrush(AppSettings.Settings.RectDetectionTextBackColor); + + // Brush forecolor = Brushes.Black; + // if (AppSettings.Settings.RectDetectionTextForeColor != Color.Gainsboro) + // forecolor = new SolidBrush(AppSettings.Settings.RectDetectionTextForeColor); + + // e.Graphics.FillRectangle(brush, + // x.ToInt(), + // y.ToInt(), + // TextSize.Width, + // TextSize.Height); //draw grey background rectangle for detection text + + // e.Graphics.DrawString(text, + // new Font(AppSettings.Settings.RectDetectionTextFont, + // AppSettings.Settings.RectDetectionTextSize), + // forecolor, + // rect); //draw detection text + + + // } + + // } + // catch (Exception ex) + // { + + // Log("Error: " + ex.Msg()); + // } + // } + + //load object rectangle overlays + //TODO: refactor detections + private void pictureBox1_Paint(object sender, PaintEventArgs e) + { + if (this.pictureBox1.BackgroundImage.IsNull()) + return; + + if (AppSettings.Settings.HistoryShowObjects && this.folv_history.SelectedObjects != null && this.folv_history.SelectedObjects.Count > 0) //if checkbox button is enabled + { + //Log("Loading object rectangles..."); + History hist = (History)this.folv_history.SelectedObjects[0]; + + if (hist == null) + { + Log("Warn: Selected history item is null?"); + return; + } + + string positions = hist.Positions; + string detections = hist.Detections; + + //int XOffset = 0; + //int YOffset = 0; + + //Camera cam = AITOOL.GetCamera(hist.Camera); + //if (cam != null) + //{ + // //apply offset if one is defined by user in json file + // XOffset = cam.XOffset; + // YOffset = cam.YOffset; + //} + + try { - string[] detections = detections_column.Split(';'); - //write the confidence of every detection into the green_values string - foreach (string detection in detections) + if (!string.IsNullOrEmpty(hist.PredictionsJSON)) { - if (detection.Contains('%')) + List predictions = hist.Predictions(); + + if (predictions.Count > 0) + { + //draw in reverse, assuming most important should be on top + for (int i = predictions.Count - 1; i >= 0; i--) + { + ClsPrediction pred = predictions[i]; + if (pred != null) + { + + AITOOL.DrawAnnotation(e.Graphics, + pred, + this.pictureBox1.BackgroundImage.Width, + this.pictureBox1.BackgroundImage.Height, + this.pictureBox1.Width, + this.pictureBox1.Height); + + //if (AppSettings.Settings.HistoryOnlyDisplayRelevantObjects && pred.Result == ResultType.Relevant) + //{ + // this.showObject(e, pred.XMin + XOffset, pred.YMin + YOffset, pred.XMax, pred.YMax, pred.ToString(), pred.Result); //call rectangle drawing method, calls appropriate detection text + //} + //else if (!AppSettings.Settings.HistoryOnlyDisplayRelevantObjects) + //{ + // this.showObject(e, pred.XMin + XOffset, pred.YMin + YOffset, pred.XMax, pred.YMax, pred.ToString(), pred.Result); //call rectangle drawing method, calls appropriate detection text + //} + } + else + { + Log($"Warn: Prediction #{i + 1} of {predictions.Count} is empty?"); + + } + + } + } + else { - //example: -> "person (41%)" - Int32.TryParse(detection.Split('(')[1].Split('%')[0], out int x_value); //example: -> "41" - green_values[x_value]++; + Log("Warn: No predictions for the selected history item."); } + } + //else + //{ + + // //we should never get here after all old AITOOL entries have been deleted + // List positionssArray = positions.SplitStr(";");//creates array of detected objects, used for adding text overlay + + // int countr = positionssArray.Count; + + // ResultType result = ResultType.Unknown; + + // if (detections.IndexOf("irrelevant", StringComparison.OrdinalIgnoreCase) >= 0 || detections.IndexOf("masked", StringComparison.OrdinalIgnoreCase) >= 0 || detections.IndexOf("confidence", StringComparison.OrdinalIgnoreCase) >= 0) + // { + // detections = detections.Split(':')[1]; //removes the "1x masked, 3x irrelevant:" before the actual detection, otherwise this would be displayed in the detection tags + // if (detections.Contains("masked")) + // { + // result = ResultType.ImageMasked; + // } + // else + // { + // result = ResultType.Unknown; + // } + // } + // else + // { + // result = ResultType.Relevant; + // } + + // List detectionsArray = detections.SplitStr(";");//creates array of detected objects, used for adding text overlay + + // if (cam != null) + // { + // //apply offset if one is defined by user in json file + // XOffset = cam.XOffset; + // YOffset = cam.YOffset; + // } + + // //display a rectangle around each relevant object + // for (int i = 0; i < countr; i++) + // { + + // //load 'xmin,ymin,xmax,ymax' from third column into a string + // List positionsplt = positionssArray[i].SplitStr(","); + + // //store xmin, ymin, xmax, ymax in separate variables + // Int32.TryParse(positionsplt[0], out int xmin); + // Int32.TryParse(positionsplt[1], out int ymin); + // Int32.TryParse(positionsplt[2], out int xmax); + // Int32.TryParse(positionsplt[3], out int ymax); + + + // this.showObject(e, xmin + XOffset, ymin + YOffset, xmax, ymax, detectionsArray[i], result); //call rectangle drawing method, calls appropriate detection text + + // } + + //} + + } - } + catch (Exception ex) + { + Log($"Error: Positions (subitem4) ='{positions}', Detections (subitem3) ='{detections}': {ex.Msg()}"); + } - //write green series in chart - int i = 0; - foreach (int y_value in green_values) - { - chart_confidence.Series[1].Points.AddXY(i, y_value); - i++; } + } + + // add new entry in left list + private bool showingtrayerr = false; + private bool showingtrayok = false; - //write orange series in chart - i = 0; - foreach (int y_value in orange_values) + private void UpdateErrorIcon() + { + try { - chart_confidence.Series[0].Points.AddXY(i, y_value); - i++; - } + //make tray icon red if there are errors + if (LogMan.ErrorCount == 0 && !showingtrayok) + { + this.notifyIcon.Icon = AITool.Properties.Resources.Logo_old; + this.Icon = AITool.Properties.Resources.Logo_old; + this.Refresh(); + showingtrayok = true; + showingtrayerr = false; + } + else if (LogMan.ErrorCount > 0 && !showingtrayerr) + { + this.notifyIcon.Icon = AITool.Properties.Resources.Logo_Error_old; + this.Icon = AITool.Properties.Resources.Logo_Error_old; + this.Refresh(); + showingtrayok = false; + showingtrayerr = true; + } + } + catch { } } - //---------------------------------------------------------------------------------------------------------- - //HISTORY TAB - //---------------------------------------------------------------------------------------------------------- - // load images from input_path to left list - /*public void LoadList() + private void UpdateStats(string Message = "") { - list1.Items.Clear(); try { - string[] files = Directory.GetFiles(input_path, $"*.jpg"); + UpdateErrorIcon(); - foreach (string file in files) - { - string fileName = Path.GetFileName(file); - ListViewItem item = new ListViewItem(new string[] { fileName, "content" }); - item.Tag = file; + if (!this.Visible || (this.WindowState == FormWindowState.Minimized) || this.IsStatsUpdating || IsClosing) + return; //save a tree - list1.Items.Add(item); + this.IsStatsUpdating = true; + int alerts = 0; + int irrelevantalerts = 0; + int falsealerts = 0; + int newskipped = 0; + int skipped = 0; + foreach (Camera cam in AppSettings.Settings.CameraList) + { + alerts += cam.stats_alerts; + irrelevantalerts += cam.stats_irrelevant_alerts; + falsealerts += cam.stats_false_alerts; + skipped += cam.stats_skipped_images; + newskipped += cam.stats_skipped_images_session; } + + Global_GUI.InvokeIFRequired(this.toolStripStatusLabelHistoryItems.GetCurrentParent(), () => + { + + double hpm = 0; + int items = 0; + int removed = 0; + + if (HistoryDB != null) + { + items = HistoryDB.HistoryDic.Count; + removed = HistoryDB.DeletedCount; + } + + if (HistoryDB != null && HistoryDB.AddedCount > 0) + hpm = HistoryDB.AddedCount / (DateTime.Now - HistoryDB.InitializeTime).TotalMinutes; + + this.toolStripStatusLabelHistoryItems.Text = $"{items} history items ({hpm.ToString("###0")}/MIN) | {removed} removed |"; + int TriggerActionQueueCount = 0; + if (TriggerActionQueue != null) + TriggerActionQueueCount = TriggerActionQueue.Count; + + this.toolStripStatusLabel1.Text = $"| {alerts} Alerts | {irrelevantalerts} Irrelevant | {falsealerts} False | {skipped} Skipped ({newskipped} new) | {qcalc.Count} ImgProcessed ({qcalc.ItemsPerMinute().ToString("###0")}/MIN) | {ImageProcessQueue.Count} ImgQueued (Max={scalc.Max},Avg={Math.Round(scalc.Avg, 0)}) | {TriggerActionQueueCount} ActQueued (Min={TriggerActionQueue.ActionTimeCalc.Min.Round(0)}ms/Max={TriggerActionQueue.ActionTimeCalc.Max.Round(0)}ms)"; + + this.toolStripStatusErrors.Text = $"{LogMan.ErrorCount} Errors"; + + if (!string.IsNullOrEmpty(Message)) + { + this.toolStripStatusLabelInfo.Text = Message; + } + else if (ImageProcessQueue.Count > 0 && this.toolStripProgressBar1.Value == 0) + { + this.toolStripStatusLabelInfo.Text = "Working..."; + } + else if (this.toolStripProgressBar1.Value == 0) + { + this.toolStripStatusLabelInfo.Text = "Idle."; + } + + //if (toolStripProgressBar1.Style == ProgressBarStyle.Marquee && toolStripStatusLabelInfo.Text == "Idle.") + //{ + // toolStripProgressBar1.Style = ProgressBarStyle.Continuous; + //} + + + if (LogMan.ErrorCount > 0) + { + this.toolStripStatusErrors.ForeColor = Color.Black; + this.toolStripStatusErrors.BackColor = Color.Red; + } + else + { + this.toolStripStatusErrors.ForeColor = this.toolStripStatusLabelHistoryItems.GetCurrentParent().ForeColor; + this.toolStripStatusErrors.BackColor = this.toolStripStatusLabelHistoryItems.GetCurrentParent().BackColor; + } + + this.toolStripStatusErrors.GetCurrentParent().Refresh(); + + }); + + Global_GUI.InvokeIFRequired(this.lblQueue, () => + { + this.lblQueue.Text = $"Images in queue: {ImageProcessQueue.Count}, Max: {scalc.Max} ({qcalc.Max}ms), Average: {scalc.Avg.ToString("#####")} ({qcalc.Avg.ToString("#####")}ms queue wait time, Trigger Actions Min={TriggerActionQueue.ActionTimeCalc.Min.Round(0)}ms/Max={TriggerActionQueue.ActionTimeCalc.Max.Round(0)}ms/Avg={TriggerActionQueue.ActionTimeCalc.Avg.Round(0)}ms)"; + + }); + Global_GUI.InvokeIFRequired(this.lbl_errors, () => + { + if (LogMan.ErrorCount > 0) + this.lbl_errors.Text = $"{LogMan.ErrorCount} error(s) occurred. Click to open Log."; //update error counter label + else + this.lbl_errors.Text = ""; + + }); + + + } - catch + catch (Exception ex) { - MessageBox.Show("Can't find the input directory, please check it."); + + Log("Error: " + ex.Msg()); } - if (list1.Items.Count > 0) + finally { - list1.Items[0].Selected = true; //select first image + this.IsStatsUpdating = false; } - }*/ - - //show or hide the privacy mask overlay - private void showHideMask() + } + //load stored entries in history CSV into history ListView + private async Task LoadHistoryAsync(bool FilterChanged, bool Follow) { - if (cb_showMask.Checked == true) //show overlay + + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + + //if (IsLoading) //when you set checkboxes during init, it may trigger the event to load the history + //{ + // //Log("---Exit (still loading)"); + // return; + //} + + //make sure only one thread updating at a time + //await Semaphore_List_Updating.WaitAsync(); + + if (this.IsHistoryListUpdating) + { + Log("Trace: ---Exit (already updating)"); + return; + } + + Stopwatch semsw = Stopwatch.StartNew(); + + this.IsHistoryListUpdating = true; + this.LastListUpdate = DateTime.Now; + + Global_GUI.CursorWait cw = null; + + try { - Log("Show mask toggled."); - if (list1.SelectedItems.Count > 0) + if (semsw.ElapsedMilliseconds >= AppSettings.Settings.loop_delay_ms) + Log($"debug: Waited {semsw.ElapsedMilliseconds}ms while waiting for other threads to finish."); + + //wait a bit for the list to be available + Stopwatch sw = Stopwatch.StartNew(); + bool displayed = false; + do + { + if (HistoryDB != null && HistoryDB.HistoryDic != null && this.folv_history != null && this.DatabaseInitialized) + break; + else if (!displayed) + { + + Log("debug: Waiting for database to finish initializing..."); + displayed = true; + } + await Task.Delay(AppSettings.Settings.loop_delay_ms); + + } while (sw.ElapsedMilliseconds < 60000); + + if (displayed) + Log($"...debug: Waited {sw.ElapsedMilliseconds}ms for the database to finish initializing/cleaning."); + + //Dont update list unless we are on the tab and it is visible for performance reasons. + if (FilterChanged || this.folv_history.Items.Count == 0 || (this.tabControl1.SelectedIndex == 2 && this.Visible && !(this.WindowState == FormWindowState.Minimized))) { - if (System.IO.File.Exists("./cameras/" + list1.SelectedItems[0].SubItems[2].Text + ".png")) //check if privacy mask file exists + + + if (this.Visible && !(this.WindowState == FormWindowState.Minimized)) + this.UpdateStats("Updating History List..."); + + if (FilterChanged) + cw = new Global_GUI.CursorWait(); + + if (await HistoryDB.HasUpdates() || FilterChanged) { - using (var img = new Bitmap("./cameras/" + list1.SelectedItems[0].SubItems[2].Text + ".png")) + + Global_GUI.InvokeIFRequired(this.folv_history, async () => { - pictureBox1.Image = new Bitmap(img); //load mask as overlay - } + + List histlist = HistoryDB.GetAllValues(); + //find the last item that is unfiltered: + History lasthist = null; + for (int i = histlist.Count - 1; i >= 0; i--) + { + if (this.checkListFilters(histlist[i])) + { + lasthist = histlist[i]; + break; + } + } + // run in another thread so gui doesnt freeze + await Task.Run(() => + { + Global_GUI.UpdateFOLV(this.folv_history, histlist, Follow || AppSettings.Settings.HistoryFollow, UseSelected: true, SelectObject: lasthist, FullRefresh: true); + }); + + //reset any that snuck in while waiting since we just did a full list update + HistoryDB.GetRecentlyAdded(); + HistoryDB.GetRecentlyDeleted(); + + if (FilterChanged) + { + + if (this.comboBox_filter_camera.Text != "All Cameras" || this.cb_filter_animal.Checked || this.cb_filter_nosuccess.Checked || this.cb_filter_person.Checked || this.cb_filter_success.Checked || this.cb_filter_vehicle.Checked || this.cb_filter_skipped.Checked) + { + //filter + this.folv_history.ModelFilter = new BrightIdeasSoftware.ModelFilter((object x) => + { + History hist = (History)x; + return this.checkListFilters(hist); + }); + } + else + { + this.folv_history.ModelFilter = null; + } + + + } + + if (Follow && this.folv_history.SelectedObject == null && this.folv_history.GetItemCount() > 0) + { + if (this.folv_history.IsFiltering) + { + //use the last filtered object as the selected object + this.folv_history.SelectedObject = this.folv_history.FilteredObjects.Cast().Last(); + } + else if (!this.folv_history.IsFiltering) + { + this.folv_history.SelectedObject = this.folv_history.Objects.Cast().Last(); + } + + if (this.folv_history.SelectedObject != null) + this.folv_history.EnsureModelVisible(this.folv_history.SelectedObject); + + } + + }); } else { - pictureBox1.Image = null; //if file does not exist, empty mask overlay (from possible overlays of previous images) + //Log("debug: No history file updates."); } - } + if (this.Visible && !(this.WindowState == FormWindowState.Minimized)) + this.UpdateStats("Idle."); + + } + else + { + //Log("debug: Not updating history, window not visible or history tab not selected."); + } + + } + catch (Exception ex) + { + Log("Error: " + ex.Msg()); + } + finally + { + if (cw != null) + cw.Dispose(); + this.LastListUpdate = DateTime.Now; + this.IsHistoryListUpdating = false; + //Semaphore_List_Updating.Release(); + //Log("---Exit"); + + } + + } + + //check if a filter applies on given string of history list entry + private bool checkListFilters(History hist) //string cameraname, string success, string objects_and_confidence + { + + bool ret = true; + + if (!hist.Success && this.cb_filter_success.Checked) + ret = false; + + if (hist.Success && this.cb_filter_nosuccess.Checked) + ret = false; + + if (!hist.WasSkipped && this.cb_filter_skipped.Checked) + ret = false; + + if (!hist.WasMasked && this.cb_filter_masked.Checked) + ret = false; + + if (!hist.IsPerson && this.cb_filter_person.Checked) + ret = false; + + if (!hist.IsVehicle && this.cb_filter_vehicle.Checked) + ret = false; + + if (!hist.IsAnimal && this.cb_filter_animal.Checked) + ret = false; + + bool CameraValid = ((string.Equals(this.comboBox_filter_camera.Text.Trim(), "All Cameras", StringComparison.OrdinalIgnoreCase)) || string.Equals(hist.Camera.Trim(), this.comboBox_filter_camera.Text.Trim(), StringComparison.OrdinalIgnoreCase)); + + if (!CameraValid) + ret = false; + + return ret; + + + } + + + + //EVENTS + + private void BeginProcessImage(string image_path) + { + + string filename = Path.GetFileName(image_path); + + Global_GUI.InvokeIFRequired(this.label2, () => { this.label2.Text = $"Processing {filename}..."; }); + + this.UpdateStats(); + + } + + private void EndProcessImage(string image_path) + { + + string filename = Path.GetFileName(image_path); + + + //output Running on Overview Tab + Global_GUI.InvokeIFRequired(this.label2, () => { this.label2.Text = $"Idle"; }); + + //only update charts if stats tab is open + + if (this.tabControl1.SelectedIndex == 1) + { + Global_GUI.InvokeIFRequired(this, () => { this.UpdatePieChart(); this.UpdateTimeline(); this.UpdateConfidenceChart(); }); + } + + //load updated cameara stats info in camera tab if a camera is selected + Global_GUI.InvokeIFRequired(this, () => + { + if (this.FOLV_Cameras.SelectedObjects != null && this.FOLV_Cameras.SelectedObjects.Count > 0) + { + + + //load only stats from Camera.cs object + //all camera objects are stored in the list CameraList, so firstly the position (stored in the second column for each entry) is gathered + Camera cam = AITOOL.GetCamera(((Camera)this.FOLV_Cameras.SelectedObjects[0]).Name); + + //load cameras stats + string stats = $"Alerts: {cam.stats_alerts.ToString()} | Irrelevant Alerts: {cam.stats_irrelevant_alerts.ToString()} | False Alerts: {cam.stats_false_alerts.ToString()}"; + if (cam.maskManager.MaskingEnabled) + { + stats += $" | Mask History Count: {cam.maskManager.LastPositionsHistory.Count} | Current Dynamic Masks: {cam.maskManager.MaskedPositions.Count}"; + } + this.lbl_camstats.Text = stats; + } + + }); + + + this.UpdateStats(); + + } + + + //event: load selected image to picturebox + + + //event: show mask button clicked + private void cb_showMask_CheckedChanged(object sender, EventArgs e) + { + if (this.folv_history.SelectedObjects != null && this.folv_history.SelectedObjects.Count > 0) + { + this.showHideMask(); + } + } + + //event: show objects button clicked + private void cb_showObjects_MouseUp(object sender, MouseEventArgs e) + { + if (this.folv_history.SelectedObjects != null && this.folv_history.SelectedObjects.Count > 0) + { + this.pictureBox1.Refresh(); + } + + } + + //event: show history list filters button clicked + //private void cb_showFilters_CheckedChanged(object sender, EventArgs e) + //{ + // if (cb_showFilters.Checked) + // { + // cb_showFilters.Text = "˅ Filter"; + // splitContainer1.Panel2Collapsed = false; + // } + // else + // { + // cb_showFilters.Text = "˄ Filter"; + // splitContainer1.Panel2Collapsed = true; + // } + + // ResizeListViews(); + + //} + + //event: filter "only revelant alerts" checked or unchecked + + + + //---------------------------------------------------------------------------------------------------------- + //CAMERAS TAB + //---------------------------------------------------------------------------------------------------------- + + //BASIC METHODS + + // load cameras to camera list + public void LoadCameras() + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + try + { + //Global_GUI.InvokeIFRequired(this.FOLV_Cameras, () => + //{ + //start by getting last selected camera if any + string oldnamecameras = ""; + if (this.FOLV_Cameras.SelectedObjects != null && this.FOLV_Cameras.SelectedObjects.Count > 0) + oldnamecameras = ((Camera)this.FOLV_Cameras.SelectedObjects[0]).Name; + + //if nothing was selected, then select the last saved camera: + if (string.IsNullOrEmpty(oldnamecameras)) + oldnamecameras = Global.GetRegSetting("LastSelectedCamera", ""); + + if (string.IsNullOrEmpty(oldnamecameras)) + oldnamecameras = "default"; + + string oldnamefilters = ""; + if (this.comboBox_filter_camera.Items.Count > 0) + oldnamefilters = this.comboBox_filter_camera.Text; + + string oldnamestats = ""; + if (this.comboBox1.Items.Count > 0) + oldnamestats = this.comboBox1.Text; + + this.comboBox1.Items.Clear(); + this.comboBox1.Items.Add("All Cameras"); + this.comboBox_filter_camera.Items.Clear(); + this.comboBox_filter_camera.Items.Add("All Cameras"); + + int i = 0; + int oldidxcameras = 0; + int oldidxfilters = 0; + int oldidxstats = 0; + Camera selectedcam = null; + foreach (Camera cam in AppSettings.Settings.CameraList) + { + //item.Tag = file; //tag is not used anywhere I can see + //add camera to combobox on overview tab and to camera filter combobox in the History tab + this.comboBox1.Items.Add($" {cam.Name}"); + this.comboBox_filter_camera.Items.Add($" {cam.Name}"); + if (string.Equals(oldnamecameras.Trim(), cam.Name.Trim(), StringComparison.OrdinalIgnoreCase)) + { + oldidxcameras = i; + selectedcam = cam; + } + if (string.Equals(oldnamefilters.Trim(), cam.Name.Trim(), StringComparison.OrdinalIgnoreCase)) + { + oldidxfilters = i + 1; + } + if (string.Equals(oldnamestats.Trim(), cam.Name.Trim(), StringComparison.OrdinalIgnoreCase)) + { + oldidxstats = i + 1; + } + i++; + + } + + if (selectedcam == null && AppSettings.Settings.CameraList.Count > 0) + selectedcam = AppSettings.Settings.CameraList[0]; + + Global_GUI.UpdateFOLV(FOLV_Cameras, AppSettings.Settings.CameraList, false, ColumnHeaderAutoResizeStyle.ColumnContent, true, true, selectedcam); + + if (this.comboBox_filter_camera.Items.Count > 0) + this.comboBox_filter_camera.SelectedIndex = oldidxfilters; + + if (this.comboBox1.Items.Count > 0) + this.comboBox1.SelectedIndex = oldidxstats; + + //}); + + } + catch (Exception ex) + { + Log("ERROR: LoadCameras() failed: " + ex.Msg()); + MessageBox.Show("ERROR: LoadCameras() failed: " + ex.Msg()); + } + + } + + //load existing camera (settings file exists) into CameraList, into Stats dropdown and into History filter dropdown + //private string LoadCamera(string config_path) + //{ + // //check if camera with specified name or its prefix already exists. If yes, then abort. + // foreach (Camera c in AppSettings.Settings.CameraList) + // { + // if (c.name == Path.GetFileNameWithoutExtension(config_path)) + // { + // return ($"ERROR: Camera name must be unique,{Path.GetFileNameWithoutExtension(config_path)} already exists."); + // } + // if (c.prefix == System.IO.File.ReadAllLines(config_path)[2].Split('"')[1]) + // { + // return ($"ERROR: Every camera must have a unique prefix ('Input file begins with'), but the prefix of {Path.GetFileNameWithoutExtension(config_path)} equals the prefix of the existing camera {c.name} ."); + // } + // } + // Camera cam = new Camera(); //create new camera object + // Log("read config"); + // cam.ReadConfig(config_path); //read camera's config from file + // Log("add"); + // AppSettings.Settings.CameraList.Add(cam); //add created camera object to CameraList + + // //add camera to combobox on overview tab and to camera filter combobox in the History tab + // comboBox1.Items.Add($" {cam.name}"); + // comboBox_filter_camera.Items.Add($" {cam.name}"); + + // return ($"SUCCESS: {Path.GetFileNameWithoutExtension(config_path)} loaded."); + //} + + //add camera + private string AddCamera(Camera cam) //, int history_mins, int mask_create_counter, int mask_remove_counter, double percent_variance) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + //check if camera with specified name already exists. If yes, then abort. + foreach (Camera c in AppSettings.Settings.CameraList) + { + if (string.Equals(c.Name.Trim(), cam.Name.Trim(), StringComparison.OrdinalIgnoreCase)) + { + MessageBox.Show($"ERROR: Camera name must be unique,{cam.Name} already exists."); + return ($"ERROR: Camera name must be unique,{cam.Name} already exists."); + } + } + + //check if name is empty + if (cam.Name == "") + { + MessageBox.Show($"ERROR: Camera name may not be empty."); + return ($"ERROR: Camera name may not be empty."); + } + + + if (AITOOL.BlueIrisInfo.Result == BlueIrisResult.Valid) + { + //http://10.0.1.99:81/admin?trigger&camera=BACKFOSCAM&user=AITools&pw=haha&memo=[summary] + cam.trigger_urls_as_string = "[BlueIrisURL]/admin?camera=[camera]&trigger&user=[Username]&pw=[Password]&flagalert=2&memo=[summary]&jpeg=[ImagePathEscaped]"; + } + + //I dont think this is used anywhere + cam.triggering_objects = cam.triggering_objects_as_string.SplitStr(",").ToArray(); //triggering_objects_as_string.Split(','); //split the row of triggering objects between every ',' + + //Split by cr/lf or other common delimiters + cam.trigger_urls = cam.trigger_urls_as_string.SplitStr("\r\n|").ToArray(); //all trigger urls in an array + cam.cancel_urls = cam.cancel_urls_as_string.SplitStr("\r\n|").ToArray(); //all trigger urls in an array + + cam.BICamName = cam.Name; + + cam.MaskFileName = $"{cam.Name}.bmp"; + + AppSettings.Settings.CameraList.Add(cam); //add created camera object to CameraList + + this.LoadCameras(); + + return ($"SUCCESS: {cam.Name} created."); + } + + + + //remove camera + private void RemoveCamera(System.Collections.IList objs) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + for (int i = 0; i < objs.Count; i++) + { + Log($"Removing camera {((Camera)objs[i]).Name}..."); + + AppSettings.Settings.CameraList.Remove((Camera)objs[i]); + + } + if (this.FOLV_Cameras.Items.Count > 0) + this.FOLV_Cameras.SelectedIndex = 0; + AppSettings.SaveAsync(true); + this.LoadCameras(); + + } + + //display camera settings for selected camera + private void DisplayCameraSettings() + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + try + { + if (this.FOLV_Cameras.SelectedObjects != null && this.FOLV_Cameras.SelectedObjects.Count > 0) + { + Camera cam = ((Camera)this.FOLV_Cameras.SelectedObjects[0]); + + if (cam.last_image_file.IsNotEmpty() && File.Exists(cam.last_image_file)) + { + using (var img = new Bitmap(cam.last_image_file)) + { + this.pictureBoxCamera.Image = new Bitmap(img); //load mask as overlay + } + } + else + { + this.pictureBoxCamera.Image = null; //if file does not exist, empty mask overlay (from possible overlays of previous images) + } + + UpdateActionsLabel(cam); + + Lbl_PredictionTolerances.Text = $"Threshold: {cam.threshold_lower}-{cam.threshold_upper}, Size: {cam.PredSizeMinPercentOfImage.ToPercent()}-{cam.PredSizeMaxPercentOfImage.ToPercent()} ; Width: {cam.PredSizeMinWidth}-{cam.PredSizeMaxWidth}, Height: {cam.PredSizeMinHeight}-{cam.PredSizeMaxHeight}, PredictionMatch: {cam.MergePredictionsMinMatchPercent.ToPercent()}"; + + this.tableLayoutPanel6.Enabled = true; + + this.tbName.Text = cam.Name; //load name textbox from name in list2 + this.tbBiCamName.Text = cam.BICamName; + this.tbCustomMaskFile.Text = cam.MaskFileName; + + //load cameras stats + string stats = $"Alerts: {cam.stats_alerts.ToString()} | Irrelevant Alerts: {cam.stats_irrelevant_alerts.ToString()} | False Alerts: {cam.stats_false_alerts.ToString()}"; + + if (cam.maskManager.MaskingEnabled) + { + stats += $" | Mask History Count: {cam.maskManager.LastPositionsHistory.Count} | Current Dynamic Masks: {cam.maskManager.MaskedPositions.Count}"; + } + this.lbl_camstats.Text = stats; + + //load if ai detection is active for the camera + if (cam.enabled == true) + { + this.cb_enabled.Checked = true; + } + else + { + this.cb_enabled.Checked = false; + } + this.tbPrefix.Text = cam.Prefix; //load 'input file begins with' + this.lbl_prefix.Text = this.tbPrefix.Text + ".××××××.jpg"; //prefix live preview + + this.cmbcaminput.Text = cam.input_path; + this.cmbcaminput.Items.Clear(); + foreach (string pth in AITOOL.BlueIrisInfo.ClipPaths) + { + this.cmbcaminput.Items.Add(pth); + } + + this.cb_monitorCamInputfolder.Checked = cam.input_path_includesubfolders; + + this.tb_camera_telegram_chatid.Text = cam.telegram_chatid; + + //load is masking enabled + this.cb_masking_enabled.Checked = cam.maskManager.MaskingEnabled; + + + this.lbl_RelevantObjects.Text = cam.DefaultTriggeringObjects.ToString(); + ////load triggering objects + ////first create arrays with all checkboxes stored in + //CheckBox[] cbarray = new CheckBox[] { this.cb_airplane, this.cb_bear, this.cb_bicycle, this.cb_bird, this.cb_boat, this.cb_bus, this.cb_car, this.cb_cat, this.cb_cow, this.cb_dog, this.cb_horse, this.cb_motorcycle, this.cb_person, this.cb_sheep, this.cb_truck }; + ////create array with strings of the triggering_objects related to the checkboxes in the same order + //string[] cbstringarray = new string[] { "Airplane", "Bear", "Bicycle", "Bird", "Boat", "Bus", "Car", "Cat", "Cow", "Dog", "Horse", "Motorcycle", "Person", "Sheep", "Truck" }; + + ////clear all checkmarks + //foreach (CheckBox cb in cbarray) + //{ + // cb.Checked = false; + //} + + ////check for every triggering_object string if it is active in the settings file. If yes, check according checkbox + //for (int j = 0; j < cbarray.Length; j++) + //{ + // if (cam.triggering_objects_as_string.IndexOf(cbstringarray[j], StringComparison.OrdinalIgnoreCase) >= 0) + // { + // cbarray[j].Checked = true; + // } + //} + + //this.tbAdditionalRelevantObjects.Text = cam.additional_triggering_objects_as_string; + + + } + + } + catch (Exception ex) + { + + string err = $"Error: While displaying camera settings, got error: {ex.Msg()}"; + Log(err); + MessageBox.Show(err); + } + } + + + + // SPECIAL METHODS + + //input file begins with live preview + private void tbPrefix_TextChanged(object sender, EventArgs e) + { + this.lbl_prefix.Text = this.tbPrefix.Text + ".××××××.jpg"; + } + + + + //event: camera list another item selected + + + //event: camera add button + private void btnCameraAdd_Click(object sender, EventArgs e) + { + + using (Frm_CameraAdd frm = new Frm_CameraAdd()) + { + + //add only cameras not already installed + foreach (string camstr in AITOOL.BlueIrisInfo.Cameras) + { + if (GetCamera(camstr, false) == null) + frm.checkedListBoxCameras.Items.Add(camstr, false); + + } + + if (frm.ShowDialog() == DialogResult.OK) + { + int added = 0; + List cams = frm.tb_Cameras.Text.SplitStr("\r\n|;,"); + Camera DefCam = GetCamera("default", true); + + foreach (var cs in cams) + { + + string cn = cs; + + //if (cn.StartsWith("ai", StringComparison.OrdinalIgnoreCase)) + //{ + // cn = Name.Substring(2).TrimStart(@"_-".ToCharArray()).Trim(); //if using dupe cam that may start with AICAMNAME or AI_CAMNAME + // string maskfile = AITOOL.GetMaskFile(cn); + // string newfile = AITOOL.GetMaskFile(cs); + // if (File.Exists(maskfile) && !File.Exists(newfile)) + // File.Move(maskfile, newfile); + //} + + if (GetCamera(cn, false) == null) + { + + Camera cam = null; // new Camera(cn); + + //Try to use the default camera settings when creating a new camera + if (!DefCam.IsNull()) + { + cam = DefCam.CloneJson(); + cam.Name = cn; + cam.BICamName = cn; + cam.Prefix = cn; + cam.UpdateCamera(); + } + else + { + cam = new Camera(cn); + } + + string camresult = this.AddCamera(cam); + + Log(camresult); + if (camresult.StartsWith("success", StringComparison.OrdinalIgnoreCase)) + { + added++; + Global.SaveRegSetting("LastSelectedCamera", cam.Name); + } + + } + else + { + Log($"Error: Camera already existed {cs}."); + } + + } + + MessageBox.Show($"Added {added} out of {cams.Count}. See log for details."); + + } + } + + //using (var form = new InputForm("Camera Name:", "New Camera", cbitems: BlueIrisInfo.Cameras)) + //{ + // var result = form.ShowDialog(); + // if (result == DialogResult.OK) + // { + // Camera cam = new Camera(form.text); + + // string camresult = this.AddCamera(cam); + + // // Old way... + // //string name, string prefix, string trigger_urls_as_string, string triggering_objects_as_string, bool telegram_enabled, bool enabled, double cooldown_time, int threshold_lower, int threshold_upper, + // // string _input_path, bool _input_path_includesubfolders, + // // bool masking_enabled, + // // bool trigger_cancels + + // MessageBox.Show(camresult); + // } + //} + } + + //event: save camera settings button + private void btnCameraSave_Click_1(object sender, EventArgs e) + { + + this.CameraSave(false); + } + + private void CameraSave(bool SaveTo) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + try + { + if (this.FOLV_Cameras.SelectedObjects.Count > 0) + { + //check if name is empty + if (String.IsNullOrWhiteSpace(this.tbName.Text)) + { + this.DisplayCameraSettings(); //reset displayed settings + MessageBox.Show($"WARNING: Camera name may not be empty.", "", MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + + if (!string.Equals(((Camera)this.FOLV_Cameras.SelectedObjects[0]).Name.Trim(), this.tbName.Text.Trim(), StringComparison.OrdinalIgnoreCase)) + { + //camera renamed, make sure name doesnt exist + Camera CamCheck = AITOOL.GetCamera(this.tbName.Text, false); + if (CamCheck != null) + { + //Its a dupe + MessageBox.Show($"WARNING: Camera name must be unique, but new camera name '{this.tbName.Text}' already exists.", "", MessageBoxButtons.OK, MessageBoxIcon.Error); + this.DisplayCameraSettings(); //reset displayed settings + return; + } + } + + Camera cam = AITOOL.GetCamera(((Camera)this.FOLV_Cameras.SelectedObjects[0]).Name, false); + + if (cam == null) + { + //should not happen, but... + MessageBox.Show($"WARNING: Camera not found??? '{((Camera)this.FOLV_Cameras.SelectedObjects[0]).Name}'", "", MessageBoxButtons.OK, MessageBoxIcon.Error); + this.DisplayCameraSettings(); //reset displayed settings + return; + } + + if (cam.input_path.IsNotNull() && cam.Action_network_folder.IsNotNull() && cam.input_path.TrimEnd("\\".ToCharArray()).EqualsIgnoreCase(cam.Action_network_folder.TrimEnd("\\".ToCharArray()))) + { + //You dont want to watch, then copy the same file back to the same folder + MessageBox.Show($"WARNING: Input path ({cam.input_path}) & 'Copy alert images to folder' path may not be the same for camera '{cam.Name}'", "", MessageBoxButtons.OK, MessageBoxIcon.Error); + this.DisplayCameraSettings(); //reset displayed settings + return; + } + + //1. GET SETTINGS INPUTTED + //all checkboxes in one array + + //person, bicycle, car, motorcycle, airplane, + //bus, train, truck, boat, traffic light, fire hydrant, stop_sign, + //parking meter, bench, bird, cat, dog, horse, sheep, cow, elephant, + //bear, zebra, giraffe, backpack, umbrella, handbag, tie, suitcase, + //frisbee, skis, snowboard, sports ball, kite, baseball bat, baseball glove, + //skateboard, surfboard, tennis racket, bottle, wine glass, cup, fork, + //knife, spoon, bowl, banana, apple, sandwich, orange, broccoli, carrot, + //hot dog, pizza, donot, cake, chair, couch, potted plant, bed, dining table, + //toilet, tv, laptop, mouse, remote, keyboard, cell phone, microwave, + //oven, toaster, sink, refrigerator, book, clock, vase, scissors, teddy bear, + //hair dryer, toothbrush. + + //CheckBox[] cbarray = new CheckBox[] { this.cb_airplane, this.cb_bear, this.cb_bicycle, this.cb_bird, this.cb_boat, this.cb_bus, this.cb_car, this.cb_cat, this.cb_cow, this.cb_dog, this.cb_horse, this.cb_motorcycle, this.cb_person, this.cb_sheep, this.cb_truck }; + ////create array with strings of the triggering_objects related to the checkboxes in the same order + //string[] cbstringarray = new string[] { "Airplane", "Bear", "Bicycle", "Bird", "Boat", "Bus", "Car", "Cat", "Cow", "Dog", "Horse", "Motorcycle", "Person", "Sheep", "Truck" }; + + ////go through all checkboxes and write all triggering_objects in one string + //cam.triggering_objects_as_string = ""; + //for (int i = 0; i < cbarray.Length; i++) + //{ + // if (cbarray[i].Checked == true) + // { + // cam.triggering_objects_as_string += $"{cbstringarray[i].Trim()}, "; + // } + //} + + + //cam.triggering_objects = cam.triggering_objects_as_string.Split(",").ToArray(); //triggering_objects_as_string.Split(','); //split the row of triggering objects between every ',' + + //cam.additional_triggering_objects_as_string = this.tbAdditionalRelevantObjects.Text.Trim(); + + cam.trigger_urls = cam.trigger_urls_as_string.SplitStr("\r\n|").ToArray(); //all trigger urls in an array + cam.cancel_urls = cam.cancel_urls_as_string.SplitStr("\r\n|").ToArray(); + + if (cam.Name != this.tbName.Text.Trim()) + { + string Oldmaskfile = cam.GetMaskFile(false); + if (!string.IsNullOrEmpty(Oldmaskfile) && File.Exists(Oldmaskfile)) + { + string ext = Path.GetExtension(Oldmaskfile); + string pth = Path.GetDirectoryName(Oldmaskfile); + string NewMaskFile = Path.Combine(pth, this.tbName.Text.Trim() + ext); + File.Move(Oldmaskfile, NewMaskFile); + cam.MaskFileName = NewMaskFile; + } + Log($"Renaming Camera '{cam.Name}' to '{this.tbName.Text}'"); + cam.Name = this.tbName.Text.Trim(); //just in case we needed to rename it + } + + cam.BICamName = this.tbBiCamName.Text.Trim(); + cam.MaskFileName = this.tbCustomMaskFile.Text.Trim(); + + cam.Prefix = this.tbPrefix.Text.Trim(); + cam.enabled = this.cb_enabled.Checked; + cam.maskManager.MaskingEnabled = this.cb_masking_enabled.Checked; + cam.input_path = this.cmbcaminput.Text.Trim(); + cam.input_path_includesubfolders = this.cb_monitorCamInputfolder.Checked; + + cam.telegram_chatid = this.tb_camera_telegram_chatid.Text.Trim(); + + int ccnt = 0; + + if (SaveTo) + { + using (Frm_ApplyCameraTo frm = new Frm_ApplyCameraTo()) + { + foreach (Camera ccam in AppSettings.Settings.CameraList) + { + if (ccam.Name != cam.Name) + frm.checkedListBoxCameras.Items.Add(ccam.Name, false); + } + + if (frm.ShowDialog() == DialogResult.OK) + { + for (int i = 0; i < frm.checkedListBoxCameras.Items.Count; i++) + { + if (frm.checkedListBoxCameras.GetItemChecked(i)) + { + Camera icam = AITOOL.GetCamera(frm.checkedListBoxCameras.Items[i].ToString(), false); + if (icam != null) + { + ccnt++; + + Log($"Updating camera '{cam.Name}' with settings from '{icam.Name}'..."); + + //icam.BICamName = cam.BICamName; + + + if (frm.cb_apply_confidence_limits.Checked) + { + icam.threshold_lower = cam.threshold_lower; + icam.threshold_upper = cam.threshold_upper; + icam.MergePredictionsMinMatchPercent = cam.MergePredictionsMinMatchPercent; + icam.PredSizeMinHeight = cam.PredSizeMinHeight; + icam.PredSizeMinWidth = cam.PredSizeMinWidth; + icam.PredSizeMaxHeight = cam.PredSizeMaxHeight; + icam.PredSizeMaxWidth = cam.PredSizeMaxWidth; + icam.PredSizeMaxPercentOfImage = cam.PredSizeMaxPercentOfImage; + icam.PredSizeMinPercentOfImage = cam.PredSizeMinPercentOfImage; + } + if (frm.cb_apply_objects.Checked) + { + icam.triggering_objects_as_string = cam.triggering_objects_as_string; + icam.additional_triggering_objects_as_string = cam.additional_triggering_objects_as_string; + icam.triggering_objects = cam.triggering_objects_as_string.SplitStr(",").ToArray(); //triggering_objects_as_string.Split(','); //split the row of triggering objects between every ',' + + icam.Action_pushover_triggering_objects = cam.Action_pushover_triggering_objects; + icam.DefaultTriggeringObjects.ObjectList = cam.DefaultTriggeringObjects.CloneObjectList(); + icam.TelegramTriggeringObjects.ObjectList = cam.TelegramTriggeringObjects.CloneObjectList(); + icam.MQTTTriggeringObjects.ObjectList = cam.MQTTTriggeringObjects.CloneObjectList(); + icam.PushoverTriggeringObjects.ObjectList = cam.PushoverTriggeringObjects.CloneObjectList(); + icam.maskManager.MaskTriggeringObjects.ObjectList = cam.maskManager.MaskTriggeringObjects.CloneObjectList(); + + } + if (frm.cb_apply_actions.Checked) + { + icam.trigger_urls_as_string = cam.trigger_urls_as_string; + icam.trigger_urls = cam.trigger_urls; + icam.Action_TriggerURL_Enabled = cam.Action_TriggerURL_Enabled; + icam.Action_CancelURL_Enabled = cam.Action_CancelURL_Enabled; + icam.cancel_urls_as_string = cam.cancel_urls_as_string; + icam.cancel_urls = cam.cancel_urls; + icam.cooldown_time_seconds = cam.cooldown_time_seconds; + + icam.DetectionDisplayFormat = cam.DetectionDisplayFormat; + + + icam.telegram_enabled = cam.telegram_enabled; + icam.telegram_caption = cam.telegram_caption; + icam.telegram_chatid = cam.telegram_chatid; + icam.telegram_active_time_range = cam.telegram_active_time_range; + + icam.Action_image_copy_enabled = cam.Action_image_copy_enabled; + icam.Action_network_folder = cam.Action_network_folder; + icam.Action_network_folder_filename = cam.Action_network_folder_filename; + icam.Action_RunProgram = cam.Action_RunProgram; + icam.Action_RunProgramString = cam.Action_RunProgramString; + icam.Action_RunProgramArgsString = cam.Action_RunProgramArgsString; + icam.Action_PlaySounds = cam.Action_PlaySounds; + icam.Action_Sounds = cam.Action_Sounds; + + icam.Action_mqtt_enabled = cam.Action_mqtt_enabled; + icam.Action_mqtt_payload = cam.Action_mqtt_payload; + icam.Action_mqtt_topic = cam.Action_mqtt_topic; + icam.Action_mqtt_payload_cancel = cam.Action_mqtt_payload_cancel; + icam.Action_mqtt_topic_cancel = cam.Action_mqtt_topic_cancel; + + icam.Action_image_merge_detections = cam.Action_image_merge_detections; + icam.Action_image_merge_jpegquality = cam.Action_image_merge_jpegquality; + + icam.Action_pushover_enabled = cam.Action_pushover_enabled; + icam.Action_pushover_title = cam.Action_pushover_title; + icam.Action_pushover_message = cam.Action_pushover_message; + icam.Action_pushover_device = cam.Action_pushover_device; + icam.Action_pushover_Sound = cam.Action_pushover_Sound; + icam.Action_pushover_Priority = cam.Action_pushover_Priority; + icam.Action_pushover_retrycallback_url = cam.Action_pushover_retrycallback_url; + icam.Action_pushover_SupplementaryUrl = cam.Action_pushover_SupplementaryUrl; + icam.Action_pushover_expire_seconds = cam.Action_pushover_expire_seconds; + icam.Action_pushover_retry_seconds = cam.Action_pushover_retry_seconds; + + icam.Action_pushover_active_time_range = cam.Action_pushover_active_time_range; + + icam.Action_queued = cam.Action_queued; + } + if (frm.cb_apply_mask_settings.Checked) + { + icam.maskManager.MaskingEnabled = cam.maskManager.MaskingEnabled; + + icam.maskManager.HistorySaveMins = cam.maskManager.HistorySaveMins; + icam.maskManager.HistoryThresholdCount = cam.maskManager.HistoryThresholdCount; + icam.maskManager.MaskRemoveThreshold = cam.maskManager.MaskRemoveThreshold; + icam.maskManager.MaskRemoveMins = cam.maskManager.MaskRemoveMins; + + icam.maskManager.PercentMatch = cam.maskManager.PercentMatch; + + icam.maskManager.ScaleConfig.IsScaledObject = cam.maskManager.ScaleConfig.IsScaledObject; + icam.maskManager.ScaleConfig.MediumObjectMatchPercent = cam.maskManager.ScaleConfig.MediumObjectMatchPercent; + icam.maskManager.ScaleConfig.MediumObjectMaxPercent = cam.maskManager.ScaleConfig.MediumObjectMaxPercent; + icam.maskManager.ScaleConfig.SmallObjectMatchPercent = cam.maskManager.ScaleConfig.SmallObjectMatchPercent; + icam.maskManager.ScaleConfig.SmallObjectMaxPercent = cam.maskManager.ScaleConfig.SmallObjectMaxPercent; + + } + + } + } + + } + + } + } + } + else + { + ccnt = 1; + } + + this.LoadCameras(); + + AppSettings.SaveAsync(true); + + UpdateWatchers(true); + + string saved = $"{ccnt} Camera(s) saved."; + Log(saved); + + MessageBox.Show(saved, "", MessageBoxButtons.OK, MessageBoxIcon.Information); + + + ////2. UPDATE SETTINGS + //// save new camera settings, display result in MessageBox + //string result = UpdateCamera(list2.SelectedItems[0].Text, tbName.Text, tbPrefix.Text, tbTriggerUrl.Text, triggering_objects_as_string, cb_telegram.Checked, cb_enabled.Checked, cooldown_time, threshold_lower, threshold_upper, + // cmbcaminput.Text, cb_monitorCamInputfolder.Checked, + // cb_masking_enabled.Checked, + // cb_TriggerCancels.Checked); //, history_mins, mask_create_counter, mask_remove_counter, percent_variance); + + + + //1 2 3 4 5 6 7 8 9 + //name, prefix, triggering_objects_as_string, trigger_urls_as_string, telegram_enabled, enabled, cooldown_time, threshold_lower, threshold_upper, + // 10 11 + // _input_path, _input_path_includesubfolders, + // 12 masking_enabled, + // 13 trigger_cancel + + + + } + this.DisplayCameraSettings(); + + } + catch (Exception ex) + { + + string err = $"Error: While saving camera, got error: {ex.Msg()}"; + Log(err); + MessageBox.Show(err); + } + + } + //event: delete camera button + private void btnCameraDel_Click(object sender, EventArgs e) + { + if (this.FOLV_Cameras.SelectedObjects.Count > 0) + { + using (var form = new InputForm($"Delete camera {((Camera)this.FOLV_Cameras.SelectedObjects[0]).Name} ?", "Delete Camera?", false)) + { + var result = form.ShowDialog(); + if (result == DialogResult.OK) + { + //Log("about to del cam"); + this.RemoveCamera(this.FOLV_Cameras.SelectedObjects); + } + } + } + } + + //event: DELETE key pressed + private void list2_KeyDown(object sender, KeyEventArgs e) + { + if (e.KeyCode == Keys.Delete) + { + if (this.FOLV_Cameras.SelectedObjects.Count > 0) + { + using (var form = new InputForm($"Delete camera {((Camera)this.FOLV_Cameras.SelectedObjects[0]).Name} ?", "Delete Camera?", false)) + { + var result = form.ShowDialog(); + if (result == DialogResult.OK) + { + this.RemoveCamera(this.FOLV_Cameras.SelectedObjects); + } + } + } + } + } + + + //---------------------------------------------------------------------------------------------------------- + //SETTING TAB + //---------------------------------------------------------------------------------------------------------- + + + + //settings save button + private async void BtnSettingsSave_Click_1(object sender, EventArgs e) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + Global_GUI.InvokeIFRequired(this.BtnSettingsSave, () => + { + BtnSettingsSave.Enabled = false; + BtnSettingsSave.Text = "Saving..."; + }); + + + Application.DoEvents(); + + Log($"Saving settings to {AppSettings.Settings.SettingsFileName}"); + //save inputted settings into App.settings + AppSettings.Settings.input_path = this.cmbInput.Text.Trim(); + AppSettings.Settings.input_path_includesubfolders = this.cb_inputpathsubfolders.Checked; + AppSettings.Settings.deepstack_urls_are_queued = this.cb_DeepStackURLsQueued.Checked; + AppSettings.Settings.telegram_chatids = this.tb_telegram_chatid.Text.SplitStr("|;,", true, true); + AppSettings.Settings.telegram_token = this.tb_telegram_token.Text.Trim(); + AppSettings.Settings.telegram_cooldown_seconds = GetNumberInt(this.tb_telegram_cooldown.Text.Trim()); + AppSettings.Settings.send_telegram_errors = this.cb_send_telegram_errors.Checked; + AppSettings.Settings.send_pushover_errors = this.cb_send_pushover_errors.Checked; + AppSettings.Settings.startwithwindows = this.cbStartWithWindows.Checked; + AppSettings.Settings.MinimizeToTray = this.cbMinimizeToTray.Checked; + + AppSettings.Settings.DefaultUserName = this.tb_username.Text.Trim(); + AppSettings.Settings.DefaultPasswordEncrypted = this.tb_password.Text.Trim().Encrypt(); + + AppSettings.Settings.BlueIrisServer = this.tb_BlueIrisServer.Text.Trim(); + + AppSettings.Settings.pushover_APIKey = this.tb_Pushover_APIKey.Text.Trim(); + AppSettings.Settings.pushover_UserKey = this.tb_Pushover_UserKey.Text.Trim(); + AppSettings.Settings.pushover_cooldown_seconds = GetNumberInt(this.tb_Pushover_Cooldown.Text.Trim()); + + Global.Startup(AppSettings.Settings.startwithwindows); + + UpdateAIURLs(); + + if (await AppSettings.SaveAsync(true)) + { + Log("...Saved."); + } + else + { + Log("...Not saved. No changes?"); + } + //update variables + //input_path = AppSettings.Settings.input_path; + //deepstack_url = AppSettings.Settings.deepstack_url; + //telegram_chatid = AppSettings.Settings.telegram_chatid; + //telegram_chatids = telegram_chatid.Replace(" ", "").Split(','); //for multiple Telegram chats that receive alert images + //telegram_token = AppSettings.Settings.telegram_token; + //log_everything = AppSettings.Settings.log_everything; + //send_errors = AppSettings.Settings.send_errors; + + Application.DoEvents(); + + //update fswatcher to watch new input folder + UpdateWatchers(true); + + //Update blue iris info + Application.DoEvents(); + + await AITOOL.BlueIrisInfo.RefreshBIInfoAsync(AppSettings.Settings.BlueIrisServer); + + AITOOL.UpdateLatLong(); + + Application.DoEvents(); + + await AITOOL.Telegram.TryStartTelegram(); + + this.cmbInput.Items.Clear(); + foreach (string pth in AITOOL.BlueIrisInfo.ClipPaths) + this.cmbInput.Items.Add(pth); + + + this.LoadSettingsTab(); + + if (AITOOL.BlueIrisInfo.Result != BlueIrisResult.Valid && AITOOL.BlueIrisInfo.Result != BlueIrisResult.NotInstalled) + MessageBox.Show($"Error: Could not connect to BlueIris server: '{AITOOL.BlueIrisInfo.Result}'. See log for more detail.", "Error", MessageBoxButtons.OK, icon: MessageBoxIcon.Error); + + Global_GUI.InvokeIFRequired(this.BtnSettingsSave, () => + { + BtnSettingsSave.Enabled = true; + BtnSettingsSave.Text = "Save"; + }); + + Application.DoEvents(); + + + } + + //input path select dialog button + private void btn_input_path_Click(object sender, EventArgs e) + { + using (CommonOpenFileDialog dialog = new CommonOpenFileDialog()) + { + if (!string.IsNullOrEmpty(this.cmbInput.Text)) + { + dialog.InitialDirectory = this.cmbInput.Text; + + } + dialog.IsFolderPicker = true; + if (dialog.ShowDialog() == CommonFileDialogResult.Ok) + { + this.cmbInput.Text = dialog.FileName; + } + } + } + + //open log button + private void btn_open_log_Click(object sender, EventArgs e) + { + OpenLogFile(); + + } + + //ask before closing AI Tool to prevent accidentally closing + private async void Shell_FormClosing(object sender, FormClosingEventArgs e) + { + if (IsClosing) + return; + + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + Log($"------Closing------- CloseReason: {e.CloseReason}"); + + + try + { + if (!Restart && !ResetSettings && !CloseImmediately && e.CloseReason != CloseReason.WindowsShutDown && AppSettings.Settings.close_instantly <= 0) //if it's either enabled or not set -1 = not set | 0 = ask for confirmation | 1 = don't ask + { + using (var form = new InputForm($"Stop and close AI Tool?", "AI Tool", false)) + { + var result = form.ShowDialog(); + if (AppSettings.Settings.close_instantly == -1) + { + //if it's the first time, ask if the confirmation dialog should ever appear again + using (var form1 = new InputForm($"Confirm closing AI Tool every time?", "AI Tool", false, "NO", "YES")) + { + var result1 = form1.ShowDialog(); + if (result1 == DialogResult.Cancel) + { + AppSettings.Settings.close_instantly = 0; + } + else + { + AppSettings.Settings.close_instantly = 1; + } + } + } + + e.Cancel = (result == DialogResult.Cancel); + } + } + + + if (!e.Cancel) + { + + if (!Restart) + Global_GUI.SaveWindowState(this); + + AppSettings.SaveAsync(true); //save settings in any case + + //if (AITOOL.DeepStackServerControl.IsInstalled && AITOOL.DeepStackServerControl.IsStarted && AppSettings.Settings.deepstack_autostart) + // await AITOOL.DeepStackServerControl.StopAsync(); + + IsClosing = true; + + if (!AppSettings.AlreadyRunning) + Global.SaveRegSetting("LastShutdownState", "graceful shutdown"); + + MasterCTS.Cancel(); + + //wait a bit for the loops to cancel to avoid other threading errors on shutdown and to allow the logs to finish updating if we need to reset settings + Global.ResponsiveSleep(1000); + + if (Restart) + Application.Restart(); + + if (ResetSettings) + { + //make a backup copy: + string cursettingsfolder = Path.GetDirectoryName(AppSettings.Settings.SettingsFileName); + string baksettingsfolder = cursettingsfolder + "_RESET_" + DateTime.Now.ToString("s").Replace(":", "_"); + if (Global.DirectoryCopy(cursettingsfolder, baksettingsfolder, true, true)) + { + MessageBox.Show($"Reset successful. Backup folder created. Folder={baksettingsfolder}"); + //delete the settings folder + try + { Directory.Delete(cursettingsfolder, true); } + catch { } + //restart so it creates all new settings + Global.DeleteRegSettings(); + Application.Restart(); + } + else + { + MessageBox.Show($"Error: Failed to fully copy to the backup folder. NOT reset. Folder={baksettingsfolder}", "Error", buttons: MessageBoxButtons.OK, icon: MessageBoxIcon.Error); + } + } + + } + + } + catch { } + + + + } + + private async Task SaveDeepStackTabAsync() + { + + Global_GUI.InvokeIFRequired(this, async () => + { + + if (DeepStackServerControl == null) + DeepStackServerControl = new DeepStack(AppSettings.Settings.deepstack_adminkey, AppSettings.Settings.deepstack_apikey, AppSettings.Settings.deepstack_mode, AppSettings.Settings.deepstack_sceneapienabled, AppSettings.Settings.deepstack_faceapienabled, AppSettings.Settings.deepstack_detectionapienabled, AppSettings.Settings.deepstack_port, AppSettings.Settings.deepstack_customModelPath, AppSettings.Settings.deepstack_stopbeforestart, AppSettings.Settings.deepstack_customModelName, AppSettings.Settings.deepstack_customModelPort, AppSettings.Settings.deepstack_customModelMode, AppSettings.Settings.deepstack_customModelApiEnabled); + + DeepStackServerControl.GetDeepStackRun(); + + if (this.RB_Medium.Checked) + AppSettings.Settings.deepstack_mode = "Medium"; + if (this.RB_Low.Checked) + AppSettings.Settings.deepstack_mode = "Low"; + if (this.RB_High.Checked) + AppSettings.Settings.deepstack_mode = "High"; + + AppSettings.Settings.deepstack_detectionapienabled = this.Chk_DetectionAPI.Checked; + AppSettings.Settings.deepstack_faceapienabled = this.Chk_FaceAPI.Checked; + AppSettings.Settings.deepstack_sceneapienabled = this.Chk_SceneAPI.Checked; + AppSettings.Settings.deepstack_autostart = this.Chk_AutoStart.Checked; + AppSettings.Settings.deepstack_autoadd = this.chk_AutoAdd.Checked; + AppSettings.Settings.deepstack_debug = this.Chk_DSDebug.Checked; + AppSettings.Settings.deepstack_highpriority = this.chk_HighPriority.Checked; + //AppSettings.Settings.deepstack_adminkey = this.Txt_AdminKey.Text.Trim(); + //AppSettings.Settings.deepstack_apikey = this.Txt_APIKey.Text.Trim(); + AppSettings.Settings.deepstack_installfolder = this.Txt_DeepStackInstallFolder.Text.Trim(); + AppSettings.Settings.deepstack_port = this.Txt_Port.Text.Trim(); + AppSettings.Settings.deepstack_customModelPath = this.Txt_CustomModelPath.Text.Trim(); + AppSettings.Settings.deepstack_customModelName = this.Txt_CustomModelName.Text.Trim(); + AppSettings.Settings.deepstack_customModelPort = this.Txt_CustomModelPort.Text.Trim(); + AppSettings.Settings.deepstack_customModelMode = this.Txt_CustomModelMode.Text.Trim(); + AppSettings.Settings.deepstack_customModelApiEnabled = this.Chk_CustomModelAPI.Checked; + + AppSettings.Settings.deepstack_stopbeforestart = this.chk_stopbeforestart.Checked; + + AppSettings.Settings.deepstack_autorestart = this.Chk_AutoReStart.Checked; + AppSettings.Settings.deepstack_autorestart_fail_count = GetNumberInt(this.txt_DeepstackRestartFailCount.Text); + AppSettings.Settings.deepstack_autorestart_minutes_between_restart_attempts = this.txt_DeepstackNoMoreOftenThanMins.Text.ToDouble(); + + if (AppSettings.Settings.deepstack_autorestart_fail_count >= AppSettings.Settings.MaxQueueItemRetries) + { + MessageBox.Show($"Note: Deepstack restart fail count is '{AppSettings.Settings.deepstack_autorestart_fail_count}' but the maximum \r\nnumber of times a URL can fail before being disabled is '{AppSettings.Settings.MaxQueueItemRetries}'\r\nTo change, see 'MaxQueueItemRetries' in AITOOL.SETTINGS.JSON file."); + } + + await AppSettings.SaveAsync(true); + + + if (DeepStackServerControl.IsInstalled) + { + if (DeepStackServerControl.IsStarted) + { + + + if (DeepStackServerControl.IsActivated) + { + this.Lbl_BlueStackRunning.Text = "*RUNNING*"; + this.Lbl_BlueStackRunning.ForeColor = Color.Green; + + this.Btn_Start.Enabled = false; + this.Btn_Stop.Enabled = true; + } + else + { + this.Lbl_BlueStackRunning.Text = "*NOT ACTIVATED, RUNNING*"; + + this.Btn_Start.Enabled = false; + this.Btn_Stop.Enabled = true; + } + } + else + { + if (DeepStackServerControl.Starting) + { + this.Lbl_BlueStackRunning.Text = "STARTING..."; + this.Lbl_BlueStackRunning.ForeColor = Color.DodgerBlue; + this.Btn_Start.Enabled = false; + this.Btn_Stop.Enabled = true; + + } + else if (DeepStackServerControl.Stopping) + { + this.Lbl_BlueStackRunning.Text = "STOPPING..."; + this.Lbl_BlueStackRunning.ForeColor = Color.DodgerBlue; + this.Btn_Start.Enabled = false; + this.Btn_Stop.Enabled = false; + + } + else + { + this.Lbl_BlueStackRunning.Text = "*NOT RUNNING*"; + this.Lbl_BlueStackRunning.ForeColor = Color.Black; + + this.Btn_Start.Enabled = true; + this.Btn_Stop.Enabled = false; + + } + } + } + else + { + this.Btn_Start.Enabled = false; + this.Btn_Stop.Enabled = false; + this.Lbl_BlueStackRunning.Text = "*NOT INSTALLED*"; + + + } + + DeepStackServerControl.Update(AppSettings.Settings.deepstack_adminkey, AppSettings.Settings.deepstack_apikey, AppSettings.Settings.deepstack_mode, AppSettings.Settings.deepstack_sceneapienabled, AppSettings.Settings.deepstack_faceapienabled, AppSettings.Settings.deepstack_detectionapienabled, AppSettings.Settings.deepstack_port, AppSettings.Settings.deepstack_customModelPath, AppSettings.Settings.deepstack_stopbeforestart, AppSettings.Settings.deepstack_customModelName, AppSettings.Settings.deepstack_customModelPort, AppSettings.Settings.deepstack_customModelMode, AppSettings.Settings.deepstack_customModelApiEnabled); + + + }); + + } + + private async Task LoadDeepStackTab() + { + + try + { + + Global_GUI.InvokeIFRequired(this, () => + { + + if (DeepStackServerControl == null) + DeepStackServerControl = new DeepStack(AppSettings.Settings.deepstack_adminkey, AppSettings.Settings.deepstack_apikey, AppSettings.Settings.deepstack_mode, AppSettings.Settings.deepstack_sceneapienabled, AppSettings.Settings.deepstack_faceapienabled, AppSettings.Settings.deepstack_detectionapienabled, AppSettings.Settings.deepstack_port, AppSettings.Settings.deepstack_customModelPath, AppSettings.Settings.deepstack_stopbeforestart, AppSettings.Settings.deepstack_customModelName, AppSettings.Settings.deepstack_customModelPort, AppSettings.Settings.deepstack_customModelMode, AppSettings.Settings.deepstack_customModelApiEnabled); + + + //first update the port in the deepstack_url if found + //string prt = Global.GetWordBetween(AppSettings.Settings.deepstack_url, ":", " |/"); + //if (!string.IsNullOrEmpty(prt) && (Convert.ToInt32(prt) > 0)) + //{ + // DeepStackServerControl.Port = prt; + //} + + //This will OVERRIDE the port if the deepstack processes found running already have a different port, mode, etc: + DeepStackServerControl.GetDeepStackRun(); + + if (string.Equals(DeepStackServerControl.Mode, "medium", StringComparison.OrdinalIgnoreCase)) + this.RB_Medium.Checked = true; + if (string.Equals(DeepStackServerControl.Mode, "low", StringComparison.OrdinalIgnoreCase)) + this.RB_Low.Checked = true; + if (string.Equals(DeepStackServerControl.Mode, "high", StringComparison.OrdinalIgnoreCase)) + this.RB_High.Checked = true; + + this.Chk_DetectionAPI.Checked = DeepStackServerControl.DetectionAPIEnabled; + this.Chk_FaceAPI.Checked = DeepStackServerControl.FaceAPIEnabled; + this.Chk_SceneAPI.Checked = DeepStackServerControl.SceneAPIEnabled; + this.Chk_CustomModelAPI.Checked = DeepStackServerControl.CustomModelEnabled; + + Global_GUI.GroupboxEnableDisable(groupBoxCustomModel, Chk_CustomModelAPI); + + //have seen a few cases nothing is checked but it is required + if (!this.Chk_DetectionAPI.Checked && !this.Chk_FaceAPI.Checked && !this.Chk_SceneAPI.Checked && !this.Chk_CustomModelAPI.Checked) + { + //If NOTHING else is enabled, force regular detection to be enabled + this.Chk_DetectionAPI.Checked = true; + DeepStackServerControl.DetectionAPIEnabled = true; + } + + this.Chk_AutoStart.Checked = AppSettings.Settings.deepstack_autostart; + this.chk_AutoAdd.Checked = AppSettings.Settings.deepstack_autoadd; + this.Chk_DSDebug.Checked = AppSettings.Settings.deepstack_debug; + this.chk_HighPriority.Checked = AppSettings.Settings.deepstack_highpriority; + //this.Txt_AdminKey.Text = DeepStackServerControl.AdminKey; + //this.Txt_APIKey.Text = DeepStackServerControl.APIKey; + this.Txt_DeepStackInstallFolder.Text = DeepStackServerControl.DeepStackFolder; + this.Txt_Port.Text = DeepStackServerControl.Port; + this.Txt_CustomModelPath.Text = AppSettings.Settings.deepstack_customModelPath; + this.Txt_CustomModelName.Text = AppSettings.Settings.deepstack_customModelName; + this.Txt_CustomModelPort.Text = AppSettings.Settings.deepstack_customModelPort; + this.Txt_CustomModelMode.Text = AppSettings.Settings.deepstack_customModelMode; + this.chk_stopbeforestart.Checked = AppSettings.Settings.deepstack_stopbeforestart; + + this.Chk_AutoReStart.Checked = AppSettings.Settings.deepstack_autorestart; + this.txt_DeepstackRestartFailCount.Text = AppSettings.Settings.deepstack_autorestart_fail_count.ToString(); + this.txt_DeepstackNoMoreOftenThanMins.Text = AppSettings.Settings.deepstack_autorestart_minutes_between_restart_attempts.ToString(); + + if (!DeepStackServerControl.IsNewVersion) + { + this.Txt_CustomModelPath.Enabled = false; + this.Txt_CustomModelName.Enabled = false; + this.Txt_CustomModelPort.Enabled = false; + } + + this.tb_DeepstackCommandLine.Text = DeepStackServerControl.CommandLine; + this.tb_DeepStackURLs.Text = DeepStackServerControl.URLS; + + //if (prt != Txt_Port.Text) + //{ + // //server:port/maybe/more/path + // string serv = Global.GetWordBetween(AppSettings.Settings.deepstack_url, "", ":"); + // if (!string.IsNullOrEmpty(serv)) + // { + // tbDeepstackUrl.Text = serv + ":" + Txt_Port.Text; + // //AppSettings.Settings.deepstack_url = serv + ":" + Txt_Port.Text; + // //AppSettings.Settings.deepstack_url = tbDeepstackUrl.Text; + // //AppSettings.Save(); + // } + //} + + if (DeepStackServerControl.IsInstalled) + { + lbl_deepstackname.Text = DeepStackServerControl.DisplayName; + lbl_Deepstackversion.Text = DeepStackServerControl.DisplayVersion; + lbl_DeepstackType.Text = DeepStackServerControl.Type.ToString(); + + if (DeepStackServerControl.IsStarted && !DeepStackServerControl.HasError) + { + if (DeepStackServerControl.IsActivated && (DeepStackServerControl.VisionDetectionRunning || DeepStackServerControl.DetectionAPIEnabled || DeepStackServerControl.CustomModelEnabled || DeepStackServerControl.FaceAPIEnabled)) + { + + this.Lbl_BlueStackRunning.Text = "*RUNNING*"; + this.Lbl_BlueStackRunning.ForeColor = Color.Green; + + this.Btn_Start.Enabled = false; + this.Btn_Stop.Enabled = true; + } + else if (!DeepStackServerControl.IsActivated) + { + this.Lbl_BlueStackRunning.Text = "*NOT ACTIVATED, RUNNING*"; + + this.Btn_Start.Enabled = false; + this.Btn_Stop.Enabled = true; + } + else if (!DeepStackServerControl.VisionDetectionRunning || DeepStackServerControl.DetectionAPIEnabled) + { + this.Lbl_BlueStackRunning.Text = "*DETECTION API NOT RUNNING*"; + + this.Btn_Start.Enabled = false; + this.Btn_Stop.Enabled = true; + } + + } + else if (DeepStackServerControl.HasError) + { + this.Lbl_BlueStackRunning.Text = "*ERROR*"; + this.Lbl_BlueStackRunning.ForeColor = Color.Red; + + this.Btn_Start.Enabled = true; + this.Btn_Stop.Enabled = true; + } + else + { + if (DeepStackServerControl.Starting) + { + this.Lbl_BlueStackRunning.Text = "STARTING..."; + this.Lbl_BlueStackRunning.ForeColor = Color.DodgerBlue; + this.Btn_Start.Enabled = false; + this.Btn_Stop.Enabled = true; + + } + else if (DeepStackServerControl.Stopping) + { + this.Lbl_BlueStackRunning.Text = "STOPPING..."; + this.Lbl_BlueStackRunning.ForeColor = Color.DodgerBlue; + this.Btn_Start.Enabled = false; + this.Btn_Stop.Enabled = false; + + } + else + { + this.Lbl_BlueStackRunning.Text = "*NOT RUNNING*"; + this.Lbl_BlueStackRunning.ForeColor = Color.Black; + + this.Btn_Start.Enabled = true; + this.Btn_Stop.Enabled = false; + + } + //if (this.Chk_AutoStart.Checked && StartIfNeeded) + //{ + // if (await DeepStackServerControl.StartDeepstackAsync()) + // { + // if (DeepStackServerControl.IsStarted && !DeepStackServerControl.HasError) + // { + // LabelUpdate = delegate { this.Lbl_BlueStackRunning.Text = "*RUNNING*"; }; + // this.Invoke(LabelUpdate); + // this.Btn_Start.Enabled = false; + // this.Btn_Stop.Enabled = true; + // } + // else if (DeepStackServerControl.HasError) + // { + // LabelUpdate = delegate { this.Lbl_BlueStackRunning.Text = "*ERROR*"; }; + // this.Invoke(LabelUpdate); + + // this.Btn_Start.Enabled = false; + // this.Btn_Stop.Enabled = true; + // } + + // } + // else + // { + // LabelUpdate = delegate { this.Lbl_BlueStackRunning.Text = "*ERROR*"; }; + // this.Invoke(LabelUpdate); + + // this.Btn_Start.Enabled = false; + // this.Btn_Stop.Enabled = true; + // } + //} + } + } + else + { + this.Btn_Start.Enabled = false; + this.Btn_Stop.Enabled = false; + this.Lbl_BlueStackRunning.Text = "*NOT INSTALLED*"; + + + } + + }); + + + } + catch (Exception ex) + { + + Log(ex.Msg()); + } + } + + private async void Btn_Start_Click(object sender, EventArgs e) + { + Global_GUI.InvokeIFRequired(this, () => + { + this.Lbl_BlueStackRunning.Text = "STARTING..."; + this.Btn_Start.Enabled = false; + this.Btn_Stop.Enabled = false; + + }); + await this.SaveDeepStackTabAsync(); + await DeepStackServerControl.StartDeepstackAsync(); + //MessageBox.Show("Started"); + await this.LoadDeepStackTab(); + + } + + private async void Btn_Save_Click(object sender, EventArgs e) + { + await this.SaveDeepStackTabAsync(); + } + + private async void Btn_Stop_Click(object sender, EventArgs e) + { + Global_GUI.InvokeIFRequired(this, () => + { + this.Lbl_BlueStackRunning.Text = "STOPPING..."; + this.Btn_Start.Enabled = false; + this.Btn_Stop.Enabled = false; + + }); + await this.SaveDeepStackTabAsync(); + await DeepStackServerControl.StopDeepstackAsync(); + //MessageBox.Show("Stopped"); + await this.LoadDeepStackTab(); + } + + + private void btnViewLog_Click(object sender, EventArgs e) + { + OpenLogFile(); + } + + private void button1_Click(object sender, EventArgs e) + { + this.ShowErrors(); + } + + private void Chk_AutoScroll_CheckedChanged(object sender, EventArgs e) + { + AppSettings.Settings.Autoscroll_log = this.Chk_AutoScroll.Checked; + } + + private void tableLayoutPanel7_Paint(object sender, PaintEventArgs e) + { + + } + + private void button2_Click(object sender, EventArgs e) + { + using (CommonOpenFileDialog dialog = new CommonOpenFileDialog()) + { + if (!string.IsNullOrEmpty(this.cmbcaminput.Text)) + { + dialog.InitialDirectory = this.cmbcaminput.Text; + + } + dialog.IsFolderPicker = true; + if (dialog.ShowDialog() == CommonFileDialogResult.Ok) + { + this.cmbcaminput.Text = dialog.FileName; + } + } + } + + + private void BtnDynamicMaskingSettings_Click(object sender, EventArgs e) + { + if (this.FOLV_Cameras.SelectedObjects.Count == 0) + { + MessageBox.Show("Select a camera first"); + return; + } + + using (Frm_DynamicMasking frm = new Frm_DynamicMasking()) + { + Camera cam = AITOOL.GetCamera(((Camera)this.FOLV_Cameras.SelectedObjects[0]).Name); + frm.cam = cam; + frm.Text = "Dynamic Masking Settings - " + cam.Name; + + //Merge ClassObject's code + frm.num_history_mins.Value = cam.maskManager.HistorySaveMins;//load minutes to retain history objects that have yet to become masks + frm.num_mask_create.Value = cam.maskManager.HistoryThresholdCount; // load mask create counter + frm.num_mask_remove.Value = cam.maskManager.MaskRemoveMins; //load mask remove counter + frm.num_percent_var.Value = (decimal)cam.maskManager.PercentMatch; + frm.numMaskThreshold.Value = cam.maskManager.MaskRemoveThreshold; + + frm.num_max_unused.Value = cam.maskManager.MaxMaskUnusedDays; + + frm.cb_enabled.Checked = this.cb_masking_enabled.Checked; + + frm.lbl_objects.Text = cam.maskManager.MaskTriggeringObjects.ToString(); + + if (frm.ShowDialog() == DialogResult.OK) + { + ////get masking values from textboxes + + cam.maskManager.HistorySaveMins = frm.num_history_mins.Text.ToInt(); + cam.maskManager.HistoryThresholdCount = frm.num_mask_create.Text.ToInt(); + cam.maskManager.MaskRemoveMins = frm.num_mask_remove.Text.ToInt(); + cam.maskManager.MaskRemoveThreshold = frm.numMaskThreshold.Text.ToInt(); + cam.maskManager.PercentMatch = frm.num_percent_var.Text.ToDouble(); + cam.maskManager.MaxMaskUnusedDays = frm.num_max_unused.Text.ToInt(); + + this.cb_masking_enabled.Checked = frm.cb_enabled.Checked; + cam.maskManager.MaskingEnabled = this.cb_masking_enabled.Checked; + //cam.maskManager.Objects = frm.tb_objects.Text.Trim(); + + AppSettings.SaveAsync(true); + } + } + } + + private void btnDetails_Click(object sender, EventArgs e) + { + if (this.FOLV_Cameras.SelectedObjects.Count == 0) + { + MessageBox.Show("Select a camera first"); + return; + } + this.ShowMaskDetailsDialog(((Camera)this.FOLV_Cameras.SelectedObjects[0]).Name); + } + + private void ShowMaskDetailsDialog(string cameraname) + { + using (Frm_DynamicMaskDetails frm = new Frm_DynamicMaskDetails()) + { + + Camera CurCam = GetCamera(cameraname); + frm.cam = CurCam; + + frm.ShowDialog(); + + this.cb_masking_enabled.Checked = CurCam.maskManager.MaskingEnabled; + + } + + } + + + + + + private void btnCustomMask_Click(object sender, EventArgs e) + { + if (this.FOLV_Cameras.SelectedObjects.Count == 0) + { + MessageBox.Show("Select a camera first"); + return; + } + this.ShowEditImageMaskDialog(((Camera)this.FOLV_Cameras.SelectedObjects[0]).Name); + } + + private void ShowEditImageMaskDialog(string cameraname) + { + using (Frm_CustomMasking frm = new Frm_CustomMasking()) + { + Camera cam = AITOOL.GetCamera(cameraname); + frm.Cam = cam; + + if (frm.ShowDialog() == DialogResult.OK) + { + cam.mask_brush_size = frm.BrushSize; + } + } + + } + + //private void btnActions_Click(object sender, EventArgs e) + //{ + // using (Frm_LegacyActions frm = new Frm_LegacyActions()) + // { + + + // Camera cam = AITOOL.GetCamera(list2.SelectedItems[0].Text); + // frm.cam = cam; + + // frm.tbTriggerUrl.Text = string.Join("\r\n", Global.Split(cam.trigger_urls_as_string, "\r\n|;,")); + // frm.tbCancelUrl.Text = string.Join("\r\n", Global.Split(cam.cancel_urls_as_string, "\r\n|;,")); + // frm.tb_cooldown.Text = cam.cooldown_time.ToString(); //load cooldown time + // //load telegram image sending on/off option + // frm.cb_telegram.Checked = cam.telegram_enabled; + + // frm.cb_copyAlertImages.Checked = cam.Action_image_copy_enabled; + // frm.tb_network_folder_filename.Text = cam.Action_network_folder_filename; + // frm.tb_network_folder.Text = cam.Action_network_folder; + // frm.cb_RunProgram.Checked = cam.Action_RunProgram; + + // if (frm.ShowDialog() == DialogResult.OK) + // { + // cam.trigger_urls_as_string = string.Join(",", Global.Split(frm.tbTriggerUrl.Text.Trim(), "\r\n|;,")); + // cam.cancel_urls_as_string = string.Join(",", Global.Split(frm.tbCancelUrl.Text.Trim(), "\r\n|;,")); + // cam.cancel_urls = Global.Split(cam.cancel_urls_as_string, "\r\n|;,").ToArray(); + // cam.cooldown_time = Convert.ToDouble(frm.tb_cooldown.Text.Trim()); + // cam.telegram_enabled = frm.cb_telegram.Checked; + // cam.Action_image_copy_enabled = frm.cb_copyAlertImages.Checked; + // cam.Action_network_folder = frm.tb_network_folder.Text.Trim(); + // cam.Action_network_folder_filename = frm.tb_network_folder_filename.Text; + // cam.Action_RunProgram = frm.cb_RunProgram.Checked; + // cam.Action_RunProgramString = frm.tb_RunExternalProgram.Text; + + // AppSettings.Save(); + + // } + // } + //} + + private void btnActions_Click_1(object sender, EventArgs e) + { + using var Trace = new Trace(); //This c# 8.0 using feature will auto dispose when the function is done. + + if (this.FOLV_Cameras.SelectedObjects.Count == 0) + { + MessageBox.Show("Select a camera first"); + return; + } + + using (Frm_LegacyActions frm = new Frm_LegacyActions()) + { + + Camera cam = AITOOL.GetCamera(((Camera)this.FOLV_Cameras.SelectedObjects[0]).Name); + + frm.cam = cam; + + frm.tb_DetectionFormat.Text = cam.DetectionDisplayFormat; + frm.lbl_DetectionFormat.Text = AITOOL.ReplaceParams(cam, null, null, frm.tb_DetectionFormat.Text, Global.IPType.Path); + + frm.tb_ConfidenceFormat.Text = AppSettings.Settings.DisplayPercentageFormat; + frm.lbl_Confidence.Text = string.Format(frm.tb_ConfidenceFormat.Text, 99.123); + + + if (cam.cancel_urls_as_string.IsEmpty()) + cam.Action_CancelURL_Enabled = false; + if (cam.trigger_urls_as_string.IsEmpty()) + cam.Action_TriggerURL_Enabled = false; + + frm.cb_UrlTriggerEnabled.Checked = cam.Action_TriggerURL_Enabled; + frm.cb_UrlCancelEnabled.Checked = cam.Action_CancelURL_Enabled; + + frm.tbTriggerUrl.Text = cam.trigger_urls_as_string.SplitStr("\r\n|").JoinStr("\r\n"); + frm.tbCancelUrl.Text = cam.cancel_urls_as_string.SplitStr("\r\n|").JoinStr("\r\n"); + + frm.tb_ActionDelayMS.Text = AppSettings.Settings.ActionDelayMS.ToString(); + frm.tb_cooldown.Text = cam.cooldown_time_seconds.ToString(); //load cooldown time + frm.tb_sound_cooldown.Text = cam.sound_cooldown_time_seconds.ToString(); //load cooldown time + + frm.tb_NetworkFolderCleanupDays.Text = cam.Action_network_folder_purge_older_than_days.ToString(); + + //load telegram image sending on/off option + frm.cb_telegram.Checked = cam.telegram_enabled; + frm.tb_telegram_caption.Text = cam.telegram_caption; + //frm.tb_telegram_triggering_objects.Text = cam.telegram_triggering_objects; + + frm.cb_telegram_active_time.Text = cam.telegram_active_time_range; + + frm.cb_copyAlertImages.Checked = cam.Action_image_copy_enabled; + frm.tb_network_folder_filename.Text = cam.Action_network_folder_filename; + frm.tb_network_folder.Text = cam.Action_network_folder; + + frm.cb_RunProgram.Checked = cam.Action_RunProgram; + frm.tb_RunExternalProgram.Text = cam.Action_RunProgramString; + frm.tb_RunExternalProgramArgs.Text = cam.Action_RunProgramArgsString; + + frm.cb_PlaySound.Checked = cam.Action_PlaySounds; + frm.tb_Sounds.Text = cam.Action_Sounds; + + frm.cb_MQTT_enabled.Checked = cam.Action_mqtt_enabled; + frm.tb_MQTT_Payload.Text = cam.Action_mqtt_payload; + frm.tb_MQTT_Topic.Text = cam.Action_mqtt_topic; + frm.tb_MQTT_Payload_cancel.Text = cam.Action_mqtt_payload_cancel; + frm.tb_MQTT_Topic_Cancel.Text = cam.Action_mqtt_topic_cancel; + frm.cb_MQTT_SendImage.Checked = cam.Action_mqtt_send_image; + + frm.cb_Pushover_Enabled.Checked = cam.Action_pushover_enabled; + frm.tb_Pushover_Title.Text = cam.Action_pushover_title; + frm.tb_Pushover_Message.Text = cam.Action_pushover_message; + frm.tb_Pushover_Device.Text = cam.Action_pushover_device; + //frm.tb_pushover_triggering_objects.Text = cam.Action_pushover_triggering_objects; + frm.tb_Pushover_Priority.Text = cam.Action_pushover_Priority; + frm.tb_Pushover_sound.Text = cam.Action_pushover_Sound; + frm.cb_pushover_active_time.Text = cam.Action_pushover_active_time_range; + + frm.cb_queue_actions.Checked = cam.Action_queued; + + frm.cb_ActivateBlueIrisWindow.Checked = cam.Action_ActivateBlueIrisWindow; + + frm.cb_mergeannotations.Checked = cam.Action_image_merge_detections; + + frm.tb_jpeg_merge_quality.Text = cam.Action_image_merge_jpegquality.ToString(); + + Global_GUI.GroupboxEnableDisable(frm.groupBoxPushover, frm.cb_Pushover_Enabled); + Global_GUI.GroupboxEnableDisable(frm.groupBoxTelegram, frm.cb_telegram); + Global_GUI.GroupboxEnableDisable(frm.groupBoxMQTT, frm.cb_MQTT_enabled); + Global_GUI.GroupboxEnableDisable(frm.groupBoxUrlTrigger, frm.cb_UrlTriggerEnabled); + Global_GUI.GroupboxEnableDisable(frm.groupBoxUrlCancel, frm.cb_UrlCancelEnabled); + + frm.tb_Sounds.Enabled = frm.cb_PlaySound.Checked; + frm.tb_RunExternalProgram.Enabled = frm.cb_RunProgram.Checked; + frm.tb_RunExternalProgramArgs.Enabled = frm.cb_RunProgram.Checked; + frm.tb_network_folder.Enabled = frm.cb_copyAlertImages.Checked; + frm.tb_network_folder_filename.Enabled = frm.cb_copyAlertImages.Checked; + + frm.tb_ActionCancelSecs.Text = AppSettings.Settings.ActionCancelSeconds.ToString(); + + frm.cb_ShowOnlyRelevant.Checked = AppSettings.Settings.HistoryOnlyDisplayRelevantObjects; + + if (frm.cb_mergeannotations.Checked) + frm.tb_jpeg_merge_quality.Enabled = true; + else + frm.tb_jpeg_merge_quality.Enabled = false; + + if (frm.ShowDialog() == DialogResult.OK) + { + cam.DetectionDisplayFormat = frm.tb_DetectionFormat.Text.Trim(); + AppSettings.Settings.DisplayPercentageFormat = frm.tb_ConfidenceFormat.Text.Trim(); + + //clean up the trigger lists by splitting and re-joining + cam.trigger_urls_as_string = frm.tbTriggerUrl.Text.Trim().SplitStr("\r\n|").JoinStr("|"); + cam.trigger_urls = cam.trigger_urls_as_string.SplitStr("\r\n|").ToArray(); + + cam.cancel_urls_as_string = frm.tbCancelUrl.Text.Trim().SplitStr("\r\n|").JoinStr("|"); + cam.cancel_urls = cam.cancel_urls_as_string.SplitStr("\r\n|").ToArray(); + + cam.Action_TriggerURL_Enabled = frm.cb_UrlTriggerEnabled.Checked; + cam.Action_CancelURL_Enabled = frm.cb_UrlCancelEnabled.Checked; + + if (cam.cancel_urls.Count() == 0) + cam.Action_CancelURL_Enabled = false; + if (cam.trigger_urls.Count() == 0) + cam.Action_TriggerURL_Enabled = false; + + AppSettings.Settings.ActionDelayMS = GetNumberInt(frm.tb_ActionDelayMS.Text.Trim()); + cam.cooldown_time_seconds = GetNumberInt(frm.tb_cooldown.Text.Trim()); + cam.sound_cooldown_time_seconds = GetNumberInt(frm.tb_sound_cooldown.Text.Trim()); + cam.telegram_enabled = frm.cb_telegram.Checked; + cam.telegram_caption = frm.tb_telegram_caption.Text.Trim(); + //cam.telegram_triggering_objects = frm.tb_telegram_triggering_objects.Text.Trim(); + + cam.Action_network_folder_purge_older_than_days = GetNumberInt(frm.tb_NetworkFolderCleanupDays.Text.Trim()); + + cam.telegram_active_time_range = frm.cb_telegram_active_time.Text.Trim(); + + cam.Action_image_copy_enabled = frm.cb_copyAlertImages.Checked; + cam.Action_network_folder = frm.tb_network_folder.Text.Trim(); + cam.Action_network_folder_filename = frm.tb_network_folder_filename.Text; + + cam.Action_RunProgram = frm.cb_RunProgram.Checked; + cam.Action_RunProgramString = frm.tb_RunExternalProgram.Text.Trim(); + cam.Action_RunProgramArgsString = frm.tb_RunExternalProgramArgs.Text.Trim(); + + cam.Action_PlaySounds = frm.cb_PlaySound.Checked; + cam.Action_Sounds = frm.tb_Sounds.Text.Trim(); + + cam.Action_mqtt_enabled = frm.cb_MQTT_enabled.Checked; + cam.Action_mqtt_payload = frm.tb_MQTT_Payload.Text.Trim(); + cam.Action_mqtt_topic = frm.tb_MQTT_Topic.Text.Trim(); + cam.Action_mqtt_payload_cancel = frm.tb_MQTT_Payload_cancel.Text.Trim(); + cam.Action_mqtt_topic_cancel = frm.tb_MQTT_Topic_Cancel.Text.Trim(); + cam.Action_mqtt_send_image = frm.cb_MQTT_SendImage.Checked; + + cam.Action_pushover_enabled = frm.cb_Pushover_Enabled.Checked; + cam.Action_pushover_title = frm.tb_Pushover_Title.Text.Trim(); + cam.Action_pushover_message = frm.tb_Pushover_Message.Text.Trim(); + cam.Action_pushover_device = frm.tb_Pushover_Device.Text.Trim(); + //cam.Action_pushover_triggering_objects = frm.tb_pushover_triggering_objects.Text.Trim(); + cam.Action_pushover_Sound = frm.tb_Pushover_sound.Text.Trim(); + cam.Action_pushover_Priority = frm.tb_Pushover_Priority.Text.Trim(); + cam.Action_pushover_active_time_range = frm.cb_pushover_active_time.Text.Trim(); + + cam.Action_image_merge_detections = frm.cb_mergeannotations.Checked; + + cam.Action_image_merge_jpegquality = Convert.ToInt64(frm.tb_jpeg_merge_quality.Text); + + cam.Action_queued = frm.cb_queue_actions.Checked; + cam.Action_ActivateBlueIrisWindow = frm.cb_ActivateBlueIrisWindow.Checked; + + AppSettings.Settings.ActionCancelSeconds = GetNumberInt(frm.tb_ActionCancelSecs.Text); + AppSettings.Settings.HistoryOnlyDisplayRelevantObjects = frm.cb_ShowOnlyRelevant.Checked; + + + cam.UpdateCamera(); + + UpdateActionsLabel(cam); + + AppSettings.SaveAsync(true); + + } + } + } + + private void UpdateActionsLabel(Camera cam) + { + Lbl_Actions.Text = ""; + if (cam.Action_TriggerURL_Enabled) + Lbl_Actions.Text += "TriggerURL"; + if (cam.Action_CancelURL_Enabled) + Lbl_Actions.Text += ", CancelURL"; + if (cam.telegram_enabled) + Lbl_Actions.Text += ", Telegram"; + if (cam.Action_pushover_enabled) + Lbl_Actions.Text += ", Pushover"; + if (cam.Action_mqtt_enabled) + Lbl_Actions.Text += ", MQTT"; + if (cam.Action_RunProgram) + Lbl_Actions.Text += ", Run"; + if (cam.Action_PlaySounds) + Lbl_Actions.Text += ", Sound"; + if (cam.Action_image_copy_enabled) + Lbl_Actions.Text += ", Copy"; + + if (Lbl_Actions.Text.IsEmpty()) + Lbl_Actions.Text = "No Actions enabled."; + else + Lbl_Actions.Text = Lbl_Actions.Text.Trim(", ".ToCharArray()); + + } + + private void tbDeepstackUrl_TextChanged(object sender, EventArgs e) + { + + } + + private void tabLog_Click(object sender, EventArgs e) + { + + } + + private void folv_history_SelectionChanged(object sender, EventArgs e) + { + + if (IsClosing) + return; + + string filename = ""; + + try + { + if (this.folv_history.SelectedObjects != null && this.folv_history.SelectedObjects.Count > 0) + { + History hist = (History)this.folv_history.SelectedObjects[0]; + + if (hist == null) + return; + + filename = hist.Filename; + + if (!filename.IsEmpty() && filename.Contains("\\") && File.Exists(filename)) + { + this.lbl_objects.Text = $"Loading Image {filename}..."; + + using ClsImageQueueItem imgq = new ClsImageQueueItem(filename, 0); + if (imgq.IsValid()) + { + this.pictureBox1.BackgroundImage = imgq.ToImage(); //load actual image as background, so that an overlay can be added as the image + } + this.showHideMask(); + this.lbl_objects.Text = hist.Detections; + } + else + { + Log("Removing missing file from database: " + filename); + HistoryDB.DeleteHistoryQueue(filename); + this.lbl_objects.Text = "Image not found"; + this.pictureBox1.BackgroundImage = null; + } + + if (!string.IsNullOrEmpty(hist.PredictionsJSON)) + { + this.toolStripButtonDetails.Enabled = true; + } + else + { + this.toolStripButtonDetails.Enabled = false; + } + + this.toolStripButtonEditImageMask.Enabled = true; + this.toolStripButtonMaskDetails.Enabled = true; + this.toolStripButtonEditURL.Enabled = true; + + } + else + { + this.lbl_objects.Text = "No selection"; + this.pictureBox1.BackgroundImage = null; + this.toolStripButtonDetails.Enabled = false; + this.toolStripButtonEditImageMask.Enabled = false; + this.toolStripButtonMaskDetails.Enabled = false; + this.toolStripButtonEditURL.Enabled = false; + } + + } + catch (Exception ex) + { + Log($"Error: {ex.Msg()} - Hist.Filename={filename}"); + + } + + + + } + + private void folv_history_FormatRow(object sender, BrightIdeasSoftware.FormatRowEventArgs e) + { + this.FormatHistoryRow(sender, e); + } + + private void FormatHistoryRow(object Sender, BrightIdeasSoftware.FormatRowEventArgs e) + { + try + { + History hist = (History)e.Model; + + // If SPI IsNot Nothing Then + if (hist.Success && !hist.HasError) + e.Item.ForeColor = Color.Green; + else if (hist.HasError) + { + e.Item.ForeColor = Color.Black; + e.Item.BackColor = Color.Red; + } + else if (hist.WasSkipped) + e.Item.ForeColor = Color.Red; + else if (hist.WasMasked) + { + e.Item.ForeColor = Color.Black; + e.Item.BackColor = Color.LightGray; + } + else if (!hist.Success && hist.Detections.IndexOf("false alert", StringComparison.OrdinalIgnoreCase) >= 0) + e.Item.ForeColor = Color.Gray; + else + e.Item.ForeColor = Color.Black; + } + + + catch (Exception) + { + } + // Log("Error: " & ex.Msg()) + finally + { + } + } + + private void FormatCameraRow(object Sender, BrightIdeasSoftware.FormatRowEventArgs e) + { + try + { + Camera cam = (Camera)e.Model; + + + if (!cam.enabled) + e.Item.ForeColor = Color.Gray; + else if (cam.Name.Equals("default", StringComparison.OrdinalIgnoreCase) || string.IsNullOrEmpty(cam.Prefix)) + e.Item.ForeColor = Color.Brown; + else + e.Item.ForeColor = Color.Black; + } + + + catch (Exception) + { + } + // Log("Error: " & ex.Msg()) + finally + { + } + } + private void btn_resetstats_Click(object sender, EventArgs e) + { + + if (string.Equals(this.comboBox1.Text.Trim(), "All Cameras", StringComparison.OrdinalIgnoreCase)) + { + foreach (Camera cam in AppSettings.Settings.CameraList) + { + cam.stats_alerts = 0; + cam.stats_irrelevant_alerts = 0; + cam.stats_false_alerts = 0; + cam.stats_skipped_images = 0; + cam.stats_skipped_images_session = 0; + } + } + else + { + + Camera cam = AITOOL.GetCamera(this.comboBox1.Text); //int i = AppSettings.Settings.CameraList.FindIndex(x => x.name.ToLower().Trim() == comboBox1.Text.ToLower().Trim()); + if (cam != null) + { + cam.stats_alerts = 0; + cam.stats_irrelevant_alerts = 0; + cam.stats_false_alerts = 0; + cam.stats_skipped_images = 0; + cam.stats_skipped_images_session = 0; + } + } + + fcalc.Clear(); + icalc.Clear(); + lcalc.Clear(); + qcalc.Clear(); + scalc.Clear(); + tcalc.Clear(); + + LogMan.ErrorCount = 0; + + AppSettings.SaveAsync(true); + + this.UpdatePieChart(); this.UpdateTimeline(); this.UpdateConfidenceChart(); + + this.UpdateStats(); + } + + private async void cb_filter_skipped_CheckedChanged(object sender, EventArgs e) + { + await this.LoadHistoryAsync(true, AppSettings.Settings.HistoryFollow).ConfigureAwait(false); + } + + private async void HistoryUpdateListTimer_Tick(object sender, EventArgs e) + { + if (IsClosing) + return; + + if (!AppSettings.AlreadyRunning) + Global.SaveRegSetting("LastShutdownState", $"checkpoint: HistoryUpdateTimer: {DateTime.Now}"); + + await this.UpdateHistoryAddedRemoved(); + + this.UpdateStats(); + } + + private void folv_history_SelectedIndexChanged(object sender, EventArgs e) + { + + } + + private void toolStripStatusErrors_Click(object sender, EventArgs e) + { + this.ShowErrors(); + LogMan.ErrorCount = 0; + } + + private async void cb_follow_CheckedChanged(object sender, EventArgs e) + { + await this.LoadHistoryAsync(true, AppSettings.Settings.HistoryFollow).ConfigureAwait(false); + } + + private async void comboBox_filter_camera_SelectionChangeCommitted(object sender, EventArgs e) + { + await this.LoadHistoryAsync(true, AppSettings.Settings.HistoryFollow).ConfigureAwait(false); + } + + private void comboBox1_SelectionChangeCommitted(object sender, EventArgs e) + { + if (this.tabControl1.SelectedIndex == 1) + { + + this.UpdatePieChart(); this.UpdateTimeline(); this.UpdateConfidenceChart(); + } + } + + private void Shell_DpiChanged(object sender, DpiChangedEventArgs e) + { + //Controls get really messed up when DPI changes when app is open. Just trying to figure out the right way to handle.... + Log($"[System DPI Changed from {e.DeviceDpiOld} to {e.DeviceDpiNew}]"); + + //we need to figure out how to force a full resize of all components - something is preventing that from happening + //automatically upon DPI change. A suspicion is the custom DBLayoutPanel, but its a clusterfuck to try to + //revert back to TableLayoutPanel for everything. + + } + + private void SaveFilters() + { + AppSettings.Settings.HistoryFilterAnimals = cb_filter_animal.Checked; + AppSettings.Settings.HistoryFilterMasked = cb_filter_masked.Checked; + AppSettings.Settings.HistoryFilterNoSuccess = cb_filter_nosuccess.Checked; + AppSettings.Settings.HistoryFilterPeople = cb_filter_person.Checked; + AppSettings.Settings.HistoryFilterSkipped = cb_filter_skipped.Checked; + AppSettings.Settings.HistoryFilterRelevant = cb_filter_success.Checked; + AppSettings.Settings.HistoryFilterVehicles = cb_filter_vehicle.Checked; + + AppSettings.SaveAsync(true); + } + + private async void cb_filter_success_Click(object sender, EventArgs e) + { + SaveFilters(); + await this.LoadHistoryAsync(true, AppSettings.Settings.HistoryFollow).ConfigureAwait(false); + } + + private async void cb_filter_nosuccess_Click(object sender, EventArgs e) + { + SaveFilters(); + await this.LoadHistoryAsync(true, AppSettings.Settings.HistoryFollow).ConfigureAwait(false); + AppSettings.SaveAsync(true); + } + + private async void cb_filter_person_Click(object sender, EventArgs e) + { + SaveFilters(); + await this.LoadHistoryAsync(true, AppSettings.Settings.HistoryFollow).ConfigureAwait(false); + AppSettings.SaveAsync(true); + } + + private async void cb_filter_animal_Click(object sender, EventArgs e) + { + SaveFilters(); + await this.LoadHistoryAsync(true, AppSettings.Settings.HistoryFollow).ConfigureAwait(false); + } + + private async void cb_filter_vehicle_Click(object sender, EventArgs e) + { + SaveFilters(); + await this.LoadHistoryAsync(true, AppSettings.Settings.HistoryFollow).ConfigureAwait(false); + } + + private async void cb_filter_skipped_Click(object sender, EventArgs e) + { + SaveFilters(); + await this.LoadHistoryAsync(true, AppSettings.Settings.HistoryFollow).ConfigureAwait(false); + } + + private async void cb_filter_masked_Click(object sender, EventArgs e) + { + SaveFilters(); + await this.LoadHistoryAsync(true, AppSettings.Settings.HistoryFollow).ConfigureAwait(false); + } + + private async void comboBox_filter_camera_DropDownClosed(object sender, EventArgs e) + { + SaveFilters(); + await this.LoadHistoryAsync(true, AppSettings.Settings.HistoryFollow).ConfigureAwait(false); + } + + private void cb_showMask_Click(object sender, EventArgs e) + { + AppSettings.Settings.HistoryShowMask = this.cb_showMask.Checked; + AppSettings.SaveAsync(true); + this.showHideMask(); + } + + private void cb_showObjects_CheckedChanged(object sender, EventArgs e) + { + if (this.folv_history.SelectedObjects != null && this.folv_history.SelectedObjects.Count > 0) + { + this.pictureBox1.Refresh(); + } + } + + private void automaticallyRefreshToolStripMenuItem_Click(object sender, EventArgs e) + { + AppSettings.Settings.HistoryAutoRefresh = this.automaticallyRefreshToolStripMenuItem.Checked; + AppSettings.SaveAsync(true); + this.HistoryStartStop(); + + } + + private void cb_showObjects_Click(object sender, EventArgs e) + { + AppSettings.Settings.HistoryShowObjects = this.cb_showObjects.Checked; + AppSettings.SaveAsync(true); + this.pictureBox1.Refresh(); + } + + private void cb_follow_Click(object sender, EventArgs e) + { + AppSettings.Settings.HistoryFollow = this.cb_follow.Checked; + AppSettings.SaveAsync(true); + } + + private void toolStripButtonDetails_Click(object sender, EventArgs e) + { + this.ViewPredictionDetails(); + } + + private void ViewPredictionDetails() + { + try + { + List allpredictions = new List(); + string filename = ""; + foreach (History hist in this.folv_history.SelectedObjects) + { + List predictions = hist.Predictions(); + + if (predictions.Count > 0) + { + allpredictions.AddRange(predictions); + filename = hist.Filename; + } + else + { + Log($"debug: No predictions for image {hist.Filename}: Json='{hist.PredictionsJSON}'"); + } + + } + + Frm_ObjectDetail frm = new Frm_ObjectDetail(); + frm.PredictionObjectDetailsList = allpredictions; + frm.ImageFileName = filename; + frm.Show(); + + } + catch (Exception ex) + { + + Log("Error: " + ex.Msg()); + } + } + + + + private void testDetectionAgainToolStripMenuItem_Click(object sender, EventArgs e) + { + + + if (this.folv_history.SelectedObjects != null && this.folv_history.SelectedObjects.Count > 0) + { + Log("----------------------- TESTING TRIGGERS ----------------------------"); + + foreach (History hist in this.folv_history.SelectedObjects) + { + if (!string.IsNullOrEmpty(hist.Filename) && File.Exists(hist.Filename)) + { + //test by copying the file as a new file into the watched folder' + string folder = Path.GetDirectoryName(hist.Filename); + string filename = Path.GetFileNameWithoutExtension(hist.Filename); + //strip out anything after the first _AITOOLTEST_ in the filename + filename = filename.GetWord("", "_AITOOLTEST_"); + string ext = Path.GetExtension(hist.Filename); + string testfile = Path.Combine(folder, $"{filename}_AITOOLTEST_{DateTime.Now.TimeOfDay.TotalSeconds.Round(0)}{ext}"); + File.Copy(hist.Filename, testfile, true); + string str = "Created test image file based on last detected object for the camera: " + testfile; + Log(str); + } + else + { + Log("Error: File does not exist for testing: " + hist.Filename); + + } + + + } + + Log("---------------------- DONE TESTING TRIGGERS -------------------------"); + + } + + } + + private void detailsToolStripMenuItem_Click(object sender, EventArgs e) + { + this.ViewPredictionDetails(); + } + + private async void refreshToolStripMenuItem_Click(object sender, EventArgs e) + { + await this.LoadHistoryAsync(false, AppSettings.Settings.HistoryFollow).ConfigureAwait(false); + } + + private void folv_history_MouseDoubleClick(object sender, MouseEventArgs e) + { + this.ViewPredictionDetails(); + } + + private void toolStripButtonMaskDetails_Click(object sender, EventArgs e) + { + + if (this.folv_history.SelectedObjects != null && this.folv_history.SelectedObjects.Count > 0) + { + History hist = (History)this.folv_history.SelectedObjects[0]; + this.ShowMaskDetailsDialog(hist.Camera); + + } + + } + + private void dynamicMaskDetailsToolStripMenuItem_Click(object sender, EventArgs e) + { + if (this.folv_history.SelectedObjects != null && this.folv_history.SelectedObjects.Count > 0) + { + History hist = (History)this.folv_history.SelectedObjects[0]; + this.ShowMaskDetailsDialog(hist.Camera); + + } + } + + private void toolStripButtonEditImageMask_Click(object sender, EventArgs e) + { + if (this.folv_history.SelectedObjects != null && this.folv_history.SelectedObjects.Count > 0) + { + History hist = (History)this.folv_history.SelectedObjects[0]; + this.ShowEditImageMaskDialog(hist.Camera); + + } + } + + private void btn_enabletelegram_Click(object sender, EventArgs e) + { + AppSettings.Settings.send_telegram_errors = cb_send_telegram_errors.Checked; + AppSettings.Settings.send_pushover_errors = cb_send_pushover_errors.Checked; + + foreach (Camera cam in AppSettings.Settings.CameraList) + { + if (AppSettings.Settings.send_telegram_errors && !cam.telegram_enabled) + { + cam.telegram_enabled = true; + Log($"Enabled Telegram on camera '{cam.Name}'."); + } + if (AppSettings.Settings.send_pushover_errors && !cam.Action_pushover_enabled) + { + cam.Action_pushover_enabled = true; + Log($"Enabled Pushover on camera '{cam.Name}'."); + } + } + AppSettings.SaveAsync(true); + } + + private void btn_disabletelegram_Click(object sender, EventArgs e) + { + AppSettings.Settings.send_telegram_errors = cb_send_telegram_errors.Checked; + AppSettings.Settings.send_pushover_errors = cb_send_pushover_errors.Checked; + + foreach (Camera cam in AppSettings.Settings.CameraList) + { + if (AppSettings.Settings.send_telegram_errors && cam.telegram_enabled) + { + cam.telegram_enabled = false; + Log($"Disabled Telegram on camera '{cam.Name}'."); + } + if (AppSettings.Settings.send_pushover_errors && cam.Action_pushover_enabled) + { + cam.Action_pushover_enabled = false; + Log($"Disabled Pushover on camera '{cam.Name}'."); + } + } + AppSettings.SaveAsync(true); + } + + private void storeFalseAlertsToolStripMenuItem_Click(object sender, EventArgs e) + { + AppSettings.Settings.HistoryStoreFalseAlerts = this.storeFalseAlertsToolStripMenuItem.Checked; + AppSettings.SaveAsync(true); + } + + private void storeMaskedAlertsToolStripMenuItem_Click(object sender, EventArgs e) + { + AppSettings.Settings.HistoryStoreMaskedAlerts = this.storeMaskedAlertsToolStripMenuItem.Checked; + AppSettings.SaveAsync(true); + + } + + private void showOnlyRelevantObjectsToolStripMenuItem_Click(object sender, EventArgs e) + { + AppSettings.Settings.HistoryOnlyDisplayRelevantObjects = this.showOnlyRelevantObjectsToolStripMenuItem.Checked; + AppSettings.SaveAsync(true); + this.pictureBox1.Refresh(); + + } + + private void btnSaveTo_Click(object sender, EventArgs e) + { + this.CameraSave(true); + } + + private async void LogUpdateListTimer_Tick(object sender, EventArgs e) + { + await this.UpdateLogAddedRemovedAsync(); + } + + private void Chk_AutoScroll_Click(object sender, EventArgs e) + { + AppSettings.Settings.Autoscroll_log = this.Chk_AutoScroll.Checked; + } + + + + private void Chk_AutoScroll_Click_1(object sender, EventArgs e) + { + AppSettings.Settings.Autoscroll_log = this.Chk_AutoScroll.Checked; + } + + private void chk_filterErrors_Click(object sender, EventArgs e) + { + this.FilterLogErrors(); + } + + private async void FilterLogErrors() + { + if (IsLoading) + return; + + + if (this.chk_filterErrors.Checked) + { + //filter + Global_GUI.InvokeIFRequired(this.folv_log, () => + { + using var cw = new Global_GUI.CursorWait(); + this.folv_log.ModelFilter = new BrightIdeasSoftware.ModelFilter((object x) => + { + ClsLogItm CLI = (ClsLogItm)x; + return (CLI.Level == LogLevel.Error || CLI.Level == LogLevel.Warn || CLI.Level == LogLevel.Fatal); + }); + }); } - else //if showmask toggle-button is not checked, hide the mask overlay + else { - pictureBox1.Image = null; + this.folv_log.ModelFilter = null; + await this.UpdateLogAddedRemovedAsync(true); } } - //show rectangle overlay - private void showObject(PaintEventArgs e, Color color, int _xmin, int _ymin, int _xmax, int _ymax, string text) + private async Task FilterHistItem(History hist) { - if (list1.SelectedItems.Count > 0) + if (IsLoading) + return false; + + bool ret = false; + + try { - //1. get the padding between the image and the picturebox border - //get dimensions of the image and the picturebox - float imgWidth = pictureBox1.BackgroundImage.Width; - float imgHeight = pictureBox1.BackgroundImage.Height; - float boxWidth = pictureBox1.Width; - float boxHeight = pictureBox1.Height; + this.toolStripButtonPauseLog.Checked = true; //pause for a bit, and stay paused if results found - //these variables store the padding between image border and picturebox border - int absX = 0; - int absY = 0; + using var cw = new Global_GUI.CursorWait(); - //because the sizemode of the picturebox is set to 'zoom', the image is scaled down - float scale = 1; + Stopwatch sw = new Stopwatch(); + //first search directly through log files (the history entry may not be from todays log) + string justfile = Path.GetFileName(hist.Filename); - //Comparing the aspect ratio of both the control and the image itself. - if (imgWidth / imgHeight > boxWidth / boxHeight) //if the image is p.e. 16:9 and the picturebox is 4:3 - { - scale = boxWidth / imgWidth; //get scale factor - absY = (int)(boxHeight - scale * imgHeight) / 2; //padding on top and below the image - } - else //if the image is p.e. 4:3 and the picturebox is widescreen 16:9 - { - scale = boxHeight / imgHeight; //get scale factor - absX = (int)(boxWidth - scale * imgWidth) / 2; //padding left and right of the image - } + List found = new List(); + + //AITool.[2020-10-19].log + //AITool.[2020-10-19.1].log.zip + List files = Global.GetFiles(Path.GetDirectoryName(AppSettings.Settings.LogFileName), "AITOOL.[*].LOG|AITOOL.[*].LOG.ZIP", SearchOption.TopDirectoryOnly); - //2. inputted position values are for the original image size. As the image is probably smaller in the picturebox, the positions must be adapted. - int xmin = (int)(scale * _xmin) + absX; - int xmax = (int)(scale * _xmax) + absX; - int ymin = (int)(scale * _ymin) + absY; - int ymax = (int)(scale * _ymax) + absY; + //sort by date so newest files are searched first + files = files.OrderByDescending((d) => d.LastWriteTime).ToList(); - //3. paint rectangle - System.Drawing.Rectangle rect = new System.Drawing.Rectangle(xmin, ymin, xmax - xmin, ymax - ymin); - using (Pen pen = new Pen(color, 2)) + string imagemaskkey = ""; + string matchedmaskkey = ""; + + foreach (var fi in files) { - e.Graphics.DrawRectangle(pen, rect); //draw rectangle - } + //AITool.[2020-10-19.1].log.zip + // ------------ + string date = fi.FullName.GetWord("[", "]"); + //AITool.[2020-10-19.1].log.zip + // ---------- + date = date.GetWord("", "."); + + if (string.IsNullOrEmpty(date)) + { + Log("Error: Date in filename is unexpected: " + fi.FullName); + continue; + } - //object name text below rectangle - rect = new System.Drawing.Rectangle(xmin - 1, ymax, (int)boxWidth, - (int)boxHeight); //sets bounding box for drawn text - Brush brush = new SolidBrush(color); //sets background rectangle color - System.Drawing.SizeF - size = e.Graphics.MeasureString(text, - new Font("Segoe UI Semibold", 10)); //finds size of text to draw the background rectangle - e.Graphics.FillRectangle(brush, xmin - 1, ymax, size.Width, - size.Height); //draw grey background rectangle for detection text - e.Graphics.DrawString(text, new Font("Segoe UI Semibold", 10), Brushes.Black, rect); //draw detection text + DateTime DATE = DateTime.MinValue; - } - } + if (Global.GetDateStrict(date, ref DATE, "yyyy-MM-dd")) + { + if (DATE.ToString("yyyy-MM-dd") == hist.Date.ToString("yyyy-MM-dd") || fi.LastWriteTime.ToString("yyyy-MM-dd") == hist.Date.ToString("yyyy-MM-dd") || fi.CreationTime.ToString("yyyy-MM-dd") == hist.Date.ToString("yyyy-MM-dd")) + { + //load into memory + Log($"Debug: Searching log file (namedate='{DATE.ToString("yyyy-MM-dd")}',moddate='{fi.LastWriteTime.ToString("yyyy-MM-dd")}',createdate='{fi.CreationTime.ToString("yyyy-MM-dd")}'): {fi.Name}..."); - //load object rectangle overlays - private void pictureBox1_Paint(object sender, PaintEventArgs e) - { - if (cb_showObjects.Checked && list1.SelectedItems.Count > 0) //if checkbox button is enabled - { - Log("Loading object rectangles..."); - int countr = list1.SelectedItems[0].SubItems[4].Text.Split(';').Count(); + this.UpdateProgressBar($"Searching {fi.Name}...", 1, 1, 1); - Color color = new Color(); - string detections = list1.SelectedItems[0].SubItems[3].Text; - if (detections.Contains("irrelevant") || detections.Contains("masked") || detections.Contains("confidence")) - { - color = Color.Silver; - detections = detections.Split(':')[1]; //removes the "1x masked, 3x irrelevant:" before the actual detection, otherwise this would be displayed in the detection tags - } - else - { - color = Color.Red; - } + List curlist = await LogMan.LoadLogFileAsync(fi.FullName, false, false); - //display a rectangle around each relevant object - for (int i = 0; i < countr - 1; i++) - { - string[] detectionsArray = detections.Split(';');//creates array of detected objects, used for adding text overlay - //load 'xmin,ymin,xmax,ymax' from third column into a string - string position = list1.SelectedItems[0].SubItems[4].Text.Split(';')[i]; + this.UpdateProgressBar($"Searching {fi.Name}...", 1, 1, curlist.Count); - //store xmin, ymin, xmax, ymax in separate variables - Int32.TryParse(position.Split(',')[0], out int xmin); - Int32.TryParse(position.Split(',')[1], out int ymin); - Int32.TryParse(position.Split(',')[2], out int xmax); - Int32.TryParse(position.Split(',')[3], out int ymax); + DateTime FirstSeen = DateTime.MinValue; - Log($"{i} - {xmin}, {ymin}, {xmax}, {ymax}"); + int fnd = 0; + int cnt = 0; + foreach (var CLI in curlist) + { + cnt++; + //this.UpdateProgressBar($"Searching {fi.Name}...", cnt, 1, curlist.Count); - showObject(e, color, xmin, ymin, xmax, ymax, detectionsArray[i]); //call rectangle drawing method, calls appropriate detection text + if (CLI.Detail.IndexOf(justfile, StringComparison.OrdinalIgnoreCase) >= 0) + { + if (FirstSeen == DateTime.MinValue) + FirstSeen = CLI.Date; + fnd++; - Log("Done."); - } - } - } + if (!found.Contains(CLI)) + found.Add(CLI); - // add new entry in left list - public void CreateListItem(string filename, string date, string camera, string objects_and_confidence, string object_positions) - { - string success; - if (objects_and_confidence.Contains("%") && !objects_and_confidence.Contains(':')) - { - success = "true"; - } - else - { - success = "false"; - } - MethodInvoker LabelUpdate = delegate - { - if (checkListFilters(camera, success, objects_and_confidence)) //only show the entry in the history list if no filter applies - { - ListViewItem item; - if (success == "true") - { - item = new ListViewItem(new string[] { filename, date, camera, objects_and_confidence, object_positions, "✓" }); + // Current object detected: key=2249882, name=Person, xmin=1789, + if (CLI.Detail.IndexOf("Current object detected:", StringComparison.OrdinalIgnoreCase) >= 0) + { + imagemaskkey = "key=" + CLI.Detail.GetWord("key=", ",| "); + Log("Debug: " + imagemaskkey); + } + // Found 'Person' (Key=191457) in last_positions_history: key=198932, name=Person, xmin=1108 + else if (CLI.Detail.IndexOf("last_positions_history: key=", StringComparison.OrdinalIgnoreCase) >= 0) + { + matchedmaskkey = "key=" + CLI.Detail.GetWord("history: key=", ",| "); + Log("Debug: " + matchedmaskkey); + } + } + else if (CLI.Detail.Contains(imagemaskkey) && ((CLI.Date - FirstSeen).TotalMinutes <= 20 || CLI.Func.StartsWith("CleanUpExpired"))) + { + if (!found.Contains(CLI)) + { + fnd++; + found.Add(CLI); + } + } + else if (CLI.Detail.Contains(matchedmaskkey) && ((CLI.Date - FirstSeen).TotalMinutes <= 20 || CLI.Func.StartsWith("CleanUpExpired"))) + { + if (!found.Contains(CLI)) + { + fnd++; + found.Add(CLI); + } + } + else if (string.Equals(CLI.Image, justfile, StringComparison.OrdinalIgnoreCase)) + { + if (!found.Contains(CLI)) + { + fnd++; + found.Add(CLI); + } + } + } + + if (curlist.Count > 0) + Log($"Debug: ...Found {fnd} out of {curlist.Count} line matches for a total of {found.Count} in {fi.Name}..."); + else + Log("Error: Log may be corrupt or an old format since no lines where returned: " + fi.Name); + + } + else + { + Log($"Debug: Skipping file because Dates dont match: file ('{DATE.ToString("yyyy-MM-dd")}' != history '{hist.Date.ToString("yyyy-MM-dd")}') :{fi.Name}"); + } } else { - item = new ListViewItem(new string[] { filename, date, camera, objects_and_confidence, object_positions, "X" }); + Log($"Error: Could not parse date '{date}' in filename, Hist.Date='{hist.Date.ToString("yyyy-MM-dd")}': " + fi.FullName); } - list1.Items.Insert(0, item); - - ResizeListViews(); } + Log($"Found {found.Count} total matched lines in {sw.ElapsedMilliseconds}ms for image '{justfile}'."); - - //update history CSV - string line = $"{filename}|{date}|{camera}|{objects_and_confidence}|{object_positions}|{success}"; - try + if (found.Count > 0) { - using (StreamWriter sw = new StreamWriter(AppDomain.CurrentDomain.BaseDirectory + "cameras/history.csv", append: true)) - { - sw.WriteLine(line); - } + LogMan.Clear(); + LogMan.AddRange(found); + String search = justfile; + if (!string.IsNullOrEmpty(imagemaskkey)) + search += "|" + imagemaskkey; + if (!string.IsNullOrEmpty(matchedmaskkey)) + search += "|" + matchedmaskkey; + this.tabControl1.SelectedTab = this.tabLog; + this.ToolStripComboBoxSearch.Text = search; //this should trigger textchanged and filer in a second. + } + else + { + this.toolStripButtonPauseLog.Checked = false; //start + MessageBox.Show($"Could not find matching log entries for '{justfile}'? See log for details. Note this function only works well if the DEBUG logging mode has been enabled."); } - catch { } - }; - Invoke(LabelUpdate); + ret = true; + } + catch (Exception ex) + { + + Log("Error: " + ex.Msg()); + } + finally + { + this.UpdateProgressBar($"", 0, 0, 0); + } + return ret; } - //remove entry from left list - public void DeleteListItem(string filename) + private void folv_log_FormatRow(object sender, BrightIdeasSoftware.FormatRowEventArgs e) + { + if (e.Model != null) + this.FormatLogRow(sender, e); + } + private void FormatLogRow(object Sender, BrightIdeasSoftware.FormatRowEventArgs e) { - Log($"Removing alert image {filename} from history list and from cameras/history.csv ..."); - MethodInvoker LabelUpdate = delegate + try { - ListViewItem listviewitem = new ListViewItem(); - for (int i = 0; i < list1.Items.Count; i++) - { - listviewitem = list1.Items[i]; - if (filename == listviewitem.Text) - { - list1.Items.Remove(listviewitem); - break; - } - } - ResizeListViews(); + ClsLogItm li = (ClsLogItm)e.Model; - //remove entry from history csv - try + // If SPI IsNot Nothing Then + if (li.FromFile) { - var oldLines = System.IO.File.ReadAllLines(@"cameras/history.csv"); - var newLines = oldLines.Where(line => !line.Split('|')[0].Contains(filename)); - System.IO.File.WriteAllLines(@"cameras/history.csv", newLines); - } - catch - { - Log("ERROR: Can't write to cameras/history.csv!"); + e.Item.BackColor = Color.Black; } + } + - }; - Invoke(LabelUpdate); + catch (Exception) + { + } + finally + { + } } - //remove all obsolete entries (associated image does not exist anymore) from the history.csv - public void CleanCSVList() + private void folv_log_FormatCell(object sender, BrightIdeasSoftware.FormatCellEventArgs e) { - Log($"Cleaning cameras/history.csv if neccessary..."); - MethodInvoker LabelUpdate = delegate + if (e.Model != null) + this.FormatCellLog(sender, e); + } + + private void FormatCellLog(object sender, BrightIdeasSoftware.FormatCellEventArgs e) + { + if (e.Column.Name == nameof(ClsLogItm.Detail)) { - try + ClsLogItm li = (ClsLogItm)e.Model; + if (li.Level == LogLevel.Error || li.Level == LogLevel.Fatal) { - string[] oldLines = System.IO.File.ReadAllLines(@"cameras/history.csv"); //old history.csv - List newLines = new List(); //new history.csv - newLines.Add(oldLines[0]); // add title line from old to new history.csv - - foreach (string line in oldLines.Skip(1)) //check for every line except title line if associated image still exists in input folder - { - if (System.IO.File.Exists(input_path + "/" + line.Split('|')[0]) && input_path != "") - { - newLines.Add(line); - } - } - System.IO.File.WriteAllLines(@"cameras/history.csv", newLines); //write new history.csv + e.SubItem.ForeColor = Color.White; + e.SubItem.BackColor = Color.Red; } - catch + else if (li.Level == LogLevel.Warn) { - Log("ERROR: Can't clean the cameras/history.csv!"); + e.SubItem.ForeColor = Color.Red; + e.SubItem.BackColor = ((FastObjectListView)sender).BackColor; } - - }; - Invoke(LabelUpdate); + else if (li.Level == LogLevel.Trace || li.Level == LogLevel.Debug) + { + e.SubItem.ForeColor = Color.Gray; + } + else if (!string.IsNullOrEmpty(li.Color)) + { + e.SubItem.ForeColor = Color.FromName(li.Color); + } + else + { + e.SubItem.ForeColor = Color.White; + } + } + else + { + e.SubItem.ForeColor = Color.DarkGray; + } } - //load stored entries in history CSV into history ListView - private void LoadFromCSV() + private void mnu_highlight_CheckStateChanged(object sender, EventArgs e) { - try - { - Log("Loading history list from cameras/history.csv ..."); + this.filter_CheckStateChanged(sender, e); - //delete obsolete entries from history.csv - //CleanCSVList(); //removed to load the history list faster - - List result = new List(); //List that later on will be containing all lines of the csv file + } - //load all lines except the first line into List (the first line is the table heading and not an alert entry) - foreach (string line in System.IO.File.ReadAllLines(@"cameras/history.csv").Skip(1)) - { - result.Add(line); - } + private void filter_CheckStateChanged(object sender, EventArgs e) + { - List itemsToDelete = new List(); //stores all filenames of history.csv entries that need to be removed + if (IsLoading) + return; - MethodInvoker LabelUpdate = delegate + ToolStripMenuItem currentItem = (ToolStripMenuItem)sender; + ToolStripDropDownButton parentItem = (ToolStripDropDownButton)currentItem.OwnerItem; + if (currentItem.Checked) + { + foreach (ToolStripMenuItem sibling in parentItem.DropDownItems) { - list1.Items.Clear(); - - //load all List elements into the ListView for each row - foreach (var val in result) + if (sibling != currentItem) { - string camera = val.Split('|')[2]; - string success = val.Split('|')[5]; - string objects_and_confidence = val.Split('|')[3]; - if (!checkListFilters(camera, success, objects_and_confidence)) { continue; } //do not load the entry if a filter applies (checking as early as possible) - string filename = val.Split('|')[0]; - string date = val.Split('|')[1]; - string object_positions = val.Split('|')[4]; - - - + sibling.Checked = false; + } + } + if (!this.mnu_Filter.Checked || !this.mnu_Highlight.Checked) + this.mnu_Filter.Checked = true; - ListViewItem item; - if (success == "true") - { - item = new ListViewItem(new string[] { filename, date, camera, objects_and_confidence, object_positions, "✓" }); - } - else - { - item = new ListViewItem(new string[] { filename, date, camera, objects_and_confidence, object_positions, "X" }); - } + AppSettings.Settings.log_mnu_Filter = this.mnu_Filter.Checked; + AppSettings.Settings.log_mnu_Highlight = this.mnu_Highlight.Checked; - list1.Items.Insert(0, item); - } + if (!IsLoading && Global.IsRegexPatternValid(this.ToolStripComboBoxSearch.Text)) + { + bool Filter = false; + if (this.mnu_Filter.Checked && !this.mnu_Highlight.Checked) + Filter = true; + else + Filter = false; - ResizeListViews(); + Global_GUI.FilterFOLV(this.folv_log, this.ToolStripComboBoxSearch.Text, Filter); - }; - Invoke(LabelUpdate); + } } - catch { } - } - //check if a filter applies on given string of history list entry - private bool checkListFilters(string cameraname, string success, string objects_and_confidence) - { - if (!objects_and_confidence.Contains("person") && cb_filter_person.Checked) { return false; } - if (!(objects_and_confidence.Contains("car") || - objects_and_confidence.Contains("boat") || - objects_and_confidence.Contains("bicycle") || - objects_and_confidence.Contains("truck") || - objects_and_confidence.Contains("airplane") || - objects_and_confidence.Contains("motorcycle") || - objects_and_confidence.Contains("horse")) && cb_filter_vehicle.Checked) { return false; } - if (!(objects_and_confidence.Contains("dog") || - objects_and_confidence.Contains("sheep") || - objects_and_confidence.Contains("bird") || - objects_and_confidence.Contains("cow") || - objects_and_confidence.Contains("cat") || - objects_and_confidence.Contains("horse") || - objects_and_confidence.Contains("bear")) && cb_filter_animal.Checked) { return false; } - if (success != "true" && cb_filter_success.Checked) { return false; } //if filter "only successful detections" is enabled, don't load false alerts - if (success == "true" && cb_filter_nosuccess.Checked) { return false; } //if filter "only unsuccessful detections" is enabled, don't load true alerts - if (comboBox_filter_camera.Text != "All Cameras" && cameraname != comboBox_filter_camera.Text.Substring(3)) { return false; } - return true; } - - - //EVENTS - - //EVENT: new image added to input_path -> START AI DETECTION - async void OnCreatedAsync(object source, FileSystemEventArgs e) + private void Log_Filter_CheckStateChanged(object sender, EventArgs e) { - System.Threading.Thread.Sleep(file_access_delay); //shorty wait to ensure that the whole image is saved correctly - - while (detection_running == true) { } //wait until other detection process is finished - detection_running = true; //set marker variable to show that a new detection process is running - - //output "Processing Image" to Overview Tab - MethodInvoker LabelUpdate = delegate { label2.Text = $"Processing Image..."; }; - Invoke(LabelUpdate); - - await DetectObjects(Path.Combine(input_path, e.Name)); //ai process image - - //output Running on Overview Tab - LabelUpdate = delegate { label2.Text = "Running"; }; - Invoke(LabelUpdate); - - //only update charts if stats tab is open + if (IsLoading) + return; - LabelUpdate = delegate + ToolStripMenuItem currentItem = (ToolStripMenuItem)sender; + ToolStripMenuItem parentItem = (ToolStripMenuItem)currentItem.OwnerItem; + if (currentItem.Checked) { - Console.WriteLine(tabControl1.SelectedIndex); - - if (tabControl1.SelectedIndex == 1) + //uncheck everything else + foreach (ToolStripMenuItem sibling in parentItem.DropDownItems) { - - UpdatePieChart(); UpdateTimeline(); UpdateConfidenceChart(); - Console.WriteLine("updated"); + if (sibling != currentItem) + { + sibling.Checked = false; + } } - }; - Invoke(LabelUpdate); + if (!IsLoading) + { + AppSettings.Settings.LogLevel = currentItem.Text; + LogMan.UpdateNLog(LogLevel.FromString(AppSettings.Settings.LogLevel), AppSettings.Settings.LogFileName, AppSettings.Settings.MaxLogFileSize, AppSettings.Settings.MaxLogFileAgeDays, AppSettings.Settings.MaxGUILogItems); + } - detection_running = false; //reset variable + Log($"Debug: Logging level changed to '{currentItem.Text}'"); + } } - - //event: image in input_path renamed - void OnRenamed(object source, RenamedEventArgs e) + private void mnu_Filter_CheckStateChanged(object sender, EventArgs e) { - DeleteListItem(e.OldName); - //CreateListItem(e.Name); + this.filter_CheckStateChanged(sender, e); } - //event: image in input path deleted - void OnDeleted(object source, FileSystemEventArgs e) + private void ToolStripComboBoxSearch_Leave(object sender, EventArgs e) { - DeleteListItem(e.Name); } - //event: load selected image to picturebox - private void list1_SelectedIndexChanged(object sender, EventArgs e) //Bild ändern + private void ToolStripComboBoxSearch_TextChanged(object sender, EventArgs e) { - try + if (IsLoading) + return; + + if (!this.tmr.Enabled) { - if (list1.SelectedItems.Count > 0) - { - using (var img = new Bitmap(input_path + "/" + list1.SelectedItems[0].Text)) - { - pictureBox1.BackgroundImage = new Bitmap(img); //load actual image as background, so that an overlay can be added as the image - } - showHideMask(); - lbl_objects.Text = list1.SelectedItems[0].SubItems[3].Text; - } + this.tmr.Enabled = true; + this.tmr.Start(); } - catch (Exception ex) - { - Log($"ERROR: Loading entry from History list failed. This might have happened because obsolete entries weren't correctly deleted. {ex.GetType().ToString()} | {ex.Message.ToString()} (code: {ex.HResult} )"); - //delete entry that caused the issue - try - { - DeleteListItem(list1.SelectedItems[0].Text); - } - //if deleting fails because the filename could not be retrieved, do a complete clean up - catch - { - CleanCSVList(); - LoadFromCSV(); - } - } - + this.TimeSinceType = DateTime.Now; } - //event: show mask button clicked - private void cb_showMask_CheckedChanged(object sender, EventArgs e) + private void mnu_Filter_Click(object sender, EventArgs e) { - if (list1.SelectedItems.Count > 0) - { - showHideMask(); - } + } - //event: show objects button clicked - private void cb_showObjects_MouseUp(object sender, MouseEventArgs e) + private void openToolStripButton_Click(object sender, EventArgs e) { - if (list1.SelectedItems.Count > 0) - { - pictureBox1.Refresh(); - } + OpenLogFile(); } - //event: show history list filters button clicked - private void cb_showFilters_CheckedChanged(object sender, EventArgs e) + private void OpenLogFile() { - if (cb_showFilters.Checked) + if (System.IO.File.Exists(LogMan.GetCurrentLogFileName())) { - cb_showFilters.Text = "˅ Filter"; - splitContainer1.Panel2Collapsed = false; + System.Diagnostics.Process.Start(LogMan.GetCurrentLogFileName()); + this.lbl_errors.Text = ""; } else { - cb_showFilters.Text = "˄ Filter"; - splitContainer1.Panel2Collapsed = true; + MessageBox.Show("log missing"); } + } - ResizeListViews(); + private void mnu_log_filter_off_Click(object sender, EventArgs e) + { } - //event: filter "only revelant alerts" checked or unchecked - private void cb_filter_success_CheckedChanged(object sender, EventArgs e) + private void mnu_log_filter_off_CheckStateChanged(object sender, EventArgs e) { - LoadFromCSV(); + this.Log_Filter_CheckStateChanged(sender, e); } - //event: filter "only alerts with people" checked or unchecked - private void cb_filter_person_CheckedChanged(object sender, EventArgs e) + private void mnu_log_filter_fatal_CheckStateChanged(object sender, EventArgs e) { - LoadFromCSV(); + this.Log_Filter_CheckStateChanged(sender, e); + } - //event: filter "only alerts with people" checked or unchecked - private void cb_filter_vehicle_CheckedChanged(object sender, EventArgs e) + private void mnu_log_filter_error_CheckStateChanged(object sender, EventArgs e) { - LoadFromCSV(); + this.Log_Filter_CheckStateChanged(sender, e); + } - //event: filter "only alerts with animals" checked or unchecked - private void cb_filter_animal_CheckedChanged(object sender, EventArgs e) + private void mnu_log_filter_warn_CheckStateChanged(object sender, EventArgs e) { - LoadFromCSV(); + this.Log_Filter_CheckStateChanged(sender, e); + } - //event: filter "only false / irrevelant alerts" checked or unchecked - private void cb_filter_nosuccess_CheckedChanged(object sender, EventArgs e) + private void mnu_log_filter_info_CheckStateChanged(object sender, EventArgs e) { - LoadFromCSV(); + this.Log_Filter_CheckStateChanged(sender, e); + } - //event: filter camera dropdown changed - private void comboBox_filter_camera_SelectedIndexChanged(object sender, EventArgs e) + private void mnu_log_filter_debug_CheckStateChanged(object sender, EventArgs e) { - LoadFromCSV(); + this.Log_Filter_CheckStateChanged(sender, e); + } - //---------------------------------------------------------------------------------------------------------- - //CAMERAS TAB - //---------------------------------------------------------------------------------------------------------- + private void mnu_log_filter_trace_CheckStateChanged(object sender, EventArgs e) + { + this.Log_Filter_CheckStateChanged(sender, e); - //BASIC METHODS + } - // load cameras to camera list - public void LoadCameras() + private void clearRecentErrorsToolStripMenuItem_Click(object sender, EventArgs e) { - list2.Items.Clear(); + LogMan.ErrorCount = 0; + } - try + private async void locateInLogToolStripMenuItem_Click(object sender, EventArgs e) + { + if (this.folv_history.SelectedObjects != null && this.folv_history.SelectedObjects.Count > 0) { - string[] files = Directory.GetFiles("./cameras", $"*.txt"); //load all settings files in a string array + History hist = (History)this.folv_history.SelectedObjects[0]; + await this.FilterHistItem(hist); - //create a camera object for every camera settings file - int i = 0; - foreach (string file in files) - { - string result = LoadCamera(file); //do LoadCamera() and save returned result in string - Log(result); + } + } - //if LoadCamera() returned an error - if (result.Contains("ERROR")) - { - MessageBox.Show($"Could not load config file {file}: {result}"); - } + private void toolStripButtonPauseLog_Click(object sender, EventArgs e) + { + this.StartPauseLog(); + } - //Add loaded camera to list2 - ListViewItem item = new ListViewItem(new string[] { CameraList[i].name }); - item.Tag = file; - list2.Items.Add(item); - i++; + private void StartPauseLog() + { + if (IsLoading) + return; - } - } - catch + if (!this.toolStripButtonPauseLog.Checked) { - Log("ERROR LoadCameras() failed."); - MessageBox.Show("ERROR LoadCameras() failed."); + this.LogUpdateListTimer.Enabled = true; + this.LogUpdateListTimer.Start(); + Log("Started auto log refresh"); } - - //select first camera - if (list2.Items.Count > 0) + else { - list2.Items[0].Selected = true; + this.LogUpdateListTimer.Stop(); + this.LogUpdateListTimer.Enabled = false; + Log("Stopped auto log refresh"); } } - //load existing camera (settings file exists) into CameraList, into Stats dropdown and into History filter dropdown - private string LoadCamera(string config_path) + private async void toolStripButtonReload_ClickAsync(object sender, EventArgs e) { - //check if camera with specified name or its prefix already exists. If yes, then abort. - foreach (Camera c in CameraList) - { - if (c.name == Path.GetFileNameWithoutExtension(config_path)) - { - return ($"ERROR: Camera name must be unique,{Path.GetFileNameWithoutExtension(config_path)} already exists."); - } - if (c.prefix == System.IO.File.ReadAllLines(config_path)[2].Split('"')[1]) - { - return ($"ERROR: Every camera must have a unique prefix ('Input file begins with'), but the prefix of {Path.GetFileNameWithoutExtension(config_path)} equals the prefix of the existing camera {c.name} ."); - } - } - Camera cam = new Camera(); //create new camera object - Log("read config"); - cam.ReadConfig(config_path); //read camera's config from file - Log("add"); - CameraList.Add(cam); //add created camera object to CameraList + this.ReloadLog(); + } + + private async void ReloadLog() + { + if (IsLoading) + return; + + using var cw = new Global_GUI.CursorWait(); + this.chk_filterErrors.Checked = false; + this.chk_filterErrorsAll.Checked = false; + LogMan.Clear(); + this.folv_log.ClearObjects(); + this.folv_log.ModelFilter = null; + await LogMan.LoadLogFileAsync(LogMan.GetCurrentLogFileName(), true, false); + Log($"Loaded {LogMan.Values.Count} lines in {LogMan.LastLoadTimeMS}ms from {LogMan.GetCurrentLogFileName()}."); + await this.UpdateLogAddedRemovedAsync(true); + this.toolStripButtonPauseLog.Checked = false; + + } + + private void toolStripComboBoxFiles_Click(object sender, EventArgs e) + { + + } - //add camera to combobox on overview tab and to camera filter combobox in the History tab - comboBox1.Items.Add($" {cam.name}"); - comboBox_filter_camera.Items.Add($" {cam.name}"); + private void toolStripComboBoxFiles_SelectedIndexChanged(object sender, EventArgs e) + { - return ($"SUCCESS: {Path.GetFileNameWithoutExtension(config_path)} loaded."); } - //add camera - private string AddCamera(string name, string prefix, string trigger_urls_as_string, string triggering_objects_as_string, bool telegram_enabled, bool enabled, double cooldown_time, int threshold_lower, int threshold_upper) + private async void toolStripButtonLoad_Click(object sender, EventArgs e) { - //check if camera with specified name already exists. If yes, then abort. - foreach (Camera c in CameraList) + using (OpenFileDialog ofd = new OpenFileDialog()) { - if (c.name == name) - { - MessageBox.Show($"ERROR: Camera name must be unique,{name} already exists."); - return ($"ERROR: Camera name must be unique,{name} already exists."); - } - } - //check if name is empty - if (name == "") - { - MessageBox.Show($"ERROR: Camera name may not be empty."); - return ($"ERROR: Camera name may not be empty."); - } + string LastFile = Global.GetRegSetting("LastLoadedLogFile", LogMan.GetCurrentLogFileName()); - Camera cam = new Camera(); //create new camera object - cam.WriteConfig(name, prefix, triggering_objects_as_string, trigger_urls_as_string, telegram_enabled, enabled, cooldown_time, threshold_lower, threshold_upper); //set parameters - CameraList.Add(cam); //add created camera object to CameraList + ofd.InitialDirectory = Path.GetDirectoryName(LastFile); + ofd.FileName = LastFile; + ofd.Title = "Browse for AITOOL Log Files"; + ofd.CheckFileExists = true; + ofd.CheckPathExists = true; + ofd.DefaultExt = "log"; + ofd.Filter = "LOG Files (AITOOL.[*.log;AITOOL.[*.zip)|AITOOL.[*.log;AITOOL.[*.zip"; + ofd.FilterIndex = 1; + ofd.RestoreDirectory = true; - //add camera to list2 - ListViewItem item = new ListViewItem(new string[] { name }); - item.Tag = name; - list2.Items.Add(item); + if (ofd.ShowDialog() == DialogResult.OK) + { + using var cw = new Global_GUI.CursorWait(); + this.toolStripButtonPauseLog.Checked = true; + this.chk_filterErrors.Checked = false; + Global.SaveRegSetting("LastLoadedLogFile", ofd.FileName); + LogMan.Clear(); + this.folv_log.ClearObjects(); + this.folv_log.ModelFilter = null; + await LogMan.LoadLogFileAsync(ofd.FileName, true, false); + Log($"Loaded {LogMan.Values.Count} lines in {LogMan.LastLoadTimeMS}ms from {ofd.FileName}."); + this.UpdateLogAddedRemovedAsync(true); + } - //add camera to combobox on overview tab and to camera filter combobox in the History tab - comboBox1.Items.Add($" {cam.name}"); - comboBox_filter_camera.Items.Add($" {cam.name}"); - //select first camera - if (list2.Items.Count == 1) - { - list2.Items[0].Selected = true; } - return ($"SUCCESS: {name} created."); } - //change settings of camera - private string UpdateCamera(string oldname, string name, string prefix, string trigger_urls_as_string, string triggering_objects_as_string, bool telegram_enabled, bool enabled, double cooldown_time, int threshold_lower, int threshold_upper) + private void chk_filterErrors_Click_1(object sender, EventArgs e) { - //1. CHECK NEW VALUES - //check if name is empty - if (name == "") - { - DisplayCameraSettings(); //reset displayed settings - return ($"WARNING: Camera name may not be empty."); - } + this.FilterLogErrors(); + } - //check if camera with specified name exists. If no, then abort. - if (!CameraList.Exists(x => x.name == oldname)) - { - return ($"WARNING: Camera can't be modified because old name {oldname} wasn't found."); - } + private async void chk_filterErrorsAll_Click(object sender, EventArgs e) + { + if (IsLoading) + return; - // check if the new name isn't taken by another camera already (in case the name was changed) - if (name != oldname && CameraList.Exists(x => String.Equals(name, x.name, StringComparison.OrdinalIgnoreCase))) + if (!this.chk_filterErrorsAll.Checked) { - DisplayCameraSettings(); //reset displayed settings - return ($"WARNING: Camera name must be unique, but new camera name {name} already exists."); + this.ReloadLog(); + return; } - int index = -1; - index = CameraList.FindIndex(x => x.name == oldname); //index of specified camera in list - - if (index == -1) { Log("ERROR updating camera, could not find original camera profile."); } - - //check if new prefix isn't already taken by another camera - if (prefix != CameraList[index].prefix && CameraList.Exists(x => x.prefix == prefix)) + try { - DisplayCameraSettings(); //reset displayed settings - return ($"WARNING: Every camera must have a unique prefix ('Input file begins with'), but the prefix of {name} already exists."); - } - //2. WRITE CONFIG - CameraList[index].WriteConfig(name, prefix, triggering_objects_as_string, trigger_urls_as_string, telegram_enabled, enabled, cooldown_time, threshold_lower, threshold_upper); //set parameters + this.toolStripButtonPauseLog.Checked = true; //pause for a bit, and stay paused if results found - //3. UPDATE LIST2 - //update list2 entry - var item = list2.FindItemWithText(oldname); - list2.Items[list2.Items.IndexOf(item)].Text = name; + this.folv_log.ClearObjects(); + this.folv_log.ModelFilter = null; + this.folv_log.EmptyListMsg = "Searching..."; + this.StartPauseLog(); - //update camera combobox on overview tab and to camera filter combobox in the History tab - comboBox1.Items[comboBox1.Items.IndexOf($" {oldname}")] = $" {name}"; - comboBox_filter_camera.Items[comboBox_filter_camera.Items.IndexOf($" {oldname}")] = $" {name}"; + using var cw = new Global_GUI.CursorWait(); + Stopwatch sw = new Stopwatch(); - return ($"SUCCESS: Camera {oldname} was updated to {name}."); - } + List found = new List(); - //remove camera - private void RemoveCamera(string name) - { - Log($"Removing camera {name}..."); - if (list2.Items.Count > 0) //if list is empty, nothing can be deleted - { - if (CameraList.Exists(x => x.name == name)) //check if camera with specified name exists in list - { + //AITool.[2020-10-19].log + //AITool.[2020-10-19.1].log.zip + List files = Global.GetFiles(AppSettings.Settings.LogFileName, "AITOOL.[*].LOG|AITOOL.[*].LOG.ZIP", SearchOption.TopDirectoryOnly, DateTime.Now.AddDays(-2), DateTime.Now.AddMinutes(1)); - //find index of specified camera in list - int index = -1; + //sort by date so newest files are searched first + files = files.OrderByDescending((d) => d.LastWriteTime).ToList(); - //check for each camera in the cameralist if its name equals the name of the camera that is selected to be deleted - for (int i = 0; i < CameraList.Count; i++) + int cur = 0; + foreach (var fi in files) + { + try { - if (CameraList[i].name.Equals(name)) - { - index = i; - } - } + cur++; - if (index != -1) //only delete camera if index is known (!= its default value -1) - { - CameraList[index].Delete(); //delete settings file of specified camera - //move all cameras following the specified camera one position forward in the list - //the position of the specified camera is overridden with the following camera, the position of the following camera is overridden with its follower, and so on - for (int i = index; i < CameraList.Count - 1; i++) - { - CameraList[i] = CameraList[i + 1]; - } + //load into memory + Log($"Debug: Searching back 2 days - {cur} of {files.Count}: {fi.Name}...", "None", "None", "None"); - CameraList.Remove(CameraList[CameraList.Count - 1]); //lastly, remove camera from list + this.UpdateProgressBar($"Searching {cur} of {files.Count}: {fi.Name}...", 1, 1, 1); - //remove list2 entry - var item = list2.FindItemWithText(name); - list2.Items[list2.Items.IndexOf(item)].Remove(); + List curlist = await LogMan.LoadLogFileAsync(fi.FullName, false, false); - //remove camera from combobox on overview tab and from camera filter combobox in the History tab - comboBox1.Items.Remove($" {name}"); - comboBox_filter_camera.Items.Remove($" {name}"); + this.UpdateProgressBar($"Searching {cur} of {files.Count}: {fi.Name}...", 1, 1, curlist.Count); - //select first camera - if (list2.Items.Count > 0) - { - list2.Items[0].Selected = true; - } - //if list2 is empty, clear settings fields (to prevent that values of a deleted camera are shown) - if (list2.Items.Count == 0) + int fnd = 0; + int cnt = 0; + int CurListCount = curlist.Count; + int HalfList = CurListCount / 2; + long LastMS = sw.ElapsedMilliseconds; + foreach (var CLI in curlist) { - tbName.Text = ""; - tbPrefix.Text = ""; - cb_enabled.Checked = false; - CheckBox[] cbarray = new CheckBox[] { cb_airplane, cb_bear, cb_bicycle, cb_bird, cb_boat, cb_bus, cb_car, cb_cat, cb_cow, cb_dog, cb_horse, cb_motorcycle, cb_person, cb_sheep, cb_truck }; - foreach (CheckBox c in cbarray) + cnt++; + + //if (cnt == 1 || cnt == HalfList || cnt > (CurListCount - 5) || (sw.ElapsedMilliseconds - LastMS >= 500)) + //{ + // Global.UpdateProgressBar($"Searching {cur} of {files.Count}: {fi.Name}...", cnt, 1, CurListCount); + // LastMS = sw.ElapsedMilliseconds; + //} + + if (CLI.Level == LogLevel.Error || CLI.Level == LogLevel.Warn || CLI.Level == LogLevel.Fatal) { - c.Checked = false; + fnd++; + found.Add(CLI); } - tbTriggerUrl.Text = ""; - cb_telegram.Checked = false; + } + + Log($"Debug: ...Found {fnd} of {curlist.Count} lines that had an error for a total of {found.Count} lines in {fi.Name}..."); + } - else + catch (Exception ex) { - Log("ERROR: Can't find the selected camera, camera wasn't deleted."); + + Log("Error: " + ex.Msg()); } + } + + Log($"Found {found.Count} errors in {sw.ElapsedMilliseconds}ms"); + if (found.Count > 0) + { + LogMan.Clear(); + LogMan.AddRange(found); + this.UpdateLogAddedRemovedAsync(false); + } + else + { + MessageBox.Show($"Could not find any error log entries in {files.Count} files."); + this.chk_filterErrorsAll.Checked = false; + this.toolStripButtonPauseLog.Checked = false; //start + this.StartPauseLog(); } + + } + catch (Exception ex) + { + + Log("Error: " + ex.Msg()); + } + finally + { + this.UpdateProgressBar($"", 0, 0, 0); } } - //display camera settings for selected camera - private void DisplayCameraSettings() + private async void toolStripButton1_Click(object sender, EventArgs e) { - if (list2.SelectedItems.Count > 0) + Debug.Print("About to run"); + try { + await Task.Run(() => longrunningtask()).CancelAfter(MasterCTS.Token, "my task canceled"); + Debug.Print("After run"); - tbName.Text = list2.SelectedItems[0].Text; //load name textbox from name in list2 + } + catch (Exception ex) + { + Debug.Print("Error: " + ex.ToString()); + } + Debug.Print("Done."); - //load remaining settings from Camera.cs object + } - //all camera objects are stored in the list CameraList, so firstly the position (stored in the second column for each entry) is gathered - int i = CameraList.FindIndex(x => x.name == list2.SelectedItems[0].Text); - //load cameras stats + private void longrunningtask() + { + Debug.Print("Starting sleep..."); + while (true) + { + Debug.Print(DateTime.Now + ": working"); + Thread.Sleep(2000); + } + Debug.Print("After sleep"); //should never get here - string stats = $"Alerts: {CameraList[i].stats_alerts.ToString()} | Irrelevant Alerts: {CameraList[i].stats_irrelevant_alerts.ToString()} | False Alerts: {CameraList[i].stats_false_alerts.ToString()}"; - lbl_camstats.Text = stats; + } - //load if ai detection is active for the camera - if (CameraList[i].enabled == true) - { - cb_enabled.Checked = true; - } - else - { - cb_enabled.Checked = false; - } - tbPrefix.Text = CameraList[i].prefix; //load 'input file begins with' - lbl_prefix.Text = tbPrefix.Text + ".××××××.jpg"; //prefix live preview - tbTriggerUrl.Text = CameraList[i].trigger_urls_as_string; //load trigger url - tb_cooldown.Text = CameraList[i].cooldown_time.ToString(); //load cooldown time - tb_threshold_lower.Text = CameraList[i].threshold_lower.ToString(); //load lower threshold value - tb_threshold_upper.Text = CameraList[i].threshold_upper.ToString(); // load upper threshold value + private void toolStripButton2_Click(object sender, EventArgs e) + { + Debug.Print("Canceling..."); + MasterCTS.Cancel(); + MasterCTS.Dispose(); + MasterCTS = new CancellationTokenSource(); + Debug.Print("Canceled."); + } - //load telegram image sending on/off option - if (CameraList[i].telegram_enabled) - { - cb_telegram.Checked = true; - } - else - { - cb_telegram.Checked = false; - } + private void cb_person_CheckedChanged(object sender, EventArgs e) + { + + } + private async void manuallyAddImagesToolStripMenuItem_Click(object sender, EventArgs e) + { + using (OpenFileDialog ofd = new OpenFileDialog()) + { - //load triggering objects - //first create arrays with all checkboxes stored in - CheckBox[] cbarray = new CheckBox[] { cb_airplane, cb_bear, cb_bicycle, cb_bird, cb_boat, cb_bus, cb_car, cb_cat, cb_cow, cb_dog, cb_horse, cb_motorcycle, cb_person, cb_sheep, cb_truck }; - //create array with strings of the triggering_objects related to the checkboxes in the same order - string[] cbstringarray = new string[] { "airplane", "bear", "bicycle", "bird", "boat", "bus", "car", "cat", "cow", "dog", "horse", "motorcycle", "person", "sheep", "truck" }; + string LastFile = Global.GetRegSetting("LastLoadedImageFile", ""); - //clear all checkmarks - foreach (CheckBox cb in cbarray) - { - cb.Checked = false; - } + if (!string.IsNullOrEmpty(LastFile)) + ofd.InitialDirectory = Path.GetDirectoryName(LastFile); - //check for every triggering_object string if it is active in the settings file. If yes, check according checkbox - for (int j = 0; j < cbarray.Length; j++) + ofd.Multiselect = true; + ofd.FileName = LastFile; + ofd.Title = "Browse for image files for AI to process"; + ofd.CheckFileExists = true; + ofd.CheckPathExists = true; + ofd.DefaultExt = "jpg"; + ofd.Filter = "Image Files (*.jpg)|*.jpg|All files (*.*)|*.*"; + ofd.FilterIndex = 1; + ofd.RestoreDirectory = true; + + if (ofd.ShowDialog() == DialogResult.OK) { - if (CameraList[i].triggering_objects_as_string.Contains(cbstringarray[j])) + // Read the files + foreach (String file in ofd.FileNames) { - cbarray[j].Checked = true; + Global.SaveRegSetting("LastLoadedImageFile", file); + AddImageToQueue(file); + //small delay + await Task.Delay(AppSettings.Settings.loop_delay_ms); } } - } - } - - // SPECIAL METHODS + } + } - //input file begins with live preview - private void tbPrefix_TextChanged(object sender, EventArgs e) + private void restrictThresholdAtSourceToolStripMenuItem_Click(object sender, EventArgs e) { - lbl_prefix.Text = tbPrefix.Text + ".××××××.jpg"; + AppSettings.Settings.HistoryRestrictMinThresholdAtSource = this.restrictThresholdAtSourceToolStripMenuItem.Checked; + AppSettings.SaveAsync(true); } - //event: if SPACE is pressed in trigger url field, automatically add a comma - private void tbTriggerUrl_KeyDown(object sender, KeyEventArgs e) + private void pictureBox1_Click(object sender, EventArgs e) { - if (e.KeyCode == Keys.Space) - { - tbTriggerUrl.Text += ","; //add comma - tbTriggerUrl.Select(tbTriggerUrl.Text.Length, 0); //move cursor to end - } + } - //event: if COMMA is pressed in trigger url field, automatically add a space behind it - private void tbTriggerUrl_KeyUp(object sender, KeyEventArgs e) + private void button1_Click_1(object sender, EventArgs e) { - if (e.KeyCode == Keys.Oemcomma) + UpdateAIURLs(); + + using (Frm_AIServers frm = new Frm_AIServers()) { - tbTriggerUrl.Text += " "; //add space - tbTriggerUrl.Select(tbTriggerUrl.Text.Length, 0); //move cursor to end + + frm.ShowDialog(this); + //sort AIURLList so that all items enabled are at the top. Use OrderbyDescending so that the order is preserved + AppSettings.Settings.AIURLList = AppSettings.Settings.AIURLList.OrderByDescending(x => x.Enabled).ToList(); } + + UpdateAIURLs(); } - //event: camera list another item selected - private void list2_SelectedIndexChanged(object sender, EventArgs e) + private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { - DisplayCameraSettings(); //display new item's settings + Process.Start("https://docs.deepstack.cc/windows/index.html"); } - //event: camera add button - private void btnCameraAdd_Click(object sender, EventArgs e) + private void bt_DeepstackReset_Click(object sender, EventArgs e) { - - using (var form = new InputForm("Camera Name:", "New Camera", true)) - { - var result = form.ShowDialog(); - if (result == DialogResult.OK) - { - string name = form.text; - AddCamera(name, name, "", "person", false, true, 0, 0, 100); - } - } + this.Lbl_BlueStackRunning.Text = "RESETTING..."; + this.Btn_Start.Enabled = false; + this.Btn_Stop.Enabled = false; + this.Btn_DeepstackReset.Enabled = false; + this.SaveDeepStackTabAsync(); + DeepStackServerControl.ResetDeepstack(); + this.Btn_DeepstackReset.Enabled = true; + this.LoadDeepStackTab(); } - //event: save camera settings button - private void btnCameraSave_Click_1(object sender, EventArgs e) + private void Btn_ViewLog_Click(object sender, EventArgs e) { - if (list2.Items.Count > 0) + string errfile = Path.Combine(Environment.GetEnvironmentVariable("LOCALAPPDATA"), "DeepStack", "logs", "stderr.txt"); + if (File.Exists(errfile)) { - //1. GET SETTINGS INPUTTED - //all checkboxes in one array - CheckBox[] cbarray = new CheckBox[] { cb_airplane, cb_bear, cb_bicycle, cb_bird, cb_boat, cb_bus, cb_car, cb_cat, cb_cow, cb_dog, cb_horse, cb_motorcycle, cb_person, cb_sheep, cb_truck }; - //create array with strings of the triggering_objects related to the checkboxes in the same order - string[] cbstringarray = new string[] { "airplane", "bear", "bicycle", "bird", "boat", "bus", "car", "cat", "cow", "dog", "horse", "motorcycle", "person", "sheep", "truck" }; - - //go through all checkboxes and write all triggering_objects in one string - string triggering_objects_as_string = ""; - for (int i = 0; i < cbarray.Length; i++) + if (new FileInfo(errfile).Length > 4) { - if (cbarray[i].Checked == true) + try + { + Process.Start(errfile); + } + catch (Exception) { - triggering_objects_as_string += $"{cbstringarray[i]}, "; + MessageBox.Show("Error: Please correct the WINDOWS File Association for .TXT files."); } } + else + MessageBox.Show("File has no lines " + errfile); + } + else + { + MessageBox.Show("Cannot find " + errfile); + } + } - //get cooldown time from textbox - Double.TryParse(tb_cooldown.Text, out double cooldown_time); - - //get lower and upper threshold values from textboxes - Int32.TryParse(tb_threshold_lower.Text, out int threshold_lower); - Int32.TryParse(tb_threshold_upper.Text, out int threshold_upper); - + private void Txt_CustomModelName_TextChanged(object sender, EventArgs e) + { - //2. UPDATE SETTINGS - // save new camera settings, display result in MessageBox - string result = UpdateCamera(list2.SelectedItems[0].Text, tbName.Text, tbPrefix.Text, tbTriggerUrl.Text, triggering_objects_as_string, cb_telegram.Checked, cb_enabled.Checked, cooldown_time, threshold_lower, threshold_upper); + } - } - DisplayCameraSettings(); + private void Chk_CustomModelAPI_CheckedChanged(object sender, EventArgs e) + { + Global_GUI.GroupboxEnableDisable(groupBoxCustomModel, Chk_CustomModelAPI); } - //event: delete camera button - private void btnCameraDel_Click(object sender, EventArgs e) + private void toolStripButtonEditURL_Click(object sender, EventArgs e) { - if (list2.Items.Count > 0) + using (Frm_AIServerDeepstackEdit frm = new Frm_AIServerDeepstackEdit()) { - using (var form = new InputForm($"Delete camera {list2.SelectedItems[0].Text} ?", "Delete Camera?", false)) + string srv = ((History)this.folv_history.SelectedObjects[0]).AIServer; + ClsURLItem url = AITOOL.GetURL(srv, false, false); + if (url != null) { - var result = form.ShowDialog(); - if (result == DialogResult.OK) - { - Log("about to del cam"); - RemoveCamera(list2.SelectedItems[0].Text); - } + frm.CurURL = url; + frm.ShowDialog(); } } } - //event: DELETE key pressed - private void list2_KeyDown(object sender, KeyEventArgs e) + private void button3_Click(object sender, EventArgs e) { - if (e.KeyCode == Keys.Delete) + + if (MessageBox.Show("Are you sure you want to reset ALL settings?", "RESET?", MessageBoxButtons.YesNo) == DialogResult.Yes) { - if (list2.Items.Count > 0) - { - using (var form = new InputForm($"Delete camera {list2.SelectedItems[0].Text} ?", "Delete Camera?", false)) - { - var result = form.ShowDialog(); - if (result == DialogResult.OK) - { - RemoveCamera(list2.SelectedItems[0].Text); - } - } - } + ResetSettings = true; + this.Close(); } + + } - //event: leaving empty lower confidence limit textbox - private void tb_threshold_lower_Leave(object sender, EventArgs e) + private void viewImageToolStripMenuItem_Click(object sender, EventArgs e) { - if (tb_threshold_lower.Text == "") + if (this.folv_history.SelectedObjects != null && this.folv_history.SelectedObjects.Count > 0) { - tb_threshold_lower.Text = "0"; + History hist = (History)this.folv_history.SelectedObjects[0]; + Process.Start(hist.Filename); + } } - //event: leaving empty upper confidence limit textbox - private void tb_threshold_upper_Leave(object sender, EventArgs e) + private void jumpToImageToolStripMenuItem_Click(object sender, EventArgs e) { - if (tb_threshold_upper.Text == "") + if (this.folv_history.SelectedObjects != null && this.folv_history.SelectedObjects.Count > 0) { - tb_threshold_upper.Text = "100"; + History hist = (History)this.folv_history.SelectedObjects[0]; + + // combine the arguments together + // it doesn't matter if there is a space after ',' + string argument = "/select, \"" + hist.Filename + "\""; + + Process.Start("explorer.exe", argument); } + } + private void flowLayoutPanel1_Paint(object sender, PaintEventArgs e) + { + } - //---------------------------------------------------------------------------------------------------------- - //SETTING TAB - //---------------------------------------------------------------------------------------------------------- + private void dbLayoutPanel11_Paint(object sender, PaintEventArgs e) + { + } - //settings save button - private void BtnSettingsSave_Click_1(object sender, EventArgs e) + private void BtnPredictionSize_Click(object sender, EventArgs e) { - //save inputted settings into App.settings - Properties.Settings.Default.input_path = tbInput.Text; - Properties.Settings.Default.deepstack_url = tbDeepstackUrl.Text; - Properties.Settings.Default.telegram_chatid = tb_telegram_chatid.Text; - Properties.Settings.Default.telegram_token = tb_telegram_token.Text; - Properties.Settings.Default.log_everything = cb_log.Checked; - Properties.Settings.Default.send_errors = cb_send_errors.Checked; - Properties.Settings.Default.Save(); + if (this.FOLV_Cameras.SelectedObjects.Count == 0) + { + MessageBox.Show("Select a camera first"); + return; + } - //update variables - input_path = Properties.Settings.Default.input_path; - deepstack_url = Properties.Settings.Default.deepstack_url; - telegram_chatid = Properties.Settings.Default.telegram_chatid; - telegram_chatids = telegram_chatid.Replace(" ", "").Split(','); //for multiple Telegram chats that receive alert images - telegram_token = Properties.Settings.Default.telegram_token; - log_everything = Properties.Settings.Default.log_everything; - send_errors = Properties.Settings.Default.send_errors; + using (Frm_PredSizeLimits frm = new Frm_PredSizeLimits()) + { - //update fswatcher to watch new input folder - UpdateFSWatcher(); + Camera cam = AITOOL.GetCamera(((Camera)this.FOLV_Cameras.SelectedObjects[0]).Name); + + frm.tb_ConfidenceLower.Text = cam.threshold_lower.ToString(); + frm.tb_ConfidenceUpper.Text = cam.threshold_upper.ToString(); + + frm.tb_maxheight.Text = cam.PredSizeMaxHeight.ToString(); + frm.tb_maxwidth.Text = cam.PredSizeMaxWidth.ToString(); + frm.tb_minwidth.Text = cam.PredSizeMinWidth.ToString(); + frm.tb_minheight.Text = cam.PredSizeMinHeight.ToString(); + frm.tb_maxpercent.Text = cam.PredSizeMaxPercentOfImage.ToString(); + frm.tb_MinPercent.Text = cam.PredSizeMinPercentOfImage.ToString(); + + frm.tb_duplicatepercent.Text = cam.MergePredictionsMinMatchPercent.ToString(); - //clean history.csv database - CleanCSVList(); + if (frm.ShowDialog() == DialogResult.OK) + { + cam.threshold_lower = GetNumberInt(frm.tb_ConfidenceLower.Text); + cam.threshold_upper = GetNumberInt(frm.tb_ConfidenceUpper.Text); + cam.PredSizeMaxHeight = GetNumberInt(frm.tb_maxheight.Text); + cam.PredSizeMaxWidth = GetNumberInt(frm.tb_maxwidth.Text); + cam.PredSizeMinWidth = GetNumberInt(frm.tb_minwidth.Text); + cam.PredSizeMinHeight = GetNumberInt(frm.tb_minheight.Text); + cam.PredSizeMaxPercentOfImage = frm.tb_maxpercent.Text.ToDouble(); + cam.PredSizeMinPercentOfImage = frm.tb_MinPercent.Text.ToDouble(); + cam.MergePredictionsMinMatchPercent = frm.tb_duplicatepercent.Text.ToDouble(); + + Lbl_PredictionTolerances.Text = $"Threshold: {cam.threshold_lower}-{cam.threshold_upper}, Size: {cam.PredSizeMinPercentOfImage.ToPercent()}-{cam.PredSizeMaxPercentOfImage.ToPercent()} ; Width: {cam.PredSizeMinWidth}-{cam.PredSizeMaxWidth}, Height: {cam.PredSizeMinHeight}-{cam.PredSizeMaxHeight}, PredictionMatch: {cam.MergePredictionsMinMatchPercent.ToPercent()}"; + + AppSettings.SaveAsync(true); + } + } - //LoadList(); } - //input path select dialog button - private void btn_input_path_Click(object sender, EventArgs e) + private void tb_threshold_lower_TextChanged(object sender, EventArgs e) { - using (CommonOpenFileDialog dialog = new CommonOpenFileDialog()) + + } + + private void FOLV_Cameras_SelectionChanged(object sender, EventArgs e) + { + if (FOLV_Cameras.SelectedObjects.Count > 0) + Global.SaveRegSetting("LastSelectedCamera", ((Camera)FOLV_Cameras.SelectedObjects[0]).Name); + + DisplayCameraSettings(); + } + + private void FOLV_Cameras_FormatRow(object sender, FormatRowEventArgs e) + { + this.FormatCameraRow(sender, e); + } + + private void BtnRelevantObjects_Click(object sender, EventArgs e) + { + if (this.FOLV_Cameras.SelectedObjects.Count == 0) { - dialog.InitialDirectory = "C:\\"; - dialog.IsFolderPicker = true; - if (dialog.ShowDialog() == CommonFileDialogResult.Ok) + MessageBox.Show("Select a camera first"); + return; + } + + using (Frm_RelevantObjects frm = new Frm_RelevantObjects()) + { + Camera cam = AITOOL.GetCamera(((Camera)this.FOLV_Cameras.SelectedObjects[0]).Name); + frm.ROMName = $"{cam.Name}\\{cam.DefaultTriggeringObjects.TypeName}"; + if (frm.ShowDialog(this) == DialogResult.OK) { - tbInput.Text = dialog.FileName; + DisplayCameraSettings(); } } } - //open log button - private void btn_open_log_Click(object sender, EventArgs e) + private void toolStripButtonAdjustAnno_Click(object sender, EventArgs e) { - if (System.IO.File.Exists("log.txt")) + using (Frm_AnnoAdjust frm = new Frm_AnnoAdjust()) { - System.Diagnostics.Process.Start("log.txt"); - lbl_errors.Text = ""; + frm.ShowDialog(); } - else + } + + private void btnPause_Click(object sender, EventArgs e) + { + using (Frm_Pause frm = new Frm_Pause()) { - MessageBox.Show("log missing"); - } + //Camera cam = AITOOL.GetCamera(((Camera)this.FOLV_Cameras.SelectedObjects[0]).Name); + //frm.CurrentCam = cam; + frm.ShowDialog(this); + } } - //ask before closing AI Tool to prevent accidently closing - private void Shell_FormClosing(object sender, FormClosingEventArgs e) + private void bt_CheckUpdates_Click(object sender, EventArgs e) { - if (Properties.Settings.Default.close_instantly <= 0) //if it's eigther enabled or not set -1 = not set | 0 = ask for confirmation | 1 = don't ask + using (Frm_UpdateCheck frm = new Frm_UpdateCheck()) { - using (var form = new InputForm($"Stop and close AI Tool?", "AI Tool", false)) + if (frm.ShowDialog(this) == DialogResult.OK) { - var result = form.ShowDialog(); - if (Properties.Settings.Default.close_instantly == -1) - { - //if it's the first time, ask if the confirmation dialog should ever appear again - using (var form1 = new InputForm($"Confirm closing AI Tool every time?", "AI Tool", false, "NO, Never!", "YES")) - { - var result1 = form1.ShowDialog(); - if (result1 == DialogResult.Cancel) - { - Properties.Settings.Default.close_instantly = 0; - Properties.Settings.Default.Save(); - } - else - { - Properties.Settings.Default.close_instantly = 1; - Properties.Settings.Default.Save(); - } - } - } + CloseImmediately = true; + Application.Exit(); - e.Cancel = (result == DialogResult.Cancel); } } + } + + private void mergeDuplicatePredictionsToolStripMenuItem_Click(object sender, EventArgs e) + { + AppSettings.Settings.HistoryMergeDuplicatePredictions = this.mergeDuplicatePredictionsToolStripMenuItem.Checked; + AppSettings.SaveAsync(true); + } + + private void notifyIcon_MouseClick(object sender, MouseEventArgs e) + { + if (e.Button == MouseButtons.Left) + { + this.ShowForm(); + } + else + { + this.notifyIcon.ContextMenuStrip.Show(); + } + } + private void exitToolStripMenuItem_Click(object sender, EventArgs e) + { + this.Close(); } - } + private void pauseToolStripMenuItem_Click(object sender, EventArgs e) + { + using (Frm_Pause frm = new Frm_Pause()) + { - //classes for AI analysis + //Camera cam = AITOOL.GetCamera(((Camera)this.FOLV_Cameras.SelectedObjects[0]).Name); + //frm.CurrentCam = cam; + frm.ShowDialog(this); + } + } - class Response - { + private void pauseAllToolStripMenuItem_Click(object sender, EventArgs e) + { + double pausetime = 0; + foreach (var cam in AppSettings.Settings.CameraList) + { + pausetime = cam.PauseMinutes; + if (!cam.Paused) + cam.Pause(); + } + MessageBox.Show($"Paused all cameras for {pausetime} minutes"); + } - public bool success { get; set; } - public Object[] predictions { get; set; } + private void resumeAllToolStripMenuItem_Click(object sender, EventArgs e) + { + foreach (var cam in AppSettings.Settings.CameraList) + { + if (cam.Paused) + cam.Resume(); + } - } + MessageBox.Show("Resumed all cameras."); + } - class Object - { + private void Shell_Activated(object sender, EventArgs e) + { + //bring this form to the top of all other windows: + this.TopMost = true; + this.TopMost = false; + this.Show(); + Log($"Trace: App Activated. TopMost={this.TopMost}, Visible={this.Visible}, state={this.WindowState}, tbicon={this.ShowInTaskbar}, trayicon={this.notifyIcon.Visible}"); - public string label { get; set; } - public float confidence { get; set; } - public int y_min { get; set; } - public int x_min { get; set; } - public int y_max { get; set; } - public int x_max { get; set; } + } } - //enhanced TableLayoutPanel loads faster - public partial class DBLayoutPanel : TableLayoutPanel + public partial class DBLayoutPanel:TableLayoutPanel { public DBLayoutPanel() { - SetStyle(ControlStyles.AllPaintingInWmPaint | + this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint, true); } @@ -2442,7 +5640,7 @@ public DBLayoutPanel() public DBLayoutPanel(IContainer container) { container.Add(this); - SetStyle(ControlStyles.AllPaintingInWmPaint | + this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint, true); } diff --git a/src/UI/Shell.resx b/src/UI/Shell.resx index 9b3be9aa..a28559fb 100644 --- a/src/UI/Shell.resx +++ b/src/UI/Shell.resx @@ -1,17 +1,17 @@  - @@ -120,6 +120,9 @@ 17, 17 + + 17, 21 + @@ -1895,8 +1898,11180 @@ AIABAACAAQAAgAEAAIABAACAAQAAgAEAAP//AAA= - - 37 + + + iVBORw0KGgoAAAANSUhEUgAAAqsAAALMCAYAAADD6vsTAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAZ + 1QAAGdUBtWjrgAAA/7JJREFUeF7snQVcXEmz9gnuEoO4u7sLcU827krc3d3d3d3d3Yi7kxCSABEIEiQ4 + 4fmq6syQgbD77r5393733rfrt8+e4czx05P+d3V1tZEyZcqUKVOmTJkyZcqUKVOmTJkyZcqUKVOmTJky + ZcqUKVOmTJkyZcqUKVOmTJkyZcqUKVOmTJkyZcqUKVOmTJkyZcqUKVOmTJkyZcqUKVOmTJkyZcqUKVOm + TJkyZcqUKVOmTJkyZcqUKVOmTJkyZcqUKVOmTJkyZcqUKVOmTJkyZcqUKVOmTJkyZcqUKVOmTJkyZcqU + KVOmTJkyZcqUKVOmTJkyZcqUKVOmTJkyZcqUKVOmTJkyZcqUKVOmTJkyZcqUKVOmTJkyZcqUKVOmTJky + ZcqUKVOmTJkyZcqUKVOmTJkyZcqUKVOmTJkyZcqUKVOmTJkyZcqUKVOmTJkyZcqUKVOmTJkyZcqUKVOm + TJkyZcqUKVOmTJkyZcqUKVOmTJkyZcqUKVOmTJkyZcqUKVOmTJkyZcqUKVOmTJkyZcqUKVOmTJkyZcqU + Gdiho4ewccsGLF62CBOnTMbgYUPRs1cPdOnaCW3atkKzZs1ETZo0QcOGDVGvXj3UqlUjUTVqVkeNGjXg + 6uoqql69uqhatWoGqpJEVatWTqaKiapSpYKoUuVyv6ty5UolUdmyJZOpdBKVLlMyqUrTOlLZsmWTqFy5 + ctqyzE+VL1tOVKFceVGl8nRtOlWuUBEVK2qqUImWlSslUaVKmirS/bAqVdZU1bUqPaOqqEGqWe2/R3yu + lCTfV63+j6tGtZT1c5saiapWgcqJXhUroQo9538lfhfJpX9P+nenf5elS1AZ+C+oVPESSVS+FJWlUmVR + pEgx5M6bDwcPH4Xu56VMmTJlypQp+z3bt38XFi2ehzHjR6Jztw5o1LQRSpcrjVx5ciJrjixwzpgetg62 + MLM044oVRqlIxqk08d9JZKwTf8fS/224jrfTfzbRyVQnw+2NkSqVye/q53b6Y6WkP3881s/rSKpURmZJ + ZGxsrn2WfQzPkfz8yZ+N7pip6Fkak3hpKGM6Hunn9WjHNf5v0s/7SFkp7fN3KqVzGkrbzjRRJvQOfspE + 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 + CUqLEJdagozsGhSXd6C6fhSa2yeio2saukbNwqixczBm4nxMnLoE02atFD0TsxeswfwlG7BkxRasWLcd + qzfsxPqte0VKwZ6DJ4QOHDuNI6euV3Jir4Vt370H6zdvAT9u3LrjN8WtfVmcQ8javpv+IBXt2LNPo3Ze + t/83tFeDNG0nSX3/6sehKvlYt+zYc5E2b981QJu27bxI8vlv2LL9z9Vm+q0tuwZo83Y+xn5toUrq2msP + tl2B1K+dqtZu3DpAK9asF1q5llqXKlq1bqPQ0pVrNGjdAC1aupr2tV0p6Iop9gfs+A234OjZm3Do1A29 + 6QSnsefwSQG+Mvyu37oHK9dvo0p3O5av5QqYyrCKVq3t18o1W8Q2y9ZsvqRWrt+BFRtY24TWbt41QKvV + tGrTTiH19xnI1cXv8z7WbNzZt791W3YL8Xmw+DNZfE4sPj+WfE58DrJUz1X9M42iRkK/Nl+kVZu3D9DG + XXuxcSd3EWvWpl0HBmj7viMqOoSdpF17+7V91/4B2rpbs7btOTBA26872KvDQjv3HhkgjgDKz+VtLqdt + ew4N0NbdB4W20DlI2ie0YdvuPq3fukto3Zadvdrd9z/2aRPVKbLEe9zTIGnNpu1Cq6meuTJtpv1IWrN5 + s3i9eQ9dn+vomAkut+89gYPHOMf7Jpw4e7tS3yimmGKKKaaYYooppphiiimmmGKKKaaYYooppphiiimm + mGKKKaaYYooppphiiimmmGKKKaaYYooppphiiimmmGKKKaaYYooppphiiimmmGKKKaaYYooppphiiimm + mGKKKaaYYooppphiiimmmGKKKaaYYooppphiiimmmGKKKaaYYooppphiiimmmGKKKaaYYooppphiiimm + mGKKKaaYYooppphiiimmmGKKKaaYYooppphiiimmmGKKKaaYYooppphiiimmmGKKKaaYYooppphi/689 + OCAAAAAAENL/1R0BAAAAAAAAAAAAAADVevEMRowbR+IAAAAASUVORK5CYII= + + + + 971, 17 + + + + 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 + + + + The setting will hide all non relevant objects. It applies to [variables], the history Detections column, +[summary]/[summarynonescaped] variables, objects shown in the right-hand image display and to +merging of annotations. + +If you enable this, the only way to see all the objects detected by DeepStack is to pick "Prediction Details" +or have the log set to Debug and view it. + + + If disabled the database will be smaller, leave enabled for better troubleshooting. Keep +in mind that Deepstack will often miss animals at night completly so you wont be able +to review footage from night before. + + + Doods has a feature that lets you specify the minimum threshold/Confidence when you give it the image to process. +If this is unchecked, it will give us EVERYTHING (including the kitchen sink) and let AI tool decide the confidence threshold +like it does for Deepstack. But rather than ever seeing ‘false alert’ you will almost always have objects detected, just with low confidence. + + + + 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 + pSg/4MXoRAouBQMAAAAASUVORK5CYII= + + + + + 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 + TgDQASA1MVpwzwAAAABJRU5ErkJggg== + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIDSURBVDhPpZLrS5NhGMb3j4SWh0oRQVExD4gonkDpg4hG + YKxG6WBogkMZKgPNCEVJFBGdGETEvgwyO9DJE5syZw3PIlPEE9pgBCLZ5XvdMB8Ew8gXbl54nuf63dd9 + 0OGSnwCahxbPRNPAPMw9Xpg6ZmF46kZZ0xSKzJPIrhpDWsVnpBhGkKx3nAX8Pv7z1zg8OoY/cITdn4fw + bf/C0kYAN3Ma/w3gWfZL5kzTKBxjWyK2DftwI9tyMYCZKXbNHaD91bLYJrDXsYbrWfUKwJrPE9M2M1Oc + VzOOpHI7Jr376Hi9ogHqFIANO0/MmmmbmSmm9a8ze+I4MrNWAdjtoJgWcx+PSzg166yZZ8xM8XvXDix9 + c4jIqFYAjoriBV9AhEPv1mH/sonogha0afbZMMZz+yreTGyhpusHwtNNCsA5U1zS4BLxzJIfg299qO32 + Ir7UJtZfftyATqeT+8o2D8JSjQrAJblrncYL7ZJ2+bfaFnC/1S1NjL3diRat7qrO7wLRP3HjWsojBeCo + mDEo5mNjuweFGvjWg2EBhCbpkW78htSHHwRyNdmgAFzPEee2iFkzayy2OLXzT4gr6UdUnlXrullsxxQ+ + kx0g8BTA3aZlButjSTyjODq/WcQcW/B/Je4OQhLvKQDnzN1mp0nnkvAhR8VuMzNrpm1mpjgkoVwB/v8D + TgDQASA1MVpwzwAAAABJRU5ErkJggg== + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIDSURBVDhPpZLrS5NhGMb3j4SWh0oRQVExD4gonkDpg4hG + YKxG6WBogkMZKgPNCEVJFBGdGETEvgwyO9DJE5syZw3PIlPEE9pgBCLZ5XvdMB8Ew8gXbl54nuf63dd9 + 0OGSnwCahxbPRNPAPMw9Xpg6ZmF46kZZ0xSKzJPIrhpDWsVnpBhGkKx3nAX8Pv7z1zg8OoY/cITdn4fw + bf/C0kYAN3Ma/w3gWfZL5kzTKBxjWyK2DftwI9tyMYCZKXbNHaD91bLYJrDXsYbrWfUKwJrPE9M2M1Oc + VzOOpHI7Jr376Hi9ogHqFIANO0/MmmmbmSmm9a8ze+I4MrNWAdjtoJgWcx+PSzg166yZZ8xM8XvXDix9 + c4jIqFYAjoriBV9AhEPv1mH/sonogha0afbZMMZz+yreTGyhpusHwtNNCsA5U1zS4BLxzJIfg299qO32 + Ir7UJtZfftyATqeT+8o2D8JSjQrAJblrncYL7ZJ2+bfaFnC/1S1NjL3diRat7qrO7wLRP3HjWsojBeCo + mDEo5mNjuweFGvjWg2EBhCbpkW78htSHHwRyNdmgAFzPEee2iFkzayy2OLXzT4gr6UdUnlXrullsxxQ+ + kx0g8BTA3aZlButjSTyjODq/WcQcW/B/Je4OQhLvKQDnzN1mp0nnkvAhR8VuMzNrpm1mpjgkoVwB/v8D + TgDQASA1MVpwzwAAAABJRU5ErkJggg== + + + + 1076, 17 + + + 710, 17 + + + + AAEAAAD/////AQAAAAAAAAAMAgAAAEZTeXN0ZW0uV2luZG93cy5Gb3JtcywgQ3VsdHVyZT1uZXV0cmFs + LCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5BQEAAAAmU3lzdGVtLldpbmRvd3MuRm9ybXMu + SW1hZ2VMaXN0U3RyZWFtZXIBAAAABERhdGEHAgIAAAAJAwAAAA8DAAAAXgsAAAJNU0Z0AUkBTAIBAQMB + AAEgAQIBIAECARABAAEQAQAE/wEJAQAI/wFCAU0BNgEEBgABNgEEAgABKAMAAUADAAEQAwABAQEAAQgG + AAEEGAABgAIAAYADAAKAAQABgAMAAYABAAGAAQACgAIAA8ABAAHAAdwBwAEAAfABygGmAQABMwUAATMB + AAEzAQABMwEAAjMCAAMWAQADHAEAAyIBAAMpAQADVQEAA00BAANCAQADOQEAAYABfAH/AQACUAH/AQAB + kwEAAdYBAAH/AewBzAEAAcYB1gHvAQAB1gLnAQABkAGpAa0CAAH/ATMDAAFmAwABmQMAAcwCAAEzAwAC + MwIAATMBZgIAATMBmQIAATMBzAIAATMB/wIAAWYDAAFmATMCAAJmAgABZgGZAgABZgHMAgABZgH/AgAB + mQMAAZkBMwIAAZkBZgIAApkCAAGZAcwCAAGZAf8CAAHMAwABzAEzAgABzAFmAgABzAGZAgACzAIAAcwB + /wIAAf8BZgIAAf8BmQIAAf8BzAEAATMB/wIAAf8BAAEzAQABMwEAAWYBAAEzAQABmQEAATMBAAHMAQAB + MwEAAf8BAAH/ATMCAAMzAQACMwFmAQACMwGZAQACMwHMAQACMwH/AQABMwFmAgABMwFmATMBAAEzAmYB + AAEzAWYBmQEAATMBZgHMAQABMwFmAf8BAAEzAZkCAAEzAZkBMwEAATMBmQFmAQABMwKZAQABMwGZAcwB + AAEzAZkB/wEAATMBzAIAATMBzAEzAQABMwHMAWYBAAEzAcwBmQEAATMCzAEAATMBzAH/AQABMwH/ATMB + AAEzAf8BZgEAATMB/wGZAQABMwH/AcwBAAEzAv8BAAFmAwABZgEAATMBAAFmAQABZgEAAWYBAAGZAQAB + ZgEAAcwBAAFmAQAB/wEAAWYBMwIAAWYCMwEAAWYBMwFmAQABZgEzAZkBAAFmATMBzAEAAWYBMwH/AQAC + ZgIAAmYBMwEAA2YBAAJmAZkBAAJmAcwBAAFmAZkCAAFmAZkBMwEAAWYBmQFmAQABZgKZAQABZgGZAcwB + AAFmAZkB/wEAAWYBzAIAAWYBzAEzAQABZgHMAZkBAAFmAswBAAFmAcwB/wEAAWYB/wIAAWYB/wEzAQAB + ZgH/AZkBAAFmAf8BzAEAAcwBAAH/AQAB/wEAAcwBAAKZAgABmQEzAZkBAAGZAQABmQEAAZkBAAHMAQAB + mQMAAZkCMwEAAZkBAAFmAQABmQEzAcwBAAGZAQAB/wEAAZkBZgIAAZkBZgEzAQABmQEzAWYBAAGZAWYB + mQEAAZkBZgHMAQABmQEzAf8BAAKZATMBAAKZAWYBAAOZAQACmQHMAQACmQH/AQABmQHMAgABmQHMATMB + AAFmAcwBZgEAAZkBzAGZAQABmQLMAQABmQHMAf8BAAGZAf8CAAGZAf8BMwEAAZkBzAFmAQABmQH/AZkB + AAGZAf8BzAEAAZkC/wEAAcwDAAGZAQABMwEAAcwBAAFmAQABzAEAAZkBAAHMAQABzAEAAZkBMwIAAcwC + MwEAAcwBMwFmAQABzAEzAZkBAAHMATMBzAEAAcwBMwH/AQABzAFmAgABzAFmATMBAAGZAmYBAAHMAWYB + mQEAAcwBZgHMAQABmQFmAf8BAAHMAZkCAAHMAZkBMwEAAcwBmQFmAQABzAKZAQABzAGZAcwBAAHMAZkB + /wEAAswCAALMATMBAALMAWYBAALMAZkBAAPMAQACzAH/AQABzAH/AgABzAH/ATMBAAGZAf8BZgEAAcwB + /wGZAQABzAH/AcwBAAHMAv8BAAHMAQABMwEAAf8BAAFmAQAB/wEAAZkBAAHMATMCAAH/AjMBAAH/ATMB + ZgEAAf8BMwGZAQAB/wEzAcwBAAH/ATMB/wEAAf8BZgIAAf8BZgEzAQABzAJmAQAB/wFmAZkBAAH/AWYB + zAEAAcwBZgH/AQAB/wGZAgAB/wGZATMBAAH/AZkBZgEAAf8CmQEAAf8BmQHMAQAB/wGZAf8BAAH/AcwC + AAH/AcwBMwEAAf8BzAFmAQAB/wHMAZkBAAH/AswBAAH/AcwB/wEAAv8BMwEAAcwB/wFmAQAC/wGZAQAC + /wHMAQACZgH/AQABZgH/AWYBAAFmAv8BAAH/AmYBAAH/AWYB/wEAAv8BZgEAASEBAAGlAQADXwEAA3cB + AAOGAQADlgEAA8sBAAOyAQAD1wEAA90BAAPjAQAD6gEAA/EBAAP4AQAB8AH7Af8BAAGkAqABAAOAAwAB + /wIAAf8DAAL/AQAB/wMAAf8BAAH/AQAC/wIAA/8bAAVJDAADJRMAAewBEgETARQBFQFDAhEBEAEPBA4C + AAHsARIBEwEUARUBQwIRAUMBUAF4AZkBeAFQAUkBAAHsARIBEwEUARUBQwIRAUQCTAEsASsBJRIAAZIB + 8AG8AgcC7wH3AZIB7QPsAQ8CAAGSAfABvAIHAu8B9wFJAngB/wJ4AUkBAAGSAfABvAIHAu8B9wElAXUB + TQIsASsBJREAAfcBBwHvAuwB7QHsAusBEgETAW0B7AEPAgAB9wEHAe8C7AHtAewB6wFJAZkD/wGZAUkB + AAH3AQcB7wLsAe0B7AHrASUBmgP/AU0BJRIAAfcBkgEAAuoBEwEUARMBQwEAARMBbQQAAfcBkgEAAuoB + EwEUAVACmQH/AngBSQIAAfcBkgEAAuoBEwEUASQBmgFTASwCUwElEwABkgHsAe8BvAIHAbwB7AEUAeoG + AAGSAewB7wG8AgcBHAFzAZkBCAF4AVABSQMAAZIB7AHvAbwCBwF0AUwBmgEaAXUBTBQAAe0BBwHvAfcD + kgH3Ae0BEwYAAe0BBwHvAfcDkgHtAXICUAUAAe0BBwHvAfcDkgF0AUwCJRUAAQcB8QHvAfAB9AHzAbwB + 9wEHAewGAAEHAfEB7wHwAfQB8wG8AfcBBwHsBgABBwHxAe8B8AH0AfMBvAH3AQcB7BUAAQcC8QHzAfAC + tQHxAfIBvAHvARMEAAEHAvEB8wHwArUB8QHyAbwB7wETBAABBwLxAfMB8AK1AfEB8gG8Ae8BExQAAbwB + 8wHxAf8BkgMJAfQCvAESBAABvAHzAfEB/wGSAwkB9AK8ARIEAAG8AfMB8QH/AZIDCQH0ArwBEhQAAfAB + 8wHyAf8BkgIJAbsB9AG8AfEBbQQAAfAB8wHyAf8BkgIJAbsB9AG8AfEBbQQAAfAB8wHyAf8BkgIJAbsB + 9AG8AfEBbRQAAfEC8wH0AfACkgHwAfMB8AG8AW0EAAHxAvMB9AHwApIB8AHzAfABvAFtBAAB8QLzAfQB + 8AKSAfAB8wHwAbwBbRUAAfMB9AHzAfQC/wHzAvEBBwYAAfMB9AHzAfQC/wHzAvEBBwYAAfMB9AHzAfQC + /wHzAvEBBxYAAfIC9AHzAgcC8gHxAZIGAAHyAvQB8wIHAvIB8QGSBgAB8gL0AfMCBwLyAfEBkhcAA/MB + 9AHzAfIB8QEHCAAD8wH0AfMB8gHxAQcIAAPzAfQB8wHyAfEBBxoAAfMC8gHxDAAB8wLyAfEMAAHzAvIB + 8RYAAUIBTQE+BwABPgMAASgDAAFAAwABEAMAAQEBAAEBBQABgBcAA/8BAAP/AcEB/wHjAgABgAEBAYAB + AAGAAQECAAGAAQEBgAEAAYADAAGAAQEBgAEAAYADAAHIARMByAEAAcgDAAHgAQcB4AEAAeABAQIAAeAB + BwHgAQMB4AEDAgAB4AEHAeABBwHgAQcCAAHAAQMBwAEDAcABAwIAAcABAwHAAQMBwAEDAgABwAEDAcAB + AwHAAQMCAAHAAQMBwAEDAcABAwIAAeABBwHgAQcB4AEHAgAB4AEHAeABBwHgAQcCAAHwAQ8B8AEPAfAB + DwIAAfwBPwH8AT8B/AE/AgAL + + + + 160, 17 + + + This is the IP Address or name of the machine that has BlueIris installed on your network. Use 127.0.0.1 for "this" machine. Do not specify the port here. You can use the [BlueIrisURL] and [BlueIrisServerIP] variables in Camera Actions. + + + If you enter more than one deepstack port separated by commas, an instance of deepstack.exe +will be opened for each one. This should better utilize CPU/GPU when more than one image is +in the queue, but remember you have to add a URL to each on the SETTINGS tab. + + + 1425, 17 + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAKmSURBVDhPjZLhT1JhFIfZ2tRWW39EXxora6u2ci6Xa42y + pATdypQkzCS/GBUg5q2FiKKGGHKpK1cCAa/KKgtn5dDCJiahpkwDthrQRA1yq9SJnOSOpV8snu18OOc9 + v2fvu72UzRw9mLEnKytPVitWfFQoHvsxZdsshhmDcjkWVDZqAtLqBmt+foko7XDGISazfHs8RqGgqHF3 + Yz2qVD7EPKHwEvj84X+WSqUPy2TKl2Kx4hQpkEgU9wYHRiEWVrZo4U5VHcjq1YDjhvVeA9dKK0DAF4NE + Ug81UgUpGRubhts84TwpuJBXzDGYetdigonxKdDrCdDpjEAQ3WAwmOCBXAUYhpO90zlDlrS6CWiZZ75R + uFxkZ2kJ7y5fiES/fF2AGNFoFPz+Ofg06YZAYAFWViIQ/vHrb7iMWwHH0k/+PrDvyEVK6XXBjYYGtP++ + pHkARTsgFFomBcN2B+BaM/RbR2BpeUMgEjVC9tlCSE1NryKvz8jl1OK4NcVisSTn5HCabLZJiEQAJl1u + aEF10NXVR77Z9n5qXfgcuNxKOHH83GsqlZpECq4U3+qwO6YqHWNedlHRzW6BQPJ9cXEVgvM/QbUuMJpe + gMcbBPuHz6B+1AmXznMWMzOZVDIcg83m7dXqzc+0T572sYvLGXT65bQhm3MpsgZg6R0EvI2A6ZkAjDrc + wGJJV7OzC4vi0a2RiJUiny8EXu8sqNU6cI57gOh8BQwGp51GoyXH17YGQZAkjeZpn9czByZTD7S2dkBZ + mcidm3t1V3zl//D5NfvNZqt/2O4CoRBZptMLTsePEqe6psXwbmgCCgrL3zKZzI2/nyhyOU4Mj7ighItY + WCwkJT5OHBQ19LhmfCBCmgd4PNmO+Dhx2gkL9411JCipwxCCILbFx5ugUP4Ah4e+ITTXqEUAAAAASUVO + RK5CYII= + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAKkSURBVDhPhZLJSxthGMaHXgsNLcWK/heCBy8i9aAoqIjH + IvbQpCCpB0tED/Ui9JRqAhWXwwSTxloXkLqEQhtbjTVJ6xpjNetkJpnoZLEmsS5tn37fODR09YEXhne+ + 37sz/1JjY2MNy7KHJpMpXVpaWqm4L9fg4OBNrVZ7TafTvUim0shks+js7LS0t7df7evru6U8+1Nms7mt + u7v7+5DJJLLseCYmxhGNiUgkkzg7P8f09PSR2WIR6ZuBgYH7CvZTV55ZR3lOiCKZSsl2IEngIjzEeBz7 + Bwc4ymSQzeVwcnqGsbGxkMLlVVtbawgTwB8MIhTm4HZ/wLvFJayurSFAfOnDQ9lyx8eoqqp6rGB59RqN + q0ECend2MT9vk4PY7fZv1ChkX1hAfP+ikie9vU4FYxiNRlNjMBgkj8fz1R8IwuVyIxqNgfTLqVSqhoKC + gjs2m01KkLZoVXQu9K3RaJTKysoqmYqK27q4lMDO7h42tjxYcizD7w+irq7hkZKDuadWD/n8ATiWV8hc + IghxHBluCuXl5W0M2bGWFwRsez9hfWMTi0sOSIkk9Hr9K4Vnhln2I93KzOwctra9CHMkSDiMkpISNf2v + am5unrVYxsUIL2BtfUMe1tTUFEZHn3+2Wq1H4xMT8jZcbjcCoTCGR0YEylCWBpBFLu9pNncMgfT/+o1d + XuMmaclDMnIk48uZGXi8O6T8CKqrq/UKlldPT4/45fQUqXQaez4fnE4XlleceK+Yk2SnwSPkVrq6uqIK + ltfDjg4HiOilUYuJotwvzUrvg/roSmPxfbS2tr5VsF+kampqmiwqKrpbXFz8YMXnO6Hl0nvwBwKZwsJC + DfGr6+vrJ+nbC+Q/6u/v3xXIdnieR0tLy18zXqYbBJyjRr6vX7h+F8P8AEZX+Ltc116WAAAAAElFTkSu + QmCC + + + + + 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 + RU5ErkJggg== + + + + + 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 + TkSuQmCC + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAI5SURBVDhPY8ACOIGYEcJEAcxAzAFh4gG8vLwWylxcxkAm + E0SEgcGMgYGPl5PTnIeHRxMqhBewSYqIJHsyMLiKsrMrawOxG5ANNDgWKIfNZRiAVUZGpiw/Q327tjL7 + W2V59rdJ7owrpKSkSoByxBmQn8i5tCBd6p+CgsJ/SUnJ//a2LH/zM5SmAuXg3sIJODk5pXyY2A6aign/ + qysv+K+nJv/fgJf7bxgL6xagNDtEFW4gGsHNf7yeke3fS3bm/9meFv9j3a3+f+Dj+b+fgelvJq/gOqAa + bohSNAAM4UAgDi0Tl32yRkjo/3sOjv+13j7/w42N//80Nvm/SVT0/zIxmRtycnLR0tLSIVBtKAAUx5w+ + kpK/VnFz///Aw/P/npnZ/yfRof+/WVj938zF9b9dTe0rUA3IG9jTgykDg/BxoO2FfHz/tUVE/u8SEv6/ + WlXtvyLQ9i1sbP+vaGn9kWRg4IIqxwrY1wsKvuwAOl+Tl/f/NkbG/5OBbEGggTNZWf/ftLR8DlTDAlGK + BQATi3mGoODZqUxM/68yMPw/CcR3WVj+7wIatJqZ+X+7vsFZDg4OWahyTMDPz1/iysVTKQd0ch/IdqBB + 84EaZwENMlRS+u8oLN4qIiLiBVWOARh1dHTqgDS/GyPLnlygpiygIclAA0qBXojg5tsgLCxsAjQgGaIc + DQAlbIFRBMpEIMDsxcA4LYmB8b43M/NVRwbGXqAYOBUKCAjYqqioGILY6ABbOgdpwiYOFWNgAAA8O5J9 + hlNj0gAAAABJRU5ErkJggg== + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAI5SURBVDhPY8ACOIGYEcJEAcxAzAFh4gG8vLwWylxcxkAm + E0SEgcGMgYGPl5PTnIeHRxMqhBewSYqIJHsyMLiKsrMrawOxG5ANNDgWKIfNZRiAVUZGpiw/Q327tjL7 + W2V59rdJ7owrpKSkSoByxBmQn8i5tCBd6p+CgsJ/SUnJ//a2LH/zM5SmAuXg3sIJODk5pXyY2A6aign/ + qysv+K+nJv/fgJf7bxgL6xagNDtEFW4gGsHNf7yeke3fS3bm/9meFv9j3a3+f+Dj+b+fgelvJq/gOqAa + bohSNAAM4UAgDi0Tl32yRkjo/3sOjv+13j7/w42N//80Nvm/SVT0/zIxmRtycnLR0tLSIVBtKAAUx5w+ + kpK/VnFz///Aw/P/npnZ/yfRof+/WVj938zF9b9dTe0rUA3IG9jTgykDg/BxoO2FfHz/tUVE/u8SEv6/ + WlXtvyLQ9i1sbP+vaGn9kWRg4IIqxwrY1wsKvuwAOl+Tl/f/NkbG/5OBbEGggTNZWf/ftLR8DlTDAlGK + BQATi3mGoODZqUxM/68yMPw/CcR3WVj+7wIatJqZ+X+7vsFZDg4OWahyTMDPz1/iysVTKQd0ch/IdqBB + 84EaZwENMlRS+u8oLN4qIiLiBVWOARh1dHTqgDS/GyPLnlygpiygIclAA0qBXojg5tsgLCxsAjQgGaIc + DQAlbIFRBMpEIMDsxcA4LYmB8b43M/NVRwbGXqAYOBUKCAjYqqioGILY6ABbOgdpwiYOFWNgAAA8O5J9 + hlNj0gAAAABJRU5ErkJggg== + + + + 305, 17 + + + + AAEAAAD/////AQAAAAAAAAAMAgAAAEZTeXN0ZW0uV2luZG93cy5Gb3JtcywgQ3VsdHVyZT1uZXV0cmFs + LCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5BQEAAAAmU3lzdGVtLldpbmRvd3MuRm9ybXMu + SW1hZ2VMaXN0U3RyZWFtZXIBAAAABERhdGEHAgIAAAAJAwAAAA8DAAAARr8BAAJNU0Z0AUkBTAIBAS4B + AAFIAQwBSAEMASABAAEgAQAE/wEhAQAI/wFCAU0BNgcAATYDAAEoAwABgAMAAYABAQIAAQEBAAEgBwAB + A/8A/wD/AP8ANQADBgEIA0oBigMhAS8DFQEcAzkBXgQCUAADAgEDAxMBGQMeASsDGwElAwwBDwQB/wB9 + AAMZASMDXwHOAQABkwG9Af8BYgFJAWIB9gNdAeoBgAIAAf8DQgFzTAADCQELAzYBWANVAa8CXwFdAc4C + XAFZAcYDTwGZAy8BSQMHAQn/AHUAAzEBTgNiAe8BAAGAAcYB/wIAAcQB/wGBAgAB/wGTAgAB/wMAAf8D + YAHjSAADCQELAz0BaQNgAeMDAAH/AwAB/wMAAf8DQAH9A10B3AM6AWADBgEH/wBpAAMDAQQDSQGHA4AB + /gIAAcsB/wIAAc8B/wIAAb0B/wGBAgAB/wGpAgAB/wGGAgAB/wMAAf8DHgEqRAADLwFJA10B1AMAAf8D + AAH/AZYBmwGOAf8BpwGwAacB/wGKAZMBiAH/AwAB/wNdAcwDJQE2/wBpAAMVAR0DWgH1AgABzgH/AgAB + zwH/AgABzwH/AgAB1gH/AYEBAAGBAf8BqgIAAf8BswIAAf8BgAF/AYAB/gNGAX1EAANRAZ4CaAFHAfkD + AAH/AYYBjAEAAf8BtQHDAb0B/wGoAbIBvAH/AY4BlwGkAf8BAAGEAQAB/wJoAUcB+QJMAUsBjwMZASID + BgEH/wBlAAFcAVkBXAHGAgABxQH/AgABzwH/AgABzQH/AQABkAG4Af8BoAGCAQAB/wG1AgAB/wHFAYIB + AAH/AY0CAAH/A1EBngMPARNAAANeAdcDAAH/AwAB/wGUAZ4BkAH/Aa0BvwG+Af8BlAGYAbkB/wGTAY4B + tQH/AYMBgAGLAf8DAAH/AmMBWgHpAlgBVwG8A0QBegMkATUDDAEPBAEUAAQBAw4BEgMbASYDFQEcAwMB + BP8AMQACWQFcAcYCAAHRAf8BAAGIAcIB/wGTAcoBkgH/AY8BxQGYAf8BogGhAQAB/wG6AgAB/wHDAgAB + /wGNAgAB/wGKAQABigH/A2IB4QMmATg8AANTAfQDAAH/AwAB/wGaAaYBmQH/AakBuwG9Af8ClQG6Af8B + jwGDAaUB/wIAAYIB/wMAAf8DAAH/AdUCAAH/Am0BUQH3Al8BWwHYA1EBogM7AWQDJQE2AxIBFwMFAQYI + AAMjATIDUQGiAl8BWwHQAlgBVwG8AzQBVAMGAQf/AC0AAlkBXAHGAQABoAGoAf8BqAHhAQAB/wGcAdcB + iAH/AaIB4AGAAf8BlAGqAYoB/wGzAgAB/wHFAYIBAAH/AaYCAAH/AYoBAAGLAf8BlwIAAf8CagFoAfkD + QAFwBAE0AANAAf0BgQIAAf8DAAH/AZYBoQGUAf8BqAK4Af8BkAGHAZ4B/wGMAgAB/wGBAQABhQH/AwAB + /wMAAf8BlAIAAf8B7wIAAf8B+AIAAf8CgAFTAf4DXgHwA18B1QNVAa0DRwGCAzYBWAMqAT8DTwGZAl8B + XgH7AwAB/wMAAf8DXQHPAysBQQQB/wApAANWAbABqwHsAQAB/wGrAewBAAH/AacB5wEAAf8BAAGhAbcB + /wIAAeMB/wGJAgAB/wGyAgAB/wGiAgAB/wGKAQABiwH/AZcCAAH/AZcCAAH/AZcCAAH/A1UBrQMMAQ8E + AAMDAQQDRwGCA08BmQMFAQYcAANZAe8BhwGGAQAB/wGCAYEBAAH/AZABlgGHAf8BqAGzAa4B/wGNAgAB + /wG6AgAB/wGfAgAB/wIAAYAB/wMAAf8DAAH/AbsCAAH/AfUCAAH/AfYCAAH/AfYCAAH/AfYCAAH/AfQC + AAH/Al8BKgH7A14B8AJhAV4B4gNlAfEDAAH/AYoBlAGKAf8BhwGPAYIB/wNAAf0CUwFSAaUDDwEU/wAp + AANHAYEBqwHsAQAB/wGeAdsBhQH/AQABhQHQAf8CAAHoAf8CAAHmAf8CAAHVAf8CAAGHAf8BjQIAAf8B + igEAAYsB/wGXAgAB/wGXAgAB/wGXAgAB/wGXAgAB/wNgAdsDIQEwA1MBqgGUAgAB/wMAAf8DPwFsHAAD + WQG7A0AB/QMAAf8BgAIAAf8BpwGoAZ0B/wGKAYIBAAH/AcUCAAH/Ab0CAAH/AgABgwH/AwAB/wMAAf8D + AAH/AdgCAAH/AfgCAAH/AfYCAAH/AfUCAAH/AfUCAAH/AfcCAAH/AbgCAAH/AwAB/wMAAf8DAAH/AZIB + iwGSAf8BnAGbAZ0B/wMAAf8DXQHfAycBOv8AKQADMQFOAY8BxgGXAf8CAAHhAf8CAAHoAf8CAAHmAf8C + AAHmAf8CAAHmAf8CAAHiAf8CAAGhAf8BigEAAYsB/wGXAgAB/wGXAgAB/wGXAgAB/wGXAgAB/wGXAgAB + /wFtAmwB9wNNAfoBmgIAAf8DAAH/AlsBWgHEHAADPgFqA10B6gMAAf8DAAH/AZYBjgGAAf8BmgGYAYoB + /wHGAYkBAAH/AdwCAAH/AYgCAAH/AwAB/wMAAf8DAAH/AacCAAH/AfsBoAEAAf8B/AGYAQAB/wH6AYgB + AAH/AfgCAAH/AfkCAAH/AdUCAAH/AwAB/wIAAZ8B/wIAAYEB/wGNAYkBjQH/AZkBigGYAf8BhAEAAYQB + /wNlAfEDNwFa/wApAAMTARkCAAHlAf8CAAHnAf8CAAHmAf8CAAHmAf8CAAHmAf8CAAHmAf8CAAHmAf8C + AAHhAf8BgwGUAakB/wGXAgAB/wGXAgAB/wGXAgAB/wGXAgAB/wGXAgAB/wGXAgAB/wGXAgAB/wGbAgAB + /wGIAgAB/wNgAeMDMQFNGAADHAEnA1cBvAMAAf8DAAH/AwAB/wGRAgAB/wHcAZUBAAH/AfABjwEAAf8B + mwIAAf8DAAH/AwAB/wGPAgAB/wHRAZoBAAH/AfYBsAEAAf8B+AGwAQAB/wH5Aa4BAAH/AfkBpwEAAf8B + +wGaAQAB/wHtAgAB/wMAAf8CAAGDAf8DAAH/AwAB/wGSAZMBlAH/AYkBggGIAf8BbAJSAfcDPwFtBAH/ + ACkAA1kBvgIAAeYB/wIAAeYB/wIAAeYB/wIAAeYB/wIAAeYB/wIAAdgB/wGFAaMBrwH/AY0BpwGjAf8B + lwIAAf8BlwIAAf8BlwIAAf8BlwIAAf8BlwIAAf8BlwIAAf8BlwIAAf8BlwIAAf8BlwIAAf8BhgIAAf8D + TAGOGAADCwEOA0sBjAFqAWgBRwH5AwAB/wMAAf8BsQGCAQAB/wH4AacBAAH/AfgBmwEAAf8BowIAAf8D + AAH/AbgBjQEAAf8B8AGuAQAB/wH5AbEBAAH/AfkBrAEAAf8B+wGoAQAB/wH8AaYBAAH/Af0BpQEAAf8B + /gGjAQAB/wH0AZoBAAH/AwAB/wMAAf8DAAH/AwAB/wGHAYwBiwH/AaABqgGiAf8DXAH4AkIBQQFzBAL/ + ACkAAwMBBANJAYcCQAGoAf0CAAHmAf8CAAHmAf8BAAGEAcsB/wGLAqkB/wGMAasBqAH/AY0BpwGjAf8B + lwIAAf8BlwIAAf8BlwIAAf8BlwIAAf8BlwIAAf8BlwIAAf8BlwIAAf8BlwIAAf8BlwIAAf8BlQIAAf8D + UQGfGAAEAQMfASwDUQGiA2IB7wHOAZ8BAAH/AeMBrgEAAf8B4AGmAQAB/wG7AYUBAAH/AY4CAAH/AcMB + iwEAAf8B6AGnAQAB/wHZAaYBAAH/AfIBoQEAAf8B/QGVAQAB/wH6AYUBAAH/AfkCAAH/AfgCAAH/AfoC + AAH/AekCAAH/AwAB/wMAAf8DAAH/AwAB/wEAAYMBgQH/AbgByAHAAf8DqAH9A1UBrAMbASX/ADEAAzAB + SgNeAe0BAAGUAbwB/wGMAasBqAH/AYwBqwGoAf8BjAGrAagB/wGNAacBowH/AZcCAAH/AZcCAAH/AZcC + AAH/AZcCAAH/AZcCAAH/AZcCAAH/AZcCAAH/AZcCAAH/AZcCAAH/AZcCAAH/AZYCAAH/A04BmAMGAQgY + AAMbASUDWAG3AY4CAAH/AY4CAAH/AwAB/wMAAf8BzwGKAQAB/wH7AaIBAAH/AbgBmgEAAf8BAAGWAaIB + /wHQAZUBAAL/AZ0BAAH/AfwBkgEAAf8B+gIAAf8B9wIAAf8B8QIAAf8BpgIAAf8DAAH/AwAB/wIAAYMB + /wIAAYkB/wMAAf8DAAH/AawBvgG1Af8DYgH2A1IBpAMbASX/ADEAA1UBrgGMAasBqAH/AYwBqwGoAf8B + jAGrAagB/wGMAasBqAH/AY0BpwGjAf8BlwIAAf8BlwIAAf8BlwIAAf8BlwIAAf8BlwIAAf8BlwIAAf8B + lwIAAf8BlwIAAf8BlwIAAf8BlwIAAf8BlwIAAf8BlwIAAf8DQgF1FAADAwEEAzMBUANfAdUDAAH/AwAB + /wMAAf8BjAIAAf8B7gIAAf8B9gIAAf8B0AIAAf8BtQIAAf8B6QIAAf8B+gIAAf8B+gIAAf8B8gIAAf8B + zwIAAf8BkwIAAf8DAAH/AgABtAH/AgABuAH/AwAB/wMAAf8CAAGKAf8CAAGIAf8BAAGFAZAB/wG6Ac8B + xgH/A20B9wNSAaADGwEl/wAtAANVAa4BjAGrAagB/wGMAasBqAH/AYwBqwGoAf8BjQGnAaQB/wGXAgAB + /wGaAgAB/wGXAgAB/wGXAgAB/wGXAgAB/wGXAgAB/wGXAgAB/wGXAgAB/wGXAgAB/wGXAgAB/wGXAgAB + /wGXAgAB/wGXAgAB/wNEAXgUAAMHAQkDRgF9A2EB5gGAAX4BcQH+AwAB/wMAAf8BmQIAAf8B0gIAAf8B + 1gIAAf8B3QIAAf8B4QIAAf8B5wIAAf8B8AIAAf8B6AIAAf8BrAIAAf8DAAH/AwAB/wIAAaQB/wIAAZkB + /wIAAYoB/wMAAf8DAAH/AwAB/wIAAbAB/wIAAYwB/wMAAf8BpAG5AbMB/wNtAfcDUgGjAxgBIf8AKQAD + VQGuAYwBqwGoAf8BjAGrAagB/wGPAZwBlwH/AZsCAAH/AZ4CAAH/AZ4CAAH/AZwCAAH/AZgCAAH/AZcC + AAH/AZcCAAH/AZcCAAH/AZcCAAH/AZcCAAH/AZcCAAH/AZcCAAH/AZcCAAH/AZcCAAH/A0QBeBQAAwQB + BQM+AWoBYQJeAeIDgAH+AaACAAH/AcwCAAH/AewBiQEAAf8B8QGIAQAB/wHnAgAB/wHdAgAB/wHZAgAB + /wHYAgAB/wHWAgAB/wG4AgAB/wMAAf8DAAH/AYsCAAH/AYgCAAH/AwAB/wMAAf8DAAH/AZoCAAH/AaEC + AAH/AaICAAH/AY0CAAH/AwAB/wMAAf8BkwGgAZgB/wNdAeoDMwFQ/wApAANVAa4BjAGrAagB/wGSAYgB + ggH/AZ0CAAH/AZ4CAAH/AZ4CAAH/AZ4CAAH/AZ4CAAH/AZ0CAAH/AZkCAAH/AZcCAAH/AZcCAAH/AZcC + AAH/AZcCAAH/AZcCAAH/AZcCAAH/AZcCAAH/AZcCAAH/A0QBeBQAAw4BEgNVAa0DgAH+Aa0B2QHgAf8B + ngG2AbEB/wHLAZIBAAL/AagBAAH/AfcBowEAAf8BrgIAAf8BhgIAAf8BlgIAAf8BxAIAAf8BvgIAAf8B + mAIAAf8BkgGPAQAB/wG0AY0BAAH/AfABmwEAAf8B0AGFAQAB/wMAAf8DAAH/AwAB/wGBAgAB/wGeAgAB + /wGyAgAB/wGxAgAB/wGzAgAB/wGoAgAB/wFtAlEB9wNUAaYDJgE4AxoBJP8AJQADVQGuAZYCAAH/AZ4C + AAH/AZ4CAAH/AZ4CAAH/AZ4CAAH/AZ4CAAH/AZ4CAAH/AZ4CAAH/AZ4CAAH/AZoCAAH/AZcCAAH/AZcC + AAH/AZcCAAH/AZcCAAH/AZcCAAH/AZcCAAH/AZcCAAH/A0QBeBQAAw8BEwNWAbIBnwGZAQAB/wGfAaoB + lAH/AZoBjgEAAf8BvQIAAf8B4wGLAQAB/wHvAZ4BAAH/AacCAAH/AwAB/wMAAf8BogIAAf8BxgGFAQAB + /wGgAgAB/wG/AgAB/wHtAgAB/wH3AgAB/wHjAgAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AYcCAAH/AbYB + ggEAAf8BuQGCAQAB/wGvAgAB/wNcAfgBWgJYAb0DVQGvA1gBuv8AJQADOwFlA00B+gGeAgAB/wGeAgAB + /wGeAgAB/wGeAgAB/wGeAgAB/wGeAgAB/wGeAgAB/wGeAgAB/wGeAgAB/wGcAgAB/wGXAgAB/wGXAgAB + /wGXAgAB/wGXAgAB/wGXAgAB/wGXAgAB/wNEAXgUAAMGAQcDSAGEAmoBQQH5AcQCAAH/AeEBgAEAAf8B + 8gGJAQAB/wHgAgAB/wHZAgAB/wHUAYoBAAH/AakCAAH/AZcCAAH/AbICAAH/AdkBlgEAAf8BzwIAAf8B + ygIAAf8BqwIAAf8BmgIAAf8BwgIAAf8CgAFZAf4DAAH/AwAB/wMAAf8DAAH/AwAB/wGEAgAB/wGvAgAB + /wG3AYMBAAH/AbECAAH/AV8BXgEnAfsDWAG6AkEBQgFz/wApAAMoATwDYAHjAZ4CAAH/AZ4CAAH/AZ4C + AAH/AZ4CAAH/AZ4CAAH/AZ4CAAH/AZ4CAAH/AZ4CAAH/AZ4CAAH/AZ0CAAH/AZgCAAH/AZcCAAH/AZcC + AAH/AZcCAAH/AZcCAAH/A0QBeBgAAyUBNgNbAcUB2gGAAQAB/wH+AaEBAAH/AfwBogEAAf8B+wGcAQAB + /wHoAgAB/wGzAgAB/wGeAgAB/wGsAgAB/wGsAgAB/wGnAgAB/wGlAgAB/wMAAf8DAAH/AwAB/wNZAe8B + VwJVAbQDXgHiAwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wGEAgAB/wGAAXgBSgH+A1IBowMSARj/ + AC0AAxABFQFZAlcBuQGeAgAB/wGeAgAB/wGeAgAB/wGeAgAB/wGeAgAB/wGeAgAB/wGeAgAB/wGeAgAB + /wGeAgAB/wGeAgAB/wGaAgAB/wGXAgAB/wGXAgAB/wGXAgAB/wNEAXgYAAMDAQQDLgFIAl0BWwHKAysB + /AHXAZcBAAH/AeEBpwEAAf8B4wGeAQAB/wG4AYUBAAH/AaQBmgGAAf8BlQIAAf8DAAH/AysB/AFhAl8B + 2gNdAcwDXgHdA1wB2QNLAY0DHAEnA0QBeQNiAe4BlQGWAYEB/wKTAQAB/wGLAYwBAAH/AwAB/wMAAf8D + AAH/AwAB/wMAAf8DXgHXAyoBQP8AMQAEAgNGAX4DKwH8AZ4CAAH/AZ4CAAH/AZ4CAAH/AZ4CAAH/AZ4C + AAH/AZ4CAAH/AZ4CAAH/AZ4CAAH/AZ4CAAH/AZwCAAH/AZcCAAH/AZcCAAH/A0QBeBwAAwMBBAMtAUQD + XQHJA4AB/gHbAd0BxQH/AeQB4gHNAf8B4gHjAc4B/wHhAekB0QH/AcsB0gG8Af8BnQGfAY0B/wNtAfcD + RgF/Ax4BKgMpAT0DJgE4Aw8BEwQAAxIBGANDAXcDXAHGA2EB3ANlAfQDgAH+AwAB/wMAAf8DAAH/AwAB + /wNdAd8DMAFK/wA5AAMrAUIBZQJcAecBngIAAf8BngIAAf8BngIAAf8BngIAAf8BngIAAf8BngIAAf8B + ngIAAf8BngIAAf8BngIAAf8BnQIAAf8BmAIAAf8DRAF4HAAEAQMmATgDVQG0AloBWQHAA10B6gHoAfIB + 2wH/Ae0B9gHjAf8B7QH0AeEB/wHpAfIB2wH/AeMB7AHUAf8DYAHzAzkBXxgAAwYBBwMYASEDJQE2A04B + lwNcAdkDWQHGA2AB2wNeAe0DQAH9A1cBwgMeASr/AD0AAxMBGQNaAb8BngIAAf8BngIAAf8BngIAAf8B + ngIAAf8BngIAAf8BngIAAf8BngIAAf8BngIAAf8BngIAAf8BnQIAAf8DOgFgHAADCQELA04BlANhAesD + RAF5A0ABcANfAdADTQH6AewB8wHgAf8B7gH1AeQB/wHoAfAB3AH/A18B0AMiATEkAAMfASwDUAGaA0kB + hwNNAZIDWwHNA10BzgM6AWEDCAEK/wBBAAMDAQQDSQGFAZwCQAH9AZ4CAAH/AZ4CAAH/AZ4CAAH/AZ4C + AAH/AZ4CAAH/AZ4CAAH/A1oB9QM4AV0gAAMEAQUDNAFUA1YBtQNFAXwDDAEQAyQBNQNKAYsDXAHLA2EB + 3ANcAcYDOgFhAwUBBiQABAIDJgE4A0cBggNKAYsDQwF3AycBOgMFAQb/AE0AAzABSgNhAesBngIAAf8B + ngIAAf8BngIAAf8BngIAAf8BXwJbAdgDHwEsKAADBAEFAxUBHAMTARoEAgQAAwkBDAMeASsDJgE5Ax0B + KQMHAQksAAQBAwoBDQMMAQ8DBAEF/wBZAAMWAR4DWwHFAZ4CAAH/AZ4CAAH/AVQCUgGoAwoBDf8A7QAD + BAEFA0oBiwFAAj8BbgQB/wD/AP8A/wBkAAMDAQQEAmQACAIsAAgC/wBRAAMMARADMAFMA0YBfQM9AWcD + GQEiBAJYAAMVAR0DOgFhAz8BbgMuAUcDDAEQHAAEAQMTARkDOQFeA0ABcAMvAUkDDwEU5AAEAQMFAQYD + AwEEVAADDgESAkYBRwGAA2AB4ANNAfoDZQHxA0kBiAMJAQwEAAQCAw0BEQMGAQg8AAMDAQQDDgQSARgD + RgF9A2IB4QNaAfUDXQHcAzYBVwQBGAADCAEKA0EBcwNeAd0DSAH2A1sB3gM6AWIEAuAAAxUBHANDAXcD + KQE+BAFMAAQBAzMBUQNgAegCAAHPAf8CAAHcAf8CXwFlAeUDOwFkAwUBBgQAAxkBIwNOAZYDMwFQBAE0 + AAMHAQkDMQFNA1MBpwNVAa0DXAHZAyoB/gMAAf8DIQH7A0QBeAMCAQMYAAMhATADVwHCAyoB/gMAAf8D + QAH9A0gBhAMEAQWMAAMCAQMDFAQbASYDDAEQRAADJAE1AVsCXwHQAzsBZQQBTAAEAgNAAXADXAH4AgAB + 1gH/A1oB9QNJAYUDCgENBAADBAEFAz4BawFcAWUBXAHnA1EBogMQARU0AAMVARwDVQGsA00B+gMAAf8D + AAH/AwAB/wMAAf8DXAHnAzIBTwgAAwMBBAMXAR8DCwEOBAAEAgM5AV4DXQHqAwAB/wMAAf8DYAHoAzcB + WgQBjAADDwETA0wBjgNdAdEDUQGiAy0BRQMHAQk8AAMoATsDXQHPAzUBVlAAAwIBAwM4AV0DXgHwAgAB + 2gH/A14B3QMqAUAIAAMiATEBWAFaAVgBvQFPAVABTwGbAVEBUgFRAaQDNgFYAwIBAzAAAxEBFgNTAaoB + MAIyAf4DAAH/AysB/ANAAf0BAAGmAasB/wNdAc8DHAEnBAIDHQEpAyoBPwJQAVEBnAM2AVgDIwEyAzAB + SgNVAa0DYgHuAQABiwGQAf8BAAGWAZsB/wNcAcgDHAEnBAAEAgMFAQYEAYAAAxUBHANEAXkDYgHuAUAC + qAH9A18B1QM+AWoDCwEOMAAIAQMrAUEDXAHLAy4BR0gAAwcBCQMjATIBPwFAAT8BbgNWAbIDXAH4AgAB + 3wH/AV0CZQHsA1ABnQM2AVcDHgErAUsBTAFLAY8BXQFfAV0BzgMlATYDMwFQA08BmQMdASgwAAMDAQQD + PAFmAVoCZwHyA4AB/gNdAeoDWgHyAwAB/wNhAdoDIQEwAwQBBQJGAUcBgQNTAakCWwFdAcoCUAFRAZ8C + VQFXAbQCVwFaAcICXQFfAc4DXQHOA0AB/QMAAf8DWwHQAyABLgQAAxMBGQM3AVoDIAEtEAADBAEFAwwB + DwMQARUDEAEVAwwBDwMEAQUsAAMEAQUDDAEPAxABFQMQARUDDAEPAwQBBRQAAUYCRwGBA0wBjwNZAb4D + YAHzAYABswL/A2AB6ANDAXcDDQERHAAEAQMQARUDJQE2AzUBVQM7AWUDOwFkA0oBiQNgAdQDNAFUAwsB + DgQBOAAEAQMbASUBRwFIAUcBgwFbAV4BWwHTAUgBYgFIAfYBPAKAAf4CAAHSAf8CAAHaAf8CAAGHAf8D + KwH8A14B7QFbAVwBWwHNAV4BYQFeAeIDSgGKAwcBCQMIAQoDPwFsAUcBSAFHAYMDKAE7AyABLgMMAQ8o + AAMxAU4DXgHtAYQBtgG8Af8DYAHzA1wB+AMAAf8DYAHoAzABTAMTARkDUAGaAlkBYgHvAVMCZQH0A14B + 8AFRAWwBbQH3A00B+gNiAeEDXgHdA4AB/gIAAYIB/wNhAd4DKgE/BAADKwFBA10ByQNGAYEMAAMEAQUD + EgEYAyMBMwMrAUEDKwFBAyMBMwMSARgDBAEFJAADBAEFAxIBGAMjATMDKwFBAysBQQMjATMDEgEYAwQB + BRAAAyoBQANNAZMDXgHSA2cB8gGAAbMC/wGAAbMC/wNhAeYDQgFzAwkBDBQAAxEBFgM1AVYDUwGlA2EB + 1gNhAesDZwHyA2cB8gNgAfMDYgH2A10BzANOAZgDOAFcAxcBHwMCAQMsAAQBAyUBNgNVAa8BSAFiAUgB + 9gMAAf8DAAH/AwAB/wIAAYYB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AVEBbQFRAfcDTQGTAxIBFwQAAxUB + HAFRAVIBUQGkAV8BYQFfAdoBWwFfAVsB0ANMAZADJgE5AwkBDAQBHAADOgFiAVMCZQH0AwAB/wMAAf8B + AAGWAZsB/wGIAbwBwgH/A20B9wNZAbkDVQGsA14B4gMAAf8DAAH/AwAB/wMAAf8CAAGsAf8DgAH+AUAB + mAGoAf0BmAHSAdgB/wGWAc8B1gH/A2AB6AMyAU8EAAMsAUMDXgHiA1IBoAQAAwUBBgMNAREDFwEfAyYB + OANiAeEDAAH/AwAB/wNiAeEDJgE4AxcBHwMRARYDEQEWAxEBFgMRARYDEQEWAxEBFgMRARYDEQEWAxEB + FgMXAR8DJgE4A2IB4QMAAf8DAAH/A2IB4QMmATgDFwEfAxEBFgMMARADBAEFCAADDgESAzUBVgNVAa4D + XQHqAYACqAH9AYABswL/A2EB5gM+AWoDCQEMCAADCQELAysBQgNTAaUDYwHpA4AB/gGAAbMC/wGAAbMC + /wGAAbMC/wGAAbMC/wGAAbMC/wGAAbMC/wGAAbMC/wOAAf4DYgHvA1kBuwM6AWADDgESKAADIAEtAVcB + WQFXAbkDKwH8AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DXgHwAUcB + SAFHAYMDDAEPAxIBFwNWAbIBAAGVAQAB/wEAAZIBAAH/AUEBagFBAfkBVwFaAVcBwgNJAYcDLwFJGAAD + AgEDA0QBeAFBAUcBaAH5AwAB/wEAAasBsQH/AaMB2gHhAf8BqAHfAeUB/wGqAd4B5QH/AakB3QHkAf8B + pgHbAeEB/wEAAZcBnAH/AwAB/wMAAf8DAAH/AwAB/wGEAbYBvAH/AZQBzAHVAf8BlQHOAdYB/wGNAcMB + ygH/AQABgwGHAf8DZQHxAzoBYgQAAx4BKwNbAdADUAGfAwMBBAMTARkDJQE2Ay4BRwNeAd0BigGIAYkB + /wOrAf8DqwH/AYoBiAGJAf8DXgHdAy4BRwMsAUMDLAFDAywBQwMsAUMDLAFDAywBQwMsAUMDLAFDAywB + QwMuAUcDXgHdAYoBiAGJAf8DqwH/A6sB/wGKAYgBiQH/A14B3QMuAUcDKwFCAyQBNAMSARcDAwEECAAE + AgMVARwDNgFZA1EBnwNhAd4DKwH8A2AB4AM/AW0DFAEbAyEBLwNHAYIDYQHWAysB/AGAAbMC/wGAAbMC + /wGAAbMC/wGAAbMC/wGAAbMC/wGAAbMC/wGAAbMC/wGAAbMC/wGAAbMC/wGAAbMC/wGAAbMC/wGAAbMC + /wNgAegDSgGJAxUBHSAAAw4BEgNOAZQDTQH6AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8D + AAH/AwAB/wMAAf8DAAH/AwAB/wFbAWEBWwHkAzYBWAMTARoDVgGyAQABmwEAAf8BAAGaAQAB/wFRAW0B + UQH3AVgBWgFYAb0DRQF8AzYBVxgAAwcBCQNMAY4BQAGdAaEB/QGpAdYB3AH/AbkB6AHuAf8BvAHqAfAB + /wG9AegB7gH/AbAB2QHeAf8BpQHLAdAB/wGsAdMB2AH/AbUB4AHmAf8BowHKAc8B/wMAAf8DAAH/AQAB + igGPAf8BmAHSAdkB/wGbAdcB3gH/AZEByAHPAf8DAAH/AwAB/wNcAfgDRgF+AwMBBAMOARIDUAGaA0YB + gQMLAQ4DIwEyAQAByQGSAf8BAAGqAQAB/wMAAf8BrAGqAasB/wOmAf8DpgH/AawBqgGrAf8DAAH/AQAB + qwEAAf8BAAHLAZQB/wEAAZgBAAH/AQABmgEAAf8BAAGbAQAB/wEAAZsBAAH/AQABmwEAAf8BAAGaAQAB + /wEAAZgBAAH/AQABywGUAf8BAAGrAQAB/wMAAf8BrAGqAasB/wOmAf8DpgH/AawBqgGrAf8DAAH/AQAB + qwEAAf8BAAHLAZMB/wNlAfEDIQEwAwoBDRAABAEDDAEPAyoBQANOAZYDZQHnA10B6gNaAb0DXQHRA1wB + +AGAAbMC/wGAAbMC/wGAAbMC/wGAAbMC/wGAAbMC/wGAAbMC/wGAAbMC/wGAAbMC/wGAAbMC/wGAAbMC + /wGAAbMC/wGAAbMC/wGAAbMC/wGAAbMC/wGAAbMC/wNgAfMDTQGSAxABFRgABAEDMgFPA2AB4wMAAf8D + AAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AQABggEAAf8D + WAG4Ax4BKgFOAU8BTgGXA2UB8QFIAWIBSAH2AVsBXwFbAdgDVgG2A0kBhwMaASQYAAMYASADWAG4AbUB + 3gHkAf8BwgHtAfQB/wHBAewB8gH/AcIB7QHzAf8BlQG2AboB/wMAAf8DAAH/AwAB/wMAAf8BowHHAcwB + /wG4AeEB5wH/Aa0B1wHdAf8BpgHaAeAB/wGdAdcB3gH/AZsB1gHdAf8BigG/AcYB/wMAAf8DAAH/AwAB + /wNaAcQDHAEnBAIDMwFSAzUBVQMQARUDXQHPAQABywGWAf8BAAGdAQAB/wMAAf8BrQGpAasB/wGmAaUB + pgH/AaYBpQGmAf8BrQGpAasB/wMAAf8BAAGVAQAB/wEAAc4BmgH/AQABvwGFAf8BAAHAAYcB/wEAAcAB + hwH/AQABwAGHAf8BAAHAAYcB/wEAAcABhwH/AQABvwGFAf8BAAHMAZcB/wEAAZ0BAAH/AwAB/wGtAakB + qwH/AaYBpQGmAf8BpgGlAaYB/wGtAakBqwH/AwAB/wEAAZUBAAH/AQABzwGZAf8BAAG/AYQB/wNZAcED + EAEVHAADDQERAzoBYgNhAdYBgAGzAv8BgAGzAv8BgAGzAv8BgAGzAv8BgAGzAv8BgAGzAv8BgAGzAv8B + gAGzAv8BgAGzAv8BgAGzAv8BgAGzAv8BgAGzAv8BgAGzAv8BgAGzAv8BgAGzAv8BgAGzAv8BgAGzAv8B + gQG0Av8BAAGtAfwB/wFdAmUB7AM7AWUDBAEFFAADCQELA04BlgMrAfwDAAH/AwAB/wMAAf8DAAH/AwAB + /wMAAf8BAAGEAQAB/wEAAYQBAAH/AQABkQEAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wEAAY4BAAH/AVoB + YwFaAekDQwF3AU4BTwFOAZcBSwFMAUsBjwFXAVoBVwHCAVcBWAFXAbwDUwGqAVMBVAFTAaYBRgFHAUYB + gBQABAEDNQFWA2AB6AHCAe4B9AH/AcIB7QHzAf8BwgHtAfMB/wHBAewB8gH/AQABhwGLAf8DAAH/AwAB + /wMAAf8DAAH/AwAB/wEAAYEBhAH/AaQByAHNAf8BvgHrAfAB/wGwAeIB6QH/AZ8B2AHfAf8BlwHRAdgB + /wEAAZoBnwH/AwAB/wMAAf8DUwH0Az0BaQMEAQUDMQFOAzIBTwMRARYBAAHBAQAB/wEAAdgBpQH/AQAB + owEAAf8DAAH/AY4BhQGIAf8BrQGpAasB/wGtAakBqwH/AY4BhQGIAf8DAAH/AQABogEAAf8BAAHVAacB + /wEAAb4BhgH/AQABvwGIAf8BAAG/AYgB/wEAAb8BiAH/AQABvwGIAf8BAAG/AYgB/wEAAb4BhgH/AQAB + 1QGnAf8BAAGiAQAB/wMAAf8BjgGFAYgB/wGtAakBqwH/Aa0BqQGrAf8BjgGFAYgB/wMAAf8BAAGiAQAB + /wEAAdcBowH/AQABwgEAAf8BAAHGAQAB/wMPARQYAAMDAQQDGwElAzcBWgNWAbUDXwH7AQABsQL/AYAB + swL/AYABswL/AYABswL/AYABswL/AYABswL/AYABswL/AYABswL/AYABswL/AYABswL/AYABswL/AYAB + swL/AYABswL/AYABswL/AYABswL/AYABswL/AYABswL/AQABlAHxAf8CAAHZAf8CWAFaAb0DHQEpFAAD + GAEhA1wByAMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wEAAZoBAAH/AQABuAEAAf8BAAG+AQAB/wEAAcMB + AAH/AQABvgEAAf8BAAGqAQAB/wEAAZMBAAH/AwAB/wMAAf8BAAGuAQAB/wFeAV8BMgH7A1IBowMqAT8D + FAEbA0kBhgFQAVEBUAGfA1UBrgNRAZ4DOwFkFAADBAEFA0oBiwMrAfwBtgHgAeUB/wG0Ad0B4wH/AbkB + 4gHoAf8BwAHrAfEB/wGgAcQByQH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wGwAdgB3QH/AcEB + 7QHzAf8BswHkAeoB/wGgAdkB4AH/AZsB1gHeAf8BAAGkAaoB/wMAAf8DKgH+A1IBoAMSARcDOQFeAywB + QwMPARMCAAHSAf8BAAHpAbIB/wEAAcYBjAH/AQABmAEAAf8DAAH/AwAB/wMAAf8DAAH/AQABmAEAAf8B + AAHDAY8B/wEAAd8BugH/AQABwAGJAf8BAAHCAYsB/wEAAcIBjAH/AQABwgGMAf8BAAHCAYwB/wEAAcIB + jAH/AQABwAGJAf8BAAHfAboB/wEAAcMBjwH/AQABmAEAAf8DAAH/AwAB/wMAAf8DAAH/AQABmAEAAf8B + AAHDAYwB/wGWAfABzAH/AgAC/wIAAeIB/wMKAQ0YAAMDAQQDIwEyA04BlgNhAeYBAAGdAv8BAAGjAv8B + AAGqAv8BAAGuAv8BAAGxAv8BAAGyAv8BAAGyAv8BgAGzAv8BgAGzAv8BgAGzAv8BgAGzAv8BgAGzAv8B + gAGzAv8BgAGzAv8BgAGzAv8BgAGzAv8BgAGzAv8BAAGfAfYB/wIAAd4B/wIAAdQB/wNgAegDOgFgEAAE + AgMoATsDYAHgAwAB/wMAAf8DAAH/AwAB/wMAAf8BAAGjAQAB/wEAAbsBAAH/AQABwQEAAf8BAAHJAQAB + /wEAAdABAAH/AQAB1AEAAf8BAAHRAQAB/wEAAc0BAAH/AQABxAEAAf8BAAHAAQAB/wEAAb0BAAH/AQAB + swEAAf8BUwFUAVMBpgMNAREEAAMkATQDUQGeAUYBRwFGAYEDQAFxAxgBIAgAAwkBDAMlATYDMwFSAzUB + VgNZAb4BhQGyAbsB/wGGAbcBwgH/AYMBtAG/Af8BhQGzAb0B/wGTAbwBxQH/Aa8B2AHeAf8BqwHRAdYB + /wGBAZ4BogH/AwAB/wMAAf8DAAH/AwAB/wMAAf8BnAG/AcQB/wHEAe8B9QH/AcAB7AHyAf8BswHlAesB + /wGgAdoB4QH/AYoBwAHGAf8DAAH/AwAB/wNYAbgDIAEuA0ABbgMfASwDCQELA1wB+AEAAYMB5AH/AQAB + 7AG9Af8BAAHGAY8B/wEAAaEBAAH/AQABmwEAAf8BAAGbAQAB/wEAAaEBAAH/AQABxgGRAf8BAAHjAcAB + /wEAAdYBrQH/AQABwwGNAf8BAAHEAY4B/wEAAcMBjQH/AQABxAGOAf8BAAHFAY8B/wEAAcUBkAH/AQAB + wwGOAf8BAAHWAa0B/wEAAeMBwAH/AQABxgGRAf8BAAGhAQAB/wEAAZsBAAH/AQABmwEAAf8BAAGhAQAB + /wEAAcYBkQH/AQAB4QG7Af8BzwH4AeID/wHnAf8DVgGyAwMBBBwABAIDEwEZA0ABcQNiAeEBAAGZAv8B + AAGaAv8BAAGdAv8BAAGgAv8BAAGjAv8BAAGlAv8BAAGnAv8BAAGpAv8BAAGsAv8BAAGwAv8BAAGyAv8B + gAGzAv8BgAGzAv8BgAGzAv8BAAGzAf4B/wEAAZ0B9QH/AgAB3wH/AgAB1AH/AgAB1AH/A1wB+ANNAZMD + FAEbAyQBNAMqAUADNQFWA0ABcAFRAVIBUQGhAVMBZQFTAfQDAAH/AwAB/wMAAf8DAAH/AQABoQGBAf8B + AAG3AQAB/wEAAb8BAAH/AQABygEAAf8BAAHTAQAB/wEAAdsBAAH/AQAB4AEAAf8BAAHhAQAB/wEAAd4B + AAH/AQAB2AEAAf8BAAHPAQAB/wEAAcUBAAH/AQABuwEAAf8DVgGzAxIBGAQAAwIBAwMqAUADMAFMAwsB + DgMCAQMEAAMGAQgDQAFxA18B2ANiAe8DXgHtA1wB+AGMAccB0wH/AYoBygHaAf8BggHEAdUB/wEAAb4B + 0QH/AQABswHFAf8BAAGrAbgB/wGmAc8B1gH/AcMB7gH0Af8BmQG7AcAB/wMAAf8DAAH/AwAB/wMAAf8B + qgHQAdYB/wGhAcUBygH/AQABkgGWAf8BjwGwAbQB/wGrAdsB4QH/AZ4B1gHdAf8BAAGWAZsB/wMAAf8D + WQG7AzABTANAAXADDwETAwIBAwNLAY0CAAHgAf8B1gH/AegB/wGMAe8B1AH/AQAB4AG5Af8BAAHgAbQB + /wEAAeABtQH/AQAB4QG6Af8BjAHtAdYB/wGDAegBzgH/AQABxQGPAf8BAAHHAZIB/wEAAcYBkQH/Aa4B + 7AHZAf8BAAHVAa0B/wEAAccBkgH/AQAByAGTAf8BAAHHAZIB/wEAAcUBjwH/AYMB6AHOAf8BjAHtAdYB + /wEAAeEBugH/AQAB4AG1Af8BAAHgAbYB/wEAAeIBvAH/AYwB7gHXAf8BAAHjAcUB/wHdAfEB3QH/A2UB + 5wMEAQUoAAMJAQsDPwFtA2EB5gEAAZkC/wEAAZkC/wEAAZkC/wEAAZkC/wEAAZkC/wEAAZoC/wEAAZsC + /wEAAZwC/wEAAaAC/wEAAaUC/wEAAa4C/wEAAbMC/wEAAbEB/gH/AQABmAHyAf8CAAHdAf8CAAHUAf8C + AAHUAf8CAAHUAf8CQAGoAf0DVQGvA0cBggNdAdEDXAHIA10BzANeAdIDXQHMAVMBZQFTAfQDAAH/AwAB + /wMAAf8DAAH/AQABrQEAAf8BAAG6AQAB/wEAAcQBAAH/AQABzwEAAf8BAAHaAQAB/wGAAeQBAAH/AYgB + 6wEAAf8BigHuAYAB/wGGAekBAAH/AQAB4AEAAf8BAAHWAQAB/wEAAcsBAAH/AQABwAEAAf8DVQGsAxAB + FQgABAIDBAEFDAADEgEYA1gBvAGoAdwB3QH/AbMB7AHvAf8BtAHvAfMB/wGuAeoB7wH/AaYB4wHrAf8B + oAHfAeoB/wGUAdUB5AH/AYUByAHbAf8BAAHDAdgB/wEAAbcBzAH/AQABqgG3Af8BswHcAeIB/wG8AeYB + 7AH/AYEBnQGhAf8DAAH/AwAB/wGGAaMBqAH/AYUBowGnAf8DAAH/AwAB/wMAAf8BAAGRAZUB/wGwAd8B + 5QH/AakB3QHjAf8BngHMAdEB/wNWAbADQQFyAzUBVQMCAQMEAAMEAQUCXAFlAecBAAHhAa8B/wHwAf8B + +gH/AagB8wHhAf8BpAHyAeIB/wGkAfIB5AH/AZoB8AHfAf8BAAHbAbgB/wEAAcUBjwH/AQAByAGTAf8B + AAHJAZUB/wEAAcgBkwH/AQABxwGSAf8BAAHHAZIB/wEAAcgBkwH/AQAByAGUAf8BAAHIAZQB/wEAAccB + kgH/AQAByAGTAf8BAAHbAbcB/wG7AfYB6QH/AbcB9gHpAf8BuwH3AeoB/wGwAfQB5AH/AQAB2AGxAf8B + XQJfAc4DVgGwAwMBBDAAAwsBDgNEAXgBXQJlAewBAAGZAv8BAAGZAv8BAAGZAv8BAAGZAv8BAAGZAv8B + AAGZAv8BAAGZAv8BAAGZAv8BAAGaAv8BAAGkAv8BAAGxAv8BAAGrAfsB/wEAAYYB6gH/AgAB3AH/AgAB + 1gH/AgAB1AH/AgAB1AH/AkABqAH9AVUCVwGxAzUBVgNdAc8DTQGTAygBPAMjATMDKwFBAVsBXgFbAdMD + AAH/AwAB/wMAAf8BAAGOAQAB/wEAAbABAAH/AQABugEAAf8BAAHGAQAB/wEAAdEBAAH/AQAB3QEAAf8B + hQHoAQAB/wGPAfIBhQH/AZQB9wGJAf8BjQHwAYIB/wGBAeUBAAH/AQAB2QEAAf8BAAHOAQAB/wFAAagB + QAH9Ak0BTAGRAwkBCxgAAwwBEAM2AVcDXQHRAZwBzAHNAf8BowHYAdsB/wGhAdYB2gH/AZABxAHKAf8B + kgHIAdAB/wGbAdUB3gH/AaQB5AHuAf8BlwHZAegB/wGDAccB2wH/AQABwQHXAf8BAAGvAcEB/wGbAcQB + ywH/AcIB7QHzAf8BwAHrAfEB/wG3AeAB5gH/AbgB4QHnAf8BAAGbAZ8B/wMAAf8DAAH/AwAB/wMAAf8D + AAH/AacBzAHRAf8BwwHvAfUB/wNcAfgDUwGpA0kBhQMZASMMAAMGAQcBAAG8AQAB/wGdAeQBzAL/Af0B + 8gL/AesBvQL/AekBuwL/AeoBvQL/AewBvwL/Ae0BwgL/Ae8BxQH/AQABygGWAv8B7wHFAv8B7QHDAv8B + 7QHDAv8B7QHDAv8B7QHDAv8B7QHDAv8B7QHCAv8B7AG/Av8B8wHTAf8BAAHiAcUB/wEAAbwBgQH/AQAB + uAEAAf8DWQG+AzoBYAQCPAADDwEUA0cBgANhAeYBAAGZAv8BAAGZAv8BAAGZAv8BAAGZAv8BAAGZAv8B + AAGZAv8BAAGbAv8BAAGkAv8BAAGuAv8BAAGzAv8BAAGyAf4B/wEAAa0B/AH/AQABoQH2Af8BAAGKAewB + /wIAAd4B/wIAAdYB/wNNAfoBTwJQAZsDBwEJAzwBZgNVAa8DJAE0BAADDQERA1MBqgMAAf8BAAGIAQAB + /wEAAZEBAAH/AQABoQEAAf8BAAGtAQAB/wEAAbkBAAH/AQABxAEAAf8BAAHQAQAB/wEAAdsBAAH/AYIB + 5gEAAf8BjAHvAYEB/wGQAfMBhQH/AYoB7QEAAf8BgAHjAQAB/wEAAdgBAAH/AQABzQEAAf8BWgFnAVoB + 8gM7AWMEAhgAAykBPQFbAlwBzQNNAfoBoQHSAdMB/wG4AfIB9AH/AYIBsAG0Af8DAAH/AQABlwGfAf8B + lgHPAdgB/wGlAeQB7gH/AaMB5AHwAf8BkQHUAeUB/wEAAcMB2QH/AQABtwHLAf8BjwG6AcMB/wG/AeoB + 8AH/Ab8B6gHwAf8BvwHqAe8B/wHBAewB8gH/AYsBqgGvAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wGwAdgB + 3QH/AcEB7AHyAf8DZwHyAVUCVwG0AzYBVwMDAQQMAAQBAxoBJAEAAbgBAAH/AZwB5QHOBv8B8gHjAv8B + 6gHSAv8B6gHVAv8B7AHYAv8B7QHaAf8BAAHMAZoC/wHuAdsC/wHsAdkC/wHsAdkC/wHsAdkC/wHsAdgC + /wHsAdcC/wHqAdQC/wHwAd0F/wEAAdQBpQH/AwkBC1AAAwwBDwM5AV4DWwHFAUkCYgH2AQABmQL/AQAB + mQL/AQABmQL/AQABmgL/AQABpgL/AQABsQL/AYABswL/AYABswL/AYABswL/AYEBtAL/AYEBtAL/AQAB + sgH+Af8BAAGlAfkB/wEAAYMB6QH/A14B7QM/AWwEAAMKAQ0DRQF8A0gBhAMKAQ0EAQM7AWUBWQFiAVkB + 7wEAAYkBAAH/AQABlAEAAf8BAAGfAQAB/wEAAaoBAAH/AQABtQEAAf8BAAHAAQAB/wEAAcsBAAH/AQAB + 1AEAAf8BAAHdAQAB/wGAAeQBAAH/AYMB5gEAAf8BgAHjAQAB/wEAAdwBAAH/AQAB0wEAAf8BAAHJAQAB + /wFbAV0BWwHKAx4BKhwAAx0BKQNaAb0DAAH/AZEBvgG/Af8BuAHzAfUB/wEAAZoBngH/AwAB/wGAAbEB + uQH/AaEB3QHmAf8BpwHnAfEB/wGkAeUB8QH/AZwB3gHsAf8BhAHJAd0B/wEAAboB0AH/AQABrAG2Af8B + oAHGAcsB/wGYAcEBxgH/AZYBvgHDAf8BrwHXAd0B/wG+AegB7QH/AaIBxwHMAf8BhAGiAaYB/wEAAZkB + nQH/AZABsAG1Af8BswHaAeAB/wHAAesB8QH/A2IB9gNdAc8DQAFuAwwBEBQABAEDGQEjAQABuAEAAf8B + AAHXAbMB/wH7AfwB9QL/AfYB3wH/Af0B4gGuAf8B+gHXAYsC/wHYAY4B/wEAAdABnwL/AdkBkAH/AfgB + 2AGQAf8B9QHYAZAB/wH1AdgBkAH/AfYB2AGOAf8B+AHXAYsC/wHoAbsC/wH9AfYB/wEAAeIBwQH/A0oB + iwMCAQNUAAMEAQUDIQEvA0YBfwNdAcwBUwJlAfQBAAGTAfsB/wEAAZwB/gH/AQABrwL/AYEBswL/AYAB + swL/AYABswL/AYABswL/AYABswL/AYABswL/AYABswL/AYEBtAL/AQABrgH8Af8DWwHFAyMBMggAAxkB + IgFMAU0BTAGRAzMBUAMPARQDGwElA1YBsgFAAYQBQAH9AQABkAEAAf8BAAGbAQAB/wEAAaUBAAH/AQAB + sAEAAf8BAAG5AQAB/wEAAcMBAAH/AQABywEAAf8BAAHTAQAB/wEAAdgBAAH/AQAB2QEAAf8BAAHXAQAB + /wEAAdIBAAH/AQABywEAAf8BWgFnAVoB8gNDAXcDBgEHHAADCAEKA00BkQOoAf0BtwLvAf8BuQH1AfYB + /wGsAeUB6QH/AaYB3wHlAf8BrAHpAfEB/wGrAekB8wH/AacB5wHxAf8BpAHkAfEB/wGgAeEB7wH/AYsB + zgHhAf8BAAGyAcYB/wEAAZ8BqAH/AYcBtgG8Af8BkAHGAcwB/wGKAb4BxAH/AZABtgG7Af8BtwHfAeUB + /wG6AeMB6QH/AboB4wHpAf8BugHjAekB/wG6AeMB6QH/A18B+wNgAeMDSwGMAyUBNwMJAQsgAAMMARAB + WwJeAdMBAAG/AYoB/wGoAeUBzQb/AfsB8AL/AfIB1AH/AQAB4wHEAv8B4wGpAf8B/AHfAaMB/wH7AeIB + qgH/AfwB4gGpAv8B7wHNAv8B+wHwAf8B9AH9AfYB/wEAAdcBrgH/A1UBrwMDAQRgAAMGAQcDHgErAz4B + agNVAa4DXQHfAysB/AEAAbEB/gH/AYEBswL/AYABswL/AYABswL/AYABswL/AYABswL/AYABswL/AYAB + swL/A1wB+ANGAX0DBwEJCAADCgENA00BkgFfAWEBXwHaA1YBswM6AWEDPgFqAVwBZQFcAecBAAGKAQAB + /wEAAZUBAAH/AQABngEAAf8BAAGoAQAB/wEAAbEBAAH/AQABuQEAAf8BAAHBAQAB/wEAAccBAAH/AQAB + ygEAAf8BAAHMAQAB/wEAAcoBAAH/AQABxwEAAf8DTQH6AVEBUgFRAaQDFwEfIAADGwElA1IBpAOoAf0B + vwH5AfoB/wG6AfUB9wH/AbcB8wH2Af8BswHxAfYB/wGvAewB9AH/AasB6QHzAf8BpwHnAfEB/wGkAeQB + 8QH/AaAB4QHvAf8BhgHFAdQB/wEAAa4BvAH/AY8BxwHPAf8BmQHUAdsB/wGbAdYB3QH/A2IB7wNXAbED + UgGgA1IBoANSAaADUgGgA1EBngNJAYYDMAFKAwsBDiwABAIDOgFgA1wB+AEAAbwBgwH/AQAB1wG1Af8B + xwHqAdUB/wHiAf0B+xH/AeUB8gHgAf8BAAHaAbYB/wEAAb4BhAH/A0IBdAQCbAADAgEDAxIBGAM5AV8B + XAJlAecBAAGMAe0B/wEAAawB/AH/AYEBtAL/AYABswL/AQABsgH+Af8BAAGuAfcB/wEAAbEB+wH/AQAB + rQH9Af8BcgKAAf4DVgGyAyoBPwgAAyABLQNcAcsBAAGaAQAB/wEAAZkBAAH/AVsBYQFbAeQDWAG3A1kB + wQFbAWEBWwHeAVMBgAFHAf4BAAGXAQAB/wEAAaABAAH/AQABpwEAAf8BAAGvAQAB/wEAAbUBAAH/AQAB + uwEAAf8BAAG+AQAB/wEAAb8BAAH/AQABvgEAAf8BUgFtAVEB9wNVAa8DIAEuBAEgAANEAXkDYwHpAaUC + 1wH/Ab4C+QH/AboB9QH3Af8BtgHyAfUB/wGyAe8B9AH/Aa8B7AH0Af8BqwHpAfMB/wGnAecB8QH/AaQB + 5QHxAf8BlwHWAeIB/wGAAbcBwgH/AZEBywHSAf8BmwHWAd0B/wGbAdYB3QH/A1wB+ANQAZsDFwEfAwwB + DwMMARADDAEQAwwBEAMMAQ8DBgEIPAAEAQMZASIDTwGZAVsCXwHQAQABuAEAAf8BAAG3AQAB/wEAAbwB + gwH/AQABvgGFAf8BAAG3AQAB/wFfAmUB5QFBAkIBcwQCfAADGwElAl0BXwHOAgAB1wH/AgAB5gH/AQAB + owH4Af8BgAGzAv8BAAGlAeoB/wIAAYsB/wEAAY8BygH/A14B8AFdAmEB3AFXAloBwgE/AkABbggAAyQB + NQNeAdcBAAGbAQAB/wEAAZoBAAH/AUgBYgFIAfYBUQFSAVEBoQMlATcDMAFKA1YBswNiAe4BAAGVAQAB + /wEAAZ0BAAH/AQABpAEAAf8BAAGpAQAB/wEAAa0BAAH/AQABsAEAAf8BXgFfAV4B+wFbAWEBWwHeA0oB + igMbASYoAAMvAUkDXAHLA4AB/gGrAeAB4QH/AbkB9AH2Af8BtwHzAfYB/wGyAfAB9QH/Aa8B7AH0Af8B + qwHqAfMB/wGnAegB8gH/AaIB4wHuAf8BhwG/AckB/wGLAcIBygH/AZsB1gHdAf8BmQHUAdoB/wNlAfQD + UQGiAxwBJ/8ABQADCgENA04BlANNAfoCAAHVAf8CAAHfAf8BAAGSAfAB/wEAAZgB4wH/AgABiAH/AVIC + bQH3A0wBjwMoATsDHAEnAwwBDwgAAxcBHwNZAbsBLQFfASEB+wFTAWUBUwH0AV8BZQFfAeUDWAG4Ay0B + RQMCAQMDGAEhAzsBYwNTAacDXQHPAV4BYQFeAeIBXwFlAV8B5QNeAd0BWQFcAVkBwwNMAY4DLgFIAwwB + DywAAwYBBwMuAUgDWQG5A14B8AGTAqgB/QGnAd8B4gH/Aa8B7AHxAf8BrQHqAfIB/wGkAeEB6gH/AaAB + 3gHoAf8BngHcAecB/wEAAbABtwH/A4AB/gNlAfEDXAHNA0YBfwMYASEEAf8ACQADHwEsAVICUwGlA14B + 7QJAAagB/QIAAdcB/wJAAagB/QFaAmMB6QFQAlEBnAMYASEUAAMSARcDUAGdA1kBvgFXAVoBVwHCA1kB + vgFSAVQBUgGoA04BlgMVAR0EAAQCAwwBEAMcAScDKAE7AyoBQAMkATQDFgEeAwcBCTgABAEDGAEhAzkB + XgNTAaUDXgHwAYEBpgGrAf8BkgHEAcwB/wGDAbIBvAH/AQABpAGwAf8BlgHNAdgB/wGWAa8BsgH/A1wB + +ANOAZUDHgEqAwYBCP8AEQAEAQMWAR4DOQFfAUwCTQGRA1IBoAFLAkwBjwM2AVkDEwEaGAADEwEZAUsB + TAFLAY8BQQFCAUEBcwNVAa8DVgGwAVMBVAFTAaYBTwFQAU8BmwMzAVEEAWQAAycBOgNfAdADXQHJA1sB + xANgAeMDYAHoA2AB1ANeAd0DTQH6A0kBhgMEAQX/AB0ABAEDBgEIAwkBDAMGAQgEARwAAwgBCgMlATcD + GgEkA0sBjQNVAa8DVQGsAzoBYQMiATEEAmQAAxIBFwM6AWIDIAEuAxgBIAMpAT4DLQFEAyABLQMwAUwD + WwHEA0ABbgMCAQP/AFUABAEDJAE0AzkBXgMtAUQDEwEaBAFoAAQBAwUBBhQAAwYBBwMpAT0DGAEg/wBd + AAQBAwMBBP8A/wD/AHsAAy0BRgNdAc4DWwHeA1wB5wNdAewDXQHqA10B7ANiAe4DXwHlA14B3QNcAdkD + WwHQAl0BYQHcA14B3QNaAekDYQHrA2EB6wNdAewDYgHuA2AB4wNbAd4DUwGnAxUBHTAAAwwBDwNJAYUD + XgHwAwAB/wMAAf8BSgFnAYAB/gNbAdgDNQFWAzYBWQNfAdoBSwFpAYAB/gMAAf8DAAH/A1kB7wNHAYID + CgENRAADCQEMA0oBiQNlAfEDVAGoAxQBG9gABAEDPQFoA1oB9QIAAYIB/wMAAf8DAAH/AwAB/wMAAf8D + AAH/AwAB/wIAAYkB/wIAAZYB/wFcAXsBgAH+AgABoQH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB + /wIAAY0B/wJdAWAB1AMjATIwAAMcAScDWgHHAgABjgH/AgABlgH/AgABmQH/AgABjQH/AysB/ANdAcwD + XQHOAkABlAH9AgABkAH/AgABmgH/AgABlQH/AgABgwH/A1oBxAMaASREAAMPARQDVgGyAs4B1AH/A2MB + 6QNJAYcDLQFGAx0BKAMOARIDBQEGBAIDEgEYAyUBNgMSARc0AAMIAQoDIwEyAysBQQMcAScDAgEDVAAD + AwEEAxABFQMhAS8QAAQBAz8BbAFRAm0B9wIAAYkB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wIAAZIB + /wIAAZwB/wMAAf8CAAGoAf8CAAGEAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8CAAGVAf8DXgHXAyQB + NTAAAxkBIwNZAcECAAGkAf8CAAGiAf8CAAGfAf8CAAGoAf8CAAGUAf8DAAH/AwAB/wIAAaUB/wIAAaoB + /wIAAaAB/wIAAaEB/wIAAZoB/wNZAb4DGAEgRAADHwEsA10BzALcAeIB/wLTAdkB/wNqAfkDYwHpA10B + 0QNVAa4DSQGGAz0BaQNTAaUDXgHdA0gBhAMIAQowAAMcAScDWgG/A10BzAMwAUwDAgEDHAAIAiwAAwYB + CAM3AVoDVgGwA1UBrhQAAzEBTgNhAesCAAGiAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8CAAGnAf8C + AAGNAf8DAAH/AgABogH/AgABngH/AwAB/wMAAf8DAAH/AwAB/wMAAf8CAAGDAf8CAAGjAf8DWgG/AxgB + ITAAAxEBFgNTAacBXAKAAf4CAAG1Af8CAAG2Af8CAAG0Af8CAAGJAf8CAAGGAf8CAAGKAf8CAAGVAf8C + AAG1Af8CAAG2Af8CAAG1Af8BWQKAAf4DUgGlAw8BFEQAAykBPgNgAeADKwH8A14B2QNYAbgDXAHNA2UB + 5QNgAfMDZQH0A14B8ANfAfsDTQH6A0wBjgMHAQksAAQBAysBQQNcAdkDSwGMAwsBDhwAAw0BEQMqAUAD + DwETKAAEAQMkATUDXQHJA2AB4wM9AWgUAAMWAR4CWAFaAb0CAAGkAf8CAAGqAf8CAAGnAf8CAAGnAf8C + AAGmAf8CAAGmAf8CAAGrAf8CAAGoAf8DAAH/AwAB/wIAAY0B/wIAAa0B/wIAAagB/wIAAacB/wIAAacB + /wIAAaYB/wIAAaYB/wIAAasB/wFIAmIB9gNGAX0DBgEILAADBwEJAz4BagNiAeECAAGDAf8CAAGVAf8C + AAGaAf8CAAGQAf8CAAGSAf8CAAGwAf8CAAGwAf8CAAGSAf8CAAGQAf8CAAGbAf8CAAGRAf8DAAH/A2AB + 4AM9AWcDBgEIQAADGAEgA1IBpANfAdUDRwGCAzABSwMtAUQDLAFDAzsBYwNRAZ8DYAHgAs4B1AH/A2IB + 9gNIAYQDCwEOLAADGgEkA1UBrQNdAdEDJQE3HAADFwEfA0sBjwNQAZ0DEAEVKAADFwEfA1UBrwNdAeoD + RAF7AwwBDxQAAxIBFwNWAbADAAH/AgABpwH/AgABrwH/AgABsAH/AgABsAH/AgABsAH/AgABqQH/AgAB + iAH/AgABiAH/AgABngH/AwAB/wIAAZYB/wIAAawB/wIAAbAB/wIAAbAB/wIAAbAB/wIAAa4B/wIAAZwB + /wNfAdUDIwEzLAAEAgMxAU0DYAHbAgABpQH/AgABrwH/AgABowH/AgABmgH/AgABlQH/AgABlQH/AgAB + kQH/AgABjwH/AgABkQH/AgABjQH/AgABkgH/AgABmwH/AgABlQH/AgABiAH/Al8BYQHaAzABSwQCOAAD + DAEQA0QBeQNfAdUDYAHoAl0BYQHcAlwBYQHWA1UBtAMuAUgDNAFTA1oBvwNeAe0DgAH+AYoBkQGdAf8D + YgHhA0ABbwMJAQsoAAMrAUEDYAHgA1YBtgMTARkcAAM2AVkDYQHrA0oBigMGAQckAAMGAQgDPwFsA1oB + 6QNPAZkDEgEYFAADAgEDAzkBXwNhAesDAAH/AwAB/wIAAZIB/wIAAZwB/wIAAZ0B/wIAAZMB/wIAAYEB + /wIAAYcB/wIAAacB/wIAAa8B/wIAAZ4B/wMAAf8CAAGHAf8CAAGYAf8CAAGeAf8CAAGaAf8CAAGKAf8D + AAH/A2EB5gM0AVMEAigAAxkBIwNWAbYCAAGoAf8CAAG6Af8CAAG6Af8CAAGqAf8CAAGIAf8DAAH/AwAB + /wMAAf8DAAH/AwAB/wMAAf8DAAH/AgABnQH/AgABrQH/AgABnQH/AgABhQH/A1YBswMYASE4AAMgAS0D + XQHPAgABhgH/AgABigH/AgABkAH/AgABgwH/AVABdAGAAf4DWwHQA1wB1gIAAY0B/wIAAYMB/wIAAYUB + /wMAAf8DAAH/A1kBwQMYASEoAAMYASEDWQG+A1UBsQMRARYUAAMCAQMDJgE5A1MBpwNeAe0DOgFiBAEU + AAMDAQQDBgEHBAEDGQEiA0MBdwNeAdcDVQGsAxsBJRgAAxYBHgNVAbQCAAGLAf8CAAGfAf8CAAGYAf8C + AAGJAf8CAAGDAf8CAAGDAf8CAAGIAf8CAAGXAf8CAAGqAf8CAAGuAf8CAAGuAf8CAAGuAf8CAAGlAf8C + AAGRAf8CAAGFAf8CAAGCAf8CAAGEAf8CAAGIAf8CAAGKAf8CQAGDAf0DUwGnAxIBFyQABAIDOwFlA2IB + 7gIAAboB/wIAAbsB/wIAAZYB/wMAAf8DAAH/AwAB/wMAAf8CAAGEAf8CAAGDAf8DAAH/AwAB/wMAAf8D + AAH/AgABhQH/AgABrAH/AgABmQH/A14B7QM6AWIEAjQAAxsBJgJZAVwBxgIAAZgB/wIAAZMB/wIAAZQB + /wIAAZsB/wIAAYcB/wMAAf8DAAH/AgABmwH/AgABnAH/AgABlAH/AgABlAH/AgABjQH/A1gBuAMVARwo + AAMHAQkDSgGLA1kBwQMbASUQAAMIAQoDLgFHA1kBwANhAeYDUwGmAx8BLBgAAw8BFAM0AVQDKgE/A08B + mwNaAfIDWgHHAyUBNwQBGAADMwFRAlwBZQHnAgABowH/AgABrQH/AgABrwH/AgABrQH/AgABqgH/AgAB + qgH/AgABrQH/AgABrwH/AgABrgH/AgABrgH/AgABpwH/AgABjgH/AgABpwH/AgABrwH/AgABrAH/AgAB + qQH/AgABqwH/AgABrQH/AgABpgH/AgABkgH/A10B3wMtAUQkAAMNARECUgFUAagCAAGyAf8CAAG1Af8C + AAGcAf8DAAH/AwAB/wMAAf8DAAH/AgABiwH/AgABiQH/AgABiAH/AgABiQH/AwAB/wMAAf8DAAH/AwAB + /wIAAYsB/wIAAaUB/wFTAXwBgAH+AlIBUwGlAwwBEDQAAxQBGwNVAa8CAAGUAf8CAAGoAf8CAAGqAf8C + AAGmAf8DAAH/AwAB/wIAAYAB/wIAAYoB/wIAAakB/wIAAakB/wIAAagB/wFVAXwBgAH+A1IBoAMPARQs + AAM2AVcDXAHWAzQBUwQBBAADBgEHAyEBMANIAYQDWgHEA1MBqQM2AVkDEgEYBAIYAAMPARQDUQGhA1wB + 2QNaAfIDYAHbAzMBUgQCGAADBQEGAkcBSAGDA00B+gIAAa4B/wIAAa4B/wIAAa4B/wIAAa4B/wIAAa4B + /wIAAa4B/wIAAa4B/wIAAa4B/wIAAa4B/wIAAa4B/wIAAZIB/wMAAf8CAAGQAf8CAAGiAf8CAAGmAf8C + AAGuAf8CAAGuAf8CAAGuAf8CAAGuAf8CAAGjAf8BSAFgAWIB9gNCAXQDAgEDIAADHQEpA10B0QIAAboB + /wIAAawB/wMAAf8DAAH/AwAB/wMAAf8CAAGVAf8CAAGrAf8DAAH/AwAB/wIAAawB/wIAAZYB/wMAAf8D + AAH/AwAB/wMAAf8CAAGcAf8CAAGcAf8CXQFfAc4DGwEmMAADCQELA0EBcgJfAWUB5QMAAf8CAAGJAf8C + AAGNAf8CAAGEAf8CAAGLAf8CAAGnAf8CAAGlAf8CAAGJAf8CAAGFAf8CAAGNAf8CAAGDAf8DAAH/A1sB + 3gM7AWUDBgEHKAADIwEyA14B1wNVAa4DGQEjAwkBCwM7AWMDWgHHA1oB8gNZAcYDQAFxAxwBJxgAAwMB + BAMYASADPgFrA18B1QMAAf8DQAH9A00BkwMKAQ0cAAMMARADUwGpAgABowH/AgABrwH/AgABrgH/AgAB + rgH/AgABrgH/AgABrgH/AgABrgH/AgABrwH/AgABrwH/AgABrgH/AgABrQH/AgABiAH/AYQBgwGFAf8C + AAGDAf8DAAH/AgABjgH/AgABrwH/AgABrgH/AgABrgH/AgABrgH/AgABrQH/AgABkAH/A1ABnAMJAQsg + AAMqAUACXwFlAeUCAAGkAf8CAAGLAf8DAAH/AgABiwH/AwAB/wMAAf8CAAGmAf8CAAGIAf8DAAH/AwAB + /wIAAYsB/wIAAacB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AgABkQH/A2AB4wMpAT0sAAMDAQQDNgFZA2AB + 4wIAAZsB/wIAAaQB/wIAAZkB/wIAAZMB/wIAAZMB/wIAAZIB/wIAAYwB/wIAAYsB/wIAAY0B/wIAAYoB + /wIAAY0B/wIAAZMB/wIAAYoB/wMAAf8DYAHbAzABSwQCJAADFQEcA1kBwwNcAfgDVgGyA0YBgQNfAdUD + AAH/AwAB/wNRAfcDWQHvA1ABmgMSARcDGwElAzEBTgM6AWADNgFXAzABSgNCAXQDWAG9A2UB8QIqASwB + /gMAAf8CKgEtAf4DVQGsAxYBHhwAAxIBGAJYAVoBvQIAAakB/wIAAa8B/wIAAa4B/wIAAa4B/wIAAa4B + /wIAAa4B/wIAAasB/wIAAaAB/wIAAaIB/wIAAawB/wIAAZ0B/wMAAf8BlgGSAZEB/wGCAgAB/wMAAf8C + AAGdAf8CAAGvAf8CAAGuAf8CAAGuAf8CAAGuAf8CAAGvAf8CAAGdAf8DVgGwAw4BEiAAAy4BSANdAeoC + AAGiAf8DAAH/AwAB/wIAAYoB/wMAAf8CAAGWAf8CAAG2Af8CAAGeAf8DAAH/AwAB/wIAAZ8B/wIAAbcB + /wIAAZYB/wMAAf8CAAGEAf8DAAH/AwAB/wIAAZMB/wJaAWMB6QMtAUUsAAMdASkDWQG+AgABngH/AgAB + rQH/AgABrgH/AgABpAH/AgABhgH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wIAAZcB/wIAAaMB + /wIAAZAB/wFPAXIBgAH+A1YBsAMXAR8YAAMOARIDCQEMBAIDDwETA1YBswMhAfsDKwH8A1EB9wMrAfwD + AAH/AwAB/wMAAf8DAAH/A18B2gNOAZQDWQHAA1wB5wNeAfADXQHsA18B5QNTAfQDAAH/AwAB/wMAAf8D + AAH/AwAB/wNeAfADRgGAAwoBDRgAAxQBGwJXAVoBwgIAAaoB/wIAAa4B/wIAAa4B/wIAAa4B/wIAAa8B + /wIAAaQB/wIAAYEB/wMAAf8DAAH/AwAB/wIAAYcB/wGvAawBqgH/AwAB/wMAAf8DAAH/AgABgAH/AgAB + pgH/AgABrgH/AgABrgH/AgABrgH/AgABrwH/AgABpAH/A1YBtgMQARUgAAMrAUICXAFlAecCAAGvAf8C + AAGMAf8CAAGsAf8CAAGxAf8CAAGpAf8CAAGlAf8CAAGlAf8CAAGsAf8CAAG0Af8CAAGzAf8CAAGqAf8C + AAGkAf8CAAGmAf8CAAGqAf8CAAGxAf8CAAGbAf8DAAH/AgABogH/Al8BZQHlAyoBPygAAwMBBANAAXAD + YAHzAgABrgH/AgABrQH/AgABmwH/AwAB/wMAAf8DAAH/AwAB/wIAAYAB/wMAAf8DAAH/AwAB/wMAAf8D + AAH/AgABiwH/AgABoQH/AgABjQH/A14B7QM6AWAEAhQAAzMBUQI/AUABbgMzAVIDFQEdAzcBWgNGAYAD + VQGuA1EB9wMAAf8DAAH/AwAB/wMAAf8DAAH/A0AB/QMrAfwDAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8D + AAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DXwHlAzYBVwMCAQMUAAMSARcDWAG6AgABqQH/AgABrwH/AgAB + rgH/AgABrgH/AgABrQH/AgABigH/AwAB/wMAAf8DAAH/AwAB/wGpAacBpQH/AbMBsAGtAf8DAAH/AwAB + /wMAAf8DAAH/AgABjQH/AgABrgH/AgABrgH/AgABrgH/AgABrwH/AgABpAH/A1UBrQMNAREgAAMgAS4C + XAFhAdYCAAGoAf8CAAGSAf8CAAG5Af8CAAG3Af8CAAGiAf8BmAGlAbYB/wGyAbkBwgH/AQABjgGmAf8C + AAGmAf8CAAGkAf8BhQGVAaoB/wG0AbsBxAH/AZABngGyAf8CAAGjAf8CAAG5Af8CAAGwAf8DAAH/AgAB + ngH/Al0BYAHUAx4BKygAAw8BFANWAbACAAGnAf8CAAGuAf8CAAGfAf8DAAH/AwAB/wIAAZAB/wIAAZ8B + /wIAAYsB/wMAAf8DAAH/AgABiQH/AgABkwH/AgABgAH/AwAB/wMAAf8CAAGOAf8CAAGeAf8BUAF1AYAB + /gNSAaADCwEOFAADLQFEA0oBiQNbAcoDTQGSAxwBJwMEAQUDPQFpA1oB8gMAAf8DAAH/AwAB/wMAAf8D + AAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8C + KgExAf4DTgGUAwkBDBQAAwoBDQJRAVIBoQFzAoAB/gIAAa8B/wIAAa4B/wIAAa4B/wIAAakB/wIAAYEB + /wIAAZwB/wIAAYUB/wMAAf8DAAH/AbIBrwGrAf8BkQGQAY8B/wMAAf8DAAH/AgABkQH/AgABkQH/AgAB + hQH/AgABrQH/AgABrgH/AgABrgH/AgABrwH/AkABoAH9A00BkwMHAQkgAAMPARQDVQGvAgABoQH/AgAB + kgH/AgABuQH/AgABqgH/AZEBnwGyAf8B8gHxAfAB/wGtAawBqwH/AY0BjgGPAf8CAAGWAf8CAAGVAf8C + iAGHAf8CvwG+Af8C8QHwAf8BgAGSAasB/wIAAa4B/wIAAbUB/wIAAYQB/wIAAZsB/wNVAawDDwETKAAD + IQEwAlsBXwHYAgABrQH/AgABqAH/AwAB/wMAAf8CAAGhAf8CAAGtAf8CAAGgAf8DAAH/AwAB/wMAAf8D + AAH/AgABoAH/AgABogH/AgABigH/AwAB/wMAAf8CAAGZAf8CAAGPAf8DXAHLAxoBJBQAAxYBHgM2A1gB + WgG9A2AB8wNVAawDJAE1A0kBhwMhAfsDAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8D + AAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/A10B6gNbAcUDWAG9A1IBpQMwAUwDBAEFFAADAwEEA0MB + dwFRAm0B9wIAAa8B/wIAAa4B/wIAAa4B/wIAAawB/wIAAYcB/wIAAY8B/wIAAZ4B/wMAAf8DAAH/AbEB + rQGoAf8CkAGPAf8DAAH/AwAB/wIAAaIB/wIAAYYB/wIAAZAB/wIAAa4B/wIAAa4B/wIAAa4B/wIAAa8B + /wNgAfMDPgFqBAIgAAMDAQQDQAFxAlMBZQH0AgABkQH/AgABtwH/AgABpwH/AaUBsAG9Af8B+gH5AfgB + /wOvAf8CngGdAf8BAAGEAZgB/wEAAYkBmgH/ApYBlQH/A8AB/wL7AfoB/wGTAaEBtAH/AgABqwH/AgAB + tQH/AgABigH/A2AB8wNAAW8DAgEDKAADLQFGAloBYwHpAgABqAH/AgABkgH/AwAB/wIAAaQB/wIAAa0B + /wIAAa4B/wIAAZIB/wMAAf8DAAH/AwAB/wMAAf8CAAGWAf8CAAGuAf8CAAGlAf8CAAGJAf8DAAH/AgAB + gQH/AgABlQH/A2AB4AMlATcUAAMJAQwDLgFIAlQBVgGrA2AB8wNNAfoDVgGwA1UBsQNAAf0DAAH/AwAB + /wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/A1YB + sAMhAS8DFgEeAw8BEwMCAQMcAAMrAUIDXgHdAgABrQH/AgABrgH/AgABrgH/AgABrgH/AgABoQH/AgAB + ggH/AgABggH/AwAB/wMAAf8CigGLAf8DAAH/AwAB/wMAAf8CAAGAAf8CAAGHAf8CAAGnAf8CAAGuAf8C + AAGuAf8CAAGuAf8CAAGsAf8CXQFgAdQDJQE3KAADHgErAlcBWgHCAgABiwH/AgABrgH/AgABsQH/AQAB + hAGkAf8B1AHWAdkB/wHnAugB/wG2AbwBxAH/AgABnAH/AgABngH/AcEBxQHKAf8D6QH/AcsBzwHTAf8C + AAGhAf8CAAG0Af8CAAGsAf8CAAGKAf8DWgG/Ax0BKSgAAw0BEQNGAX4DWgH1AwAB/wIAAYEB/wIAAZkB + /wIAAa4B/wIAAa0B/wIAAa4B/wIAAaQB/wMAAf8DAAH/AwAB/wMAAf8CAAGlAf8CAAGuAf8CAAGtAf8C + AAGkAf8DAAH/AwAB/wMAAf8DZQHxA0EBcgMLAQ4QAAMEAQUDJAE0A0kBhgNbAdMDQAH9A0EB+QNZAe8C + KwEwAf4DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB + /wMAAf8DKwH8A0oBiwMGAQcoAAMQARUDUgGjAXICgAH+AgABrwH/AgABrgH/AgABrgH/AgABrgH/AgAB + qAH/AgABlQH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AgABkgH/AgABqQH/AgABrgH/AgABrgH/AgAB + rgH/AgABrwH/AysB/ANOAZYDDAEQKAADBAEFAzoEYAHoAgABmQH/AgABugH/AgABrAH/AgABoQH/AQAB + hgGmAf8CAAGiAf8CAAGxAf8CAAGvAf8CAAGiAf8BAAGGAaYB/wIAAaEB/wIAAa4B/wIAAboB/wIAAZYB + /wNhAeYDOAFdAwMBBCQAAwYBCANAAXACXwFlAeUDAAH/AwAB/wMAAf8CAAGoAf8CAAGuAf8CAAGtAf8C + AAGoAf8CAAGmAf8CAAGkAf8CAAGdAf8CAAGeAf8CAAGlAf8CAAGmAf8CAAGpAf8CAAGtAf8CAAGtAf8C + AAGPAf8DAAH/AwAB/wMAAf8DYgHhAz0BaQMFAQYMAAQBAxEBFgNCAXQDWwHTAyEB+wMAAf8DAAH/AwAB + /wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB + /wNTAfQDOwFlBAEoAAQBAy4BSAJbAWEB3gIAAasB/wIAAa4B/wIAAa4B/wIAAa4B/wIAAZoB/wMAAf8D + AAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AgABkwH/AgABqwH/AgABrgH/AgABrgH/AgABqgH/AlwB + YQHWAykBPTAAAwsBDgJGAUcBgAJZAWIB7wIAAagB/wIAAbgB/wIAAbYB/wIAAbMB/wIAAbgB/wIAAboB + /wIAAboB/wIAAbcB/wIAAbMB/wIAAbcB/wIAAbgB/wIAAagB/wJZAWIB7wNGAX0DCgENKAADFQEdAlkB + XAHDAgABhQH/AgABgwH/AwAB/wMAAf8CAAGsAf8CAAGtAf8CAAGfAf8CAAGaAf8BAAGHAaMB/wIAAZsB + /wIAAaUB/wIAAaQB/wIAAZsB/wEAAYcBowH/AgABmgH/AgABoAH/AgABrgH/AgABnwH/AwAB/wMAAf8C + AAGEAf8DAAH/A1gBugMSARgUAAMZASMDSQGHA2AB6AMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMrAfwD + XQHsA1wB1gNcAcgDXQHUA1oB8gMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wNiAeEDKAE8MAADCQELA0YB + fgNlAfECAAGuAf8CAAGvAf8CAAGWAf8DAAH/AwAB/wIAAZcB/wIAAaAB/wIAAYwB/wIAAYkB/wIAAZoB + /wIAAY8B/wMAAf8DAAH/AgABjQH/AgABrQH/AgABrQH/AV0CZQHsA0EBcgMGAQg0AAMwAUsDYgHhAwAB + /wIAAZcB/wIAAboB/wIAAbsB/wIAAboB/wIAAboB/wIAAboB/wIAAboB/wIAAbsB/wIAAboB/wIAAZgB + /wMAAf8DWwHkAzMBUSwAAxQBGwJZAVoBwAIAAYUB/wIAAYcB/wMAAf8DAAH/AgABrgH/AgABpAH/AQAB + gAGeAf8B1gHXAdoB/wLMAcsB/wGaAZ0BowH/AgABkgH/AgABkgH/AZoBngGiAf8D0QH/AdMB1QHYAf8C + AAGcAf8CAAGmAf8CAAGmAf8DAAH/AwAB/wIAAYYB/wMAAf8DWAG4AxEBFhQABAIDHQEpA1ABnQNaAfID + AAH/AwAB/wMAAf8DAAH/AyEB+wNYAbgDOAFcAyQBNAMbASUDLwFJA1cBuQMhAfsDAAH/AwAB/wMAAf8D + AAH/AwAB/wNXAcIDFgEeNAADEwEZA0wBkAFaAmcB8gIAAZ0B/wMAAf8CAAGMAf8CAAGpAf8CAAGuAf8C + AAGEAf8DAAH/AwAB/wIAAYQB/wIAAasB/wIAAZ8B/wMAAf8DAAH/AgABlgH/AVkCYgHvA0kBhQMPARQ0 + AAMKAQ0DTgGWA00B+gMAAf8CAAGCAf8CQAGnAf0BYgKAAf4CAAG5Af8CAAG6Af8CAAG6Af8CAAG5Af8B + YgKAAf4DKwH8AgABggH/AwAB/wMrAfwDUgGgAw0BESgAAwcBCQJLAUwBjwEnAV4BXwH7AgABiAH/AwAB + /wMAAf8CAAGmAf8CAAGdAf8BoAGqAbkB/wH5AfgB9wH/A5gB/wGAAgAB/wEAAYEBlAH/AQABgAGRAf8D + AAH/A6UB/wL7AfoB/wGZAaQBtAH/AgABnwH/AgABogH/AwAB/wMAAf8CAAGFAf8DTQH6A0kBhgMGAQcQ + AAMIAQoDHwEsAzABTANFAXwDXAHWAwAB/wMAAf8DAAH/AwAB/wNAAf0DUQGhAwsBDggAAxABFQNLAY0D + YgHuAwAB/wMAAf8DAAH/AwAB/wIqAS4B/gNPAZsDCQELOAADFAEbA0oBigJdAWUB7AIAAYgB/wIAAasB + /wIAAa8B/wIAAasB/wMAAf8DAAH/AwAB/wMAAf8CAAGsAf8CAAGtAf8CAAGgAf8DAAH/A1wB5wJGAUcB + gAMRARY4AAMdASkDWwHQAwAB/wMAAf8DYAHzA1MBpwNSAaMCWgFjAekCAAGUAf8BSAFgAWIB9gNdAc8D + UQGeA1IBoAJZAWIB7wMAAf8DAAH/A1sB2AMiATEoAAQBAy0BRQNgAdsCAAGGAf8CAAGGAf8DAAH/AgAB + jgH/AgABoAH/AYcBlgGqAf8C8gHxAf8B5AHjAeIB/wLEAcUB/wIAAZUB/wIAAZYB/wHEAsUB/wHoAecB + 5gH/AfEC8AH/AYABkAGnAf8CAAGhAf8CAAGMAf8DAAH/AgABhgH/AwAB/wNfAdUDKgE/FAADCQELAxwB + JwMmATkDMQFOA0QBeQNWAbUDWwHkA00B+gMAAf8DQQH5A1EBngMMAQ8IAAMEAQUDNwFaA18B2gMAAf8D + AAH/AwAB/wMAAf8DUQH3A0IBdQMJAQwDIgExAx8BLAMIAQowAAMbASYCWQFcAcMCAAGlAf8CAAGvAf8C + AAGuAf8CAAGuAf8CAAGPAf8DAAH/AwAB/wIAAZUB/wIAAa4B/wIAAa4B/wIAAa0B/wIAAZQB/wNYAbgD + FwEfPAADKgE/A18B5QNNAfoCXQFhAdwDSQGGAxUBHAMQARUCUAFRAZwCQAGQAf0CWgFjAekDPQFpAwsB + DgMSARgDRgF+AlsBXwHYAkEBaAH5A10B6gMuAUgsAAMJAQwDRAF7A14B8AIAAYcB/wMAAf8DAAH/AgAB + nAH/AgABmQH/AY0BmwGtAf8BsAG4AcIB/wEAAY4BpgH/AgABmwH/AgABmwH/AYEBkQGoAf8BsAG4AcMB + /wGKAZgBqwH/AgABmgH/AgABmwH/AwAB/wIAAYAB/wIAAYIB/wNeAe0DQQFzAwgBCiAABAEDBAEFAxIB + GAMtAUUDRwGCA1EBngNGAYADIAEuBAEIAAQCAyoBQANbAcUDAAH/AwAB/wMAAf8DAAH/A1oB8gNEAXkD + PgFqA10B0QNdAdEDPQFnAwUBBiwAAyUBNgJcAWEB1gIAAa0B/wIAAa4B/wIAAakB/wIAAaAB/wIAAaQB + /wIAAa0B/wIAAa0B/wIAAaQB/wIAAaAB/wIAAakB/wIAAa8B/wIAAaYB/wJbAVwBzQMfASw8AAMZASMD + SQGIAkYBRwGBAy0BRAMMAQ8EAAQBAzMBUgNgAeMCQAGdAf0CVwFYAbwDJQE2BAEDCgENAyoBQANGAX4D + SwGMAx0BKTAAAxIBGANKAYoDYAHoAgABgQH/AwAB/wIAAYAB/wIAAacB/wIAAZ8B/wIAAZsB/wIAAaEB + /wIAAawB/wIAAawB/wIAAaEB/wIAAZsB/wIAAZ8B/wIAAacB/wMAAf8DAAH/AwAB/wNfAeUDSAGEAxAB + FTQAAwYBBwMLAQ4DBgEHEAAEAgMxAU0DWwHTAioBLQH+AwAB/wMAAf8DAAH/AyEB+wNdAdwDYAHoAwAB + /wMAAf8DUAGdAwkBDCwAAy4BSAFfAmUB5QIAAa4B/wIAAaYB/wIAAZQB/wGJAZUBoQH/AQABgwGTAf8C + AAGTAf8CAAGUAf8BAAGEAZMB/wGHAZMBoAH/AgABkwH/AgABpgH/AgABrAH/AlsBYQHeAykBPTwABAID + BgEIAwQBBRAAAwwBEANGAX4DXgHtAVkCgAH+A1kBvgMpAT0EAQQAAwQBBQMHAQkEAjQAAw4BEgM3AVoD + UwGnAlsBXQHKA18B5QJAAZcB/QIAAa8B/wIAAa4B/wIAAa4B/wIAAa0B/wIAAa0B/wIAAa4B/wIAAa4B + /wIAAa8B/wMrAfwDYAHjA10ByQNRAaQDNQFVAwwBEFgAAxMBGgJGAUcBgQNcAecDAAH/AwAB/wMAAf8D + AAH/AwAB/wMAAf8CKgEsAf4DWgHHAysBQgQCLAADLQFFAV4CYQHiAgABrgH/AgABlQH/AbABtgG8Af8C + zgHNAf8DAAH/AwAB/wIAAYMB/wOBAf8BxgHFAcQB/wGuAbQBugH/AgABlgH/AgABrQH/A2AB2wMnATpc + AAMQARUDRAF7AlsBYQHkAysB/ANWAbIDFwEfSAAEAgMPARQDHQEoAzQBUwNVAa4DXgHwAgABpgH/AgAB + rAH/AgABrQH/AgABrgH/AgABrQH/AV8CgAH+A14B7QJTAVQBpgMwAUwDHAEnAw8BEwQCYAADJwE6A1kB + uwNBAfkDAAH/AwAB/wMAAf8DAAH/AwAB/wNcAecDNwFaAwMBBDAAAyABLQJbAVwBzQIAAasB/wIAAZIB + /wHHAcoBzQH/A+UB/wGwAq8B/wEAAYUBigH/AYUBiwGQAf8DsgH/A98B/wHFAcgBywH/AgABkwH/AgAB + qgH/AlkBXAHDAxoBJGAAAwkBDAM9AWkDYQHrA14B8AM2AVdYAAMVARwDPAFmAlQBVgGrA14B0gJcAWUB + 5wMrAfwBUgF2AYAB/gJbAV0BygM6AWEDEgEXcAADEQEWAj8BQAFuA1sB0ANAAf0DAAH/AwAB/wMAAf8D + TQH6A04BlwMMAQ80AAMNARECUAFRAZ8BcwKAAf4CAAGeAf8BAAGOAZ0B/wHJAcwBzwH/AbYBuwHAAf8C + AAGOAf8CAAGOAf8BtwG8AcEB/wHJAcsBzgH/AQABjAGdAf8CAAGfAf8DKwH8A00BkwMJAQxkAAMeASoD + XgHSA1wB+AM+AWsEAVgAAwIBAwMRARYDIQEwAzgEWwHYAwAB/wNSAaUDDwETeAADDAEPAy4BSANRAZ4D + XgHdAyEB+wMAAf8DWgHEAyUBNjgAAxIBFwNRAaQCQAGMAf0CAAGrAf8CAAGeAf8CAAGSAf8CAAGUAf8C + AAGlAf8CAAGlAf8CAAGUAf8CAAGSAf8CAAGeAf8CAAGrAf8DKwH8A04BlwMNARFUAAMDAQQDDAEQAxIB + GAMdASkDRgF+A2UB8QNeAe0DMwFRaAADBgEHA0gBhAFBAUYBagH5A10BzwMgAS6AAAMMARADKQE+A1UB + rQNdAeoDSQGHAwkBCzgAAykBPgNdAdwDAAH/AgABjwH/AgABrgH/AgABrwH/AgABrgH/AgABrwH/AgAB + rwH/AgABrgH/AgABrwH/AgABrQH/AgABjAH/AwAB/wNbAdMDIwEzUAADCQEMAzkBXwNRAaICVQFXAbED + XAHIAVkCYgHvAgABsAH/AlkBWgHAAxkBI2wAAyABLgJXAVoBwgNhAesDNQFViAADKgE/AkcBSAGDAzIB + TwMKAQ04AAMlATYDXgHSAwAB/wFTAW8BgAH+A2AB8wNcAfgCAAGpAf8CAAGtAf8CAAGtAf8CAAGoAf8B + UQJtAfcBUwFkAWUB9AFTAW8BgAH+AwAB/wNdAckDIAEtUAADGQEjAlkBWgHAAgABpwH/AgABrwH/AgAB + swH/A00B+gNdAcwDMwFRAwIBA2wAAwIBAwMzAVEDXAHWA0YBfgMDAQSEAAMFAQYDDwETAwgBCgMCAQM4 + AAMMAQ8DRgF+A14B7QNgAeADSgGJAlABUQGcA10B3wNNAfoBQQFHAWoB+QNgAdsCTgFPAZcDTAGOA18B + 5QNaAekDQQFyAwkBC1AAAwwBEANLAYwCWgFnAfIDKwH8AlsBYQHkAlIBUwGlAy0BRQMGAQh0AAMIAQoD + QwF2A0cBggMIAQrQAAMFAQYDFwEfAyYBOAMwAUsDOQFfAz8BbAFCAkEBcwNDAXYDQAFvAzsBYwMzAVED + KAE7AxkBIwMJAQy4AAMFAQYDDQQRARYDDAEQAwQBBWwAAwUBBgMNBBEBFgMMARADBAEFbAADCQELAxsB + JQM1AVUDTgGYA1oBxwFlAlwB5wFqAWkBQQH5AeECAAH/AeoCAAH/Ae8CAAH/AfABgAEAAf8B8AGAAQAB + /wHwAYABAAH/Ae4CAAH/AekCAAH/AagCQAH9A14B7QNdAdEBUQJQAZ8DNQFVAxgBIAMCAQOoAAMMAQ8D + IAEuAyoBQAMkATUDEwEZAwQBBWgAAwwBDwMgAS4DKgFAAyQBNQMTARkDBAEFYAADAwEEAzsBZQNcAcsD + XQHqAZACAAH/AdsCAAH/AfICAAH/AfEBgAEAAf8B7gIAAf8B6wIAAf8B6gIAAf8B6QIAAf8B6QIAAf8B + 6QIAAf8B6QIAAf8B6QIAAf8B6gIAAf8B7AIAAf8B7wGAAQAB/wHvAgAB/wHUAgAB/wGVAgAB/wNgAeMD + WQG+AzkBXwQCoAADCwEOAQABigHMAf8BAAGHAcoB/wNeAfADJwE6AxQBGwMGAQcEAWAAAwsBDgOTAf8D + kAH/A14B8AMnAToDFAEbAwYBBwQBWAADQAFwAYwCAAH/AaoCAAH/AaQCAAH/Ab8CAAH/AeYCAAH/AeoC + AAH/AekCAAH/AekCAAH/AekBgAEAAf8B6gGBAQAB/wHqAYIBAAH/AeoBggEAAf8B6gGCAQAB/wHqAYIB + AAH/AeoBgQEAAf8B6gGBAQAB/wHpAgAB/wHpAgAB/wHrAYABAAH/Ad8CAAH/Aa8CAAH/AZwCAAH/AZkC + AAH/AYcCAAH/A0ABcaAAAwQBBQNWAbUBtwH7Av8BngHnAfwB/wNhAesDKQE+AxcBHwMGAQgEAVwAAwQB + BQNWAbUD9AH/A+UB/wNhAesDKQE+AxcBHwMGAQgEAVQAA00BkgGsAgAB/wGcAgAB/wGYAgAB/wHFAgAB + /wHwAYwBAAH/AesBhgEAAf8B6gGDAQAB/wHqAYIBAAH/AeoBgwEAAf8B6gGFAQAB/wHqAYUBAAH/AeoB + hgEAAf8B6gGGAQAB/wHqAYYBAAH/AeoBhQEAAf8B6gGEAQAB/wHqAYMBAAH/AeoBggEAAf8B6gGAAQAB + /wHtAYIBAAH/AbQCAAH/AY4CAAH/AY0CAAH/AbcCAAH/A0kBiAwAAwIBAwMJAQsDDQERAw8BEwMPARMD + DwETAw8BEwMPARMDDwETAw8BEwMPARMDDwETAw8BEwMPARMDDwETAw8BEwMPARMDDwETAw8BEwMPARMD + DwETAw8BEwMPARMDDwETAw8BEwMPARMDDwETAw8BEwMPARMDDQERAwkBCwMCAQMUAAQBAyEBLwEAAckB + 7AH/AbwB/AL/AZ0B5QH6Af8BAAGKAcwB/wM1AVYDGAEhAwgBCgQCWAAEAQMhAS8DywH/A/UB/wPjAf8D + kwH/AzUBVgMYASEDCAEKBAJQAAM2AVkBtgIAAf8BvAIAAf8BpAIAAf8B1QIAAf8B9QGgAQAB/wHwAZgB + AAH/Ae4BkgEAAf8B7AGLAQAB/wHqAYcBAAH/AesBiAEAAf8B6wGJAQAB/wHrAYkBAAH/AesBigEAAf8B + 6wGJAQAB/wHrAYkBAAH/AesBiAEAAf8B6wGIAQAB/wHqAYcBAAH/AeoBhgEAAf8B7wGHAQAB/wHFAgAB + /wGlAgAB/wHPAgAB/wHMAgAB/wMvAUkMAAMTARoDMQFOAz8BbQNBAXIDQQFyA0EBcgNBAXIDQQFyA0EB + cgNBAXIDQQFyA0EBcgNBAXIDQQFyA0EBcgNBAXIDQQFyA0EBcgNBAXIDQQFyA0EBcgNBAXIDQQFyA0EB + cgNBAXIDQQFyA0EBcgNBAXIDQQFyAz8BbQMxAU4DEwEaGAADCAEKA1wB+AG7AfoC/wGyAfYC/wGxAfQC + /wEAAZsB1QH/AzUBVgMbASYDCgENBAJYAAMIAQoDXAH4A/QB/wPxAf8D7wH/A6IB/wM1AVYDGwEmAwoB + DQQCTAADJgE5AaACAAH/AbwCAAH/AbgCAAH/AdwBgQEAAf8B8gGdAQAB/wHyAZwBAAH/AfIBmwEAAf8B + 8QGVAQAB/wHvAY4BAAH/Ae0BjQEAAf8B7QGPAQAB/wHtAY8BAAH/Ae0BjwEAAf8B7QGOAQAB/wHtAYwB + AAH/Ae0BiQEAAf8B7AGEAQAB/wHrAgAB/wHoAgAB/wHnAgAB/wHZAgAB/wHVAgAB/wHbAgAB/wNNAfoD + GwEmDAADLQFEA10B6gPzAf8D9AH/A/QB/wPzAf8D8gH/A/AB/wPuAf8D7QH/A+sB/wPqAf8D6AH/A+gB + /wPoAf8D6AH/A+gB/wPoAf8D6AH/A+gB/wPoAf8D6AH/A+gB/wPoAf8D6AH/A+gB/wPoAf8D6AH/A+gB + /wPoAf8DYwHpAy0BRBgABAIDRAF6AZYB2QHzAf8BsAHzAv8BrgHzAv8BtQHyAv8BAAGZAdQB/wNJAYYD + HQEoAwwBEAMCAQNUAAQCA0QBegPZAf8D7wH/A+8B/wPvAf8DoQH/A0kBhgMdASgDDAEQAwIBA0gAAxYB + HgNaAfUBqAIAAf8BrQIAAf8B0AIAAf8B4gGHAQAB/wHmAYoBAAH/AeoBiAEAAf8B7AGDAQAB/wHsAgAB + /wHsAgAB/wHtAgAB/wHuAgAB/wHuAgAB/wHtAgAB/wHrAgAB/wHpAgAB/wHmAgAB/wHjAgAB/wHeAgAB + /wHZAgAB/wHQAgAB/wHIAgAB/wHFAgAB/wFhAl4B4gMJAQsMAAM3AVoD8gH/A4oB/wOnAf8DpwH/A6gB + /wOoAf8DpwH/A6YB/wOlAf8DpAH/A6MB/wOiAf8DoQH/A6AB/wOgAf8DoAH/A6AB/wOgAf8DoAH/A6AB + /wOgAf8DoAH/A6AB/wOgAf8DoAH/A6AB/wOgAf8DoAH/A4UB/wPqAf8DNwFaHAADDAEPAQABrQHdAf8B + uwH3Av8BowHvAv8BrAHxAv8BvwH7Av8BAAGxAeAB/wNJAYYDIAEuAw8BEwMCAQNUAAMMAQ8DsgH/A/IB + /wPrAf8D7QH/A/UB/wO2Af8DSQGGAyABLgMPARMDAgEDRAADBwEJA10B3wGrAgAB/wG0AgAB/wG1AgAB + /wG9AgAB/wHGAgAB/wHNAgAB/wHUAgAB/wHZAgAB/wHdAgAB/wHgAgAB/wHhAgAB/wHhAgAB/wHfAgAB + /wHdAgAB/wHaAgAB/wHWAgAB/wHSAgAB/wHNAgAB/wHHAgAB/wG9AgAB/wG4AgAB/wGzAgAB/wNZAcEQ + AAM5AV4D8gH/A6gB/wPyAf8D8wH/A/QB/wP1Af8D9QH/A/QB/wPzAf8D8QH/A/AB/wPuAf8D7QH/A+sB + /wPqAf8D6gH/A+oB/wPqAf8D6gH/A+oB/wPqAf8D6gH/A+oB/wPqAf8D6gH/A+oB/wPqAf8D6gH/A6MB + /wPrAf8DOAFdHAADBgEHAVoCWwHEAbcB8AH+Af8BrAHxAv8BogHuAv8BqwHxAv8BwgH6Av8BAAGwAd8B + /wNWAbMDIwEyAxEBFgMEAQVQAAMGAQcDWwHEA+0B/wPtAf8D6gH/A+0B/wP1Af8DtQH/A1YBswMjATID + EQEWAwQBBUQAAVoCVwHCAc4BhwEAAf8B3wGiAQAB/wHBAgAB/wGxAgAB/wG0AgAB/wG5AgAB/wG+AgAB + /wHBAgAB/wHDAgAB/wHFAgAB/wHHAgAB/wHHAgAB/wHHAgAB/wHHAgAB/wHFAgAB/wHCAgAB/wG/AgAB + /wG7AgAB/wGwAgAB/wGmAgAB/wGnAgAB/wGhAgAB/wFQAk8BmxAAAzkBXgPxAf8DqQH/A/IB/wPyAf8D + 8wH/A/QB/wP0Af8D9QH/A/QB/wPzAf8D8gH/A+8B/wOvAf8DAAH/AwAB/wMAAf8DAAH/A6oB/wPoAf8D + 6gH/A+oB/wPqAf8D6gH/A+oB/wPqAf8D6gH/A+oB/wPqAf8DpQH/A+wB/wM4AV0cAAQBAzUBVgEAAcEB + 5wH/AbkB9gL/AaIB7QL/AaIB7QL/AakB8QL/AcMB+gL/AYsB0QHvAf8BVwJaAcIDJQE2AxIBGAMEAQVM + AAQBAzUBVgPFAf8D8QH/A+oB/wPqAf8D7QH/A/UB/wPSAf8DWgHCAyUBNgMSARgDBAEFQAADQwF3AcAB + gwEAAf8B/AHoAdgB/wHHAgAB/wGdAgAB/wGWAgAB/wGdAgAB/wGhAgAB/wGnAgAB/wGtAgAB/wGyAgAB + /wG0AgAB/wG0AgAB/wG1AgAB/wG0AgAB/wGyAgAB/wGwAgAB/wGrAgAB/wGjAgAB/wGZAgAB/wGSAgAB + /wGWAgAB/wGMAgAB/wM6AWIQAAM5AV4D8QH/A6sB/wPxAf8D8gH/A/MB/wPzAf8D9AH/A/QB/wPMAf8D + AAH/A7cB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/A7EB/wPrAf8D6wH/A+sB/wPrAf8D + 6wH/A+sB/wPrAf8D6wH/A6cB/wPsAf8DOAFdIAADCQEMAQABlwHTAf8ByQH9Av8BpQHvAv8BogHtAv8B + ogHtAv8BqQHwAv8BwAH5Av8BjwHRAe4B/wFbAmEB3gMmATkDEwEaAwUBBgQBSAADCQEMA6AB/wP3Af8D + 6wH/A+oB/wPqAf8D7AH/A/QB/wPSAf8DYQHeAyYBOQMTARoDBQEGBAE4AAMJAQsBUgJRAaQBnQIAAf8B + nAIAAf8BhwIAAf8BhQIAAf8BigIAAf8BkgIAAf8BngIAAf8BqQIAAf8BtQIAAf8BxgIAAf8ByQIAAf8B + tAIAAf8BpwIAAf8BogIAAf8BnQIAAf8BlwIAAf8BkAIAAf8BigIAAf8BhAIAAf8BgAIAAf8DUwGnAwkB + CxAAAzgBXQPxAf8DrAH/A/EB/wPyAf8D8gH/A/MB/wPzAf8D9AH/AwAB/wMAAf8DAAH/AwAB/wPEAf8D + 8wH/A/IB/wPxAf8D8AH/A8AB/wMAAf8DAAH/A5IB/wPsAf8D7AH/A+wB/wPsAf8D7AH/A+wB/wPsAf8D + qQH/A+0B/wM4AV0gAAMCAQMBUAJRAZ8BpwHcAfQB/wGzAfMC/wGiAe0C/wGjAe0C/wGjAe0C/wGlAe8C + /wHDAfgC/wGvAegB+gH/AV0CZQHsAygBOwMWAR4DBgEIBAFEAAMCAQMDUQGfA90B/wPvAf8D6gH/A+oB + /wPqAf8D6wH/A/QB/wPnAf8DZQHsAygBOwMWAR4DBgEIBAE8AAMtAUUBVwJVAbQDQAH9AwAB/wMAAf8B + iwIAAf8BnwIAAf8BuwIAAf8B1wIAAf8B4QIAAf8B4gIAAf8B2AIAAf8BswIAAf8BmQIAAf8BjQIAAf8B + hwIAAf8BgwIAAf8DQAH9A1YBtQMuAUgYAAM4AV0D8AH/A64B/wPwAf8D8QH/A/IB/wPyAf8D8wH/A/MB + /wO3Af8DAAH/AwAB/wMAAf8D8wH/A/QB/wP0Af8D8wH/A/IB/wPwAf8D7QH/AwAB/wMAAf8DsgH/A+wB + /wPsAf8D7AH/A+wB/wPsAf8D7AH/A6sB/wPuAf8DOAFdJAADEwEZAQABrQHdAf8ByAH6Av8BoQHuAv8B + ogHtAv8BogHtAv8BogHtAv8BpAHuAv8BvwH3Av8BswHpAfkB/wNdAeoDNQFWAxgBIAMIAQoEAUQAAxMB + GQOzAf8D9gH/A+oB/wPqAf8D6gH/A+oB/wPqAf8D8wH/A+cB/wNdAeoDNQFWAxgBIAMIAQoEAUAAAyQB + NANQAZoDYQHrAY4CAAH/AaoCAAH/Ab0CAAH/AcECAAH/AcICAAH/AcMCAAH/AcQCAAH/AcACAAH/AaEC + AAH/AYUCAAH/A14B7QFRAlABnAMkATQgAAM5AV4D8AH/A68B/wPwAf8D8QH/A/EB/wPyAf8D8gH/A/EB + /wMAAf8DAAH/AwAB/wMAAf8DAAH/A/MB/wP1Af8D9AH/A/MB/wPzAf8D8QH/A+8B/wMAAf8DAAH/A+sB + /wPtAf8D7QH/A+0B/wPtAf8D7QH/A64B/wPvAf8DOAFdEAADBQEGAw0EEQEWAxEBFgMRBBYBHgNhAeYB + wQHvAfwB/wGlAe4C/wGdAesB/gH/AZ4B6wH+Af8BngHrAf4B/wGdAesB/gH/AZ0B7AH+Af8BugH0Av8B + xQH1Av8BAAGgAdgB/wM1AVYDGgEkAwoBDQQCLAADBQEGAw0EEQEWAxEBFgMRBBYBHgNhAeYD7QH/A+sB + /wPoAf8D6AH/A+gB/wPoAf8D6AH/A/AB/wPyAf8DqAH/AzUBVgMaASQDCgENBAJEAAMRARYDOAFdAWEC + WwHkAZYCAAH/AZoCAAH/AZwCAAH/AZ0CAAH/AZgCAAH/AZsCAAH/AWECXQHcAzoBYQMTARkoAAM5AV4D + 8AH/A7EB/wPwAf8D8AH/A/EB/wPyAf8D8gH/A7EB/wMAAf8DxAH/A/IB/wMAAf8DAAH/AwAB/wPzAf8D + 9QH/A/UB/wP0Af8D8wH/A/IB/wPCAf8DAAH/A64B/wPuAf8D7gH/A+4B/wPuAf8D7gH/A7AB/wPvAf8D + OAFdEAADDQERAyMBMgMsAUMDLAFDAywBQwMtAUQDRQF8AZMByQHqAf8BuQHzAv8BmAHqAf4B/wGaAeoB + /gH/AZoB6gH+Af8BmgHqAf4B/wGZAeoB/gH/AZgB6gH+Af8BrgHxAv8BxwH1Av8BAAGgAdgB/wNDAXcD + HAEnAwsBDgQCKAADDQERAyMBMgMsAUMDLAFDAywBQwMtAUQDRQF8A8wB/wPwAf8D5gH/A+cB/wPnAf8D + 5wH/A+cB/wPmAf8D7QH/A/IB/wOoAf8DQwF3AxwBJwMLAQ4EAkgAA0wBjgMAAf8DAAH/AZkCAAH/AbEC + AAH/AaUCAAH/AwAB/wNJAYgwAAM5AV4D8AH/A7IB/wPvAf8D8AH/A/EB/wPxAf8D8gH/AwAB/wMAAf8D + 9AH/A/QB/wPyAf8DAAH/AwAB/wMAAf8D8wH/A/UB/wP1Af8D9AH/A/QB/wPzAf8DAAH/AwAB/wPwAf8D + 7wH/A+4B/wPuAf8D7gH/A7IB/wPwAf8DOAFdEAADEQEWAQABkQHRAf8BAAGNAdAB/wEAAYsBzwH/AQAB + iwHPAf8BAAGLAc4B/wEAAYgBzQH/AQABlwHUAf8BygH3Av8BkwHoAf0B/wGVAegB/QH/AZUB6AH9Af8B + lQHoAf0B/wGVAegB/QH/AZQB6AH9Af8BkwHoAf0B/wGoAe4C/wHRAfwC/wEAAbUB4gH/A0kBhgMeASsD + DQERAwMBBCQAAxEBFgObAf8DlwH/A5YB/wOWAf8DlQH/A5MB/wOhAf8D9AH/A+QB/wPlAf8D5QH/A+UB + /wPlAf8D5QH/A+QB/wPrAf8D+AH/A7oB/wNJAYYDHgErAw0BEQMDAQREAANJAYYDAAH/AYACAAH/AZcC + AAH/AbICAAH/AdUCAAH/AbQCAAH/A0sBjDAAAzkBXgPzAf8DxQH/A/QB/wP2Af8D9wH/A/gB/wP5Af8D + twH/A7UB/wP6Af8D+gH/A/sB/wP6Af8DxQH/A5QB/wO+Af8D+QH/A/kB/wP5Af8D+AH/A/YB/wMAAf8D + AAH/A/IB/wPxAf8D8AH/A+8B/wPvAf8DtAH/A/EB/wM4AV0QAAMPARQBAAGPAdEB/wHZA/8BpQHzAv8B + mAHuAv8BmgHvAv8BmQHuAv8BlQHrAv8BkQHoAf0B/wGQAeYB/AH/AZEB5gH8Af8BkQHmAfwB/wGRAeYB + /AH/AZEB5gH8Af8BkQHmAfwB/wGRAeYB/AH/AY8B5gH8Af8BngHrAv8BzQH6Av8BAAG0AeEB/wNOAZUD + IgExAw8BFAMDAQQgAAMPARQDmQH/A/oB/wPuAf8D6QH/A+oB/wPpAf8D5wH/A+QB/wPjAf8D4wH/A+MB + /wPjAf8D4wH/A+MB/wPjAf8D4wH/A+gB/wP2Af8DuQH/A04BlQMiATEDDwEUAwMBBEAAA1EBogMAAf8B + jgIAAf8BnQIAAf8BuQIAAf8B1QIAAf8B7AIAAf8DVQGuMAADOQFeA/cB/wPWAf8D9gH/A/cB/wP3Af8D + 9wH/A/gB/wOnAf8DpAH/A/kB/wP5Af8D+gH/A/oB/wP5Af8DtgH/A44B/wO3Af8D+gH/A/sB/wP7Af8D + +wH/A8AB/wO1Af8D+AH/A/YB/wPzAf8D8QH/A/EB/wO3Af8D8QH/AzgBXRAAAwwBEAFIAmIB9gG/Ae4B + /AH/AasB7gH+Af8BigHmAfwB/wGNAecB/AH/AY0B5wH8Af8BjAHmAfsB/wGMAeUB+wH/AYwB5QH7Af8B + jAHlAfsB/wGMAeUB+wH/AYsB5QH7Af8BiwHlAfsB/wGLAeUB+wH/AYsB5QH7Af8BigHlAfsB/wGJAeUB + +wH/AZQB6QH8Af8BzAH4Av8BmAHSAfAB/wFXAloBwgMgAS4DCgENIAADDAEQA2IB9gPtAf8D6wH/A+IB + /wPjAf8D4wH/A+IB/wPhAf8D4QH/A+EB/wPhAf8D4QH/A+EB/wPhAf8D4QH/A+EB/wPhAf8D5QH/A/UB + /wPUAf8DWgHCAyABLgMKAQ08AAMTARkDYQHmAZICAAH/AZECAAH/AaECAAH/AbwCAAH/AdgCAAH/AeYC + AAH/A1oB9QMkATQsAAM5AV4D9wH/A9MB/wP2Af8D9gH/A/YB/wP2Af8D9wH/A7IB/wOIAf8D+AH/A/gB + /wP5Af8D+QH/A/kB/wP5Af8DsQH/A4UB/wOyAf8D+gH/A/sB/wP7Af8DnwH/A8EB/wP6Af8D+gH/A/sB + /wP5Af8D9gH/A7oB/wPyAf8DOAFdEAADCQELA1sBxQGiAdgB8gH/AbcB8QH+Af8BhAHjAfoB/wGIAeMB + +gH/AYgB4wH6Af8BiAHjAfoB/wGIAeMB+gH/AYgB4wH6Af8BhwHjAfoB/wGFAeMB+gH/AYQB4wH6Af8B + gwHjAfoB/wGDAeMB+gH/AYMB4wH6Af8BgwHjAfoB/wGDAeMB+gH/AYIB4wH6Af8BiAHlAf0B/wHAAfYC + /wGYAdMB7wH/A1kBwQMQARUgAAMJAQsDWwHFA9kB/wPuAf8D3wH/A98B/wPfAf8D3wH/A98B/wPfAf8D + 3wH/A98B/wPfAf8D3wH/A98B/wPfAf8D3wH/A98B/wPfAf8D4QH/A/IB/wPUAf8DWQHBAxABFTwAAVEC + UAGcAZkCAAH/AY0CAAH/AY0CAAH/AZ8CAAH/AboCAAH/AdcCAAH/AeYCAAH/AekCAAH/AloBWAG9AwIB + AygAAzkBXgP3Af8D0QH/A/YB/wP2Af8D9gH/A/YB/wP2Af8D0QH/AwAB/wPeAf8D+AH/A/gB/wP5Af8D + +QH/A/kB/wP4Af8DqwH/AwAB/wOsAf8D+QH/A+QB/wOFAf8D2wH/A/oB/wP6Af8D+gH/A/oB/wP6Af8D + 5QH/A/kB/wM4AV0QAAMEAQUBTAJNAZEBhQHEAekB/wHHAfQC/wEAAeAB+gH/AYIB4QH6Af8BgwHhAfoB + /wGDAeEB+gH/AYMB4QH6Af8BgwHhAfoB/wGAAeAB+gH/AaAB6QH8Af8B0gH4Av8B0wH6Av8B1AH7Av8B + 1AH7Av8B1AH7Av8B1AH7Av8B0wH7Av8B0gH7Av8B1QH8Av8B6wP/AQABrAHfAf8DDQERIAADBAEFA00B + kQPIAf8D8gH/A90B/wPeAf8D3gH/A94B/wPeAf8D3gH/A90B/wPmAf8D9QH/A/cB/wP3Af8D9wH/A/cB + /wP3Af8D9wH/A/cB/wP4Af8D/AH/A7MB/wMNARE4AAMTARoBZQJTAfQDAAH/AwAB/wGAAgAB/wGZAgAB + /wG0AgAB/wHQAgAB/wHkAgAB/wHnAgAB/wKAAW8B/gMfASwoAAM5AV4D9wH/A9AB/wP2Af8D9gH/A/YB + /wP2Af8D9gH/A/UB/wMAAf8DjAH/A/YB/wP4Af8D+AH/A/gB/wP5Af8D+QH/A/gB/wOnAf8DAAH/A6gB + /wOcAf8DkAH/A/kB/wP6Af8D+gH/A/oB/wP6Af8D+gH/A98B/wP6Af8DOAFdEAAEAgMtAUUBAAGpAd0B + /wHUAfkC/wEAAeEB+QH/AQAB4AH5Af8BAAHgAfkB/wEAAeAB+QH/AQAB4AH5Af8BAAHgAfkB/wEAAeAB + +QH/AdAB+AL/AQABjQHRAf8BAAGLAdAB/wEAAY4B0QH/AQABjgHRAf8BAAGOAdEB/wEAAY4B0QH/AQAB + jgHRAf8BAAGOAdEB/wEAAY4B0QH/AQABkAHSAf8BAAGTAdMB/wMFAQYgAAQCAy0BRQOwAf8D9gH/A90B + /wPcAf8D3AH/A9wB/wPcAf8D3AH/A9wB/wP1Af8DmAH/A5YB/wOYAf8DmAH/A5gB/wOYAf8DmAH/A5gB + /wOYAf8DmgH/A50B/wMFAQY4AAMwAUsDAAH/AwAB/wMAAf8DAAH/AZECAAH/AasCAAH/AcQCAAH/AdsC + AAH/AeYCAAH/AecCAAH/AzYBVygAAzkBXgP2Af8DzgH/A/UB/wP1Af8D9QH/A/YB/wP2Af8D9gH/A9EB + /wMAAf8DogH/A/YB/wP3Af8D+AH/A/gB/wP4Af8D+AH/A/gB/wOiAf8DAAH/AwAB/wPXAf8D+gH/A/oB + /wP6Af8D+gH/A/oB/wP6Af8D3QH/A/sB/wM4AV0UAAMYASEBAAGWAdUB/wHYAfoC/wGKAeIB+QH/AQAB + 3QH4Af8BAAHeAfgB/wEAAd4B+AH/AQAB3gH4Af8BAAHeAfgB/wEAAd0B+AH/AdAB9gL/AZUBzAHrAf8B + UQJSAaEDGQEjAwYBB0QAAxgBIQOfAf8D9wH/A98B/wPZAf8D2gH/A9oB/wPaAf8D2gH/A9kB/wP0Af8D + zwH/A1IBoQMZASMDBgEHWAADRgF9AwAB/wMAAf8DAAH/AwAB/wGFAgAB/wGeAgAB/wG1AgAB/wHKAgAB + /wHcAgAB/wHkAgAB/wNLAYwoAAM5AV4D9gH/A80B/wP1Af8D9QH/A/UB/wP1Af8D9gH/A/YB/wP2Af8D + uwH/AwAB/wOEAf8D2gH/A/cB/wP3Af8D+AH/A/gB/wPcAf8DjQH/AwAB/wMAAf8DsgH/A/oB/wP6Af8D + +gH/A/oB/wP6Af8D+gH/A9wB/wP6Af8DOQFeFAADDQERAQABkAHTAf8BxQHvAfoB/wGiAeoB+wH/AQAB + 2wH3Af8BAAHcAfcB/wEAAdwB9wH/AQAB3AH3Af8BAAHcAfcB/wEAAdsB9wH/AYIB4AH4Af8B4gH9Av8B + AAGqAd4B/wM5AV8DEwEaAwMBBEAAAw0BEQObAf8D7QH/A+cB/wPXAf8D2AH/A9gB/wPYAf8D2AH/A9cB + /wPcAf8D+gH/A7EB/wM5AV8DEwEaAwMBBFQAA1UBrAMAAf8DAAH/AwAB/wMAAf8DAAH/AY0CAAH/AaIC + AAH/AbQCAAH/AcUCAAH/AdICAAH/A1oBvygAAzkBXgP2Af8DywH/A/UB/wP1Af8D9gH/A/YB/wP2Af8D + 9gH/A/YB/wP2Af8DzwH/AwAB/wMAAf8DAAH/A4cB/wOIAf8DAAH/AwAB/wMAAf8D1AH/A68B/wPgAf8D + +QH/A/kB/wP5Af8D+gH/A/oB/wP6Af8D2gH/A/sB/wM5AV4UAAMJAQwBXAJhAdYBqgHaAfIB/wG2Ae8B + /AH/AQAB2AH3Af8BAAHaAfcB/wEAAdoB9wH/AQAB2gH3Af8BAAHaAfcB/wEAAdoB9wH/AQAB2QH3Af8B + pAHpAfsB/wHaAfgC/wEAAZcB1QH/AyQBNAMNAREEAjwAAwkBDANhAdYD2wH/A+wB/wPVAf8D1wH/A9cB + /wPXAf8D1wH/A9cB/wPVAf8D5gH/A/YB/wOgAf8DJAE0Aw0BEQQCTAADAwEEA14B0gMAAf8DAAH/AwAB + /wMAAf8DAAH/AwAB/wGNAgAB/wGeAgAB/wGsAgAB/wG2AgAB/wFhAlsB3gMGAQgkAAM5AV4D9wH/A8sB + /wP1Af8D9QH/A/UB/wP1Af8D9QH/A/YB/wP2Af8D9gH/A/YB/wP1Af8DygH/A50B/wOIAf8DigH/A6AB + /wPNAf8D9wH/A/gB/wP4Af8D+AH/A/kB/wP5Af8D+QH/A/kB/wP5Af8D+QH/A9kB/wP6Af8DOQFeFAAD + BgEHAUwCTQGRAY4BxwHqAf8BzwH3Af4B/wEAAdcB9gH/AQAB2QH2Af8BAAHZAfYB/wEAAdkB9gH/AQAB + 2QH2Af8BAAHZAfYB/wEAAdkB9gH/AQAB1wH2Af8BzAH2Af4B/wGxAd0B8gH/AV0CXwHOAx0BKAMIAQo8 + AAMGAQcDTQGRA8sB/wP0Af8D0wH/A9UB/wPVAf8D1QH/A9UB/wPVAf8D1QH/A9MB/wPzAf8D3gH/A18B + zgMdASgDCAEKTAADCQEMA2AB4wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8BhQIAAf8BkgIAAf8B + mgIAAf8BYwJaAekDDAEQJAADOAFdA/YB/wPKAf8D9QH/A/UB/wP1Af8D9QH/A/YB/wP2Af8D9gH/A/YB + /wP2Af8D9gH/A/YB/wP2Af8D9gH/A/YB/wP2Af8D9wH/A/cB/wP3Af8D+AH/A/gB/wP5Af8D+QH/A/kB + /wP5Af8D+gH/A/oB/wPYAf8D+wH/AzkBXxQAAwIBAwM/AW0BAAGrAd8B/wHjAfwB/gH/AQAB1wH1Af8B + AAHWAfUB/wEAAdcB9QH/AQAB1wH1Af8BAAHXAfUB/wEAAdcB9QH/AQAB1wH1Af8BAAHWAfUB/wEAAdkB + 9gH/AeAB+wL/AQABuQHlAf8BRwJIAYMDFQEdAwQBBTgAAwIBAwM/AW0DswH/A/kB/wPTAf8D0gH/A9MB + /wPTAf8D0wH/A9MB/wPTAf8D0gH/A9YB/wP5Af8DvwH/A0gBgwMVAR0DBAEFSAADBQEGAWECXwHaAYsC + AAH/AYYCAAH/AYICAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AYgCAAH/AWECWwHeAwYBCCQAAzUB + VgP3Af8DuwH/A8sB/wPLAf8DywH/A8sB/wPLAf8DywH/A8wB/wPMAf8DzQH/A80B/wPOAf8DzgH/A84B + /wPPAf8DzwH/A88B/wPQAf8D0QH/A9IB/wPSAf8D0wH/A9QB/wPUAf8D1AH/A9UB/wPVAf8DyQH/A/oB + /wM2AVgUAAQBAyMBMgEAAZkB2AH/AecB/gL/AQAB3QH2Af8BAAHTAfQB/wEAAdUB9AH/AQAB1QH0Af8B + AAHVAfQB/wEAAdUB9AH/AQAB1QH0Af8BAAHVAfQB/wEAAdMB9AH/AZcB5QH5Af8B4gH5Af4B/wEAAaAB + 2gH/AykBPgMPARQDAgEDNAAEAQMjATIDowH/A/sB/wPZAf8D0AH/A9EB/wPRAf8D0QH/A9EB/wPRAf8D + 0QH/A88B/wPiAf8D9wH/A6kB/wMpAT4DDwEUAwIBA0gAA1YBsgGtAgAB/wG3AgAB/wG7AgAB/wGfAgAB + /wMAAf8DAAH/AwAB/wMAAf8DAAH/AZICAAH/AVcCVQG0KAADJQE2A2UB5wP4Af8D+AH/A/gB/wP4Af8D + +AH/A/gB/wP4Af8D+AH/A/gB/wP4Af8D+AH/A/gB/wP4Af8D+AH/A/gB/wP4Af8D+AH/A/gB/wP4Af8D + +QH/A/kB/wP5Af8D+QH/A/kB/wP6Af8D+gH/A/oB/wP6Af8DZQHnAyUBNxgAAw4BEgEAAZMB1gH/AdUB + 8QH7Af8BnQHnAfkB/wEAAdIB8wH/AQAB1AHzAf8BAAHUAfMB/wEAAdQB8wH/AQAB1AHzAf8BAAHUAfMB + /wEAAdQB8wH/AQAB1AHzAf8BAAHSAfMB/wG+AfAB+wH/AcQB5QH2Af8DXgHdAx8BLAMKAQ0EATQAAw4B + EgOeAf8D8AH/A+QB/wPOAf8D0AH/A9AB/wPQAf8D0AH/A9AB/wPQAf8DzwH/A84B/wPtAf8D5gH/A14B + 3QMfASwDCgENBAFEAAM2AVcBrQIAAf8B2QGkAYYB/wH7AeoB3QH/AdIBlQEAAf8BnwIAAf8DAAH/AwAB + /wMAAf8BiQIAAf8BogIAAf8DNgFXKAADDAEPAyEBLwMtAUQDLgFIAy4BSAMuAUgDLgFIAy4BSAMuAUgD + LgFIAy4BSAMuAUgDLgFIAy4BSAMuAUgDLgFIAy4BSAMuAUgDLgFIAy4BSAMuAUgDLgFIAy4BSAMuAUgD + LgFIAy4BSAMuAUgDLgFIAy4BSAMtAUQDIQEvAwwBDxgAAwsBDgNfAdUBuAHeAfMB/wG6Ae4B+wH/AQAB + zwHzAf8BAAHRAfMB/wEAAdEB8wH/AQAB0QHzAf8BAAHRAfMB/wEAAdEB8wH/AQAB0QHzAf8BAAHRAfMB + /wEAAdAB8wH/AQAB0QHyAf8B5wH8Av8BmQHJAesB/wNRAaIDGAEgAwUBBjQAAwsBDgNfAdUD4AH/A+wB + /wPLAf8DzQH/A80B/wPNAf8DzQH/A80B/wPNAf8DzQH/A8wB/wPNAf8D+gH/A80B/wNRAaIDGAEgAwUB + BkQAAwMBBAFZAlcBuQHAAgAB/wHnAcoBvAH/AdsBqgGPAf8BqwIAAf8BgQIAAf8DAAH/AYECAAH/AZ0C + AAH/A1gBugMDAQQoAAQBAwIBAwMFAQYDBQEGAwUBBgMFAQYDBQEGAwUBBgMFAQYDBQEGAwUBBgMFAQYD + BQEGAwUBBgMFAQYDBQEGAwUBBgMFAQYDBQEGAwUBBgMFAQYDBQEGAwUBBgMFAQYDBQEGAwUBBgMFAQYD + BQEGAwUBBgMFAQYDAgEDBAEYAAMIAQoDVgG1AZsBywHsAf8B3AH6Af4B/wEAAcwB8QH/AQABzQHxAf8B + AAHNAfEB/wEAAc0B8QH/AQABzQHxAf8BAAHNAfEB/wEAAc0B8QH/AQABzQHxAf8BAAHNAfEB/wEAAcwB + 8QH/AQAB2wH1Af8B8gP/AQABqAHeAf8DMQFOAwwBEDQAAwgBCgNWAbUDzwH/A/cB/wPHAf8DyQH/A8kB + /wPJAf8DyQH/A8kB/wPJAf8DyQH/A8kB/wPHAf8D1wH/A/0B/wOwAf8DMQFOAwwBEEgAAw4BEgNVAa8B + nQIAAf8BogIAAf8BkwIAAf8DAAH/AwAB/wGCAgAB/wNWAbUDDwEUxAADBAEFA0YBfQEAAbEB4gH/AfcD + /wH2A/8B+AP/AfgD/wH4A/8B+AP/AfgD/wH4A/8B+AP/AfgD/wH3A/8B8wP/AfoD/wHZAfQB/AH/A14B + 7QMMARA0AAMEAQUDRgF9A7gB/wP+Af8D/gH/A/4B/wP+Af8D/gH/A/4B/wP+Af8D/gH/A/4B/wP+Af8D + /gH/A/0B/wP+Af8D8wH/A14B7QMMARBQAAMtAUQDSwGMA1oBvwFbAloBxANOAZQDMAFLzAAEAQMqAUAB + AAGbAdoB/wEAAZcB2AH/AQABlgHYAf8BAAGVAdgB/wEAAZUB2AH/AQABlQHYAf8BAAGVAdgB/wEAAZUB + 2AH/AQABlQHYAf8BAAGVAdgB/wEAAZUB2AH/AQABlQHYAf8BAAGVAdgB/wEAAZYB2AH/AQABmQHZAf8B + AAGcAdoB/wMFAQY0AAQBAyoBQAOlAf8DoQH/A6EB/wOgAf8DoAH/A6AB/wOgAf8DoAH/A6AB/wOgAf8D + oAH/A6AB/wOgAf8DoQH/A6MB/wOmAf8DBQEG/wDFAAMhATADRwGAA1oBvwP2Af8D9gH/A/YB/wP2Af8D + 9gH/A/YB/wNaAb8DRwGAAyEBMP8AyQADOgFgA10BzwP2Af8D9gH/A/YB/wP2Af8D9gH/A/YB/wP2Af8D + 9gH/A/YB/wP2Af8D9gH/A/YB/wNdAc8DMwFQ/wC5AAMYASADWgG/A/YB/wP2Af8B5wHoAfUB/wGKAZMB + 7wH/AgAB6gH/AgAB5gH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5gH/AgAB6gH/AYoBkwHvAf8B + 5wHoAfUB/wP2Af8D9gH/A1oBvwMYASBIAAMwAUsDUQGeAw8BEwgAAzsBYwNOAZUDAwEE/wBFAAMzAVAD + YgHvA/YB/wHnAegB9QH/AgAB6wH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB + 5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB6wH/AecB6AH1Af8D9gH/A2IB7wMzAVBE + AANZAcMDUgGlA0MBdgQAAwgBCgNgAeADWgG/AyQBNVgAAx4BKwMzAVEDIQEvA1IBpQMrAUEoAAM5BF4B + 3QM+AWqgAAM6AWAD9gH/A/YB/wGpAa8B8QH/AgAB5gH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB + 5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB + 5gH/AakBrwHxAf8D9gH/A/YB/wM6AWBAAANZAb4DQAFxA04BlwQAAxwBJwNYAb0DVgGzAyEBMFgAA10B + 3AMyAU8DVQGtA0MBdgNbAcooAANaAcQDJAE1A18B2pwAAzMBUAP2Af8D9gH/AgAB7AH/AgAB5QH/AgAB + 5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB + 5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB7AH/A/YB/wP2Af8DMwFQPAAD + VgGyAzoBYQNWAbUEAAMtAUQDUgGgA1kBwAMZASNYAANiAeEDAwEEA10BzAMeASoDXQHRKAADWwHFAxYB + HgNiAeEDAwEEJAADKQE+Az0BZwMNAREsAAMaASQDPgFrAxwBJwQAAxoBJAM+AWsDHAEnHAADGAEgA2IB + 7wP2Af8CAAHsAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8C + AAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8C + AAHlAf8CAAHlAf8CAAHsAf8D9gH/A2IB7wMYASA4AANSAaUDMwFQA14B0gQAAzoBYgNHAYIDWwHNAxEB + FlgAA1oBxwMVAR0DXgHiAw8BFANdAdEoAANbAcUDFQEdA1kBxgMWAR4gAAMuAUcDWwHNA0oBiQNcAcgo + AAMPARQDXQHcA0YBfgNdAdwDIAEuA10B3ANGAX4DXQHcAxMBGhgAA1oBvwP2Af8BqQGvAfEB/wIAAeUB + /wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB/wIAAe8B/wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB + /wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB/wIAAe8B/wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB + /wIAAeUB/wGpAa8B8QH/A/YB/wNaAb84AANOAZgDMAFMA2AB4wQAA0YBfwM7AWUDXAHZAwgBClgAA1UB + rwMnAToDWwHeAw4BEgNdAdEoAANZAcYDFQEdA1MBpgMpAT4QAAMGAQcDQgF0A0YBgANEAXgDRgF+A0AB + cAMCAQMDWwHkAxUEHQEpAxcBHwMiATEDIgExAyIBMQMiATEDIgExAyIBMQMiATEDLQFEA1YBsgQAA1MB + qQNGAX0DVgGyBAADUwGpAzEBTQMiATEDHQEoDAADOgFgA/YB/wHnAegB9QH/AgAB5gH/AgAB5QH/AgAB + 5QH/AgAB5QH/AgAB5QH/AZ8BpwH1Bf8BnwGnAfUB/wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB + /wIAAeUB/wIAAeUB/wIAAeUB/wGfAacB9QX/AZ8BpwH1Af8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8C + AAHmAf8B5wHoAfUB/wP2Af8DMwFQNAADSgGLAzYBWANcAdYDCwEOA1ABnQMuAUgDXgHiBAFYAANRAaID + OwFkA1cBwgMOARIDXQHRKAADWQHGAxUBHANJAYYDOQFeEAADLQFGA1kBwAM6AWIDOAFbAxkBIgNcAdkD + XAHZA0oBigM4AVwDSQGHA1wBywNYAbcDVQGxA1UBsQNVAbEDVQGxA1UBsQNVAbEDKQE9A1cBvANbAdMD + VwHCAwkBDANXAbwDWwHTA1kBwwMpAT4DVQGxA10B3AMrAUIIAANdAc8D9gH/AgAB6wH/AgAB5QH/AgAB + 5QH/AgAB5QH/AgAB5QH/AZ8BpwH1Df8BnwGnAfUB/wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB + /wIAAeUB/wGfAacB9Q3/AZ8BpwH1Af8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHrAf8D9gH/A10B + zzQAA0YBfwM7AWQDVwG5Ax4BKwNYAboDHgEqA2AB41wAA1IBoANLAY0DUAGfAw4BEgNdAdEMAAMIAQoE + AhAAAxMBGgNdAdwDBgEHA1EBogM1AVYQAAMwAUwDTgGWDAADBAEFAw8BEwQAAzgBXANJAYcDWwHNAxEB + FhwABAEDEQEWBAEEAAQBAxEBFgQBCAADTAGOAzUBVQQAAyEBMAP2Af8B5wHoAfUB/wIAAeUB/wIAAeUB + /wIAAeUB/wIAAeUB/wIAAe8V/wGfAacB9QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5QH/AZ8BpwH1Ff8C + AAHvAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8B5wHoAfUB/wP2Af8DIQEwMAADQQFyA0ABcQNPAZsD + LwFJA14B1wMNAREDXQHfXAADUwGmA14B7QMtAUQDDQERA14B0gNGAX8DXQHMA10B1ANfAdoDYAHbA18B + 1QNXAbkDFwEfAwIBAwNVAawDOwFjAxQBGwNcAdYDBQEGAykBPQwAAzMBUANNAZMYAAM4AVwDSQGHA1sB + zQMRARZAAANMAY4DNQFVBAADRwGAA/YB/wGKAZMB7wH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB + 5QH/AZ8BpwH1Ff8BnwGnAfUB/wIAAeUB/wIAAeUB/wGfAacB9RX/AZ8BpwH1Af8CAAHlAf8CAAHlAf8C + AAHlAf8CAAHlAf8CAAHlAf8BigGTAe8B/wP2Af8DRwGAMAADOwFlA0YBfgNGAX4DPQFnA1sB5AMKAQ0D + XAHWVAADIAEtA18B1QNdAc8DMwFSBAADEAEVA10BzgMTARkDFQEdAxMBGgQAAxsBJQMRARYDKwFBAykB + PgNcAcsDTgGWBAEDNAFTA00BkgMJAQsDXAHZDAADLQFFA1IBqBgAAzgBXANJAYcDWwHNAxEBFkAAA0wB + jgM1AVUEAANaAb8D9gH/AgAB6gH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5QH/AZ8B + pwH1Ff8BnwGnAfUB/wGfAacB9RX/AZ8BpwH1Af8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8C + AAHlAf8CAAHqAf8D9gH/A1oBvywAA1ABnQNEAXgDSgGKAzoBYQNOAZcDXQHRAxMBGgNdAckDUwGmAxIB + F0wAA1EBogMzAVIMAAMlATcDVgGyHAADRAF4AzMBUggAAz8BbANGAX0EAANeAeIEAggAAwMBBANbAdgD + EgEXAzsBZQNTAaoDUwGqAy4BRwQAAzgBXANJAYcDWwHNAxEBFkAAA0wBjgM1AVUEAAP2Af8D9gH/AgAB + 5gH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5QH/AZ8BpwH1Kf8BnwGnAfUB + /wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB/wIAAeYB/wP2Af8D9gH/LAAD + VQGuA0YBgANOAZcDKgFAA10B3ANWAbADHAEnA1cBvANcAcsDEgEYSAADMQFOA10BzwMTARkMAAMkATQD + LQFELAADGwElA18B2gMEAQUDWwHQAw8BFAwAA0kBiAM7AWUDFQEcAyYBOQNKAYoDRAF7BAADOAFcA0kB + hwNbAc0DEQEWQAADTAGOAzUBVQQAA/YB/wP2Af8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8C + AAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8BnwGnAfUh/wGfAacB9QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB + 5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5QH/A/YB/wP2Af8sAANSAaMDRgF+A1EBpAMEAQUD + UgGgAykBPgMkATQDVQGvA1wB1gMKAQ1IAANQAZoDMwFRSAADVgGzAyQBNANZAbsDHQEoDAADKAE7A1UB + sQgAAz0BaANEAXsEAAM4AVwDSQGHA1sBzQMRARZAAANMAY4DNQFVBAAD9gH/A/YB/wIAAeUB/wIAAeUB + /wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB/wGfAacB9Rn/AZ8B + pwH1Af8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8C + AAHlAf8D9gH/A/YB/ywAA08BmQNFAXwDVQGxDAADKgFAA1IBowNdAd8DAwEERAADPgFqA10B0QMgAS5I + AANVAa8DJQE3A1UBrgMkATUMAAMCAQMDYgHhAwcBCQQAAzEBTgM5AV4EAAM4AVwDSQGHA1sBzQMRARZA + AANMAY4DNQFVBAAD9gH/A/YB/wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB + /wIAAeUB/wIAAeUB/wIAAeUB/wGfAacB9Rn/AZ8BpwH1Af8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8C + AAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8D9gH/A/YB/ywAA0wBjgNEAXoDWQG+DAAD + MQFNA04BlgNgAeNIAANgAdsDDQERTAADWQHBAyABLQNVAawDJQE2EAADSgGJA04BmAMEAQUMAAM4AVwD + SQGHA1sBzQMRARZAAANMAY4DNQFVBAAD9gH/A/YB/wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB + /wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB/wGfAacB9SH/AZ8BpwH1Af8CAAHlAf8CAAHlAf8CAAHlAf8C + AAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8D9gH/A/YB/ywAA0gBhANEAXgDWwHKDAAD + NwFaA0oBiQNgAeNEAAMTARkDXQHfUAADVgGwAygBPANZAcEDGgEkEAADBAEFA0sBjwNcAdkDRwGCAy4B + RwMZASIDPQFoA0kBhwNbAc0DEQEWQAADTAGOAzUBVQQAA/YB/wP2Af8CAAHmAf8CAAHlAf8CAAHlAf8C + AAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8BnwGnAfUp/wGfAacB9QH/AgAB5QH/AgAB5QH/AgAB + 5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5gH/A/YB/wP2Af8sAANEAXkDQwF2A14B1wwAAzwB + ZgNGAX8DYAHgNAADBAEFA0EBcgNPAZkDOgFiA1YBsAM7AWNMAAM9AWcDWQHDAzsBYwNeAdcEAhgAAxMB + GgM9AWkDUgGgA1kBwQNdAc8DLQFGA1cBwgNRAZ4DTgGVA04BlQNOAZUDTgGVA04BlQNOAZUDTgGVA04B + lQNOAZUDTgGVA04BlQNOAZUDTgGVA04BlQNOAZUDTgGVA1sB0AMwAUoEAANaAb8D9gH/AgAB6gH/AgAB + 5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5QH/AZ8BpwH1Ff8BnwGnAfUB/wGfAacB9RX/AZ8B + pwH1Af8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHqAf8D9gH/A1oBvywAAz8B + bgNDAXYDYgHhDAADQQFzA0UBfANeAdc0AAM8AWYDUwGqAzABTANJAYgDSQGFBAFIAAMDAQQDXQHMAxsB + JgMaASQDEgEYNAADFQEdAzEBTgMyAU8DMgFPAzIBTwMyAU8DMgFPAzIBTwMyAU8DMgFPAzIBTwMyAU8D + MgFPAzIBTwMyAU8DMgFPAzIBTwMyAU8DLQFFBAIEAANHAYAD9gH/AYoBkwHvAf8CAAHlAf8CAAHlAf8C + AAHlAf8CAAHlAf8CAAHlAf8BnwGnAfUV/wGfAacB9QH/AgAB5QH/AgAB5QH/AZ8BpwH1Ff8BnwGnAfUB + /wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB/wGKAZMB7wH/A/YB/wNHAYAsAAM7AWQDRgF/A1oB + xAwAAz8BbgM/AWwDXQHMNAADMAFKA1cBvAQBDAADMwFSAykBPQMRARYDRAF7AwYBBwMPARMDFQEdBAAD + KQE+Ay0BRQMgAS4DSQGFAy0BRAMsAUMDUwGmAzoBYgM4AVsDXAHIA1sB5ANJAYiUAAMhATAD9gH/AecB + 6AH1Af8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHvFf8BnwGnAfUB/wIAAeUB/wIAAeUB/wIAAeUB + /wIAAeUB/wGfAacB9RX/AgAB7wH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5QH/AecB6AH1Af8D9gH/AyEB + MCwAAy4BRwNTAakUAAMqAUADVQGvOAADUgGlA0ABcQgAAwMBBANJAYYDWwHTAyYBOANKAYoDYgHhA2IB + 4QNdAc8DWwHkA1kBwQNSAaUDWAG4A0YBfgNTAacDUwGqAzgBXQNJAYYDSwGMAyYBOQQBnAADXQHPA/YB + /wIAAesB/wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB/wGfAacB9Q3/AZ8BpwH1Af8CAAHlAf8CAAHlAf8C + AAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8BnwGnAfUN/wGfAacB9QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB + 5QH/AgAB6wH/A/YB/wNdAc8wAAMDAQQDWwHFA0kBhwMtAUQDKwFCAywBQwM4BFsB2AMoATs4AAMMARAD + WwHKA0kBhwMGAQgIAANGAX4DPAFmBAADBQEGAwUBBtAAAzoBYAP2Af8B5wHoAfUB/wIAAeYB/wIAAeUB + /wIAAeUB/wIAAeUB/wIAAeUB/wGfAacB9QX/AZ8BpwH1Af8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8C + AAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8BnwGnAfUF/wGfAacB9QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB + 5QH/AgAB5gH/AecB6AH1Af8D9gH/AzoBYDQAAwkBCwNCAXQDUAGfA1IBoANSAaADTAGQAyEBMEAAAwYB + BwNKAYsDXgHiA10BzgNRAZ4DXAHZAyMBM+AAA1oBvwP2Af8BqQGvAfEB/wIAAeUB/wIAAeUB/wIAAeUB + /wIAAeUB/wIAAeUB/wIAAe8B/wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB + /wIAAeUB/wIAAeUB/wIAAeUB/wIAAe8B/wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB/wGpAa8B + 8QH/A/YB/wNaAb9AAAMSARgDQgF0AzABSlAABAIDEwEaAy0BRgMbASXkAAMYASADYgHvA/YB/wIAAewB + /wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB + /wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB + /wIAAewB/wP2Af8DYgHvAxgBIEAAA1sBzQNEAXsDXAHIAzABSv8ARQADMwFQA/YB/wP2Af8CAAHsAf8C + AAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8C + AAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHsAf8D9gH/A/YB + /wMzAVBAAAMCAQMDWwHkAwYBBwNEAXsDQgF1/wBJAAM6AWAD9gH/A/YB/wGpAa8B8QH/AgAB5gH/AgAB + 5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB + 5QH/AgAB5QH/AgAB5QH/AgAB5QH/AgAB5gH/AakBrwHxAf8D9gH/A/YB/wM6AWBIAANDAXcDWwHkA10B + zgMSARj/AE0AAzMBUANiAe8D9gH/AecB6AH1Af8CAAHrAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8C + AAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHlAf8CAAHrAf8B5wHoAfUB + /wP2Af8DYgHvAzMBUFAAAwIBA/8AWQADGAEgA1oBvwP2Af8D9gH/AecB6AH1Af8BigGTAe8B/wIAAeoB + /wIAAeYB/wIAAeUB/wIAAeUB/wIAAeUB/wIAAeUB/wIAAeYB/wIAAeoB/wGKAZMB7wH/AecB6AH1Af8D + 9gH/A/YB/wNaAb8DGAEg/wC5AAM6AWADXQHPA/YB/wP2Af8D9gH/A/YB/wP2Af8D9gH/A/YB/wP2Af8D + 9gH/A/YB/wP2Af8D9gH/A10BzwM6AWD/AMkAAyEBMANHAYADWgG/A/YB/wP2Af8D9gH/A/YB/wP2Af8D + 9gH/A1oBvwNHAYADIQEwJAAC/wEAAf//AP8A/wD/AP8A/wCuAAQBOAAEAUAAAxcBHwQCAxYBHgNPAZkD + KwFCIAADOAFcA00BkgMMAQ//AD0AAyQBNQNeAeIDVgG2AwIBAyAAAxEBFgNbAcoDUwGnAzMBUQNhAeYD + VQG0BAI0AANHAYMDYAHbA08BmQNJAYYDTgGUA2AB2wQCGAAEAQNdAeoDSwGMA0sBj/8APQADRAF5A0AB + cQNVAa0DLgFHIAADOQFeA0sBjQQAA1UBrgMmATgDWwHKAxUBHDQAA1EBoQMyAU8DXQHUAzsBZQNLAY0D + WAG6AyABLhwAA1gBugMqAUADYgHhBAHAAAM3AVoDVQG0AysBQSQAAyQBNAMTARkDDgESAysBQgMOARIk + AAMJAQwDSQGFA1ABnAMcAScDKQE9A1UBrAMwAUoDUwGpIAADLQFFA1IBoAQAA1wByAMVARwDWQHBAxkB + IjQAAzYBWQNMAZAEAgNBAXMDQAFxA1kBxgMYASEcAAM8AWYDSAGEA1sBxQMZASMIAAMcASccAAMLAQ4D + OwFlA0MBdgMeASswAAMdASkDQgF1Az0BZwMMARBIAANgAeMDLgFHA1wByyAAAz0BaANcAcgDXgHdAy4B + RwNSAaMDXAHLIAAEAQNYAboDRgGAAz0BaQNVAa8DDAEPA1wB1gMCAQMDYAHjAwMBBBwAAxcBHwNZAcYE + AANdAd8DBAEFA1kBwQMZASI0AAMVARwDWwHNBAADTgGVAzMBUgNgAeMEARwAAx0BKQNYAb0DSwGMAzgB + XAQAAzQBUwNYAboYAAMSARgDXAHWA0oBiQNAAXEDXwHVAzUBVigAAzMBUQNcAdYDQAFxA0kBhwNbAdgD + EwEaRAADWgHEAxkBIwNgAeMDAgEDHAADRwGCAzwBZgNNAZIDOAFcBAADYAHjIAADJAE1A1cBvAQAAxcB + HwM8AWYEAQNeAeIEAANZAcEDGwElHAADHAEnA2AB6AMCAQMDYgHhBAADWgHEAxYBHjgAA18B2gMLAQ4D + WQHAAzIBTwNZAcAgAAMEAQUDXQHfAzQBUwNOAZUEAANdAdEDGwElGAADSgGKAz8BbQgAAxMBGQNeAd0E + AQMGAQgDDAEQAwwBEAMMARADDAEQAwwBEAMMARADCQEMBAADXwHaAxUBHQgAAz0BZwNMAZAEAAQBPAAD + UgGjAyoBQANXAcIDGgEkHAADLgFIA1EBoQMqAT8DVgGwBAADYAHjIAADOgFhA0cBgwwAAwUBBgNbAd4E + AANQAZoDMAFLGAADLQFGA2AB2wMxAU4DQgF1A1UBtAMEAQUDYgHhAwYBBzgAA1sBygMbASYDXQHfAzAB + SgNQAZokAANhAeYDFQEcA1sBzQMVAR0DXAHLHAADVQGtAyUBNwwAA18B2gMIAQoDWQHBA1wB1gNeAdID + XgHSA14B0gNeAdIDXgHSA2AB8wMMARADXQHfDAADIgExA1YBswMGAQcDWwHTPAADUQGkAyoBPwNOAZcD + MgFPHAADDAEPA18B2gMCAQMDXQHfAwoBDQNgAeMgAAM5AV8DSgGKAx4BKgNdAdEDIwEyAyMBMwNYAbcE + AANBAXMDQQFyAwcBCQMRARYDCQEMAwIBAwgAA1IBoAMZASMDOgFgA1cBwgMJAQsDLQFGA1IBqDwAA2AB + 4wMwAUwDUQGhAzIBTwNOAZQgAAM4AVsDVQGxAxUBHQNhAeYDLQFFA1IBoBwAA0EBcwNNAZIIAAMmATkD + WwHNAwMBBANiAeEDBAEFFAADVgG2AyQBNANcAcgDKQE+CAADSwGNA0QBeQMgAS4DXAH4BAEwAAMjATMD + VQGuA14B8AMVAR0DPwFsA0QBeQMRARYDLQFGAzUBVQM4AVsDNgFXAy0BRQMVAR0DBAEFA2EB5gMGAQgD + UQGiAzcBWgNhAeYDCQELAwgBChgAAw8BFANdAdwDFQEcAycBOgNcAdYDXQHPAzoBYgQAAzMBUgNNAZID + OQFeA1sBzQNcAdYDYgHhA2AB4ANZAbsDQwF3AyIBMQNdAdwDEAEVBAADTAGRAzgBWzwAAzMBUgNLAY8D + OAFcAzQBVANLAY8cAAMJAQsDYAHbAxYBHgNSAaADOQFfAzYBVwNLAYwcAAMGAQcDVQGvA1sBxQNVAa0D + XAHZAyEBLwM6AWIDUQGhBAADDAEPAysBQgMTARoIAAM5AV4DUQGkAx8BLANbAdgDVQGuA1oBxANVAbQD + BgEIA1ABnwNcAfgEASwAAxABFQNeAd0DPAFmA1sB0AQAAysBQgNRAaQDMwFSA1ABnQNMAY4DSQGHA0sB + jANSAaADWwHNA10B3ANRAZ4EAANDAXcDUwH0AzoBYQNPAZsDMwFRHAADNwFaA1UBtAQCA0YBfwNaAcQD + AwEEBAADSgGLAz0BZxAAAwQBBQMgAS0DRAF6A1kB7wM+AWsIAANFAXwDPwFsPAADMAFLA2IB4QMSARcD + NgFYA1sBxQNOAZgDUQGkA0wBkQM5AV4DDAEQCAADQwF2A0kBhwMKAQ0DXQHfAwMBBAM4AVsDSQGIGAAD + JQE3A1UBrQMPARQDHQEpAyYBOQMGAQcDNQFVA1wB2QMTARkDMwFQA14B3QNRAaQDXwHaAzoBYQQABAID + WAG4A0kBhQMJAQwDJgE5Ax4BKgMEAQUDRgGAA1oBxANgAeAwAAMuAUgDUAGfAzkBXgNJAYgEAAMSARcD + XQHPHAADWwHFAyMBMwQAAzABSgNZAb4EAQNiAeEDBgEHIAADVgGyA14B0gNSAaUDCwEOCAADWgHEAxgB + IBwAA14B1wMOARIIAAMtAUQDUgGjOAADQAFvA1kBxgMcAScEAAMdASkDRAF5AzABTAMqAT8DNAFUA0sB + jwNbAd4DQwF2AwMBBANgAdsDEgEXAyQBNQNdAdEDAwEEAzYBVwNKAYscAAM9AWkDWwHeA1YBsgNRAaID + WwHTA1UBtAMWAR4DLQFGA1sB0wMUARsEAAMOARIDXQHUAyQBNAQAAwcBCQNNAZIDXgHdA1IBpQNRAaQD + XQHcA04BlgMqAUADVgGzMAADUAGcAzgBWwMxAU0DQAFvBAADCQELA10B3BgAAwwBDwNgAdsIAAMGAQcD + XgHiAxIBGANdAc4kAAMlATYDWwHQBAEMAANYAboDHwEsHAADXgHiDAADEgEYA10BzDQAAxABFQNdAdwD + CgENIAADCwEOA0gBhAMvAUkDVQGvCAADUAGdAzsBZAMzAVEDTQGSIAADBQEGA00BkgNSAaMDEwEaBAAD + EAEVA10B3AMaASQMAAMrAUEDWAG9DAADDAEQAyoBQAMqAUADDQERBAADRgGBAz8BbRgAAxcBHwNSAagD + XQHUA1sB0wNWAbADTgGWA1oBxwMEAQUMAAMlATYDVgGwGAADJQE2A1UBrgwAA1oBxwMmATgDXAHIJAAD + OAFcA0oBihAAAzgBXAMlATYcAANVAa4MAAMmATgDVgGzNAADNAFUA00BkywAA0ABcQNBAXMIAAMeASsD + UwGnAzEETgGVJAADEAEVA14B3QMJAQsEAANQAZ0DPgFrFAADWwHKAyUBNgMQARUDEAEVAxABFQMQARUD + EAEVAxABFQMSARgDYAHbAxIBFxgAA1EBngM/AW0DDAEPAwwBEAMmATgDOgFhAwkBCxAAAzwBZgNGAYAY + AAMXAR8DTgGVDAADWQHAAx0BKQNdAd8kAANGAX0DPQFnRAADGQEjAyQBNTQAAz0BaANEAXosAANCAXUD + QAFvEAADNwFaA0oBiigAA0kBiANCAXUEAgNTAacDAgEDFAADRgGAA14B4gNdAc4DXQHOA10BzgNdAc4D + XQHOA10BzgNdAdwDUwGpHAADVQGtAzQBVAwAAwcBCQNGAYEDTAGOAxgBIQgAA04BlAMzAVEsAANEAXsD + BQEGA2AB4yQAA0sBjQM1AVaAAANJAYUDQAFvLAADEgEXAxgBIBAAA0sBjwM4AV0oAAMQARUDXgHdAwkB + CxwAA0QBeAM+AWoYAAMuAUgDUAGaHAADGAEhA10B3AMOARIIAAMHAQkDOgFgAzwBZgNdAckIAAMZASMD + BgEIMAADBgEIA10B3yQAA00BkgMzAVEQAAMIAQoDDAEQAw4BEgMPARQDEQEWAxIBGAMTARoDFQEcAxUB + HQMZASIDLgFIAzwBZgMsAUMDAgEDIAADBgEIAzgBXAMjATIIAAMKAQ0DWwHeAwwBD0AAAxMBGQNeAd0D + DAEPLAADSgGJA0IBdRwAA0QBeANZAb4DTAGQA0wBkANMAZADTAGQA0wBkANMAZADVQGvA1ABmiAAA0AB + bwNSAaAMAANWAbADUwGqA1kBwUAAAzkBXwNRAZ4YAAQBAxMBGQMlATYDVQGvAzYBVwwAAwsBDgNiAe4D + XgHSA10B0QNdAc8DWwHNA1wBywNdAckDWgHHA1kBxgNZAcEDUAGfA0YBfQNVAawDXQHMAxUBHRwAA1UB + rgNPAZsDYAHbAyIBMQQAAz8BbQNJAYYUAAMIAQoDHgEqAykBPgMtAUYDKQE+AxkBIgQCEAADJAE0A10B + 0QM1AVYwAAMQARUDXgHdAwkBCxgAAxUBHQM0AVQDNAFUAzQBVAM0AVQDNAFUAzQBVAM0AVQDNAFUAx0B + KCAAAwIBAwNdAdEDPgFrBAEDPQFpAyYBOQMsAUMDDwEUBAE0AAMEAQUDOAFcA18B2gMVAR0UAAMGAQgD + XQHJA1wBywNVAa4DTAGRAxsBJgwAAzUBVgNQAZowAAMzAVEDWgHHBAEYAANcAcsDHgErAyQBNQNfAdoD + WwHFAyQBNAMIAQoMAAMVARwDWwHNA10B3ANYAboDUgGlA1ABnQNTAaYDWgHEA2AB4ANWAbUDUAGfA1MB + pwNbAdMDWwHFAyoBPzgAA0kBiANEAXgDCQELAwkBCwQBVAADTQGSA1gBvQNdAckDXwHaA1YBtQNdAdQD + CAEKA1sBzQNgAeMDXwHaA1oBvwNTAacDUwGpA1YBsANWAbYDWAG9A1kBwwNbAcoDWwHQA1wB1gNeAd0D + YgHhA2AB4wNRAaIDFgEeGAADOQFeA04BlxgABAIDXAHIAyUBNjQAA1cBvAMhATAYAAM1AVYDVAGrCAAD + OgFiA1ABmgwABAEDVgGwAzoBYRgAAwYBCAMhATADLQFEAygBPAMRARZAAAMQARUDWwHQA1sB2ANbAdgD + KgE/VAADPwFsAwIBAwQCAw8BEwQBA14B4gMDAQQIAAMIAQoDGwElAygBPAMmATkDIwEzAyABLQMbASYD + GAEgAxMBGgMPARMDCgENAwUBBgQCJAADPQFoA2AB2wNVAbEDPgFrDAAEAgNLAY0DUAGcOAADUQGeAy0B + RhgABAEDWgHHAyUBNwQAAwgBCgMmATgMAANHAYMDUwGpBAHoAAMaASRoAAMRARYDIwEzA1YBtQNVAbQD + OAFbBAADVwG5A1EBngMFAQY0AAMFAQYDXAHZAxIBFxwAAz0BaANOAZgQAAMKAQ0DUAGcA1YBswMGAQj/ + AGEABAIDKgFAA1sB3gMYASADXQHROAADCgENA1MBqgNJAYYgAAMFAQYDWAG3A0sBjQQCAx8BLANEAXsD + XQHcA0QBeQMCAQP/AG0AA10B3ANbAcoDNgFYMAADGAEgA0kBhwNeAd0DQwF3BAEkAANPAZsDTgGVA04B + mANdAcwDQwF2AxEBFv8AdQADOgFiAy8BSTQAAygBPAM9AWgDDgESLAADNgFYA1YBsANGAX4DBgEH/wD/ + AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AJsAAxMBGgM/AW4DJgE5LAAE + AQM0AVMDOwFkAwcBCTAAAwkBCwQCPAAEAQMJAQxAAAMVAR0DLgFIAwIBAxAAAxIBFwMuAUgDAwEEEAAD + HQEpAxIBF6wACwIBAwMEAQUDXAHWA0YBfgNdAdQDJQE2AwIEAwEEAwMEBAEFAwQEBQEGAwUBBgMFAQYD + BQEGAwIBAwNAAXEDVgG2A04BmANSAaUoAAMVARwDVwHCA18B2gNbAd4DNQFWNAADMAFKA2AB2wNcAdkD + XQHJAxoBJDQABAIDXAHZA1kBwAM9AWkQAANWAbADWwHFA0sBjAwAAxcBHwNgAeADWwHQAwIBA6QAA1YB + sgNgAeMDYAHjA2AB4wM6AWADXAHWBAEDSQGGAz0BaANbAdADWwHeA1sB3gNbAd4DXQHsA14B8ANdAeoD + UwH0A10B3ANSAaADUgGjAy8BSQMQARUDXgHXAz4BawNeAdcDVQGuAyYBOQwAAwQBBQMmATgEAANYAbcD + NgFZBAADEgEYA1wB2QMWAR4DEQEWAy4BRwMrAUIDKQE9AyYBOAMjATMDIAEuAx0BKQMbASUDGAEgAwgB + CgMPARMDXgHdAxgBIAQAAy8BSQNaAcckAANQAZoDTwGbCAADEgEYA10BzAMlATcDVgGzEAADUgGlAzEB + TgNdAc4DMQFOCAADJgE5A1QBqwNTAaYDMgFPKAADGQEjAzoBYQNMAY4DUwGmA1EBogNHAYIDLwFJAwgB + ChgAAwMBBAMoATwDRAF5A1IBoANRAaQDRgF9AyEBLygAA1wB5wNfAeUDUgGoCAADTwGZA18B2gNdAdED + DwEUEAADQQFzA0ABcAMjATMDVgGwCAADJgE5A10B3wNgAeADOgFiBAADCwEOA0oBiQNDAXcMAANNAZMD + VQGvAwIBAwNfAeUMAANLAY8DNQFWAyYBOQNPAZsDUgGgA1IBpQNTAaoDVQGvA1UBtANXAbkDWQG+A1kB + wwM9AWgDLQFFA1IBoAwAA1sB3gMGAQcDBgEIGAADGwElA1wByAMEAQUDCgENBAADCQELA1wB1gMFAQYD + XgHiBAEMAAMxAU0DVQGsAxYBHgNeAdcDJwE6BAADCgENA1sB3gMuAUgDUwGqGAADBwEJAzgBXANNAZID + XgHXA1wByANKAYoDWgHyAzsBYwMrAUEDOwFlA1EBogNgAeADVgG2A0QBewM4AVsDOAFbA0QBeQNVAa8D + YgHhA1UBrwM/AW4DLAFDAywBQwNgAeADYQHmA10B1ANMAZEDPQFnAzUBVgMHAQkUAANfAeUDAwEEAz0B + aAQBCAADDAEQBAIUAANBAXMDQAFwAyMBMwNWAbAMAAMGAQcDCQELDAADPgFrA0YBfgwAA1YBtgMgAS0E + AANcAcsDIwEzBAADBAEFA1wBywMgAS4sAAMXAR8DXwHVAwgBCgQAAxsBJgNeAdcDCgENA10B0RgAAxgB + IQNbAcoDJAE0A10BzwMmATgEAQMVAR0EAANZAb4DIAEtDAADEgEYA14B4gQAAzABSgNWAbMDSwGPA14B + 1wNTAfQDJgE4A2IB4QMFAQYUAAMQARUDSQGGAzYBWAMPARQEAAMrAUIDVQGsEAAEAgMkATQDPgFrA0kB + iANJAYgDPwFsAycBOgMEAQUQAAMpAT0DXAHLAxQBGwM2AVcDRQF8A0oBiwMQARUMAAMDAQQEAANbAdgD + QgF1A1wB2QNGAYEDEQEWIAADQQFzA0ABcAMjATMDVgGwIAADPAFmA0YBfwwAA1kBwwNJAYcDQwF3AywB + QwNdAdwDUgGoA14B0gNHAYM0AANCAXQDXgHXA1MBpgNeAd0DKwFCBAADYAHjHAADWAG6AzoBYgMtAUUD + WQG7A18B2gNRAZ4DKAE8A0ABbwNLAYwIAAQBA1QBqwM7AWMEAAMwAUsDXAH4Az8BbgMMARAIAANUAasD + KgE/KAADPAFmA0sBjAMVARwDFQEcAxUBHAMVARwDFQEcAxUBHAMVARwDFQEcAxUBHAMVARwDFQEcAxUB + HAMVARwDFQEcAxUBHAMVARwDFQEcA1gBuAM1AVUYAAMHAQkDXAHWAwIBAwNWAbYDIwEzAxABFQNDAXYD + XgHdA0oBigNEAXsDRgF9A0YBgANHAYIDSAGEA0kBhgNJAYgDWgG/A0ABcAMkATQDXgHdA04BlANOAZYD + TgGYA1ABmgNQAZ0DUAGfA1EBoQNAAXADPQFpA0YBfgwAA1IBowNWAbUDTgGVA1MBqQMJAQwDKQE+AxgB + IDwAAxUBHAMqAT8DDAEPBAADPwFuA1kBxhwAAxABFQNaAccDTgGWAw4BEgMMARADMAFMA2AB2wMbASYD + WAG3A1sB5ANgAeMDXQHqA1sB0AQBBAADHgErA10BzwNGAX8IAAMZASMDYAHbAyEBLygAAycBOgNZAcYD + WQHGA1kBxgNZAcYDWQHGA1kBxgNZAcYDWQHGA1kBxgNbAc0DYAHzA1kBxgNZAcYDWQHGA1kBxgNZAcYD + WQHGA1kBxgNZAcYDQwF2HAADUgGlA1EBogNcAcsDOgFiCAADDQERAzwBZgM9AWgDOwFlAzsBYwM6AWED + OQFfAzgBXAM3AVoDUQGkA1EBpANGAX0DXQHJAzIBTwMxAU0DMAFLAy4BSAMtAUYDLQFEAysBQQMcAScD + RAF7Az8BbQwAAxUBHANfAdoDMQFNAysBQRAAAwYBCAMgAS0DHgErAx0BKQMdASgDGwEmAxoBJAMZASID + GAEhAxcBHwMVAR0DFQEcAxMBGgMSARgDDAEPDAADXAHnAw8BFCAAAwYBBwNcAecDJQE2CAADWgHEAx0B + KAwAAzMBUANKAYkMAAMGAQcDWAG9AzQBVAMCAQMDWwHNAy4BR1QAAxYBHgNcAchAAAMSARgDNgFXA0kB + hwNVAbEDBQEGKAADIgExA0kBhgNJAYYDMwFRIAADUAGdAzABTBAAAxgBIQNZAcMDVgG2AzgBWwMdASgD + DgESAwgBCgMmATgDVgG2A1gBuANXAbkDWQG7A1gBvQNZAb4DWQHAA1cBwgNaAcQDWwHFA1oBxwNdAckD + WwHKA1wB5wgAAwUBBgNiAeEkAAMmATgDVQG0DAADKQE+EAAEAgMEAQUQAAMgAS0DVwHCAwQBBQM2AVhA + AANYAbgDXgHiA14B4gNeAeIDXgHiA10B3wMiATEDQQH5A18B5QNfAeUDXwHlA18B5QNfAeUDXwHlA18B + 5QNeAd0DBwEJJAADAgEDA1MBqQNiAeEDXAHnA2AB6ANbAeQDYAHgA14B3QNbAdgDXwHVA1sB0wNbAdMD + XgHSA14B0gNdAdEDXQHRA10B0QNeAdIDXgHSA1sB0wNbAdMDWwHTA1wB1gNcAdkDXgHdA1wB5wMTARoU + AAQBAyoBQANMAY4DWAG9A10B0QNgAeMDQwF3AwIBAzAAA2AB4wgAAzgBXANSAaAUAAMvAUkDVgGwA1EB + ngM2AVkDKwFBA1MBpzgABAEDWwHYSAADRgF9A0YBfwwAA1sBxQMoATwDXQHMGAIDQgF1A08BmTwABAED + BgEIAwkBDAMKAQ0DDAEQAw8BEwMQARUDEgEXAxIBGAMSARgDEgEYAxIBGAMSARcDEgEXAxEBFgMPARQD + DgESAwwBDwMKAQ0DCQEMAwMBBCwAAwsBDgNQAZoDWgHHAx0BKSwAA1ABnwQAAyQBNQNdAdwDFwEfEAAD + CQEMA1sB3gMoATsDLgFIA0MBdwMGAQcDXAHZAx4BKzgAAwYBB0gAAw0BEQNbAd4DCgENCAADWwHFAygB + PANdAcwUAAMTARoDXQHfAw8BFMQAAy4BRwNcAdkDQgF0AwIBAyQAAwwBEANGAX8DWwHYAyIBMRQAAygB + OwNVAbQQAAMoATsDNQFWMAADBQEGAxQBGwMHAQlMAANJAYcDQgF1CAADWwHFAygBPANdAcwUAANTAaYD + PQFnzAADDAEPA1ABnANfAdoDWwHFA1oBxANZAcMDWQHBA1kBwANaAb8DWQG+A1cBvANZAcADWwHeA0YB + gAMKAQ0YAAMGAQcDXQHRAyUBNzgABAIDLAFDA1UBrwNiAeEDXAHIA10B3wNVAbQDHgErRAADEQEWA14B + 3QMGAQgEAANbAcUDKAE8A10BzBAAAyoBQANbAcoEAtQAAw0BEQMVAR0DFwEfAxgBIAMYASEDGQEjAxoB + JAMbASUDGwEmAxsBJQMDAQQkAAMlATcDXQHRAwUBBgQAAwkBDAM9AWkDVAGrA1cBwgNWAbADSAGEAzkB + XwMuAUcDKgFAAzABTAM+AWoDUAGdA14B3QNWAbADLQFGBAIEAAQBAy8BSQNgAdsDJQE2RAADTAGRAz4B + agQAA1sBxQMoATwDXQHMDAADBAEFA10B0QMlATf/ACkAAwoBDQNiAeEDDQERAxcBHwNdAdQDSgGLAygB + OwMYASEDJAE1AzoBYgNJAYYDUAGcA1EBogNOAZgDRAF7AzABTAMMAQ8YAAMjATMDWwHNRAADFQEdA1wB + 2QMEAQUDWwHFAygBPANdAcwMAANAAXEDUAGc/wAtAAM9AWcDXAHnA1kBuwNdAdwDMAFMTAADXgHSAw8B + E0QAA08BmwM6AWADWwHFAygBPANdAcwIAAMTARkDXQHfAxABFf8ALQADBAEFAx4BKwMeASoDAwEEUAAD + WwHYAwkBDEQAAxoBJANdAdQDXAHIAygBPANdAcwIAANSAaMDPQFp/wCRAANYAbcDKgE/BAADBQEGQAAD + UgGlA00B+gMoATwDXQHMBAADKQE+A10BzAMCAQP/AJEAAyMBMgNdAdwDVQGtA1wB2QMFAQY8AAMfASwD + QAH9AygBPANdAcwDAwEEA1sB0AMmATn/AJkAAwwBDwMnAToDCQEMRAADRAF7Ax4BKwNdAcwDQAFvA1EB + nv8A8QADFwEfA2AB4ANdAd8DEQEW/wDxAAMXAR8DAAH/Az8BbP8A9QADDwEUA1cBuQMCAQP/AP8A/wD/ + AP8A/wD/AP8A/wDhAAMGAQgoAAMzAVEEAf8AyQADAgEDA2AB4AM9AWcgAAMaASQDTQH6A04BmAQB/wDF + AAMxAU0DVgGzA10ByQM7AWUcAANCAXQDRgF9A1EBpANLAY9cAAQCAx4BKgMhAS8DAwEE/wBZAANVAbQD + KgFAAw0BEQNcAcsDMAFLGAADWQHDAx4BKgMDAQQDWwHNAx4BK0AAAxsBJgM0AVQMAAMnAToDWwHTA1kB + vgNYAbcDXQHcAzcBWgwAAzEBTQMgAS3/AD0AAxcBHwNfAdUEAQQAA0cBggM7AWMUAAMSARcDXAHWCAAD + RgF+AzwBZjgAAwUBBgNLAY0DXwHVAzUBVQgAAwwBDwNbAdgDIQEvCAADFAEbA1MBqQMMAQ8IAAMvAUkD + XQHPA1ABmgMIAQo0AAMVARwDOAFbAz0BaAMqAUADAwEEJAADCwEOAzMBUQM9AWkDMQFOAwkBDFAAAxIB + F1wAAw0BEQNRAaEDQAFxCAADUAGfAy0BRRQAAzsBZQNKAYkIAANKAYoDOAFbNAADAwEEA1YBtgNMAY4D + BQEGEAADKQE+AzsBYwMaASQDGgEkAzsBYwMnAToQAAMCAQMDRgF+A1kBwwMHAQkoAAMGAQcDTgGVA1wB + 1gNKAYsDRQF8A1UBrANcAdYDKAE7HAADPQFoA1sB3gNOAZgDRAF6A08BmwNbAd4DOgFgTAADVQGxA10B + 0QNJAYYDUgGgA1sBygM9AWdAAAMeASoDTgGXA1sB3gNHAYMDCQEMCAADVgG1AzMBUBQAA1YBtQMmATkI + AANWAbUDIgExNAADRQF8A04BlxQAA00BkgNXAcIDRwGDA1sB2ANbAdgDRwGDA1cBwgNNAZIUAANIAYQD + SwGPKAADVAGrA0oBiwMDAQQMAAMgAS4DXAHZAyMBMhQAAz4BagNXAbwDDgESDAADEAEVA1kBwwM6AWJM + AAM1AVYDWgHHAy0BRgMVARwEATgAAwQBBQNXAbkDWwHNAzoBYAMGAQgQAAMdASkDYAHbAykBPQwAAwoB + DQNbAd4EAggAA10B3AMHAQkwAAMIAQoDXQHfAwoBDRAAAzMBUANYAbcDBgEIA0gBhANZAb4DWQG+A0gB + hAMGAQgDWAG3AzMBUBAAAwUBBgNgAdsDEAEVIAADLgFIA1gBuBgAAygBPANaAcQQAAMNAREDXQHcAwwB + EBQAAw8BFANbAd4DCgENSAADUQGiAzMBUkgAA0wBjgNUAasDBAEFGAADHQEpA18B2gMpAT0IAAM1AVUD + TgGYCAADCQEMA1wB2TQAAy4BSANRAaEUAANEAXkDQAFwAyoBQANWAbUDRwGCA0cBgwNWAbUDKgRAAXAD + RAF5FAADTAGOAzgBWyAAA1ABnAMxAU0cAANdAc8DEwEaDAADNgFZA0wBjhwAA04BlwMzAVIcAAMmATgD + LAFDAygBPAMhATADGAEgAwkBDBQAA1oBxAM7AWUDGQEiRAAEAQNMAZEDVAGrAzoBYgNIAYQDTgGWAw0B + EQwAAx0BKQNgAdsDKQE9BAADUwGmAy4BSAgAAyQBNQNVAbE0AANAAXADQgF0FAADIwEzA1kBuwMCAQMD + CgENCAADCgENAwIBAwNZAbsDIwEzFAADOgFhA0cBgyAAA1cBuQMeASsIAAMHAQkDVAGrDAADUgGgAyAB + LgMgAS0DRAF5A0kBiANZAcEDXQHMA1YBtQNaAcQDWwHTA10BzwQBCAADQgF0A0ABbxwAA1YBtgNTAfQD + UgGoA1YBswNZAcMDXgHXA2AB4wNeAdIDVAGrA0QBewMpAT4DKAE8A04BlgNbAcoDWgHHAyoBP0AABAED + QwF2A0cBggM6AWEDRAF4A1sBygMNAREMAAMdASkDYAHbAykBPQNJAYcDBgEHCAADOgFgA0kBhjQAA0QB + eAM+AWoUAAQBA18B2gMKAQ0QAAMKAQ0DXwHaBAEUAAM2AVcDSgGLIAADUQGiAy0BRggABAIDYgHhDAAE + AQMWAR4DXQHcA2UB8QM5AV4DSgGLA1EBpAMgAS4DFwEfA0QBeANOAZQMAANMAZADNgFYHAADBgEHA1IB + pQNSAaUDBgEHCAAEAQMPARMDKAE7Az8BbQNVAawDXQHfA0kBhgMLAQ4DJAE1A10BzgNBAXJQAAM7AWUD + WwHKAw4BEgwAAx0BKQNgAdsDKQE9DAADSgGKAzgBXDQAAz0BZwNGAX0YAAMSARgYAAMSARgYAAM+AWoD + RAF6IAADNAFUA1IBqAwAA14B4gQBCAADDAEPA1sB0AMxAU0DUAGdAz8BbAMTARoDXQHcAwgBCgMGAQcD + XwHaAxUBHAgAAwkBDANeAd0DDwEUIAAEAQNJAYUDWQHDAxEBFhAAAyIBMQNFAXwDGwEmA0UBfANcAdYD + HgErAwkBCwNaAcQDOAFdUAADOwFlA1wBywMOARIMAAMdASkDXwHaAykBPQgAA1YBtQMiATE0AAMpAT4D + UwGqGAADKgE/Aw8BExAAAw8BEwMqAT8YAANOAZcDMwFRIAAEAgNZAb4DQAFvCAADXQHUAwwBDwQAAwQB + BQNZAbsDPwFsBAADEAEVA14B3QMSARcDRgGAA1QBqwNBAXIDTgGUCAADBwEJA1UBrwNDAXcsAAM5AV4D + WwHYAyEBLwwAAw0BEQNCAXUDXgHdAz8BbgMoATwDYAHbAxsBJgMRARYDYAHbAxkBIlAAAzsBZANcAcsD + DgESDAADHQEpA2AB2wMpAT0EAANdAdwDBwEJNAADBwEJA2AB4AMDAQQQAAMVAR0DAAH/A0sBjxAAA0wB + kAMAAf8DFQEdFAADWwHYAw8BFCQAAwwBEANWAbIDWQHBA0ABbwNbAdgDGQEjBAEDUQGeA0wBjgwAAz0B + aQNSAaAEAQNJAYcDKwH8A00BkwM5AV4DRgF/A1wB2QNGAX0EATAAAyYBOANfAdoDNwFaEAADDAEQA1YB + sgNLAYwDLAFDA1sB0AMGAQgDOAFdA1ABnFQAAzsBZANcAcsDDgESDAADHQEpA2AB2wMpAT0DLgFHPAAD + UwGnAzEBTRQAAzgBWwMYASAQAAMYASADOAFbBAEQAAMnAToDWAG6MAADJAE1A0QBeANfAdUDJQE3A0UB + fANVAa0EAgwAAwIBAwNbAcoDKAE8Az0BaQNRAaIDPwFsA0kBhgM+AWoDFQEdPAADEgEYA1kBwQNOAZQD + BgEIDAADBAEFA1kBuwM4AVwDSwGMA0QBeQMGAQcDYAHgSAADBQEGAy0BRgNOAZYDNwFaAzsBZANcAcsD + DgESDAADHQEpA18B2gMpAT08AAMmATkDWQHDSAADVgGwAzABTDgAA04BmANRAaQDWgHHAwgBChQAAyUB + NgNdAc8DXwHaAxUBHFAAAwMBBANJAYcDWwHNAx4BKwwAAxQBGwNeAd0DFQEcA1sB3gMDAQQDYAHjPAAD + CgENAzUBVQNSAaUDYgHhA1IBqAM2AVgDCQEMBAADOwFjA1wBywMOARIMAAMdASkDXwHaAykBPTQAAzYB + WAM/AW4DVgG1AzMBUkAAAyoBPwNZAcYDQAFvAzYBWDQAA0gBhAMAAf8DXgHXA1kBxgNZAcYDWQHGA1kB + xgNZAcYDWQHGA1kBxgNIAfYDUwGnXAADKwFCA1wB2QNEAXkDBAEFCAADTgGUAzoBYANYAbcDIQEwA14B + 4jAAAxIBFwM7AWQDVQG0A10B3wNPAZkDLwFJAwYBBxQAAzsBYwNdAcwDDgESDAADHQEpA2AB2wMpAT0s + AAMpAT4DXQHRAxIBFwMZASMDYAHbAxIBGDgAAwwBEANcAdYDIgExAxIBFwNdAdEDKQE+MAADQAFvA0gB + hAMVARwDFQEcAxUBHAMVARwDFQEcAxUBHAMVARwDFQQcAScDUAGfYAADCwEOA00BkwNbAdMDMwFRBAID + GwEmAywBQwNXAbkDIwEyA2AB4wMGAQcoAAM6AWIDWgHyA0oBigMnAToEAiQAAzoBYgNdAcwDDwETDAAD + HQEpA18B2gMpAT4oAANaAccDJAE0CAADNgFYA10BzAMSARcwAAMMARADWQHBAz0BaQgAAyQBNANaAccw + AAM4AVsDSQGIHAADTgGYA14B3QNeAd0DTgGYZAADHwEsA1QBqwNbAdgDTgGWA1UBrQNXAbwEAgNJAYcD + UQGhAwYBCCQAAwgBCgNYAb0DRAF6FAAEAgMZASIDMQFNA0MBdwNRAaIDXAHLAyMBMgM6AWIDXQHMAw8B + EwwAAx0BKQNdAdwDGAEgIAADFgEeA1sBzRAAAzYBWQNeAd0DLAFDKAADJQE2A2AB2wM9AWgQAANbAc0D + FgEeLAADGQEiA0MBdwMmATgDDAEPFAADAgEDAwUBBgMFAQYDAgEDbAADFAEbAz4BagMrAUEDAgEDBAAD + AgEDA0kBhQNbAdgDPwFsAxEBFiAAAwgBCgNYAbcDVQGvAzsBZQNEAXgDUQGiA10BzANgAeMDWgHEA08B + mQM/AW4DLQFEAxMBGggAAzoBYgNdAcwDDwETDAADQQFyA0QBeCAAAywBQwNSAaAUAAMPARQDDwETAx8B + LCAAAx8BLAMLAQ4DEwEZFAADUgGgAywBQywAAxQBGwNTAakDUwGqAyoBP5AAAxgBIANdAdEDAgEDEAAD + GAEhA0sBjANBAfkDMAFLIAAEAQMwAUoDRgF/Az8BbgMtAUQDEwEaJAADOgFiA10BzAMPARMIAANAAW8D + RAF4IAADJwE6A1UBrBgAAy0BRANfAdoEARgAAwkBCwNfAeUDLQFEGAADVQGsAyYBOdAAA1ABmgNKAYsE + AgQAAyABLgNGAX8DUwGqA2AB4ANOAZYDBgEIYAADOgFhA10BzwMxAU4DNgFXA14B1wMeASsgAAMHAQkD + XQHfAw0BERAAAyUBNgNgAdsDVwG8A18B2gNSAaMDRgGAA0IBdANEAXsDTgGVA1cBwgNgAeADUgGgA2AB + 2wMlATYQAAMNAREDXQHfAwcBCdAAAwYBBwNQAZ0DXQHcA1sB0wNdAcwDPgFqAykBPQMHAQlsAAMpAT4D + UAGaA00BkgMmATgoAAM/AWwDWAG6Aw4BEgQAAwIBAwM8AWYDXwHaAyEBLwQAAwwBEAMsAUMDOwFjA0AB + bwM9AWgDMgFPAxsBJgQBBAADIQEvA18B2gM8AWYDAgEDBAADDgESA1gBugM+AWvcAAMKAQ0DDwEUuAAD + PgFqA10B3wNXAcIDXQHfA1IBoAMSARcwAAMSARcDUgGgA10B3wNXAcIDXQHfAz4Bav8ApQADBQEGAxkB + IgMJAQtAAAMJAQsDGQEiAwUBBv8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8AtQADAgEDAwQBBQMdASkD + MAFMAy8BSQMtAUYDKgFAAycBOgMVAR1MAAMRARYDHwEsAzABSgM9AWcDNwFaAzEBTQMdASkDBAEFAwIB + A3wAAyoBQANGAYADRgGAA0YBgAMqAUAUAAMqAUADRgGAAyoBQLwAAwQBBQMHAQkDMwFRA04BmANNAZID + SgGLA0YBgANCAXQDJwE6TAADHwEsAzYBWANNAZMCXwFdAc4DVwG0A08BmQMzAVEDBwEJAwQBBXwAA0YB + gAMAAf8DAAH/AwAB/wNGAYAUAANGAYADAAH/A0YBgDwAAyoBQANGAYADRgGAA0YBgAMqAUAsAAMqAUAD + RgGAA0YBgANGAYADKgFAJAADBwEJAw4BEgMqBD8BbAJQAVEBnANdAcwDXQHJA1sBxQNZAb4DVgG2AzkB + XgMEAQUDAgEDQAADGwElAz0BaANVAawDXQHJA2UB5wNhAdoDXQHMA1EBnAM+AWsDJQE2fAADRgGAAwAB + /wNZAcADRgGAAyoBQAwAAyoBQANGAYADRgGAA0YBgANGAYADRgGAAyoBQDQAA0YBgAMAAf8DAAH/AwAB + /wNGAYAsAANGAYADAAH/AwAB/wMAAf8DRgGAJAADDgESAxkBIwNEAXkDXwHOAlwBZQHnAgABxQH/AwAB + /wGPAgAB/wMrAfwDXAH4A0cBgQMIAQoDBAEFQAADLwFJA1EBpAMAAf8DAAH/AZYBmwGOAf8BkAGXAYsB + /wGKAZMBiAH/A2EB5gNdAcwDPAFmfAADRgGAAwAB/wNGAYAUAANGAYADAAH/A0YBgAQAA0YBgAMAAf8D + RgGALAADKgFAA0YBgANZAcADAAH/AwAB/wMAAf8DWQHAA0YBgAMqAUAcAAMqAUADRgGAA1kBwAMAAf8D + AAH/AwAB/wNZAcADRgGAAyoBQBwAAxwBJwMxAU0DUAGaAlwBZQHnA2AB8wIAAcgB/wMAAf8BmAIAAf8B + gAJBAf4DKwH8A00BkwMeASsDEQEWQAADTAGQA1wByAMAAf8DAAH/AaIBrQGmAf8BmAGfAaMB/wGPAZEB + nwH/A2AB8wNhAeYDUQGiAzkBXgMpAT0DFAEbAwsBDggBDAAIAQMIAQoDDwETAwkBCwQCBAE8AAMqAUAD + RgGAAyoBQAwAAyoBQANGAYADWQHAAwAB/wNGAYAEAANGAYADAAH/A0YBgCwAA0YBgAMAAf8DAAH/AwAB + /wMAAf8DAAH/AwAB/wMAAf8DRgGAHAADRgGAAwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wNGAYAc + AAMoATsDQwF2A1kBuwIAAcwB/wIAAcsB/wIAAcoB/wMAAf8BoAIAAf8BowIAAf8BpgIAAf8BUwJSAaUD + MAFLAxsBJkAAA14B1wNhAesDAAH/AwAB/wGtAb8BvgH/AaABpwG6Af8BkwGOAbUB/wMAAf8DAAH/A1sB + 3gJYAVcBvANEAXkDJAE1AxQBGwgBDAAIAQMPARQDGwEmAxABFQMDAQQEAlQAA0YBgAMAAf8DAAH/AwAB + /wNGAYAEAANGAYADAAH/A0YBgCAAA0YBgANGAYADRgGAA1kBwAMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB + /wMAAf8DWQHAA0YBgANGAYADRgGAA0YBgANGAYADRgGAA0YBgANZAcADAAH/AwAB/wMAAf8DAAH/AwAB + /wMAAf8DAAH/A1kBwANGAYADKgFAFAADJQE3Az8BbQNWAbYCAAG9Af8CAAG0Af8CAAGsAf8DAAH/AaUC + AAH/AagCAAH/AasCAAH/AV8CWwHQA1IBoQM+AWsDJAE0AxMBGjgAA10B6gNaAfUDAAH/AwAB/wGrAbwB + uwH/AZ0BggGLAf8BkAIAAf8DAAH/AwAB/wNZAe8BYQJbAd4BWAJXAbwDUAGaA0oBigNEAXkDPQFoAzYB + VwMrAUIDHwEsAykBPQMxAU0DQAFwA00BkwNGAX4DPgFqAyQBNQgBTAADRgGAAwAB/wMAAf8DAAH/A0YB + gAQAA0YBgAMAAf8DRgGAIwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8D + AAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8D + AAH/AwAB/wMAAf8DRgGAFAADIwEyAzsBYwFVAlcBsQEAAZoBrQH/AQABtgGdAf8BmAHRAY0B/wGhAa4B + AAH/AakBigEAAf8BrAIAAf8BrwIAAf8BXwIyAfsBbQFsAW0B9wNWAbADPQFoAyQBNDgAA0AB/QNKAf4D + AAH/AwAB/wGoArgB/wGaAgAB/wGMAgAB/wMAAf8DAAH/AwAB/wGUAgAB/wHGAgAB/wH4AgAB/wNcAfgD + XgHwA10BzwNVAa0DSAGDAzYBWANEAXkDTwGZA10BzAMAAf8DXAHnA10BzwM9AWgIAUwAA0YBgAMAAf8D + AAH/AwAB/wNGAYAEAANGAYADAAH/A0YBgCMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB + /wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB + /wMAAf8DAAH/AwAB/wMAAf8DAAH/AUcCRgGAFAADHwEsAzYBWANVAawBAAHBAQAB/wEAAb0BAAH/AQAB + twGkAf8CAAGEAf8DAAH/AwAB/wGjAgAB/wGSAkAB/QFfAjIB+wFfAlsB2AFXAlUBtAFIAkcBgwM0AVMD + JgE4AxUBHAMqAUADOwFjAyYBOQMMAQ8DBgEIGAADXQHcA2IB7gMAAf8DAAH/AagBsAGrAf8BqAIAAf8B + qQIAAf8DAAH/AwAB/wMAAf8DAAH/AZkCAAH/AegCAAH/AysB/ANcAfgBZQJcAecBYQJcAdYDWQHBA1UB + rANXAbwDXQHMA2EB5gMAAf8DYAHzA1wB5wNHAYMDFgEeAwwBDzQAAyoBQANGAYADKgFABAADKgFAA0YB + gANGAYADRgGAA1kBwAMAAf8DRgGABAADRgGAAwAB/wNGAYAgAAG/AYABAAH/AdYBqQEAAf8B7QHSAbsB + /wHRAbkBpQH/AbUBoAGPAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wGhAYwBAAH/AcUBqAEAAf8B6AHEAagB + /wHoAcMBpgH/AecBwgGkAf8B5gHBAaIB/wHlAb8BoAH/AeUBvgGfAf8B5QG9AZ0B/wHKAacBAAH/Aa8B + kQEAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wG7AZgBAAH/AawCAAH/AZwCAAH/AUcCRgGAFAADGwEmAzAB + TAJUAVMBpgGoAegBAAH/AQABwwEAAf8BAAGdAbsB/wIAAcAB/wIAAcQB/wMAAf8BlwIAAf8BlAIAAf8B + kAIAAf8BlAIAAf8BlwIAAf8DXgHSA1MBpQNAAW8DJgE4A0YBfwJcAVkBxgNBAXIDFQEdAwwBDxgAA1kB + uwNeAd0DAAH/AwAB/wGnAagBnQH/AbYCAAH/AcUCAAH/AwAB/wIAAYMB/wMAAf8DAAH/AwAB/wHYAgAB + /wHnAgAB/wH2AgAB/wH2AgAB/wH1AgAB/wHXAgAB/wG4AgAB/wMAAf8DAAH/AwAB/wGSAYsBkgH/AwAB + /wMAAf8DUAGdAycBOgMVAR00AANGAYADAAH/A0YBgAQAA0YBgAMAAf8DRgGABAADRgGAAwAB/wNGAYAE + AANGAYADAAH/A0YBgCAAAcEBggEAAf8B1wGrAQAB/wHtAdMBvAH/Ad0BwQGoAf8BzQGuAZUB/wGfAYQB + AAH/AwAB/wMAAf8DAAH/AZgCAAH/Ab8BmwEAAf8B0QGoAQAB/wHiAbUBAAH/AeIBtAEAAf8B4QGyAQAB + /wHgAbABAAH/Ad8BrgEAAf8B3gGtAQAB/wHeAasBAAH/AdABnwEAAf8BwgGTAQAB/wGWAgAB/wMAAf8D + AAH/AwAB/wGdAgAB/wHPAagBAAH/AbUCAAH/AZsCAAH/AUcCRgGAFAADEwEaAyMBMwNQAZoBAAG1AQAB + /wEAAYIBnQH/AgAB0QH/AgAB0wH/AgAB1QH/AgABnwH/AwAB/wMAAf8BjwIAAf8BlAIAAf8BlwIAAf8B + YwJaAekDXgHSA1YBtgNQAZoDWQG+A2AB4wNOAZgDMQFNAxwBJxgAA0ABcQNYAbgDAAH/AwAB/wMAAf8B + kgIAAf8B0QIAAf8BkAIAAf8DAAH/AwAB/wMAAf8DAAH/AdUCAAH/AeYCAAH/AfcCAAH/AfgCAAH/AfcC + AAH/AeUCAAH/AdMCAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/A1MBqgM0AVQDHgEqNAADRgGAAwAB + /wNZAcADRgGAA1kBwAMAAf8DWQHAA0YBgANGAYADRgGAA0YBgANGAYADRgGAA0YBgAMqAUAgAAHCAYQB + AAH/AdgBrAEAAf8B7QHTAbwB/wHpAcgBqwH/AeUBvAGaAf8B4wG4AZMB/wHhAbMBjAH/AeABsQGJAf8B + 3wGvAYUB/wHeAa0BAAH/Ad0BqgEAAf8B3AGoAQAB/wHbAaYBAAH/AdsBpAEAAf8B2gGhAQAB/wHZAZ8B + AAH/AdgBnQEAAf8B1wGbAQAB/wHWAZgBAAH/AdUBlgEAAf8B1AGUAQAB/wHUAZIBAAH/AdMBkAEAAf8B + 0wGQAQAB/wHTAY8BAAH/AdsBowEAAf8B4gG3AZYB/wG+AgAB/wGZAgAB/wFHAkYBgBQAAwoBDQMTARoD + SwGNAQABgQHRAf8CAAHcAf8CAAHmAf8CAAHmAf8CAAHmAf8CAAHcAf8CAAHSAf8DAAH/AY4CAAH/AZMC + AAH/AZcCAAH/AZcCAAH/AZcCAAH/A0AB/QJfAV4B+wNAAf0BiQIAAf8DWQG+A0YBfQMqAT8YAAMcAScD + TQGTAwAB/wMAAf8DAAH/AwAB/wHcAZUBAAH/AbwCAAH/AZsCAAH/AwAB/wMAAf8DAAH/AdEBmgEAAf8B + 5QGlAQAB/wH4AbABAAH/AfkBrAEAAf8B+QGnAQAB/wHzAgAB/wHtAgAB/wMAAf8CAAGDAf8DAAH/AwAB + /wMAAf8BiQGCAYgB/wNWAbYDPwFtAyUBNzQAA0YBgAMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8D + RgGABAADRgGAAwAB/wNGAYAoAAHDAYYBAAH/AdQBpAEAAf8B4wHBAaIB/wHnAcUBqAH/AeoByQGuAf8B + 5wHCAaIB/wHkAbkBlQH/AeIBtgGQAf8B4AGyAYoB/wHfAbABAAH/Ad4BrQEAAf8B3gGrAQAB/wHdAakB + AAH/AdwBpwEAAf8B2wGkAQAB/wHaAaIBAAH/AdkBoAEAAf8B2QGeAQAB/wHYAZsBAAH/AdgBnQEAAf8B + 2AGeAQAB/wHbAaMBAAH/AdwBqAEAAf8B3AGnAQAB/wHcAaYBAAH/AdgBpAEAAf8B0gGiAQAB/wG1AgAB + /wGXAgAB/wFHAkYBgBQAAwYBBwMKAQ0DPgFrA10ByQJbAWEB5AIAAbMB/wIAAcsB/wIAAeMB/wIAAdQB + /wIAAcQB/wIAAYMB/wGQAgAB/wGUAgAB/wGXAgAB/wGXAgAB/wGXAgAB/wGAAUIBQQH+A0AB/QGAAUIB + QQH+AZACAAH/A14B0gNSAaQDMwFSGAADDwEUA0EBcwNdAdEDYAHoAwAB/wGjAgAB/wHeAZ4BAAH/AboC + AAH/AZUCAAH/AYUCAAH/AwAB/wGrAgAB/wHiAZ4BAAH/Ae4BnAEAAf8B+QGbAQAB/wH5AgAB/wH5AgAB + /wHyAgAB/wHrAgAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AaEBpQGkAf8DXAHGA0sBjQMuAUc0AAMqAUAD + RgGAA0YBgANGAYADWQHAAwAB/wMAAf8DAAH/A0YBgAQAAyoBQANGAYADKgFAKAABxAGHAQAB/wHPAZsB + AAH/AdkBrgGHAf8B5AHCAaQB/wHuAdYBwQH/AeoBywGwAf8B5gG/AZ4B/wHkAboBlgH/AeEBtAGOAf8B + 4AGyAYsB/wHfAbABiAH/Ad8BrgGFAf8B3gGsAYIB/wHdAaoBAAH/AdwBpwEAAf8B2wGlAQAB/wHaAaIB + AAH/AdoBoAEAAf8B2QGeAQAB/wHbAaMBAAH/AdwBqAEAAf8B4QG0AQAB/wHlAb8BoAH/AeUBvgGeAf8B + 5QG8AZwB/wHUAaQBAAH/AcIBjAEAAf8BrAIAAf8BlQIAAf8BRwJGAYAcAAMvAUkDTQGSA1wByAF2AoAB + /gIAAa8B/wIAAd8B/wIAAcsB/wEAAZsBtgH/AQABkwGdAf8BkgGKAYQB/wGVAgAB/wGXAgAB/wGXAgAB + /wGXAgAB/wGXAgAB/wGXAgAB/wGXAgAB/wGXAgAB/wFlAl8B5QNdAcoDOwFlGAAEAQMzAVIDUQGiA10B + 0QHOAZ8BAAH/AdcBowEAAf8B4AGmAQAB/wG3AgAB/wGOAgAB/wG7AgAB/wHoAacBAAH/Ae0BpAEAAf8B + 8gGhAQAB/wH2AZMBAAH/AfoBhQEAAf8B+QIAAf8B+AIAAf8B8QIAAf8B6QIAAf8DAAH/AwAB/wMAAf8D + AAH/AwAB/wG4AcgBwAH/A2EB1gNVAawDNQFWRAADRgGAAwAB/wMAAf8DAAH/A0YBgDgAAkcBRgGAAloB + WQHAAc8BmwEAAf8B2QGuAQAB/wHjAcEBoQH/Ac4BkQEAAf8BuQIAAf8BvwIAAf8BxAGeAQAB/wHSAakB + AAH/AeEBswGMAf8B0QGlAQAB/wHBAZcBAAH/AbgCAAH/Aa8CAAH/Aa4CAAH/Aa0CAAH/AcQCAAH/AdoB + oQEAAf8BxgIAAf8BsgIAAf8BkwIAAf8DAAH/AwAB/wMAAf8BkQIAAf8BrgIAAf8BWgJZAcABRwJGAYAD + KgFAHAADGwElAy8BSQNIAYMBVwJYAbwCWwFhAd4CAAHGAf8CAAG7Af8BAAGjAa8B/wEAAZcBmgH/AZIB + igGEAf8BlQIAAf8BlwIAAf8BlwIAAf8BlwIAAf8BlwIAAf8BlwIAAf8BlwIAAf8BlwIAAf8BZwJaAfIB + ZQJfAeUDTgGUAywBQwMZASIQAAQBAykBPQNEAXkBWgJYAb0DAAH/AwAB/wMAAf8BlwIAAf8BvgIAAf8B + zQIAAf8B3AIAAf8B5QIAAf8B7gIAAf8B9AIAAf8B+gIAAf8B7wIAAf8B5AIAAf8BrQIAAf8DAAH/AwAB + /wMAAf8DAAH/AwAB/wMAAf8CAAGkAf8DYQHrA2EB1gNNAZMDMwFQAx0BKDwAA0YBgAMAAf8DAAH/AwAB + /wNGAYA8AAJHAUYBgAHEAYcBAAH/Ac4BmQEAAf8B1wGrAYEB/wGxAgAB/wGLAgAB/wGZAgAB/wGmAYgB + AAH/AcQBnwEAAf8B4gG2AZAB/wHDAZwBAAH/AaMBgQEAAf8BkgIAAf8BgQIAAf8BgQIAAf8BgAIAAf8B + rgIAAf8B2wGkAQAB/wGxAgAB/wGHAgAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AZoCAAH/AUcCRgGALAAD + KQE9A0QBeQNYAbwBhwGlAa0B/wGKAagBqwH/AYwBqwGoAf8BjwGbAZYB/wGSAYoBhAH/AZUCAAH/AZcC + AAH/AZcCAAH/AZcCAAH/AZcCAAH/AZcCAAH/AZcCAAH/AZcCAAH/AZcCAAH/AZcCAAH/AVoCVwHCA0kB + hQMsAUMUAAMdASgDMwFQA1IBqAMAAf8DAAH/AwAB/wMAAf8B7gIAAf8B3wIAAf8B0AIAAf8B3QIAAf8B + 6QIAAf8B8gIAAf8B+gIAAf8B5QIAAf8BzwIAAf8DAAH/AwAB/wMAAf8CAAG4Af8DAAH/AwAB/wMAAf8C + AAGIAf8CAAGnAf8BugHPAcYB/wNfAdADUgGgAzMBUDwAA0YBgAMAAf8DAAH/AwAB/wNGAYA8AAMqAUAC + RwFGAYACWgFZAcABzwGbAQAB/wG2AgAB/wGdAgAB/wGaAgAB/wGWAgAB/wG9AYABAAH/AeQBuwGXAf8B + xQGgAQAB/wGlAYUBAAH/AZ0CAAH/AZQCAAH/AYsCAAH/AYECAAH/AbACAAH/Ad0BqgEAAf8BsgIAAf8B + hwIAAf8DAAH/AwAB/wNZAcADRgGAAUcCRgGAAUcCRgGAAyoBQCwAAyQBNQM9AWgDVwG0AYoBqAGrAf8B + jAKjAf8BjgGeAZoB/wGTAgAB/wGXAgAB/wGYAgAB/wGYAgAB/wGYAgAB/wGXAgAB/wGXAgAB/wGXAgAB + /wGXAgAB/wGXAgAB/wGXAgAB/wGXAgAB/wFfAlsB0ANSAaEDMwFRFAADIQEvAzgBXQNVAa4DAAH/AwAB + /wMAAf8BqwIAAf8B8AIAAf8B4wIAAf8B1wIAAf8B3AIAAf8B4QIAAf8B3QIAAf8B2QIAAf8BoQIAAf8D + AAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wNgAegDXwHQA0UBfAMdASgD + DwEUNAADRgGAAwAB/wNZAcADRgGAA0YBgANGAYADKgFAPAACRwFGAYABxgGLAQAB/wG7AZUBAAH/Aa8B + nwGQAf8BmwIAAf8BhgIAAf8BtgIAAf8B5gG/AZ4B/wHHAaQBAAH/AacBiQEAAf8BpwGIAQAB/wGmAYYB + AAH/AZQCAAH/AYICAAH/AbECAAH/Ad8BrwGGAf8BswIAAf8BhwIAAf8DAAH/AwAB/wNGAYA8AAMfASwD + NgFXA1YBqwGMAasBqAH/AY4BngGaAf8BkAGRAYsB/wGWAgAB/wGbAgAB/wGaAgAB/wGYAgAB/wGYAgAB + /wGXAgAB/wGXAgAB/wGXAgAB/wGXAgAB/wGXAgAB/wGXAgAB/wGXAgAB/wFhAlsB3gNYAbwDOQFeFAAD + JAE1Az4BagNXAbQDgAH+AaYCAAH/AcwCAAH/Ad8CAAH/AfEBiAEAAf8B5wIAAf8B3QIAAf8B2wIAAf8B + 2AIAAf8ByAIAAf8BuAIAAf8DAAH/AwAB/wMAAf8BiAIAAf8DAAH/AwAB/wMAAf8BmgIAAf8BngIAAf8B + ogIAAf8DAAH/AwAB/wMAAf8BkwGgAZgB/wNUAagDMwFQAx0BKDQAA0YBgAMAAf8DRgGABAADRgGAAwAB + /wNGAYA8AAJHAUYBgAHGAYoBAAH/AcYBmQEAAf8BxQGoAYwB/wG/AYkBAAH/AbkCAAH/AdEBmQGEAf8B + 6QHIAawB/wHZAboBhAH/AckBrAEAAf8ByQGrAQAB/wHIAaoBAAH/Ab8BiAEAAf8BtgIAAf8BzQGSAQAB + /wHjAbwBmgH/AcoBigEAAf8BsAIAAf8BgwIAAf8DAAH/AUcCRgGAPAADHwEsAzYBVwFWAlQBqwGQAgAB + /wGUAgAB/wGXAgAB/wGaAgAB/wGdAgAB/wGcAgAB/wGbAgAB/wGaAgAB/wGZAgAB/wGYAgAB/wGXAgAB + /wGXAgAB/wGXAgAB/wGXAgAB/wGXAgAB/wFhAlsB3gNYAbwDOQFeFAADLgFHA0wBjgNaAccBjwGMAQAB + /wGqAgAB/wHFAgAB/wHbAgAB/wHwAZMBAAH/AbACAAH/AwAB/wGWAgAB/wG9AgAB/wG1AgAB/wGsAgAB + /wGSAgAB/wMAAf8BlgIAAf8BtgIAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wGVAgAB/wMAAf8DAAH/AoAB + XwH+AysB/ANZAb4DRwGAAyoBQDQAA0YBgAMAAf8DWQHAA0YBgANGAYADRgGAAyoBQDwAAkcBRgGAAcYB + iQEAAf8B0AGdAQAB/wHaAbEBiAH/AeMBwQGgAf8B6wHRAbgB/wHsAdEBuQH/AewB0QG6Af8B6wHQAbkB + /wHqAc8BtwH/AeoBzgG1Af8B6gHNAbMB/wHqAc0BtAH/AeoBzQG0Af8B6QHLAbEB/wHnAckBrgH/AeAB + vAGdAf8B2AGuAYwB/wHBAgAB/wGpAgAB/wFHAkYBgDwAAx8BLAM2AVcBVgJUAasBlAIAAf8BmQIAAf8B + ngIAAf8BngIAAf8BngIAAf8BngIAAf8BngIAAf8BnAIAAf8BmgIAAf8BmQIAAf8BlwIAAf8BlwIAAf8B + lwIAAf8BlwIAAf8BlwIAAf8BYQJbAd4DWAG8AzkBXhQAAzYBWQNWAbIDXgHZAZ8BqgGUAf8BrgIAAf8B + vQIAAf8B1gIAAf8B7wGeAQAB/wMAAf8DAAH/AwAB/wGiAgAB/wGhAgAB/wGgAgAB/wHHAgAB/wHtAgAB + /wHoAgAB/wHjAgAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AYcCAAH/AaACAAH/AbkBggEAAf8DKwH8A1wB + +ANdAdQDVQGvAzYBWDQAA0YBgAMAAf8DAAH/AwAB/wNGAYBEAAMqAUACRwFGAYACWgFZAcAB0AGdAQAB + /wHUAaUBAAH/AdgBrAEAAf8B2AGsAQAB/wHXAasBAAH/AdYBqQEAAf8B1QGoAQAB/wHUAYcBAAH/AdMC + AAH/AdICAAH/AdECAAH/AdACAAH/Ac4CAAH/AckCAAH/AcQCAAH/AVoCWQHAAUcCRgGAAyoBQDwAAxUB + HQMmATgDTAGOA2AB4wNlAfEBngIAAf8BngIAAf8BngIAAf8BngIAAf8BngIAAf8BnQIAAf8BnAIAAf8B + mwIAAf8BmgIAAf8BmQIAAf8BlwIAAf8BlwIAAf8BlwIAAf8BYQJbAd4DWAG8AzkBXhQAAycBOgNCAXQD + WAG6Ab0BlQEAAf8BzQIAAf8B3QIAAf8B5AIAAf8B7AIAAf8BngIAAf8DAAH/AwAB/wGnAgAB/wGlAgAB + /wGjAgAB/wGNAgAB/wMAAf8BXwIhAfsBbQJRAfcBZQJTAfQDZQHxA1wB+AMAAf8DAAH/AwAB/wMAAf8D + AAH/AYABYQE5Af4DKwH8AV4CWwHTA1MBqQM1AVU0AANGAYADAAH/AwAB/wMAAf8DRgGATAACRwFGAYAB + xgGJAQAB/wHFAYgBAAH/AcQBhwEAAf8BwwGGAQAB/wHCAYQBAAH/AcEBggEAAf8BvwGAAQAB/wG+AgAB + /wG8AgAB/wG6AgAB/wG4AgAB/wG2AgAB/wG0AgAB/wGyAgAB/wGwAgAB/wFHAkYBgEQAAwoBDQMTARkD + QAFwAVwCWQHGA2AB4wGeAgAB/wGeAgAB/wGeAgAB/wGeAgAB/wGeAgAB/wGeAgAB/wGeAgAB/wGdAgAB + /wGcAgAB/wGaAgAB/wGXAgAB/wGXAgAB/wGXAgAB/wFhAlsB3gNYAbwDOQFeFAADFAEbAyUBNgJQAU8B + mwHaAYABAAH/AesBkQEAAf8B/AGiAQAB/wHyAgAB/wHoAgAB/wHDAgAB/wGeAgAB/wGlAgAB/wGsAgAB + /wGpAgAB/wGlAgAB/wMAAf8DAAH/A1EB9wNZAe8DWgHpA14B4gNlAfEDAAH/AwAB/wMAAf8DAAH/AwAB + /wMAAf8BhAIAAf8DXQHRA1IBowMzAVI0AANGAYADAAH/AwAB/wMAAf8DRgGATAADKgFAAkcBRgGAAkcB + RgGAAkcBRgGAAkcBRgGAAkcBRgGAAkcBRgGAAkcBRgGAAUcCRgGAAUcCRgGAAUcCRgGAAUcCRgGAAUcC + RgGAAUcCRgGAAUcCRgGAAUcCRgGAAyoBQEQAAwYBBwMKAQ0DJwE6AzwBZgFPAk4BlwNaAccDYAHjAY8C + AAH/AZcCAAH/AZ4CAAH/AZ4CAAH/AZ4CAAH/AZ4CAAH/AZ0CAAH/AZwCAAH/AZoCAAH/AZkCAAH/AZgC + AAH/AWECWwHeA1gBvAM5AV4UAAMLAQ4DFAEbAzkBXwNRAaICXwFbAdABvgGRAQAB/wHSAYEBAAH/AeYC + AAH/AdMCAAH/AcACAAH/AbICAAH/AaUCAAH/A10B3wNaAb8DVQGvA1EBngNMAZADRwGBA0cBgANGAX0D + VgGwA2AB4wNiAe4DTQH6A0AB/QMAAf8DAAH/AwAB/wNgAeADWQHBAzoBYTQAAyoBQANGAYADRgGAA0YB + gAMqAUDcAAMCAQMDBAEFAzABSgNMAY4BXAJZAcYBgAFsAV8B/gGPAgAB/wGeAgAB/wGeAgAB/wGeAgAB + /wGeAgAB/wGeAgAB/wGeAgAB/wGdAgAB/wGbAgAB/wGYAgAB/wFhAlsB3gNYAbwDOQFeHAADGQEiAy0B + RANSAaEDgAH+AbIBsQGkAf8B5AHiAc0B/wHjAeYBzwH/AeEB6QHRAf8BvwHEAa8B/wGdAZ8BjQH/A1oB + vwNGAX8DOQFeAykBPQMdASgDDwETAxEBFgMSARgDQAFvA1wBxgNeAd0DZQH0A00B+gMAAf8DAAH/AwAB + /wNZAe8DXQHfA0ABcP8AJQAHAgEDAxsBJQMuAUcDRAF4AVQCUgGoAV8CWwHQAW0CUQH3AV8CIQH7AZ4C + AAH/AZ4CAAH/AZ4CAAH/AZ4CAAH/AZ4CAAH/AZ0CAAH/AZsCAAH/A14B3QNXAbkDOAFdHAADJQE2Az8B + bANOAZQDWAG8A14B0gNgAegDZQH0AecB7gHZAf8B1QHbAccB/wHDAcgBtQH/A1UBrAM2AVgDKAE8AxcB + HwMPARQDCAEKAwkBCwMJAQwDJgE4AzsBYwNEAXoDTAGQA1MBqgNZAcMDXwHVA2EB5gNZAcMDUgGgAzMB + UTwAAyoBQANGAYADKgFA7AADHQEpAzMBUQNSAaABYgJZAe8BbQJRAfcBngIAAf8BngIAAf8BngIAAf8B + ngIAAf8BngIAAf8BngIAAf8BngIAAf8DYAHbA1YBtgM4AVscAAMwAUoDTgGUA0kBhwNEAXkDUwGlA18B + 0ANgAegB7AHzAeAB/wHqAfIB3gH/AegB8AHcAf8DTgGYAyIBMQMTARkcAAMRARYDHwEsAzcBWgNJAYcD + UwGqA1sBzQNPAZcDOgFhAyIBMTwAA0YBgAMAAf8DRgGA7AADEAEVAx0BKQM2AVkDSgGJA1gBtwNhAeYD + YAHzAZ4CAAH/AZ4CAAH/AZ4CAAH/AWICWQHvA10B3wNSAaMDPQFnAyQBNBwAAxwBJwMxAU0DMAFMAzAB + SgM2AVkDPQFoA0YBfwNOAZUDTgGVA04BlAM2AVcDEwEZAwoBDRwAAwkBCwMRARYDIgExAzABSgM3AVoD + PQFpAzEBTQMiATEDEwEZPAADKgFAA0YBgAMqAUD0AAMNAREDGQEiA0MBdwNdAcwDYQHmAZ4CAAH/AZ4C + AAH/AZ4CAAH/A10B3wNZAb4DPgFrAxIBFwMJAQwcAAMCAQMDBAEFAwwBEAMTARoDCgENBAADEQEWAx4B + KwMeASoDHQEpAxABFSwAAwYBBwMKAQ0DBwEJAwQBBQMCAQP/AEUAAwcBCQMNAREDKQQ+AWoDUAGaAV0C + WwHKAVwCWQHGAVoCVwHCA00BkgM6AWEDJQE3AwkBDAMFAQYcAAcCAQMDBgEIAwoBDQMGAQcEAAMJAQsD + EQEWAxABFQMQARUDCQELLAADAwEEAwYBBwMEAQUDAgEDBAL/AE0AAwMBBAMGAQcDMQROAZUDSwGNA0kB + hQMtAUQDAgEDBAL/AP8A/wD/AP8AKQADKgFAA0YBgAMqAUBcAAMqAUADRgGAAyoBQLQAAyoBQAJGAUcB + gAJGAUcBgAJGAUcBgAJGAUcBgAJGAUcBgAMqAUBcAAMqAUADRgGAAyoBQCQAAyoBQANGAYADRgGAA0YB + gAMqAUAcAANGAYADAAH/A0YBgFwAA0YBgAMAAf8DRgGAtAACRgFHAYACAAHEAf8CAAHNAf8CAAHVAf8C + AAHRAf8CAAHNAf8CRgFHAYBcAANGAYADAAH/A0YBgCQAA0YBgAMAAf8DAAH/AwAB/wNGAYAcAANGAYAD + AAH/A0YBgBwAAyoBQANGAYADKgFALAADKgFAA0YBgANGAYADRgGAAyoBQFQAAyoBQAFGAkcBgAMqAUBU + AAJGAUcBgAIAAccB/wIAAdAB/wIAAdgB/wJZAVoBwAJGAUcBgAMqAUAEAAMqAUADRgGAAyoBQDwAAyoB + QANGAYADRgGAA0YBgANZAcADAAH/A0YBgCQAA0YBgAMAAf8DAAH/AwAB/wNGAYAcAANGAYADAAH/A0YB + gBwAA0YBgAMAAf8DRgGALAADRgGAAwAB/wNGAYBcAAFGAkcBgAEAAZkC/wFGAkcBgFQAAkYBRwGAAgAB + yQH/AgAB0gH/AgAB2wH/AkYBRwGADAADRgGAAwAB/wNGAYA8AANGAYADAAH/AwAB/wMAAf8DAAH/AwAB + /wNGAYAkAANGAYADAAH/AwAB/wMAAf8DRgGAHAADRgGAAwAB/wNGAYAUAAMqAUADRgGAA1kBwAMAAf8D + RgGAJAADKgFAA0YBgANGAYADRgGAAyoBQAwAAyoBQAFGAkcBgAFGAkcBgAFGAkcBgAMqAUA8AAFGAkcB + gAEAAZkC/wFGAkcBgFQAAkYBRwGAAgAByQH/AgABygH/AgABywH/AkYBRwGABAADKgFAA0YBgANGAYAD + RgGAA0YBgAFGAUcBRgGAAyoBQDQAA0YBgAMAAf8DAAH/AwAB/wMAAf8DAAH/A0YBgAQAAyoBQAJGAUcB + gAJGAUcBgAJGAUcBgAJGAUcBgAJGAUcBgAJGAUcBgAJGAUcBgANZAcADAAH/AwAB/wMAAf8DRgGAHAAD + RgGAAwAB/wNGAYAUAANGAYADAAH/AwAB/wMAAf8DRgGAJAADRgGAAwAB/wNGAYAUAAFGAkcBgAEAAZIB + 8AH/AQABogH3Af8BAAGxAf4B/wFGAkcBgDwAAUYCRwGAAQABmQL/AUYCRwGAVAACRgFHAYACAAHJAf8C + AAHCAf8CAAG7Af8CRgFHAYAEAANGAYADAAH/A0YBgAQAAUYBRwFGAYABAAGLAQAB/wFGAUcBRgGANAAD + RgGAAwAB/wMAAf8DAAH/AwAB/wMAAf8DRgGABAACRgFHAYACAAGPAf8CAAGYAf8CAAGhAf8CAAGTAf8C + AAGEAf8CAAGJAf8CAAGOAf8DAAH/AwAB/wMAAf8DAAH/A0YBgBwAA0YBgAMAAf8DRgGADAADKgFAA0YB + gANGAYADRgGAA0YBgANGAYADKgFAHAADKgFAA0YBgANZAcADAAH/A0YBgBQAAUYCRwGAAQABoQH3Af8B + AAGqAfsB/wEAAbIC/wFZAloBwANHAYADKgFAHAADKgFAA0cBgANHAYADRwGAA0cBgANHAYABWQJaAcAB + AAGlAv8BWQJaAcADRwGAAyoBQDwAAyoBQANGAYADRgGAA0YBgAJZAVoBwAIAAagB/wIAAYMB/wMAAf8D + WQHAA0YBgANZAcADAAH/A0YBgAQAAyoBQAFGAUcBRgGAAUYBRwFGAYABRgFHAUYBgAFGAUcBRgGAAUYB + RwFGAYADKgFAJAADRgGAAwAB/wMAAf8DAAH/AwAB/wMAAf8BWQJaAcABRgJHAYACWQFaAcADAAH/AwAB + /wMAAf8DAAH/AwAB/wMAAf8CAAGlAf8CAAGJAf8DAAH/AwAB/wMAAf8BRgJHAYAEAAMqAUADRgGAAyoB + QAwAA0YBgAMAAf8DRgGADAADRgGAAwAB/wNGAYAsAANGAYADAAH/AwAB/wMAAf8DRgGAFAABRgJHAYAB + AAGwAf4B/wEAAbIC/wGAAbMC/wGAAbMC/wGAAbMC/wNHAYAcAANHAoABswL/AYABswL/AYABswL/AYAB + swL/AYABswL/AQABsgL/AQABsAL/AQABsgL/AYABswL/A0cBgDwAA0YBgAMAAf8DAAH/AwAB/wMAAf8C + AAGGAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DRgGADAABRgFHAUYBgAEAAYsBAAH/AQABjQEAAf8B + AAGOAQAB/wFGAUcBRgGAJAACRgFHAYACAAGAAf8DAAH/AwAB/wMAAf8BiAG8AcIB/wEAAbUBuwH/AQAB + rQGzAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AQABhwG7Af8BAAGtAcoB/wGYAdIB2AH/AQAB + sgG4Af8BAAGSAZcB/wFGAkcBgAQAA0YBgAMAAf8DRgGADAADRgGAAwAB/wNZAcADRgGAA0YBgANGAYAD + WQHAAwAB/wNZAcADRgGAA0YBgANGAYADRgGAA0YBgANGAYADRgGAA0YBgANGAYADRgGAA0YBgANZAcAD + AAH/AwAB/wMAAf8DRgGAFAADKgFAAUYCRwGAAUYCRwGAA0cBgANaAcABgAGzAv8DWgHAA0cBgAMqAUAE + AAMqAUADRwGAA0cBgANHAYADWgHAAYABswL/AYABswL/AYABswL/AYABswL/AYABswL/AQABswL/AQAB + sgL/AQABswL/AYABswL/A1oBwANHAYADRwGAA0cBgAMqAUAkAAMqAUADRgGAA1kBwAMAAf8DAAH/AwAB + /wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/A1kBwAFGAUcBRgGAAyoBQAQAAUYBRwFGAYAB + AAGTAQAB/wEAAZIBAAH/AQABkAEAAf8BRgFHAUYBgCQAAUYCRwGAAgABkQH/AgABhAH/AwAB/wGAAaMB + qAH/AaMB0gHYAf8BAAHIAc0B/wEAAbwBwgH/AQABlwGbAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wIAAYsB + /wEAAa8BzQH/AQABjQGdAf8DAAH/AwAB/wMAAf8BRgJHAYAEAANGAYADAAH/A0YBgAwAA0YBgAMAAf8D + AAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8D + AAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/A0YBgCQAA0cCgAGzAv8BgAGzAv8BgAGzAv8DRwGABAAD + RwKAAbMC/wGAAbMC/wGAAbMC/wGAAbMC/wGAAbMC/wGAAbMC/wGAAbMC/wGAAbMC/wGAAbMC/wGAAbMC + /wGAAbMC/wGAAbMC/wGAAbMC/wGAAbMC/wGAAbMC/wGAAbMC/wGAAbMC/wNHAYAkAANGAYADAAH/AwAB + /wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wEAAYsBAAH/AUYB + RwFGAYAEAAFGAUcBRgGAAQABmwEAAf8BAAGXAQAB/wEAAZIBAAH/AUYBRwFGAYAkAAFGAkcBgAEAAZ0B + oQH/AQABwwHIAf8BuQHoAe4B/wG7AegB7gH/Ab0B6AHuAf8BsQHaAd8B/wGlAcsB0AH/Aa0B1gHbAf8B + tQHgAeYB/wMAAf8DAAH/AwAB/wEAAYoBjwH/AQABsQG3Af8BmwHXAd4B/wMAAf8DAAH/AwAB/wMAAf8D + RgGABAADRgGAAwAB/wNGAYAEAAMqAUADRgGAA0YBgANGAYADRgGAA0YBgANZAcADAAH/AwAB/wMAAf8D + AAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8D + AAH/A1kBwANGAYADKgFAHAADKgFAA0cBgANHAYADRwGAA0cBgANHAYADWgHAAYABswL/AYABswL/AYAB + swL/AYABswL/AYABswL/AYABswL/AYABswL/AYABswL/AYABswL/AYABswL/AYABswL/AYABswL/AYAB + swL/AYABswL/AYABswL/AQABsgL/AQABsAH+Af8BRgJHAYAcAAMqAUADRgGAA1kBwAMAAf8DAAH/AwAB + /wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AQABjQEAAf8BRgFHAUYB + gAQAAUYBRwFGAYABAAGSAQAB/wEAAY4BAAH/AQABigEAAf8BWQFaAVkBwAFGAUcBRgGAAyoBQBwAA0cB + gAEAAcYBywH/AZAB2QHeAf8BvgHrAfEB/wGOAdEB1wH/AQABuAG9Af8BAAGPAZMB/wMAAf8DAAH/AwAB + /wMAAf8DAAH/AgABgQH/AQABuwHAAf8BAAHKAdAB/wGdAdgB3wH/AQABkwGXAf8DAAH/AwAB/wMAAf8D + RgGABAADKgFAA0YBgAMqAUAEAANGAYADAAH/A0YBgAwAA0YBgAMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB + /wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB + /wNGAYAsAANHAoABswL/AYABswL/AYABswL/AYABswL/AYABswL/AYABswL/AYABswL/AYABswL/AYAB + swL/AYABswL/AYABswL/AYABswL/AYABswL/AYABswL/AYABswL/AYABswL/AYABswL/AQABsAH+Af8B + AAGtAfwB/wFGAkcBgBwAA0YBgAMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AQABhAEAAf8B + AAGLAQAB/wEAAZEBAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8BAAGOAQAB/wFGAUcBRgGABAABRgFHAUYB + gAEAAYgBAAH/AQABhQEAAf8BAAGBAQAB/wEAAYYBAAH/AQABiwEAAf8BRgFHAUYBgBwAA0cBgAHCAe4B + 9AH/AcIB7gH0Af8BwgHtAfMB/wEAAboBvwH/AQABhwGLAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wEAAYEB + hAH/AQABtgG6Af8BvgHrAfAB/wGvAeIB6AH/AZ8B2AHfAf8BAAG5Ab8B/wEAAZoBnwH/AwAB/wMAAf8D + RgGAFAADRgGAAwAB/wNZAcADRgGAA0YBgANGAYADWQHAAwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB + /wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DWQHAA0YBgANGAYADRgGAAyoB + QCQAAyoBQAFGAkcBgAFZAloBwAEAAagC/wEAAawC/wEAAa8C/wEAAbEC/wEAAbIC/wEAAbMC/wEAAbMC + /wEAAbMC/wGAAbMC/wGAAbMC/wGAAbMC/wGAAbMC/wGAAbMC/wGAAbMC/wGAAbMC/wGAAbMC/wGAAbMC + /wEAAYUB9wH/AgAB7QH/AlkBWgHAAkYBRwGAAyoBQBQAA0YBgAMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB + /wMAAf8DAAH/AQABowEAAf8BAAGqAQAB/wEAAbEBAAH/AQABjQEAAf8DAAH/AwAB/wMAAf8BAAGEAQAB + /wEAAaYBAAH/AVkBWgFZAcABRgFHAUYBgAFGAUcBRgGAAUYBRwFGAYABWQFaAVkBwAEAAYsBAAH/AVkB + WgFZAcABRgFHAUYBgAMqAUAUAAMqAUADRgGAA1oBwAGkAdMB2wH/AaQB0gHaAf8BpAHQAdgB/wEAAcAB + xwH/AQABsAG1Af8BAAGAAYMB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AQABiwGOAf8BrQHVAdoB/wGvAdwB + 4gH/AbAB4gHpAf8BgAHOAdUB/wEAAboBwAH/AwAB/wMAAf8DWQHAA0YBgAMqAUAMAANGAYADAAH/AwAB + /wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB + /wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wNGAYA0AAFGAkcBgAEAAZoC/wEAAZwC/wEAAZ0C/wEAAaQC + /wEAAaoC/wEAAa4C/wEAAbEC/wEAAbIC/wEAAbIC/wEAAbMC/wGAAbMC/wGAAbMC/wGAAbMC/wGAAbMC + /wGAAbMC/wGAAbMC/wGAAbMC/wGAAbMC/wGAAbMC/wIAAe8B/wIAAd4B/wIAAdkB/wIAAdQB/wJGAUcB + gBQAA0YBgAMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wEAAaMBAAH/AQABsgEAAf8BAAHBAQAB/wEAAckB + AAH/AQAB0AEAAf8BAAHRAQAB/wEAAdEBAAH/AQABywEAAf8BAAHEAQAB/wEAAcEBAAH/AQABvQEAAf8B + AAGzAQAB/wEAAagBAAH/AUYBRwFGAYAEAAFGAUcBRgGAAQABlQEAAf8BRgFHAUYBgBwAA0YBgAMAAf8D + AAH/AYYBtwHCAf8BhgG1AcAB/wGFAbMBvQH/AZoBxgHOAf8BrwHYAd4B/wGYAbsBwAH/AYEBngGiAf8D + AAH/AwAB/wMAAf8DAAH/AwAB/wGcAb8BxAH/Aa4B1gHbAf8BwAHsAfIB/wGwAeMB6gH/AaAB2gHhAf8D + AAH/AwAB/wMAAf8DAAH/A0YBgAwAAyoBQANGAYADWQHAAwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB + /wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wNZAcADRgGAAyoB + QDQAAyoBQAFGAkcBgAFGAkcBgAFGAkcBgAFZAloBwAEAAaIC/wEAAaQC/wEAAaUC/wEAAaYC/wEAAaYC + /wEAAacC/wEAAacC/wEAAakC/wEAAaoC/wEAAa0C/wEAAbEC/wEAAbIC/wEAAbIC/wEAAYYB9wH/AgAB + 7gH/AgAB5AH/AgAB2QH/AgAB1wH/AgAB1AH/AkYBRwGAA0YBgANGAYADRgGAAUYBRwFGAYABRgFHAUYB + gANZAcADAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8BAAGvAQAB/wEAAbwBAAH/AQAByAEAAf8BAAHSAQAB + /wEAAdoBAAH/AQAB3QEAAf8BAAHgAQAB/wEAAdkBAAH/AQAB0gEAAf8BAAHMAQAB/wEAAcQBAAH/AQAB + uQEAAf8BAAGuAQAB/wFGAUcBRgGABAADKgFAAUYBRwFGAYADKgFADAADKgFAAUYCRwGAA0cBgANHAYAD + WgHAAwAB/wEAAaEBpwH/AZMBywHWAf8BjQHFAdIB/wGFAb4BzAH/AQABwwHRAf8BAAHIAdUB/wEAAcMB + zAH/AZoBvQHCAf8BAAGGAYoB/wMAAf8DAAH/AwAB/wMAAf8BkQGxAbYB/wEAAZQBmAH/AwAB/wEAAZYB + mwH/AQABtgG7Af8BAAGSAZcB/wMAAf8DAAH/AwAB/wJGAUcBgBQAA0YBgAMAAf8DAAH/AwAB/wMAAf8D + AAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8D + RgGATAABRgJHAYABAAGZAv8BAAGZAv8BAAGZAv8BAAGZAv8BAAGZAv8BAAGaAv8BAAGbAv8BAAGeAv8B + AAGgAv8BAAGnAv8BAAGuAv8BAAGwAv8BAAGxAf4B/wIAAe4B/wIAAd0B/wIAAdkB/wIAAdQB/wIAAdQB + /wIAAdQB/wJGAUcBgAMAAf8DAAH/AwAB/wMAAf8BAAGXAQAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB + /wMAAf8BAAG6AQAB/wEAAcUBAAH/AQABzwEAAf8BAAHaAQAB/wGAAeQBAAH/AYUB6QEAAf8BigHuAYAB + /wEAAecBAAH/AQAB4AEAAf8BAAHWAQAB/wEAAcsBAAH/AQABvwEAAf8BAAGzAQAB/wFGAUcBRgGAHAAB + RgJHAYABAAKlAf8BAAHJAcoB/wGzAewB7wH/AbEB6wHvAf8BrgHqAe8B/wGnAeUB7QH/AaAB3wHqAf8B + kwHUAeMB/wGFAcgB2wH/AQABwAHUAf8BAAG3AcwB/wEAAcoB1wH/AbMB3AHiAf8BmgG9AcIB/wGBAZ0B + oQH/AwAB/wMAAf8DAAH/AYUBowGnAf8DAAH/AwAB/wMAAf8BAAGRAZUB/wEAAbcBvAH/AakB3QHjAf8B + AAGzAbgB/wEAAYkBjQH/AUYCRwGAFAADRgGAAwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wNZAcAD + RgGAA0YBgANGAYADWQHAAwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wNGAYBMAAFGAkcBgAEAAZkC + /wEAAZkC/wEAAZkC/wEAAZkC/wEAAZkC/wEAAZoC/wEAAZoC/wEAAZwC/wEAAZ4C/wEAAaYC/wEAAa4C + /wEAAbAC/wEAAbIB/gH/AQABggH0Af8CAAHqAf8CAAHiAf8CAAHZAf8CAAHXAf8CAAHUAf8CRgFHAYAD + RgGAA1kBwAMAAf8BWQFaAVkBwAFGAUcBRgGAA1kBwAMAAf8DAAH/AwAB/wMAAf8DAAH/AQABhQEAAf8B + AAG6AQAB/wEAAcUBAAH/AQAB0AEAAf8BAAHbAQAB/wGBAeUBAAH/AYcB6wEAAf8BjQHxAYMB/wEAAekB + AAH/AQAB4gEAAf8BAAHXAQAB/wEAAcwBAAH/AVkBWgFZAcABRgFHAUYBgAMqAUAcAAFGAkcBgAMAAf8B + AAGhAaIB/wG2Ae8B8gH/AYcBsgG1Af8DAAH/AQABpwGtAf8BmwHXAeEB/wGYAdcB5AH/AZQB1gHmAf8B + AAHKAd0B/wEAAb0B0wH/AQABxQHTAf8BoQHLAdMB/wGhAcgBzgH/AaABxAHJAf8BgQGdAaEB/wMAAf8D + AAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8BAAGXAZsB/wG1AeUB6wH/AQABlQGZAf8DAAH/AkYBRwGAFAAD + RgGAAwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wNGAYAMAANGAYADAAH/AwAB/wMAAf8DAAH/AwAB + /wMAAf8DAAH/A0YBgEwAAUYCRwGAAQABmQL/AQABmQL/AQABmQL/AQABmQL/AQABmQL/AQABmQL/AQAB + mQL/AQABmgL/AQABmwL/AQABpQL/AQABrgL/AQABsAL/AQABsgH+Af8BAAGqAfoB/wEAAaEB9gH/AgAB + 6gH/AgAB3gH/AgAB2QH/AgAB1AH/AkYBRwGABAABRgFHAUYBgAEAAZMBAAH/AUYBRwFGAYAEAANGAYAD + AAH/AwAB/wEAAYgBAAH/AQABlQEAAf8BAAGhAQAB/wEAAa0BAAH/AQABuQEAAf8BAAHFAQAB/wEAAdAB + AAH/AQAB2wEAAf8BggHmAQAB/wGJAe0BAAH/AZAB8wGFAf8BiAHrAQAB/wGAAeMBAAH/AQAB2AEAAf8B + AAHNAQAB/wFGAUcBRgGAJAADRgGAAwAB/wMAAf8BuAHyAfQB/wMAAf8DAAH/AwAB/wGWAc8B2AH/AZ0B + 2gHkAf8BowHkAfAB/wEAAdQB5QH/AQABwwHZAf8BAAG/Ac4B/wGPAboBwwH/AacB0gHaAf8BvwHqAfAB + /wHAAesB8QH/AcEB7AHyAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AcEB7AHyAf8DAAH/AwAB + /wNGAYAUAAMqAUADRgGAA1kBwAMAAf8DAAH/AwAB/wMAAf8DAAH/A0YBgAwAAyoBQANGAYADWQHAAwAB + /wMAAf8DAAH/AwAB/wMAAf8DRgGATAADKgFAAUYCRwGAAUYCRwGAAUYCRwGAAVkCWgHAAQABmQL/AQAB + mAH+Af8BAAGWAf0B/wEAAZ4B/gH/AQABpQL/AQABqwL/AQABsQL/AQABsgL/AQABswL/AQABrwH9Af8B + AAGqAfsB/wEAAYMB9QH/AgAB7wH/AgAB6AH/AgAB4gH/AUYCRwGABAADKgFAAUYBRwFGAYADKgFABAAD + KgFAA0YBgANZAcABAAGGAQAB/wEAAZMBAAH/AQABngEAAf8BAAGqAQAB/wEAAbUBAAH/AQABwAEAAf8B + AAHKAQAB/wEAAdMBAAH/AQAB3QEAAf8BAAHiAQAB/wEAAeYBAAH/AQAB4QEAAf8BAAHbAQAB/wEAAdEB + AAH/AQAByAEAAf8BRgFHAUYBgCQAA0cBgAMAAf8BhwGxAbIB/wG5AfQB9QH/AYYBsgG0Af8DAAH/AQAB + pgGsAf8BoQHcAeYB/wGjAeEB6wH/AaQB5AHxAf8BAAHXAecB/wEAAckB3QH/AQABuwHKAf8BAAGtAbYB + /wEAAcMBygH/AagB2AHeAf8BqAHVAdsB/wGpAdEB1wH/AYMBogGmAf8DAAH/AwAB/wMAAf8DAAH/AwAB + /wMAAf8DAAH/A1kBwANGAYADKgFAHAADRgGAAwAB/wMAAf8DAAH/AwAB/wMAAf8DRgGAFAADRgGAAwAB + /wMAAf8DAAH/AwAB/wMAAf8DRgGAXAABRgJHAYABAAGYAf4B/wEAAZYB/QH/AQABkwH7Af8BAAGhAf0B + /wEAAa8C/wEAAbEC/wGAAbMC/wGAAbMC/wGAAbMC/wGAAbMC/wGAAbMC/wGBAbQC/wGBAbQC/wEAAaIB + 9wH/AQABkAHvAf8BRgJHAYAcAAFGAUcBRgGAAQABhAEAAf8BAAGQAQAB/wEAAZsBAAH/AQABpgEAAf8B + AAGwAQAB/wEAAboBAAH/AQABwwEAAf8BAAHLAQAB/wEAAdMBAAH/AQAB1gEAAf8BAAHZAQAB/wEAAdYB + AAH/AQAB0gEAAf8BAAHKAQAB/wEAAcIBAAH/AUYBRwFGAYAkAANHAYABqgLcAf8BsgLpAf8BuQH1AfYB + /wGwAeoB7gH/AaYB3wHlAf8BqQHkAewB/wGrAekB8wH/AagB5wHyAf8BpAHkAfEB/wGYAdkB6QH/AYsB + zgHhAf8BAAG3AcUB/wEAAZ8BqAH/AQABswG6Af8BkAHGAcwB/wGQAb4BxAH/AZABtgG7Af8BpQHNAdIB + /wG6AeMB6QH/AboB4wHpAf8BugHjAekB/wG2Ad8B5QH/AbIB2gHgAf8DAAH/AwAB/wNGAYAkAAMqAUAD + RgGAA0YBgANGAYADRgGAA0YBgAMqAUAUAANGAYADAAH/AwAB/wMAAf8DAAH/AwAB/wNZAcADRgGAA0YB + gANGAYADKgFATAADKgFAAUYCRwGAAUYCRwGAAUYCRwGAAVkCWgHAAgAB7wH/AQABhAH2Af8BAAGwAf4B + /wEAAbIC/wGAAbMC/wEAAbIB/QH/AQABsQH7Af8BAAGxAf0B/wEAAbEB/gH/AgAB+AH/AgAB8QH/AUYC + RwGADAADKgFAAUYBRwFGAYABRgFHAUYBgAFGAUcBRgGAAVkBWgFZAcABAAGIAQAB/wEAAY4BAAH/AQAB + lAEAAf8BAAGfAQAB/wEAAagBAAH/AQABsQEAAf8BAAG5AQAB/wEAAcABAAH/AQABxwEAAf8BAAHKAQAB + /wEAAcwBAAH/AQABygEAAf8BAAHGAQAB/wFZAVoBWQHAAUYBRwFGAYADKgFAJAADRwGAAagC2gH/AbEC + 6AH/AboB9QH3Af8BswHuAfIB/wGsAecB7QH/AawB6AHwAf8BqwHpAfMB/wGoAecB8gH/AaQB5QHxAf8B + lQHUAeIB/wGGAcMB0gH/AQABvwHLAf8BAAG7AcMB/wEAAcEByAH/AZABxwHNAf8DWgHAA0cBgANHAYAD + RwGAA0cBgANHAYADRwGAA0cBgANHAYADRgGAAyoBQFQAA0YBgAMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB + /wMAAf8DAAH/AwAB/wNGAYBcAAJGAUcBgAIAAd4B/wIAAe0B/wEAAawB/AH/AQABsAH+Af8BgAGzAv8B + AAGxAfsB/wEAAa4B9wH/AQABrgH6Af8BAAGtAf0B/wIAAfgB/wIAAfMB/wJGAUcBgAwAAUYBRwFGAYAB + AAGaAQAB/wEAAZUBAAH/AQABkAEAAf8BAAGOAQAB/wEAAYsBAAH/AQABjAEAAf8BAAGNAQAB/wEAAZcB + AAH/AQABoAEAAf8BAAGoAQAB/wEAAa8BAAH/AQABtQEAAf8BAAG7AQAB/wEAAb0BAAH/AQABvwEAAf8B + AAG9AQAB/wEAAboBAAH/AUYBRwFGAYAsAANHAYABpQLXAf8BsAHmAecB/wG6AfUB9wH/AbYB8gH2Af8B + sgHvAfQB/wGvAewB9AH/AasB6QHzAf8BqAHnAfIB/wGkAeUB8QH/AZIBzgHaAf8BgAG3AcIB/wGOAccB + 0AH/AZsB1gHdAf8BlgHPAdYB/wGQAccBzgH/A0cBgHwAA0YBgAMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB + /wMAAf8DWQHAA0YBgAMqAUBcAAJGAUcBgAIAAdoB/wIAAeEB/wIAAekB/wIAAfEB/wEAAaMB+AH/AgAB + 3AH/AgABwAH/AQABgAHdAf8BAAGoAfsB/wFZAloBwAJGAUcBgAMqAUAMAAFGAUcBRgGAAQABmAEAAf8B + AAGSAQAB/wEAAYwBAAH/AVkBWgFZAcABRgFHAUYBgAFGAUcBRgGAAUYBRwFGAYABWQFaAVkBwAEAAZYB + AAH/AQABnQEAAf8BAAGkAQAB/wEAAakBAAH/AQABrgEAAf8BAAGvAQAB/wEAAbEBAAH/AVkBWgFZAcAB + RgFHAUYBgAMqAUAsAANHAYABAAG1AbgB/wEAAckBywH/AacB3QHfAf8BrAHlAekB/wGxAe4B8wH/Aa0B + 6gHxAf8BqAHlAe8B/wGlAeMB7gH/AaEB4QHsAf8BlAHOAdgB/wGGAbsBwwH/AQABvgHGAf8BAAHBAcgB + /wNaAcADRwGAAyoBQHwAA0YBgAMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DRgGAZAACRgFHAYAC + AAHVAf8CAAHVAf8CAAHVAf8CAAHjAf8BAAGSAfAB/wIAAbwB/wIAAYgB/wIAAcAB/wEAAaIB+AH/AUYC + RwGAFAABRgFHAUYBgAEAAZYBAAH/AQABjwEAAf8BAAGHAQAB/wFGAUcBRgGADAABRgFHAUYBgAEAAYsB + AAH/AQABkgEAAf8BAAGYAQAB/wEAAZwBAAH/AQABoAEAAf8BAAGhAQAB/wEAAaIBAAH/AUYBRwFGAYA0 + AAFGAkcBgAEAAZMBmAH/AQABrAGvAf8BkwHEAcYB/wGhAdgB3AH/Aa8B7AHxAf8BqgHnAe4B/wGkAeEB + 6gH/AaEB3wHpAf8BngHcAecB/wGVAc0B1QH/AYsBvgHDAf8BAAG1AbsB/wEAAawBsgH/AUYCRwGAhAAD + KgFAA0YBgANZAcADAAH/AwAB/wMAAf8DAAH/AwAB/wNGAYBkAAMqAUACRgFHAYACRgFHAYACRgFHAYAC + WQFaAcACAAHiAf8CWQFaAcACRgFHAYABRgJHAYABRgJHAYADKgFAFAADKgFAAUYBRwFGAYABWQFaAVkB + wAEAAYkBAAH/AVkBWgFZAcABRgFHAUYBgAMqAUAEAAMqAUABRgFHAUYBgAFGAUcBRgGAAUYBRwFGAYAB + RgFHAUYBgAFGAUcBRgGAAUYBRwFGAYABRgFHAUYBgAMqAUA0AAMqAUABRgJHAYADRwGAA0cBgANaAcAD + AAH/AQABnwGlAf8BlAHIAdAB/wEAAcUBzgH/AQABwQHKAf8BgQHGAcwB/wGyAcwBzgH/A1oBwAFGAkcB + gAMqAUCMAANGAYADAAH/AwAB/wMAAf8DAAH/AwAB/wNGAYB0AAJGAUcBgAIAAdQB/wJGAUcBgCwAAUYB + RwFGAYABAAGLAQAB/wEAAZEBAAH/AQABlwEAAf8BRgFHAUYBgGwAA0YBgAMAAf8DAAH/AYMBrgG2Af8B + AAGqAbIB/wEAAaUBrQH/AQABvwHDAf8B2ALZAf8DRwGAlAADKgFAA0YBgANGAYADRgGAA0YBgANGAYAD + KgFAdAADKgFAAkYBRwGAAyoBQCwAAyoBQAFGAUcBRgGAAUYBRwFGAYABRgFHAUYBgAMqAUBsAAMqAUAD + RgGAAUYCRwGAA0cBgAFGAkcBgAFGAkcBgANHAYADRwGAAyoBQP8A/wD/AP8A/wD/AOIAAyoBQAJGAUcB + gAJGAUcBgANGAYADRgGAA0YBgANGAYADRgGAA0YBgAJGAUcBgANGAYADRgGAA0YBgANGAYADRgGAA0YB + gANGAYADRgGAA0YBgANGAYADRgGAA0YBgAMqAUA0AAMqAUACRgFHAYACRgFHAYACRgFHAYACRgFHAYAC + RgFHAYADRgGAA0YBgAJGAUcBgAJGAUcBgAJGAUcBgAJGAUcBgAJGAUcBgANGAYADKgFARAADKgFAA0YB + gANHAYADRwGAAyoBQNwAAkYBRwGAAgABkgH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wIAAYkB + /wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wNGAYA0AAJGAUcB + gAIAAY4B/wIAAZQB/wIAAZkB/wIAAY8B/wIAAYUB/wMAAf8DAAH/AwAB/wIAAZAB/wIAAZMB/wIAAZUB + /wMAAf8DAAH/A0YBgEQAA0YBgAMAAf8DAAH/AsIByAH/A0cBgFwAAyoBQAFGAkcBgAFGAkcBgAFGAkcB + gAMqAUBsAAJGAUcBgAIAAY8B/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8CAAGYAf8DAAH/AwAB + /wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DRgGANAACRgFHAYACAAGXAf8C + AAGgAf8CAAGoAf8CAAGYAf8CAAGHAf8DAAH/AwAB/wMAAf8CAAGjAf8CAAGkAf8CAAGlAf8DAAH/AwAB + /wNGAYBEAANHAYADAAH/AoEBhQH/ArQBuQH/A1oBwAJGAUcBgANHAYADRwGAA0cBgANHAYADRwGAA0cB + gAMqAUA8AAFGAkcBgAEAAawB3gH/AQABqwHdAf8BAAGpAdwB/wFGAkcBgGwAAkYBRwGAAgABjAH/AwAB + /wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wIAAacB/wMAAf8DAAH/AwAB/wIAAZ4B/wMAAf8DAAH/AwAB + /wMAAf8DAAH/AgABgwH/AwAB/wMAAf8DRgGANAACRgFHAYACAAGfAf8CAAGrAf8CAAG2Af8CAAGgAf8C + AAGJAf8CAAGKAf8CAAGKAf8CAAGgAf8CAAG1Af8CAAG1Af8CAAG1Af8DAAH/AwAB/wNGAYBEAANHAYAC + nAGgAf8CoQGlAf8CpQGqAf8CAAGWAf8CAAGCAf8CAAGhAf8CugG/Af8CwgHHAf8CyQHPAf8CzwHVAf8C + 1QHbAf8DRwGAPAADKgFAAUYCRwGAAVkCWgHAAQABrAHeAf8BWQJaAcABRgJHAYABRgJHAYABRgJHAYAD + KgFAXAADRgGAAwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8CAAGYAf8DAAH/AwAB/wMAAf8C + AAGaAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wIAAZAB/wJZAVoBwANGAYADKgFALAADKgFAA0YBgAJZAVoB + wAIAAacB/wIAAagB/wIAAagB/wIAAZwB/wIAAY8B/wIAAY4B/wIAAY0B/wIAAZcB/wIAAaEB/wIAAaUB + /wIAAagB/wMAAf8DAAH/A0YBgEQAA0cBgAMAAf8DAAH/AwAB/wMAAf8DAAH/AlkBWgHAA0cBgANaAcAD + AAH/AYwBjQGSAf8BswG1AbwB/wNaAcADRgGAAyoBQDwAAUYCRwGAAQABrgHfAf8BAAGtAd8B/wEAAasB + 3gH/AQABqQHdAf8BAAGmAdsB/wFGAkcBgFwAA0YBgAMAAf8DAAH/AgABpwH/AgABrAH/AgABsAH/AgAB + sAH/AgABsAH/AgABnAH/AgABiAH/AgABkwH/AgABngH/AgABmgH/AgABlgH/AgABowH/AgABsAH/AgAB + sAH/AgABsAH/AgABpgH/AgABnAH/AkYBRwGANAADRgGAAwAB/wMAAf8CAAGvAf8CAAGlAf8CAAGaAf8C + AAGYAf8CAAGVAf8CAAGSAf8CAAGPAf8CAAGOAf8CAAGNAf8CAAGUAf8CAAGbAf8CAAGSAf8CAAGIAf8C + RgFHAYBEAANGAYADAAH/AwAB/wMAAf8DAAH/AwAB/wNGAYAEAANGAYADAAH/AwAB/wGQAZQBnAH/AwAB + /wMAAf8DRgGAPAABRgJHAYABAAGvAd8B/wEAAbIB4QH/AQABtAHjAf8BAAGyAeIB/wEAAbAB4QH/AVkC + WgHAAUYCRwGAAyoBQFQAA0YBgAMAAf8DAAH/AgABoAH/AgABnQH/AgABmgH/AgABmwH/AgABnAH/AgAB + mwH/AgABmQH/AgABoAH/AgABpgH/AgABogH/AgABngH/AgABnAH/AgABmwH/AgABmwH/AgABmgH/AgAB + lwH/AgABkwH/AlkBWgHAA0YBgAMqAUAsAAJGAUcBgAMAAf8CAAGAAf8CAAGjAf8DAAH/AwAB/wMAAf8D + AAH/AwAB/wIAAYkB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AgABmgH/AlkBWgHAA0YBgAMqAUA8AAJGAUcB + gAMAAf8DAAH/AwAB/wMAAf8DAAH/A1kBwANGAYADWQHAAwAB/wMAAf8CAAGUAf8DAAH/AwAB/wNGAYA8 + AAFGAkcBgAEAAa8B3wH/AQABtgHjAf8BAAG8AecB/wEAAbsB5wH/AQABugHmAf8BAAGxAeEB/wEAAacB + 3AH/AUYCRwGAVAACRgFHAYACAAGLAf8CAAGSAf8CAAGYAf8CAAGOAf8CAAGDAf8CAAGGAf8CAAGIAf8C + AAGZAf8CAAGqAf8CAAGsAf8CAAGuAf8CAAGqAf8CAAGlAf8CAAGVAf8CAAGFAf8CAAGFAf8CAAGEAf8C + AAGHAf8CAAGKAf8DAAH/AwAB/wNGAYAsAAJGAUcBgAIAAboB/wIAAagB/wIAAZYB/wMAAf8DAAH/AwAB + /wMAAf8DAAH/AgABgwH/AwAB/wMAAf8DAAH/AwAB/wMAAf8CAAGsAf8DAAH/AwAB/wNGAYA8AAJGAUcB + gAIAAZsB/wIAAZUB/wIAAY4B/wIAAZYB/wIAAZ4B/wMAAf8DAAH/AwAB/wIAAZ8B/wIAAZUB/wIAAYsB + /wMAAf8DAAH/A0YBgDwAAyoBQAFGAkcBgAFZAloBwAEAAbYB4wH/AQABvgHoAf8BAAHHAewB/wEAAb0B + 5wH/AQABsgHiAf8BWQJaAcABRgJHAYABRgJHAYABRgJHAYADKgFAPAADKgFAA0YBgAJZAVoBwAIAAZ0B + /wIAAaAB/wIAAaMB/wIAAZ4B/wIAAZkB/wIAAZoB/wIAAZsB/wIAAaQB/wIAAawB/wIAAaYB/wIAAaAB + /wIAAZ4B/wIAAZsB/wIAAZgB/wIAAZYB/wIAAZgB/wIAAZkB/wIAAZsB/wIAAZwB/wMAAf8DAAH/A0YB + gCQAAyoBQAJGAUcBgAJZAVoBwAIAAbMB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB + /wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8CRgFHAYA8AAJGAUcBgAMAAf8DAAH/AwAB/wMAAf8C + AAGUAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wNGAYBEAAFGAkcBgAEAAa8B3wH/AQAB + wQHpAf8BhAHTAfIB/wEAAcgB7QH/AQABvQHnAf8BAAG0AeMB/wEAAaoB3gH/AQABpwHdAf8BAAGkAdsB + /wFGAkcBgDwAA0YBgAMAAf8DAAH/AgABrgH/AgABrgH/AgABrgH/AgABrgH/AgABrgH/AgABrgH/AgAB + rgH/AgABrgH/AgABrgH/AgABoAH/AgABkgH/AgABkQH/AgABkAH/AgABmwH/AgABpgH/AgABqgH/AgAB + rgH/AgABrgH/AgABrgH/AwAB/wMAAf8DRgGAJAACRgFHAYACAAGLAf8CAAGcAf8CAAGsAf8DAAH/AwAB + /wMAAf8DAAH/AwAB/wIAAasB/wMAAf8DAAH/AwAB/wIAAZYB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AgAB + nAH/AkYBRwGAPAADRgGAAwAB/wMAAf8DAAH/AwAB/wIAAYkB/wIAAZwB/wIAAa4B/wMAAf8DAAH/AwAB + /wMAAf8DAAH/AwAB/wNGAYBEAAFGAkcBgAEAAbEB4AH/AQABwAHoAf8BAAHQAfAB/wEAAcwB7wH/AQAB + yAHtAf8BAAHCAesB/wEAAbwB6AH/AQABtQHkAf8BAAGtAeAB/wFZAloBwAFGAkcBgAMqAUA0AANGAYAD + AAH/AwAB/wIAAa8B/wIAAa8B/wIAAa4B/wIAAa4B/wIAAa4B/wIAAasB/wIAAacB/wIAAaoB/wIAAa0B + /wMAAf8DAAH/AwAB/wMAAf8DAAH/AgABogH/AgABqAH/AgABrgH/AgABrgH/AgABrgH/AwAB/wMAAf8C + RgFHAYAkAAJGAUcBgAIAAZIB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8CAAGlAf8DAAH/AwAB + /wMAAf8CAAGnAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wIAAZgB/wJGAUcBgDQAAyoBQAJGAUcBgAJZAVoB + wAMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DWQHAA0YB + gAMqAUA8AAFGAkcBgAEAAbIB4AH/AQABvwHnAf8BAAHMAe4B/wEAAc8B8AH/AYMB0gHyAf8BAAHQAfIB + /wEAAc4B8QH/AQABwgHrAf8BAAG2AeQB/wEAAa4B4AH/AQABpQHbAf8BRgJHAYA0AANGAYADAAH/AwAB + /wIAAa8B/wIAAa8B/wIAAa4B/wIAAa4B/wIAAa4B/wIAAacB/wIAAaAB/wIAAaYB/wIAAawB/wMAAf8D + AAH/AwAB/wGCAgAB/wMAAf8CAAGdAf8CAAGmAf8CAAGuAf8CAAGuAf8CAAGuAf8CAAGmAf8CAAGdAf8C + RgFHAYAkAAJGAUcBgAIAAZkB/wMAAf8DAAH/AwAB/wIAAYoB/wIAAZAB/wIAAZYB/wIAAZoB/wIAAZ4B + /wMAAf8DAAH/AwAB/wIAAbcB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AgABkwH/AkYBRwGANAACRgFHAYAC + AAGoAf8CAAGrAf8CAAGtAf8CAAGZAf8CAAGEAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AgAB + jAH/AgABlAH/AgABmwH/AwAB/wMAAf8DRgGAGAABRgJHAYABRgJHAYABRgJHAYABRgJHAYABRgJHAYAB + RgJHAYABRgJHAYABRgJHAYABRgJHAYABWQJaAcABAAG0AeEB/wEAAcMB6QH/AQAB0gHxAf8BAAHLAfAB + /wEAAcQB7wH/AQABxQHvAf8BAAHFAe8B/wEAAcQB7QH/AQABwgHrAf8BAAG5AeYB/wEAAa8B4QH/AVkC + WgHAAUYCRwGAAUYCRwGAAUYCRwGAAyoBQCQAA0YBgAMAAf8DAAH/AgABrwH/AgABrwH/AgABrgH/AgAB + pQH/AgABnAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8CAAGuAf8C + AAGuAf8CAAGuAf8CAAGoAf8CAAGhAf8CRgFHAYAkAAJGAUcBgAIAAZQB/wMAAf8DAAH/AwAB/wIAAaEB + /wIAAaQB/wIAAaYB/wIAAaQB/wIAAaIB/wMAAf8DAAH/AgABiAH/AgABvgH/AgABiAH/AwAB/wMAAf8D + AAH/AwAB/wIAAZkB/wJGAUcBgCwAAyoBQANGAYACWQFaAcACAAGrAf8CAAGBAf8DAAH/AwAB/wIAAY8B + /wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wIAAY4B/wMAAf8DAAH/A0YBgBkAAcIB + 5wH/AQABwQHnAf8BAAHAAeYB/wEAAb8B5gH/AQABvQHlAf8BAAG8AeUB/wEAAbsB5AH/AQABugHkAf8B + AAG4AeMB/wEAAbcB4wH/AQABtgHiAf8BAAHHAesB/wGGAdcB8wH/AQABxwHvAf8BAAG2AesB/wEAAbkB + 7AH/AQABvAHsAf8BAAHFAe8B/wGAAc4B8QH/AQABxAHsAf8BAAG5AeYB/wEAAbEB4gH/AQABqAHdAf8B + AAGlAdwB/wEAAaIB2gH/AUYCRwGAJAADRgGAAwAB/wMAAf8CAAGvAf8CAAGvAf8CAAGuAf8CAAGcAf8C + AAGKAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wGzAbABrQH/AwAB/wMAAf8DAAH/AwAB/wMAAf8CAAGuAf8C + AAGuAf8CAAGuAf8CAAGpAf8CAAGkAf8CRgFHAYAkAAJGAUcBgAIAAY8B/wIAAZEB/wIAAZIB/wIAAaUB + /wIAAbcB/wIAAbcB/wGYAaUBtgH/AQABmgGuAf8BAAGOAaYB/wIAAaUB/wIAAaQB/wIAAbQB/wG0AbsB + xAH/AgABtAH/AgABowH/AgABqgH/AgABsAH/AgABpwH/AgABngH/AkYBRwGALAADRgGAAwAB/wMAAf8C + AAGtAf8DAAH/AwAB/wMAAf8CAAGZAf8CAAGdAf8CAAGgAf8DAAH/AwAB/wMAAf8CAAGeAf8DAAH/AwAB + /wMAAf8CAAGBAf8CAAGDAf8CAAGFAf8CRgFHAYAZAAHDAegB/wEAAcgB6gH/AQABzQHsAf8BAAHQAe4B + /wEAAdIB7wH/AQAB0QHvAf8BAAHPAe4B/wEAAc4B7gH/AQABzAHtAf8BAAHLAe0B/wEAAckB7AH/AQAB + 0gHwAf8BiAHZAfQB/wEAAdAB8gH/AQABxwHwAf8BAAHHAfAB/wEAAcgB7wH/AQABywHxAf8BAAHPAfEB + /wEAAckB7wH/AQABwwHrAf8BAAG+AekB/wEAAbkB5gH/AQABsgHjAf8BAAGrAd8B/wFZAloBwAFGAkcB + gAMqAUAcAAMqAUADRgGAAlkBWgHAAgABrwH/AgABrwH/AgABrgH/AgABnAH/AgABiQH/AwAB/wMAAf8D + AAH/AwAB/wMAAf8BogGgAZ4B/wMAAf8DAAH/AwAB/wMAAf8DAAH/AgABrgH/AgABrgH/AgABrgH/AgAB + pgH/AgABnQH/AkYBRwGAJAADKgFAAkYBRwGAAlkBWgHAAgABkgH/AgABoQH/AgABrwH/AgABxAH/AckB + zwHXAf8BjAGzAb0B/wEAAZYBogH/AgABoQH/AgABnwH/AQABggGxAf8BugG+AcIB/wGCAYgBtwH/AgAB + rAH/AgABsAH/AgABswH/AgABpgH/AgABmAH/AkYBRwGALAADRgGAAwAB/wMAAf8CAAGnAf8DAAH/AwAB + /wMAAf8CAAGjAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wIAAZoB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AgAB + lQH/AkYBRwGAGQABxAHoAf8BAAHPAe0B/wEAAdoB8gH/AQAB4AH1Af8BkwHmAfgB/wGSAeUB+AH/AZEB + 4wH3Af8BjwHiAfcB/wGNAeAB9gH/AYwB3gH2Af8BigHcAfUB/wGKAdwB9QH/AYoB2wH1Af8BiQHZAfUB + /wGIAdcB9AH/AYYB1QHzAf8BhAHTAfIB/wEAAdEB8gH/AQABzwHxAf8BAAHOAfEB/wEAAcwB8AH/AQAB + ywHwAf8BAAHJAe8B/wEAAb8B6QH/AQABtAHjAf8BAAGsAd8B/wEAAaMB2gH/AUYCRwGAJAACRgFHAYAC + AAGvAf8CAAGvAf8CAAGuAf8CAAGbAf8CAAGHAf8CAAGTAf8CAAGeAf8DAAH/AwAB/wMAAf8CkAGPAf8D + AAH/AwAB/wMAAf8CAAGGAf8CAAGaAf8CAAGuAf8CAAGuAf8CAAGuAf8CAAGiAf8CAAGVAf8CRgFHAYAs + AAJGAUcBgAIAAZEB/wIAAZwB/wIAAacB/wIAAdAB/wH6AfkB+AH/AswBywH/Ap4BnQH/AQABlAGcAf8B + AAGJAZoB/wEAAaUBrQH/A8AB/wGqAbEBugH/AZMBoQG0Af8CAAG1Af8CAAG1Af8CAAGkAf8CAAGSAf8C + RgFHAYAsAAJGAUcBgAIAAYAB/wIAAZAB/wIAAaAB/wIAAacB/wIAAa0B/wIAAa0B/wIAAa0B/wMAAf8D + AAH/AwAB/wMAAf8DAAH/AgABlQH/AgABoQH/AgABrQH/AwAB/wMAAf8DAAH/AgABpQH/AkYBRwGAGAAB + RgJHAYABWQJaAcABAAHUAe8B/wEAAd4B9AH/AZYB6AH5Af8BAAHiAfcB/wEAAdwB9QH/AQAB2gH1Af8B + AAHYAfQB/wEAAdYB9AH/AQAB0wHzAf8BAAHRAfIB/wEAAc8B8gH/AQAB1AHzAf8BiQHYAfQB/wEAAc4B + 7wH/AQABxAHqAf8BAAHCAeoB/wEAAcAB6QH/AQABvwHpAf8BAAG9AegB/wEAAbwB6AH/AQABuwHnAf8B + AAG1AeMB/wEAAa8B4AH/AQABqgHeAf8BAAGlAdsB/wFZAloBwAFGAkcBgAMqAUAcAAJGAUcBgAIAAakB + /wIAAawB/wIAAa4B/wIAAaUB/wIAAZsB/wIAAZsB/wIAAZoB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB + /wMAAf8CAAGYAf8CAAGjAf8CAAGuAf8CAAGvAf8CAAGvAf8DAAH/AwAB/wJGAUcBgCwAAkYBRwGAAwAB + /wMAAf8CAAGxAf8CAAG/Af8CAAHNAf8CAAG3Af8CAAGgAf8CAAGjAf8CAAGlAf8CAAGsAf8BAAGjAbMB + /wIAAbIB/wIAAbEB/wIAAawB/wIAAaYB/wJZAVoBwAJGAUcBgAMqAUAsAAJGAUcBgAIAAYUB/wMAAf8D + AAH/AwAB/wIAAa0B/wIAAa0B/wIAAa0B/wIAAYIB/wMAAf8DAAH/AwAB/wMAAf8CAAGfAf8CAAGnAf8C + AAGuAf8CAAGAAf8DAAH/AwAB/wMAAf8DWQHAA0YBgAMqAUAUAAFGAkcBgAEAAc0B7AH/AQAB2wHzAf8B + mAHpAfkB/wEAAd8B9gH/AQAB1QHzAf8BAAHSAfIB/wEAAc8B8QH/AQABzQHxAf8BAAHKAfAB/wEAAcYB + 7wH/AQABwgHuAf8BAAHOAfEB/wGJAdkB9AH/AQABxwHrAf8BAAG0AeEB/wEAAbMB4QH/AQABsQHgAf8B + AAGwAeAB/wEAAa4B3wH/AQABrQHfAf8BAAGsAd4B/wEAAasB3QH/AQABqQHcAf8BAAGoAdwB/wEAAaYB + 2wH/AQABpQHbAf8BAAGjAdoB/wFGAkcBgBwAAkYBRwGAAgABogH/AgABqAH/AgABrgH/AgABrgH/AgAB + rgH/AgABogH/AgABlQH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wIAAakB/wIAAawB/wIAAa4B + /wIAAa8B/wIAAa8B/wMAAf8DAAH/A0YBgCwAA0YBgAMAAf8DAAH/AgABugH/AgABrgH/AgABoQH/AgAB + ogH/AgABogH/AgABqQH/AgABrwH/AgABqwH/AQABhgGmAf8CAAGqAf8CAAGuAf8CAAGiAf8CAAGWAf8C + RgFHAYA0AAJGAUcBgAIAAYoB/wMAAf8DAAH/AwAB/wIAAa0B/wIAAa0B/wIAAa0B/wIAAa0B/wIAAa0B + /wIAAa4B/wIAAa8B/wIAAawB/wIAAakB/wIAAawB/wIAAa4B/wIAAakB/wIAAaMB/wMAAf8DAAH/AwAB + /wMAAf8DRgGAFAABRgJHAYABAAHKAesB/wEAAdgB8QH/AYwB5QH3Af8BAAHiAfcB/wEAAd4B9gH/AQAB + 1wH0Af8BAAHRAfIB/wEAAc8B8gH/AQABzAHxAf8BAAHIAfAB/wEAAcUB7wH/AQAB0AHyAf8BiwHbAfUB + /wEAAcwB7gH/AQABvQHmAf8BWQJaAcABRgJHAYABRgJHAYABRgJHAYABRgJHAYABRgJHAYABRgJHAYAB + RgJHAYABRgJHAYABRgJHAYABRgJHAYABRgJHAYADKgFAHAADKgFAAkYBRwGAAlkBWgHAAgABrgH/AgAB + qAH/AgABogH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wIAAYIB/wIAAa4B + /wIAAaYB/wIAAZ4B/wJZAVoBwANGAYADKgFALAADKgFAA0YBgANZAcADAAH/AgABhgH/AgABrgH/AgAB + rgH/AgABrgH/AgABsgH/AgABtQH/AgABswH/AgABsQH/AgABqgH/AgABowH/AwAB/wMAAf8CRgFHAYAs + AAMqAUADRgGAAlkBWgHAAgABiQH/AwAB/wMAAf8DAAH/AgABrQH/AgABvwH/AgAB0QH/AgABwQH/AgAB + sgH/AgABpgH/AgABmwH/AgABrAH/AgABvQH/AgABsQH/AgABpQH/AgABpwH/AgABqQH/AwAB/wMAAf8D + AAH/AwAB/wNGAYAUAAFGAkcBgAEAAccB6QH/AQAB1AHvAf8BgAHhAfUB/wGHAeQB9wH/AY4B5gH4Af8B + AAHcAfYB/wEAAdIB8wH/AQAB0AHyAf8BAAHNAfEB/wEAAcoB8AH/AQABxwHvAf8BAAHSAfIB/wGMAdwB + 9QH/AQAB0QHwAf8BAAHGAeoB/wFGAkcBgFQAAkYBRwGAAgABrgH/AgABogH/AgABlgH/AwAB/wMAAf8D + AAH/AgABoAH/AgABlQH/AgABiQH/AgABjAH/AgABjwH/AwAB/wMAAf8DAAH/AgABrQH/AgABnQH/AgAB + jAH/AkYBRwGAPAADRgGAAwAB/wMAAf8CAAG6Af8CAAG6Af8CAAG6Af8CAAG6Af8CAAG6Af8CAAG7Af8C + AAG7Af8CAAGqAf8CAAGYAf8DAAH/AwAB/wNGAYAsAANGAYADAAH/AwAB/wIAAYcB/wMAAf8DAAH/AwAB + /wIAAa0B/wIAAdEB/wH2AfUB9AH/AdcB1gHVAf8BuAG3AbYB/wIAAZ4B/wIAAYYB/wIAAasB/wPQAf8B + qwGvAbYB/wGFAY4BmwH/AgABpQH/AgABrgH/AwAB/wMAAf8DAAH/AwAB/wNGAYAUAAMqAUABRgJHAYAB + WQJaAcABAAHbAfIB/wEAAeIB9gH/AZQB6AH5Af8BAAHeAfcB/wEAAdQB9AH/AQAB0gHzAf8BAAHPAfIB + /wEAAcwB8QH/AQAByQHwAf8BAAHRAfIB/wEAAdkB9AH/AQAB1AHyAf8BAAHPAe8B/wFZAloBwAFGAkcB + gAMqAUBMAAJGAUcBgAMAAf8DAAH/AgABjwH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wIAAZ4B + /wMAAf8DAAH/AwAB/wMAAf8CWQFaAcACRgFHAYADKgFANAADKgFAA0YBgANZAcADAAH/AwAB/wMAAf8D + AAH/AgABoAH/AgABogH/AgABpAH/AgABgQH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DRgGALAADKgFAA0YB + gAJZAVoBwAIAAYgB/wMAAf8DAAH/AwAB/wIAAa4B/wIAAdMB/wH4AfcB9gH/AucB5gH/AdcB1gHVAf8C + AAGtAf8CAAGFAf8CAAG2Af8D5gH/AbkBvAHDAf8BigGSAZ4B/wIAAaAB/wIAAaIB/wMAAf8DAAH/AwAB + /wMAAf8DRgGAHAABRgJHAYABAAHUAe8B/wEAAd8B9AH/AZkB6gH5Af8BAAHgAfcB/wEAAdYB9AH/AQAB + 0wHzAf8BAAHQAfIB/wEAAc4B8QH/AQABywHwAf8BAAHQAfIB/wEAAdUB8wH/AQAB1gHzAf8BAAHXAfMB + /wEAAcwB7QH/AQABwAHnAf8BRgJHAYBMAANGAYADAAH/AwAB/wIAAYgB/wIAAZwB/wIAAa8B/wMAAf8D + AAH/AwAB/wMAAf8DAAH/AgABrAH/AgABpgH/AgABoAH/AwAB/wMAAf8DRgGAPAADRgGAAwAB/wMAAf8D + AAH/AwAB/wMAAf8DAAH/AgABhQH/AgABiQH/AgABjQH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/A0YB + gDQAAkYBRwGAAgABiAH/AwAB/wMAAf8DAAH/AgABrwH/AgAB1AH/AvkB+AH/AvcB9gH/AvUB9AH/AgAB + vAH/AgABgwH/AgABwAH/A/wB/wHGAckBzwH/AY8BlgGhAf8CAAGbAf8CAAGVAf8CAAGPAf8CAAGIAf8D + AAH/AwAB/wNGAYAcAAFGAkcBgAEAAc8B7QH/AQAB3QHzAf8BlgHqAfkB/wEAAeMB+AH/AQAB3AH2Af8B + AAHXAfQB/wEAAdIB8wH/AQAB0AHyAf8BAAHNAfEB/wEAAc4B8gH/AQABzwHyAf8BAAHVAfMB/wEAAdoB + 9AH/AQAB0QHwAf8BAAHIAesB/wFZAloBwAFGAkcBgAMqAUBEAAMqAUADRgGAAlkBWgHAAgABmwH/AgAB + pAH/AgABrAH/AgABgAH/AwAB/wMAAf8DAAH/AwAB/wIAAaYB/wIAAacB/wIAAagB/wMAAf8DAAH/A0YB + gDwAA0YBgAMAAf8DWQHAA0YBgANGAYADRgGAA0YBgAJGAUcBgAJZAVoBwAIAAZUB/wJZAVoBwANGAYAD + RgGAA0YBgANGAYADRgGAAyoBQDQAAyoBQAJGAUcBgANZAcADAAH/AwAB/wMAAf8CAAGVAf8CAAHSAf8C + AAHRAf8CAAHQAf8CAAG0Af8CAAGYAf8CAAG1Af8CAAHQAf8CAAG9Af8CAAGoAf8DAAH/AwAB/wMAAf8D + AAH/A1kBwANGAYADKgFAHAABRgJHAYABAAHKAeoB/wEAAdoB8gH/AZMB6QH5Af8BAAHlAfgB/wEAAeEB + 9wH/AQAB2wH1Af8BAAHUAfMB/wEAAdEB8wH/AQABzgHyAf8BAAHMAfEB/wEAAckB8AH/AQAB0wHzAf8B + igHcAfUB/wEAAdYB8gH/AQAB0AHvAf8BAAHGAeoB/wEAAbsB5AH/AUYCRwGATAACRgFHAYACAAGtAf8C + AAGrAf8CAAGpAf8CAAGnAf8CAAGkAf8CAAGpAf8CAAGtAf8CAAGnAf8CAAGgAf8CAAGoAf8CAAGvAf8D + AAH/AwAB/wNGAYA8AANGAYADAAH/A0YBgBQAAkYBRwGAAgABnQH/AkYBRwGAVAACRgFHAYACAAGDAf8D + AAH/AwAB/wMAAf8CAAGsAf8CAAGsAf8CAAGrAf8CAAGsAf8CAAGtAf8CAAGpAf8CAAGkAf8CAAGqAf8C + AAGvAf8DAAH/AwAB/wMAAf8DAAH/A0YBgCQAAUYCRwGAAQABywHrAf8BAAHXAfAB/wEAAeEB9QH/AQAB + 5AH3Af8BAAHmAfkB/wEAAd8B9gH/AQAB1gH0Af8BAAHUAfQB/wEAAdEB8wH/AQABzwHyAf8BAAHMAfEB + /wEAAc8B8gH/AQAB0gHzAf8BAAHUAfMB/wEAAdYB8gH/AQABzQHuAf8BAAHDAekB/wFZAloBwAFGAkcB + gAMqAUBEAAJGAUcBgAIAAa4B/wIAAbAB/wIAAbMB/wIAAYMB/wMAAf8DAAH/AgABmAH/AgABpgH/AgAB + sgH/AgABqwH/AgABowH/AwAB/wMAAf8DRgGAPAADKgFAA0YBgAMqAUAUAAMqAUACRgFHAYACRgFHAYAC + RgFHAYADKgFATAADKgFAAkYBRwGAA0YBgANGAYACWQFaAcACAAGZAf8CAAGjAf8CAAGtAf8CAAGtAf8C + AAGuAf8CAAGsAf8CAAGpAf8CAAGBAf8DAAH/A1kBwANGAYADRgGAA0YBgAMqAUAkAAFGAkcBgAEAAcwB + 6wH/AQAB0wHuAf8BAAHZAfEB/wEAAeIB9gH/AZoB6wH6Af8BAAHiAfcB/wEAAdgB9AH/AQAB1gH0Af8B + AAHTAfMB/wEAAdEB8gH/AQABzgHxAf8BAAHLAfEB/wEAAcgB8AH/AQAB0gHzAf8BjAHcAfUB/wEAAdQB + 8QH/AQABywHtAf8BAAHCAegB/wEAAbgB4wH/AUYCRwGARAACRgFHAYACAAGuAf8CAAG1Af8BsAG2AbwB + /wMAAf8DAAH/AwAB/wIAAYMB/wIAAaQB/wHGAcUBxAH/AgABrQH/AgABlgH/AwAB/wMAAf8DRgGAZAAC + RgFHAYACAAGWAf8CRgFHAYBcAAJGAUcBgAIAAYYB/wIAAZoB/wIAAa4B/wIAAa4B/wIAAa4B/wIAAa4B + /wIAAa0B/wMAAf8DAAH/A0YBgDQAAyoBQAFGAkcBgAFZAloBwAEAAdMB7gH/AQAB4AH1Af8BmgHsAfoB + /wEAAecB+QH/AQAB4gH3Af8BAAHgAfcB/wEAAd4B9gH/AQAB3AH2Af8BAAHaAfUB/wEAAdgB9QH/AQAB + 1QH0Af8BAAHaAfUB/wGNAd4B9gH/AQAB2QH0Af8BAAHTAfEB/wEAAcoB7AH/AQABwAHnAf8BWQJaAcAB + RgJHAYADKgFAPAACRgFHAYACAAGpAf8CAAGrAf8BAAGiAa0B/wEAAYABhwH/AwAB/wMAAf8CAAGJAf8C + AAGpAf8CyAHJAf8CAAGyAf8CAAGbAf8DAAH/AwAB/wNGAYBkAAJGAUcBgAIAAZAB/wJGAUcBgFwAAyoB + QAJGAUcBgAJGAUcBgAJGAUcBgAJZAVoBwAMAAf8DAAH/AwAB/wNZAcADRgGAAyoBQDwAAUYCRwGAAQAB + zAHrAf8BAAHdAfMB/wGaAe0B+gH/AZoB7AH6Af8BmQHrAfkB/wGYAeoB+QH/AZcB6AH5Af8BlgHnAfkB + /wGUAeUB+AH/AZMB5AH4Af8BkQHiAfcB/wGQAeEB9wH/AY4B3wH2Af8BjQHdAfYB/wGLAdsB9QH/AQAB + 0QHwAf8BAAHHAesB/wEAAcAB5wH/AQABuAHjAf8BRgJHAYA8AAJGAUcBgAIAAaMB/wIAAaAB/wEAAY4B + nQH/AQABpQGvAf8BtgG7AcAB/wIAAacB/wIAAY4B/wIAAa4B/wHJAcsBzgH/AgABtwH/AgABnwH/AwAB + /wMAAf8DRgGAZAACRgFHAYACAAGJAf8CRgFHAYBsAANGAYADAAH/AwAB/wMAAf8DRgGARAABRgJHAYAB + AAHNAewB/wEAAdUB8AH/AQAB3QHzAf8BAAHcAfMB/wEAAdsB8gH/AQAB2gHyAf8BAAHZAfEB/wEAAdgB + 8QH/AQAB1gHxAf8BAAHVAfEB/wEAAdMB8AH/AQAB0gHwAf8BAAHRAe8B/wEAAc8B7wH/AQABzgHuAf8B + AAHIAesB/wEAAcIB6AH/AQABvgHmAf8BAAG6AeQB/wFZAloBwAFGAkcBgAMqAUA0AAJGAUcBgAMAAf8D + AAH/AgABpgH/AgABrwH/AgABtwH/AgABqwH/AgABnwH/AgABrwH/AgABvwH/AgABqwH/AgABlgH/AwAB + /wMAAf8DRgGAVAADKgFAA0YBgANGAYACRgFHAYACWQFaAcACAAGdAf8CRgFHAYBsAAMqAUADRgGAA1kB + wAMAAf8DRgGARAABRgJHAYABAAHOAewB/wEAAc0B7AH/AQABzAHrAf8BAAHMAesB/wEAAcsB6gH/AQAB + ygHqAf8BAAHJAekB/wEAAcgB6QH/AQABxwHpAf8BAAHGAekB/wEAAcQB6AH/AQABwwHoAf8BAAHCAecB + /wEAAcEB5wH/AQABwAHmAf8BAAG/AeYB/wEAAb0B5QH/AQABvAHlAf8BAAG7AeQB/wEAAboB5AH/AQAB + uAHjAf8BRgJHAYA0AANGAYADAAH/AwAB/wIAAa4B/wIAAa4B/wIAAa4B/wIAAa8B/wIAAa8B/wIAAa8B + /wIAAa8B/wIAAZ4B/wIAAYwB/wMAAf8DAAH/A0YBgFQAA0YBgAMAAf8DAAH/AgABgAH/AgABmAH/AgAB + sAH/AkYBRwGAdAADRgGAAwAB/wNGAYBEAAMqAUABRgJHAYABRgJHAYABRgJHAYABRgJHAYABRgJHAYAB + RgJHAYABRgJHAYABRgJHAYABRgJHAYABRgJHAYABRgJHAYABRgJHAYABRgJHAYABRgJHAYABRgJHAYAB + RgJHAYABRgJHAYABRgJHAYABRgJHAYABRgJHAYABRgJHAYADKgFANAADRgGAAwAB/wMAAf8DAAH/AwAB + /wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DWQHAA0YBgAMqAUBUAANGAYADAAH/AwAB/wMAAf8C + WQFaAcACRgFHAYADKgFAdAADKgFAA0YBgAMqAUDUAANGAYADAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8D + AAH/AwAB/wMAAf8DAAH/AwAB/wNGAYBcAAJGAUcBgAIAAYIB/wMAAf8DAAH/A0YBgP8AXQADKgFAAUcC + RgGAAUcCRgGAAUcCRgGAAUcCRgGAAUcCRgGAAUcCRgGAAUcCRgGAAUcCRgGAAUcCRgGAAUcCRgGAAUcC + RgGAAyoBQLwAAwsBDgMVARwDCwEO/wAFAAFHAkYBgAGxAgAB/wG3AgAB/wG9AgAB/wHBAgAB/wHFAgAB + /wHGAgAB/wHHAgAB/wHFAgAB/wHCAgAB/wG+AgAB/wG5AgAB/wFHAkYBgLwAAxUBHAMlATcDFQEchAAD + KgFAAkYBRwGAAkYBRwGAAkYBRwGAAkYBRwGAAkYBRwGAAkYBRwGAAkYBRwGAAkYBRwGAAkYBRwGAAkYB + RwGAAkYBRwGAAyoBQDQAAyoBQAFHAkYBgAFHAkYBgAFHAkYBgAFHAkYBgAFHAkYBgAFaAlkBwAHNAgAB + /wHRAgAB/wHVAgAB/wHXAgAB/wHZAgAB/wHaAgAB/wHaAgAB/wHZAgAB/wHYAgAB/wHVAgAB/wHSAgAB + /wFaAlkBwAFHAkYBgAFHAkYBgAFHAkYBgAFHAkYBgAFHAkYBgAMqAUCkAAMtAUQDSQGGA0YBfgNCAXUD + KQE+AwUBBgMCAQN0AAJGAUcBgAIAAbMB/wIAAbcB/wIAAboB/wIAAboB/wIAAboB/wIAAboB/wIAAboB + /wIAAbcB/wIAAbMB/wIAAbIB/wIAAbEB/wJGAUcBgDQAAUcCRgGAAYYCAAH/AZECAAH/AZwCAAH/AbkC + AAH/AdUCAAH/Ad8CAAH/AekCAAH/AesCAAH/Ae0BgAEAAf8B7QGBAQAB/wHtAYIBAAH/Ae0BggEAAf8B + 7QGCAQAB/wHtAYIBAAH/Ae0BgQEAAf8B7AIAAf8B6wIAAf8B4QIAAf8B1gIAAf8BtwIAAf8BmAIAAf8B + jQIAAf8BgQIAAf8BRwJGAYCkAAM+AWsDXwHVA10B3wNjAekDRAF7AwkBDAMFAQZkAAMqAUACRgFHAYAC + RgFHAYACRgFHAYACWQFaAcACAAG8Af8CAAG+Af8CAAHAAf8CAAHAAf8CAAHAAf8CAAHAAf8CAAHAAf8C + AAG9Af8CAAG5Af8CAAG4Af8CAAG2Af8CWQFaAcACRgFHAYACRgFHAYACRgFHAYADKgFAJAABRwJGAYAB + lgIAAf8BoQIAAf8BrAIAAf8BxwIAAf8B4QIAAf8B5gIAAf8B6wIAAf8B7AIAAf8B7AGEAQAB/wHsAYUB + AAH/AewBhQEAAf8B7AGFAQAB/wHsAYUBAAH/AewBhQEAAf8B7AGEAQAB/wHsAgAB/wHrAgAB/wHmAgAB + /wHgAgAB/wHEAgAB/wGpAgAB/wGgAgAB/wGXAgAB/wFHAkYBgKQAAyYBOQNAAXEDVgGzA2UB9ANaAb0D + SQGGAzwBZgMtAUUDGQEjXAACRgFHAYACAAG1Af8CAAG4Af8CAAG6Af8CAAHAAf8CAAHFAf8CAAHFAf8C + AAHFAf8CAAHFAf8CAAHFAf8CAAHFAf8CAAHFAf8CAAHCAf8CAAG/Af8CAAG9Af8CAAG6Af8CAAG4Af8C + AAG1Af8CAAG0Af8CAAGzAf8CRgFHAYAkAAFHAkYBgAGlAgAB/wGxAgAB/wG8AgAB/wHVAgAB/wHtAY4B + AAH/Ae0BjQEAAf8B7QGLAQAB/wHsAYkBAAH/AesBhwEAAf8B6wGIAQAB/wHqAYgBAAH/AeoBiAEAAf8B + 6gGIAQAB/wHrAYgBAAH/AesBhwEAAf8B6wGGAQAB/wHrAYUBAAH/AeoCAAH/AekCAAH/AdECAAH/AbkC + AAH/AbMCAAH/Aa0CAAH/AUcCRgGApAADBQEGAwkBDANJAYYDrgH/A60B/wOsAf8DWwHFA0oBigMtAUVU + AAMqAUACRgFHAYACWQFaAcACAAHAAf8CAAHEAf8CAAHHAf8CAAHIAf8CAAHIAf8CAAHGAf8CAAHEAf8C + AAHGAf8CAAHJAf8CAAHHAf8CAAHFAf8CAAHDAf8CAAHBAf8CAAHCAf8CAAHDAf8CAAHBAf8CAAG9Af8C + AAG4Af8CAAGzAf8CWQFaAcACRgFHAYADKgFAHAABRwJGAYABpAIAAf8BsgIAAf8BvwIAAf8B1QIAAf8B + 6gGNAQAB/wHrAYwBAAH/AewBiwEAAf8B7AGIAQAB/wHsAYQBAAH/AewBhQEAAf8B6wGFAQAB/wHrAYUB + AAH/AesBhAEAAf8B7AIAAf8B6wIAAf8B6gIAAf8B6QIAAf8B5wIAAf8B5QIAAf8B1QIAAf8BxQIAAf8B + vAIAAf8BtAIAAf8BRwJGAYAQAANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYAD + RwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYAD + RwGAA0cBgANHAYADRwGAA0cBgANHAYADKgFAFAADAgEDAwUBBgNDAXcDZQHnA2AB8wO0Af8DYQHiA1sB + xQNRAZ4DQwF3AysBQQMJAQwDBQEGRAACRgFHAYACAAG6Af8CAAHDAf8CAAHLAf8CAAHPAf8CAAHTAf8C + AAHPAf8CAAHLAf8CAAHHAf8CAAHCAf8CAAHHAf8CAAHMAf8CAAHJAf8CAAHFAf8CAAHEAf8CAAHCAf8C + AAHHAf8CAAHMAf8CAAHJAf8CAAHFAf8CAAG8Af8CAAGzAf8CAAG1Af8CAAG3Af8CRgFHAYAcAAFHAkYB + gAGiAgAB/wGyAgAB/wHCAgAB/wHUAgAB/wHmAYwBAAH/AekBiwEAAf8B6wGKAQAB/wHsAYYBAAH/AewB + gQEAAf8B7AGBAQAB/wHsAYEBAAH/AewBgQEAAf8B7AGAAQAB/wHsAgAB/wHrAgAB/wHpAgAB/wHnAgAB + /wHkAgAB/wHhAgAB/wHZAgAB/wHQAgAB/wHFAgAB/wG6AgAB/wFHAkYBgBAAA6QB/wOxAf8DvgH/A78B + /wPAAf8DwAH/A8AB/wPAAf8DvwH/A74B/wO9Af8DvQH/A7wB/wO7Af8DugH/A7oB/wO6Af8DuwH/A7sB + /wO6Af8DuQH/A7kB/wO4Af8DuQH/A7kB/wO5Af8DuAH/A7cB/wO2Af8DqgH/A50B/wNHAYAcAAM9AWgD + XQHPA2UB5wO8Af8DvAH/A7sB/wNiAfYDXgHtA0cBggMSARcDCQEMPAADKgFAAkYBRwGAAlkBWgHAAgAB + wgH/AgABywH/AgAB0gH/AgAB3QH/AgAB6AH/AgAB4gH/AgAB3AH/AgAB0gH/AgAByAH/AgAByQH/AgAB + ywH/AgAByQH/AgABxwH/AgABygH/AgABzQH/AgAB2QH/AgAB5AH/AgAB3gH/AgAB1wH/AgABxwH/AgAB + twH/AgABtwH/AgABtgH/AkYBRwGAHAABRwJGAYABogIAAf8BswIAAf8BwwIAAf8BywIAAf8B0gIAAf8B + 1gIAAf8B2QIAAf8B2wIAAf8B3QIAAf8B3gIAAf8B3wIAAf8B4AIAAf8B4AIAAf8B3wIAAf8B3gIAAf8B + 3AIAAf8B2QIAAf8B1QIAAf8B0QIAAf8BygIAAf8BwgIAAf8BtgIAAf8BqgIAAf8BRwJGAYAQAAOoAf8D + uQH/A8oB/wPQAf8D1QH/A9YB/wPWAf8D1QH/A9MB/wPQAf8DzQH/A8QB/wO7Af8DsgH/A6kB/wOpAf8D + qQH/A7EB/wO4Af8DwQH/A8oB/wPMAf8DzQH/A84B/wPOAf8DzgH/A80B/wPIAf8DwgH/A7IB/wOiAf8D + RwGAHAADJAE0Az0BaANXAbQDtQH/A78B/wPHAf8DXwH7A2IB9gNZAcEDSgGLAz4BagMuAUgDGgEkNAAC + RgFHAYACAAG/Af8CAAHFAf8CAAHKAf8CAAHSAf8BhwGDAdkB/wG+AbwB6wH/AvQB/AH/AdsB2gH0Af8B + wQG/AewB/wIAAd0B/wIAAc0B/wIAAcsB/wIAAckB/wIAAckB/wIAAcgB/wIAAdAB/wGFAYEB1wH/Ab0B + uwHqAf8C9AH8Af8B3QHcAfMB/wHFAcQB6QH/AgAB0gH/AgABugH/AgABuAH/AgABtQH/AkYBRwGAHAAB + RwJGAYABogIAAf8BswIAAf8BxAIAAf8BwQIAAf8BvQIAAf8BwgIAAf8BxgIAAf8BygIAAf8BzgIAAf8B + 0AIAAf8B0gIAAf8B0wIAAf8B0wIAAf8B0gIAAf8B0QIAAf8BzgIAAf8BywIAAf8BxgIAAf8BwAIAAf8B + ugIAAf8BswIAAf8BpgIAAf8BmQIAAf8BRwJGAYAQAAOrAf8DwAH/A9UB/wPgAf8D6gH/A+sB/wPsAf8D + 6gH/A+cB/wPiAf8D3AH/A8sB/wO5Af8DqQH/A5gB/wOYAf8DlwH/A6YB/wO1Af8DyAH/A9sB/wPfAf8D + 4gH/A+IB/wPiAf8D4gH/A+EB/wPYAf8DzgH/A7oB/wOmAf8DRwGAJAADRwGAA64B/wPBAf8D0wH/A8kB + /wO+Af8DtQH/A6sB/wNcAcgDTAGQAy4BSDQAAkYBRwGAAgABwgH/AgABygH/AgAB0QH/AgAB1AH/AgAB + 1gH/AZIBkAHkAf8B3wHeAfIB/wHfAd4B8wH/Ad4B3QH0Af8BnwGeAekB/wIAAdwB/wIAAdQB/wIAAcsB + /wIAAc8B/wIAAdMB/wGDAYEB3wH/Ab0BuwHqAf8B2AHWAfIB/wHyAfEB+gH/AdMB0QHuAf8BswGwAeIB + /wIAAdEB/wIAAcAB/wIAAbwB/wIAAbgB/wJZAVoBwAJGAUcBgAMqAUAUAAMqAUABRwJGAYABWgJZAcAB + vAIAAf8BswIAAf8BqQIAAf8BrAIAAf8BrwIAAf8BtgIAAf8BvAIAAf8BwgIAAf8ByAIAAf8ByQIAAf8B + ygIAAf8BxQIAAf8BwAIAAf8BvAIAAf8BtwIAAf8BsgIAAf8BqwIAAf8BpgIAAf8BnwIAAf8BlgIAAf8B + jQIAAf8BRwJGAYAQAAOrAf8DwgH/A9gB/wPjAf8D7gH/A/AB/wPxAf8D5AH/A9YB/wOiAf8DAAH/AwAB + /wMAAf8DAAH/A5kB/wOYAf8DlwH/AwAB/wMAAf8DiQH/A7gB/wPNAf8D4QH/A+UB/wPoAf8D5wH/A+YB + /wPcAf8D0gH/A7wB/wOnAf8DRwGAJAADQAFwA2AB4ANeAfAD0AH/A8wB/wPIAf8DwwH/A70B/wNhAeQD + XAHIA1IBoANEAXkDKwFCAwkBDAMFAQYkAAJGAUcBgAIAAcUB/wIAAc8B/wIAAdgB/wIAAdUB/wIAAdIB + /wIAAd0B/wHJAccB5wH/AeIB4QHyAf8C+wH8Af8B3QHcAfQB/wG+AbwB6wH/AgAB3AH/AgABzQH/AgAB + 1QH/AZIBjgHdAf8BwwHBAe0B/wL0AfwB/wHyAfEB+gH/Ae8B7gH3Af8ByAHFAekB/wGgAZwB2gH/AgAB + 0AH/AgABxQH/AgABwAH/AgABugH/AgABvAH/AgABvgH/AkYBRwGAHAABRwJGAYABtAIAAf8BpAIAAf8B + lAIAAf8BlgIAAf8BlwIAAf8BoQIAAf8BqgIAAf8BtAIAAf8BvgIAAf8BvwIAAf8BwAIAAf8BuAIAAf8B + rwIAAf8BqQIAAf8BowIAAf8BnQIAAf8BlgIAAf8BkQIAAf8BiwIAAf8BhgIAAf8BgAIAAf8BRwJGAYAQ + AAOrAf8DwwH/A9oB/wPmAf8D8gH/A/QB/wP2Af8D3QH/A8QB/wMAAf8DAAH/AwAB/wMAAf8DAAH/A5kB + /wOYAf8DlwH/AwAB/wMAAf8DAAH/A5QB/wO6Af8D4AH/A+cB/wPtAf8D7AH/A+sB/wPgAf8D1QH/A74B + /wOnAf8DRwGAJAADOgFgA1oBwANgAeADzAH/A88B/wPSAf8D0QH/A88B/wPDAf8DtwH/A1wB+ANlAfED + SAGEAxIBFwMJAQwgAAJGAUcBgAJZAVoBwAIAAdAB/wIAAdcB/wIAAd4B/wIAAdgB/wIAAdMB/wIAAdUB + /wIAAdYB/wGfAZ4B4gH/AdkB2AHuAf8B2gHZAfEB/wHZAdgB9AH/AaMBogHqAf8CAAHgAf8BmAGWAeYB + /wHDAcEB7QH/AdcB1gHyAf8B6wHqAfcB/wHSAbEB7QH/AbgBAAHiAf8BhAEAAdgB/wIAAc4B/wIAAcoB + /wIAAcUB/wIAAcEB/wIAAbwB/wIAAbsB/wIAAbkB/wJGAUcBgBwAAyoBQAFHAkYBgAFaAlkBwAMAAf8D + AAH/AY0CAAH/AZwCAAH/AaoCAAH/AbYCAAH/AcICAAH/AcMCAAH/AcMCAAH/AbgCAAH/AawCAAH/AaEC + AAH/AZUCAAH/AwAB/wMAAf8BWgJZAcABRwJGAYABRwJGAYABRwJGAYADKgFAEAADqwH/A8MB/wPaAf8D + 5gH/A/IB/wPzAf8D9AH/A9MB/wOyAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wO7Af8DwQH/A8YB/wOaAf8D + AAH/AwAB/wMAAf8DhgH/A8IB/wPYAf8D7QH/A+wB/wPsAf8D4QH/A9YB/wPAAf8DqQH/A0cBgANEAXgD + RAF6A0UBfANFAXwDRQF8A0UBfANFAXwDRQF8A0UBfANVAawDXQHcA2IB7gPRAf8DywH/A8UB/wPGAf8D + xgH/A8UB/wPDAf8DKwH8A1wB+ANaAcIDSgGLAz8BbQMyAU8DHQEoGgABxQH/AgAB0AH/AgAB2wH/AgAB + 3wH/AgAB4wH/AgAB2wH/AgAB0wH/AgABzAH/AgABxQH/AgAB0gH/AbcBtQHfAf8B1gHVAe4B/wL0AfwB + /wHmAeUB9wH/AdcB1QHyAf8B5gHlAfcB/wL0AfwB/wHrAeoB9wH/AeIB4AHyAf8BsQEAAd8B/wGAAQAB + zAH/AgABxwH/AgABwgH/AgABxAH/AgABxQH/AgABwgH/AgABvgH/AgABuQH/AgABswH/AkYBRwGAJAAD + RgGAAwAB/wMAAf8BggIAAf8BlgIAAf8BqgIAAf8BuAIAAf8BxQIAAf8BxgIAAf8BxgIAAf8BuAIAAf8B + qQIAAf8BmAIAAf8BhgIAAf8DAAH/AwAB/wNGAYAgAAOrAf8DwwH/A9oB/wPmAf8D8QH/A/IB/wPyAf8D + yQH/A58B/wMAAf8DAAH/AwAB/wMAAf8DAAH/A90B/wPpAf8D9AH/A+cB/wPZAf8DAAH/AwAB/wMAAf8D + owH/A8gB/wPsAf8D7AH/A+wB/wPiAf8D1wH/A8EB/wOqAf8DRwGAA14B8ANkAfQDXAH4A1wB+ANcAfgD + XAH4A1wB+ANcAfgDXAH4A1wB+ANcAfgDKwH8A9YB/wPHAf8DtwH/A7oB/wO9Af8DxgH/A88B/wPFAf8D + uwH/A7MB/wOqAf8DXwHOA1ABnQMyAU8aAAHFAf8CAAHWAf8CAAHmAf8CAAHoAf8CAAHqAf8CAAHjAf8C + AAHbAf8CAAHTAf8CAAHLAf8CAAHPAf8CAAHSAf8CogHlAf8C6AH3Af8B6QHoAfcB/wHpAegB9wH/AfEB + 8AH6Af8C+AH8Af8BtQG0Ae4B/wIAAeAB/wIAAdMB/wIAAcYB/wIAAcYB/wIAAcYB/wIAAcYB/wIAAcUB + /wIAAcIB/wIAAb4B/wIAAbwB/wIAAbkB/wJGAUcBgCQAAyoBQANGAYADRgGAAUcCRgGAAVoCWQHAAZgC + AAH/AaMCAAH/Aa4CAAH/AbICAAH/AbYCAAH/AakCAAH/AZwCAAH/AVoCWQHAAUcCRgGAA0YBgANGAYAD + KgFAIAADrAH/A8QB/wPbAf8D5wH/A/EB/wPvAf8D7AH/A8EB/wOVAf8DAAH/AwAB/wMAAf8DAAH/AwAB + /wO0Af8D0gH/A+8B/wPtAf8D6QH/A6YB/wMAAf8DAAH/AwAB/wOdAf8D5wH/A+oB/wPtAf8D4wH/A9kB + /wPCAf8DqwH/A0cBgANeAe0DZQH0AysB/AMrAfwDKwH8AysB/AMrAfwDKwH8AysB/AMrAfwDKwH8A4AB + /gPYAf8D0AH/A8cB/wPIAf8DyAH/A8wB/wPQAf8DygH/A8UB/wPAAf8DuwH/A2UB5wNfAc4DUwGlA0QB + ewMtAUYDDgESAwcBCQoAAcUB/wIAAdsB/wGGAYIB8QH/AYYBggHxAf8BhgGCAfEB/wIAAeoB/wIAAeMB + /wIAAdoB/wIAAdAB/wIAAcsB/wIAAcUB/wIAAdwB/wHcAdsB8gH/AewB6wH3Af8C+wH8Af8C+wH8Af8C + +wH8Af8CAAHlAf8CAAHNAf8CAAHHAf8CAAHAAf8CAAHFAf8CAAHKAf8CAAHIAf8CAAHFAf8CAAHCAf8C + AAG+Af8CAAG+Af8CAAG+Af8CRgFHAYA0AAFHAkYBgAGGAgAB/wGOAgAB/wGWAgAB/wGeAgAB/wGlAgAB + /wGaAgAB/wGOAgAB/wFHAkYBgDAAA6wB/wPEAf8D3AH/A+cB/wPxAf8D7AH/A+YB/wO5Af8DiwH/A54B + /wOxAf8DmgH/A4IB/wOHAf8DiwH/A7sB/wPqAf8D8gH/A/kB/wPeAf8DwgH/AwAB/wMAAf8DAAH/A+EB + /wPoAf8D7gH/A+QB/wPaAf8DwwH/A6sB/wNHAYADYwHpA2UB9APWAf8D3AH/A+IB/wPhAf8D3wH/A94B + /wPdAf8D3AH/A9oB/wPaAf8D2QH/A9gB/wPXAf8D1QH/A9MB/wPSAf8D0AH/A88B/wPOAf8DzQH/A8sB + /wPBAf8DtgH/A00B+gNaAfUDSwGMAxkBIwMOARIKAAHIAf8CAAHgAf8CkAH3Af8CkAH3Af8CkAH3Af8C + AAHwAf8CAAHpAf8CAAHiAf8CAAHaAf8CAAHZAf8CAAHXAf8BogGhAecB/wHsAesB9wH/AfQB8wH6Af8C + +wH8Af8C+wH8Af8C+wH8Af8CrwHsAf8CAAHbAf8CAAHRAf8CAAHHAf8CAAHIAf8CAAHKAf8CAAHIAf8C + AAHFAf8CAAHEAf8CAAHCAf8CAAHAAf8CAAG+Af8CRgFHAYA0AANGAYADAAH/AwAB/wGSAgAB/wGjAgAB + /wGyAgAB/wGrAgAB/wGjAgAB/wFHAkYBgDAAA7AB/wPJAf8D4QH/A+sB/wP0Af8D7wH/A+kB/wPGAf8D + ogH/A7gB/wPOAf8DwwH/A7cB/wOuAf8DowH/A70B/wPXAf8D5wH/A/YB/wPlAf8D1AH/A5MB/wMAAf8D + mwH/A+MB/wPqAf8D8AH/A+YB/wPcAf8DxAH/A6wB/wNHAYADTAGQA1wByAPPAf8D2QH/A+MB/wPdAf8D + 1wH/A9UB/wPUAf8D0gH/A9AB/wPPAf8DzQH/A9IB/wPYAf8DwQH/A6kB/wOgAf0DKwH8AysB/AMrAfwD + KwH8AysB/AMrAfwDKwH8A2oB+QNSAfcDWgG/A0kBhgMtAUQKAAHLAf8CAAHkAf8BmgGeAf0B/wGaAZ4B + /QH/AZoBngH9Af8BjgGRAfYB/wGCAYMB7wH/AgAB6QH/AgAB4wH/AgAB5gH/AbEBrQHoAf8B1gHUAfIB + /wL7AfwB/wL7AfwB/wL7AfwB/wL7AfwB/wL7AfwB/wLgAfMB/wHFAcQB6QH/AgAB2wH/AgABzQH/AgAB + ywH/AgAByQH/AgABxwH/AgABxQH/AgABxQH/AgABxQH/AgABwgH/AgABvgH/AkYBRwGANAADRgGAAwAB + /wMAAf8BjgIAAf8BpwIAAf8BvwIAAf8BuwIAAf8BtwIAAf8BRwJGAYAwAAO0Af8DzQH/A+YB/wPuAf8D + 9gH/A/EB/wPrAf8D0gH/A7gB/wPSAf8D6wH/A+wB/wPsAf8D1AH/A7sB/wO/Af8DwwH/A9sB/wPyAf8D + 7AH/A+YB/wPFAf8DowH/A8QB/wPkAf8D6wH/A/IB/wPoAf8D3QH/A8UB/wOtAf8DRwGAAyUBNwNQAZsD + yAH/A9YB/wPkAf8D2QH/A84B/wPMAf8DygH/A8gB/wPGAf8DwwH/A8AB/wPMAf8D2AH/A6wB/wOAAf4D + XwH7A1wB+ANcAfgDXAH4A1wB+ANcAfgDXAH4A1wB+ANcAfgDXAH4A2UB8QNjAekDQgF1CgABygH/AgAB + 4gH/AaMBqAH6Af8BpAGqAfwB/wGlAawB/gH/AZkBnwH4Af8BjgGQAfEB/wIAAe8B/wIAAe0B/wGeAZ0B + 8AH/AdYB1AHyAf8B4QHgAfUB/wHsAesB9wH/ArUB7QH/AgAB4gH/Aq8B6wH/AuAB8wH/AuAB8wH/AuAB + 8wH/AqMB6wH/AgAB4gH/AgAB2AH/AgABzQH/AgAByQH/AgABxQH/AgABxQH/AgABxQH/AgABwgH/AgAB + vgH/AkYBRwGANAADRgGAAwAB/wMAAf8BkwIAAf8BqwIAAf8BwwIAAf8ByQIAAf8BzwIAAf8BRwJGAYAw + AAO2Af8DzwH/A+gB/wPvAf8D9gH/A/IB/wPtAf8D0wH/A7gB/wPPAf8D5AH/A+0B/wP0Af8D5gH/A9YB + /wPMAf8DwgH/A84B/wPZAf8D3QH/A+AB/wPLAf8DtQH/A9EB/wPsAf8D8QH/A/YB/wPtAf8D5AH/A8sB + /wOyAf8DRwGAAxUBHANKAYsDTQH6A6gB/QPgAf8D3AH/A9gB/wPSAf8DywH/A8kB/wPHAf8DxQH/A8IB + /wPOAf8D2QH/A74B/wOiAf8DYAHbA1YBtgNPAZkDRQF8A0UBfANFAXwDRQF8A0UBfANFAXwDRQF8A0QB + eQNCAXUDKAE7CgAByAH/AgAB4AH/AasBsgH3Af8BrQG2AfsB/wGvAboC/wGkAawB+QH/AZkBnQHyAf8B + sgG0AfUB/wHLAcoB9wH/AuMB+gH/AvsB/AH/AewB6wH3Af8B3AHbAfIB/wIAAd0B/wIAAcgB/wIAAdkB + /wHFAcQB6QH/AuAB8wH/AvsB/AH/AuMB+gH/AcsBygH3Af8CAAHkAf8CAAHRAf8CAAHLAf8CAAHFAf8C + AAHFAf8CAAHFAf8CAAHCAf8CAAG+Af8CRgFHAYA0AAFHAkYBgAGIAgAB/wGQAgAB/wGXAgAB/wGvAgAB + /wHHAgAB/wHXAgAB/wHmAgAB/wFHAkYBgDAAA7gB/wPRAf8D6gH/A/AB/wP2Af8D8wH/A+8B/wPUAf8D + uAH/A8sB/wPdAf8D7QH/A/wB/wP3Af8D8QH/A9kB/wPAAf8DwAH/A8AB/wPNAf8D2gH/A9EB/wPHAf8D + 3QH/A/MB/wP2Af8D+QH/A/IB/wPqAf8D0AH/A7YB/wNHAYAEAANEAXoDZQH0A00B+gPcAf8D3wH/A+EB + /wPXAf8DzAH/A8oB/wPIAf8DxgH/A8MB/wPPAf8D2gH/A88B/wPEAf8DWAG6A0IBdAMnATowAAJGAUcB + gAJZAVoBwAIAAeAB/wGJAZIB7wH/AbwBygH+Af8BqgGyAfQB/wGXAZoB6QH/AbQBtQHtAf8B0QHPAfEB + /wHgAd8B9QH/Ae8B7gH3Af8BrwGuAesB/wIAAd8B/wIAAdIB/wIAAcUB/wIAAc4B/wIAAdYB/wKhAeQB + /wHgAd4B8gH/AeIB4QH2Af8C4wH6Af8BmwGaAegB/wIAAdUB/wIAAc0B/wIAAcUB/wIAAcUB/wIAAcUB + /wJZAVoBwAJGAUcBgAMqAUAsAAMqAUADRgGAA1kBwAMAAf8DAAH/AY8CAAH/AagCAAH/AcECAAH/AdIC + AAH/AeMCAAH/AVoCWQHAAUcCRgGAAyoBQCgAA7cB/wPRAf8D6gH/A/AB/wP2Af8D9QH/A/MB/wPaAf8D + wQH/A8QB/wPGAf8D3QH/A/IB/wP0Af8D9gH/A+cB/wPXAf8DyAH/A7gB/wO4Af8DtwH/A8EB/wPKAf8D + 4QH/A/cB/wP4Af8D+gH/A/QB/wPuAf8D1AH/A7kB/wNHAYAEAAMzBFIBpANeAdID1QH/A90B/wPjAf8D + 2QH/A84B/wPMAf8DyQH/A8cB/wPFAf8DzgH/A9YB/wPSAf8DzQH/A14B3QNYAboDQAFwAxsBJQMPARMs + AAJGAUcBgAIAAcgB/wIAAeMB/wHIAdkB/QH/Aa8BuAHuAf8BlQGXAd8B/wG2AbUB5QH/AdcB0wHrAf8B + 3QHaAe8B/wHiAeAB8gH/AgAB3wH/AgABywH/AgABxwH/AgABwgH/AgABwwH/AgABwwH/AgAB1QH/AcQB + wQHnAf8B4AHeAfIB/wL7AfwB/wHQAc4B6wH/AaQBoAHZAf8CAAHPAf8CAAHFAf8CAAHFAf8CAAHFAf8C + RgFHAYA0AANGAYADAAH/AwAB/wMAAf8DAAH/AYcCAAH/AaECAAH/AboCAAH/Ac0CAAH/AeACAAH/AeUC + AAH/AekCAAH/AUcCRgGAKAADtgH/A9AB/wPpAf8D7wH/A/UB/wP2Af8D9gH/A+AB/wPJAf8DvAH/A68B + /wPMAf8D6AH/A/EB/wP6Af8D9AH/A+4B/wPPAf8DsAH/A6IB/wOTAf8DsAH/A80B/wPkAf8D+gH/A/oB + /wP6Af8D9gH/A/EB/wPXAf8DvAH/A0cBgAQAAx4BKgM0AVQDUwGqA84B/wPaAf8D5QH/A9oB/wPPAf8D + zQH/A8oB/wPIAf8DxgH/A8wB/wPSAf8D1AH/A9UB/wPKAf8DvgH/A1MBpQMwAUoDGwElLAACRgFHAYAC + AAHIAf8CAAHkAf8BzQHcAf4B/wG8AccB9AH/AasBsQHpAf8BrQGuAeUB/wGuAaoB4QH/AZABjgHhAf8C + AAHfAf8CAAHcAf8CAAHXAf8CAAHVAf8CAAHTAf8CAAHPAf8CAAHLAf8CAAHRAf8CAAHWAf8BlAGSAd4B + /wHGAcMB5gH/AYwBigHbAf8CAAHPAf8CAAHKAf8CAAHFAf8CAAHDAf8CAAHAAf8CRgFHAYA0AANGAYAD + AAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8BrAIAAf8BvwIAAf8B0wIAAf8B2gIAAf8B4QIAAf8BRwJGAYAo + AAO2Af8D0AH/A+kB/wPvAf8D9QH/A/YB/wP3Af8D6gH/A9wB/wPJAf8DtgH/A78B/wPHAf8D0QH/A9oB + /wPYAf8D1QH/A8AB/wOrAf8DpAH/A5wB/wO5Af8D1gH/A+kB/wP7Af8D+wH/A/oB/wP2Af8D8QH/A9cB + /wO8Af8DRwGABAADEAEVAx4BKgNOAZQDKwH8A4AB/gPlAf8D3QH/A9UB/wPRAf8DzAH/A8oB/wPIAf8D + ygH/A8wB/wPSAf8D2AH/A88B/wPGAf8DXgHSA1MBpQM4AVsDDgESAwcBCSQAAkYBRwGAAgAByAH/AgAB + 5AH/AdEB3wL/AckB1QH5Af8BwAHLAfMB/wGjAaYB5QH/AYUBgQHXAf8CAAHSAf8CAAHMAf8CAAHYAf8C + AAHjAf8CAAHjAf8CAAHjAf8CAAHbAf8CAAHTAf8CAAHMAf8CAAHEAf8CAAHKAf8BkAGKAdAB/wIAAcoB + /wIAAcQB/wIAAcUB/wIAAcUB/wIAAcAB/wIAAboB/wJGAUcBgDQAA0YBgAMAAf8DAAH/AwAB/wMAAf8D + AAH/AwAB/wGdAgAB/wGxAgAB/wHFAgAB/wHPAgAB/wHYAgAB/wFHAkYBgCgAA7YB/wPPAf8D6AH/A+8B + /wP1Af8D9gH/A/cB/wPzAf8D7wH/A9YB/wO8Af8DsQH/A6UB/wOwAf8DugH/A7sB/wO8Af8DsQH/A6UB + /wOlAf8DpAH/A8EB/wPeAf8D7QH/A/sB/wP7Af8D+gH/A/UB/wPwAf8D1gH/A7wB/wNHAYAMAANGAX0D + agH5AysB/APkAf8D4AH/A9sB/wPUAf8DzQH/A8sB/wPJAf8DxwH/A8UB/wPQAf8D2gH/A9QB/wPOAf8D + xAH/A7kB/wNNAZEDGQEjAw4BEiQAAyoBQAJGAUcBgANaAcABtgG+AfUB/wHBAcsB9wH/AcwB2QH5Af8B + uQHCAfEB/wGlAaoB6AH/AQABgQHlAf8CAAHhAf8CAAHpAf8CAAHwAf8CAAHtAf8CAAHqAf8CAAHjAf8C + AAHbAf8CAAHUAf8CAAHNAf8CAAHNAf8CAAHNAf8CAAHLAf8CAAHJAf8CAAHHAf8CAAHFAf8CWQFaAcAC + RgFHAYADKgFANAADRgGAAwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8BrQIAAf8BtwIAAf8B + wAIAAf8BRwJGAYAoAAO2Af8DzgH/A+YB/wPsAf8D8gH/A/MB/wPzAf8D8gH/A/AB/wPiAf8D1AH/A8gB + /wO8Af8DvAH/A7sB/wO8Af8DvQH/A74B/wO+Af8DwwH/A8cB/wPXAf8D5wH/A/AB/wP4Af8D+AH/A/cB + /wPyAf8D7gH/A9UB/wO8Af8DRwGADAADOgFgA1kBvgNdAd8D3AH/A98B/wPhAf8D2AH/A88B/wPNAf8D + ywH/A8kB/wPHAf8DywH/A88B/wPSAf8D1AH/A8sB/wPBAf8DXAHIA0wBkAMuAUgsAANHAYABmwGcAesB + /wG5AcEB9QH/AdcB5gL/Ac4B3QH8Af8BxAHTAfgB/wG2AcAB9wH/AacBrQH1Af8BoQGmAfkB/wGaAZ4B + /QH/ApAB9wH/AYYBggHxAf8CAAHqAf8CAAHjAf8CAAHcAf8CAAHVAf8CAAHQAf8CAAHKAf8CAAHMAf8C + AAHNAf8CAAHJAf8CAAHFAf8CRgFHAYA8AANGAYADAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB + /wGVAgAB/wGfAgAB/wGoAgAB/wFHAkYBgCgAA7YB/wPNAf8D4wH/A+kB/wPuAf8D7wH/A+8B/wPwAf8D + 8AH/A+4B/wPsAf8D3wH/A9IB/wPHAf8DvAH/A70B/wO9Af8DygH/A9YB/wPgAf8D6gH/A+0B/wPwAf8D + 8gH/A/QB/wP0Af8D8wH/A+8B/wPrAf8D1AH/A7wB/wNHAYAMAAMrAUIDSAGDA1kBwQPTAf8D3QH/A+YB + /wPbAf8D0AH/A84B/wPMAf8DygH/A8gB/wPGAf8DxAH/A88B/wPaAf8D0gH/A8kB/wOAAf4DKwH8A0YB + fiwAAyoBQANHAYADWgHAAbkBwQH1Af8BwAHLAfkB/wHHAdQB+wH/AcABywH7Af8BuAHBAfoB/wGvAbYB + /AH/AaYBqgH+Af8BmgGbAfoB/wGPAYsB9gH/AgAB7wH/AgAB6AH/AgAB4QH/AgAB2wH/AgAB1gH/AgAB + 0AH/AgABzQH/AgAByQH/AlkBWgHAAkYBRwGAAyoBQDwAAUcCRgGAAwAB/wMAAf8DAAH/AwAB/wMAAf8D + AAH/AwAB/wMAAf8DAAH/AwAB/wGaAgAB/wFHAkYBgCgAA7cB/wPLAf8D3wH/A+IB/wPlAf8D5gH/A+YB + /wPmAf8D5gH/A+YB/wPlAf8D3wH/A9gB/wPTAf8DzQH/A84B/wPOAf8D1QH/A9sB/wPgAf8D5QH/A+cB + /wPoAf8D6gH/A+sB/wPrAf8D6gH/A+gB/wPmAf8D0QH/A7wB/wNHAYAMAAMYASEDKwFCA1IBoAOAAf4D + 0gH/A+cB/wPhAf8D2wH/A9oB/wPYAf8D1wH/A9UB/wPTAf8D0gH/A9cB/wPbAf8D1wH/A9IB/wOlAf8D + gAH+A1gBvANEAXoDKQE9LAADRwGAAZsBnAHrAf8BsgG5AfUB/wHJAdUB/gH/AckB1QH+Af8ByQHVAf4B + /wG9AcUC/wGxAbUC/wGkAaUB/QH/AZcBlAH7Af8CAAH0Af8CAAHsAf8CAAHmAf8CAAHgAf8CAAHbAf8C + AAHVAf8CAAHNAf8CAAHFAf8CRgFHAYBEAAFHAkYBgAGRAgAB/wGYAgAB/wGeAgAB/wGRAgAB/wGDAgAB + /wMAAf8DAAH/AwAB/wMAAf8DAAH/AYsCAAH/AUcCRgGAKAADuAH/A8kB/wPaAf8D2wH/A9wB/wPcAf8D + 3AH/A9wB/wPcAf8D3QH/A90B/wPeAf8D3gH/A94B/wPeAf8D3wH/A98B/wPfAf8D3wH/A+AB/wPgAf8D + 4AH/A+AB/wPhAf8D4QH/A+EB/wPhAf8D4QH/A+AB/wPOAf8DvAH/A0cBgBQAA0YBfwOoAf0DgAH+A+cB + /wPnAf8D5gH/A+UB/wPkAf8D4wH/A+EB/wPgAf8D3wH/A94B/wPcAf8D2wH/A9oB/wPQAf8DxQH/A2oB + +QNgAfMDRAF6LAADKgFAA0cBgANaAcABqwGwAe8B/wGrAbAB7wH/AasBsAHvAf8BggGGAe0B/wIAAekB + /wIAAegB/wIAAecB/wIAAeQB/wIAAeEB/wIAAdsB/wIAAdYB/wIAAdIB/wIAAc0B/wJZAVoBwAJGAUcB + gAMqAUBEAAMqAUABRwJGAYABWgJZAcABtgIAAf8BqgIAAf8BnQIAAf8DAAH/AwAB/wMAAf8DAAH/AwAB + /wGMAgAB/wFHAkYBgCgAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYAD + RwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYAD + RwGAA0cBgANHAYADRwGAA0cBgAMqAUAUAAM9AWgDXwHQA2AB6APWAf8D1gH/A9UB/wPVAf8D1AH/A9MB + /wPRAf8D0AH/A88B/wPOAf8DzQH/A8wB/wPLAf8DxgH/A8AB/wMrAfwDagH5A1YBtQNAAXEDJgE5LAAD + RwGAAY0BiwHgAf8BjQGLAeAB/wGNAYsB4AH/AgAB2gH/AgAB0wH/AgAB0wH/AgAB0wH/AgAB1AH/AgAB + 1QH/AgAB0AH/AgABywH/AgAByAH/AgABxQH/AkYBRwGAVAADRwGAAc4BmQGAAf8BwwIAAf8BtwIAAf8D + AAH/AwAB/wMAAf8BgQIAAf8BhwIAAf8BjQIAAf8BRwJGAYC8AAMzBFEBogNdAdEDxQH/A8UB/wPEAf8D + xAH/A8MB/wPCAf8DwQH/A8AB/wO/Af8DvgH/A70B/wO9Af8DvAH/A7sB/wO6Af8DuQH/A7gB/wNeAfAD + YgHhA0ABcSwAAyoBQANHAYADRwGAA0cBgAFHAUYBRwGAAkYBRwGAAkYBRwGAAkYBRwGAAkYBRwGAAkYB + RwGAAkYBRwGAAkYBRwGAAkYBRwGAAkYBRwGAAyoBQFQAAkcBRgGAAbUCAAH/Aa4CAAH/AaUCAAH/AwAB + /wMAAf8DAAH/AwAB/wFaAlkBwAFHAkYBgAMqAUC8AAMdASkDMwFRAz0BaQNHAYADRwGAA0cBgANHAYAD + RwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0QBeANAAXED + JgE5vAABRwJGAYABnAIAAf8BmAIAAf8BkwIAAf8DAAH/AwAB/wMAAf8DAAH/A0YBgP8AMQABQgFNAT4H + AAE+AwABKAMAAYADAAGAAQECAAEBAQABAQYAARgWAAP/AQAI/wgACP8IAAH/AQMC/wHAA/8IAAH+AQMC + /wGAAX8C/wgAAfwBAwL/AQABPwL/CAAB8AEBAv8BAAE/Av8IAAHwAQEC/wEAAQ8C/wgAAfgBAAL/AQAB + AQHwAX8IAAH4AQABfwH/AgABMAE/CAAB+AEAAR8B/wMAAR8IAAH4AQABCAF/AwABHwgAAfgCAAF/AwAB + HwgAAfgCAAF/AwABHwgAAfgCAAE/AwABDwgAAfwCAAE/AwABDwgAAfwCAAE/AwABDwgAAf8CAAEPAcAC + AAEHCAAB/wGAAQABDwGAAgABAwgAAf8BgAEAAQ8BgAIAAQEIAAH/AYABAAEPAYACAAEBCAAB/wGAAQAB + DwGACwAB/wGAAQABDwGACwAB/wGAAQABDwGACwAB/wHAAQABDwHACwAB/wHgAQABDwHACwAB/wHwAQAB + DwHgAQABEAkAAf8B/AEAAQ8B4AEBAfgJAAH/Af4BAAEPAeABAQH/CQAC/wEAAR8B4AEBAf8BAQgAAv8B + wAE/AfABgwH/AYcIAAL/AeABfwT/CAAC/wHwBf8IABb/AT8C/wHnAf8BPwn/AfwBDwL/AcEB/AEPBv8B + +AL/AfgBCAH/Af4BAAH8AQcG/wH4AX8B/wHwAQgBfwH8AQAB/AEHBP8BDwH/AfgBfwH/AfABEAF/AfwB + AQGIAQcE/wEDAf8B+AL/AfABMAE/AfwCAAEIBP8BAQH/AeAC/wHAAQABPwH8AgABCAHwAT8B+AEfAQAB + /gEAAT8B/wIAAQcB/gIAAQgB4AEfAfABDwEAAXwBAAEPAf4BAAEBAQAB/gIAAQgBgAIAAQEBgAEwAQAB + BwH+AwAB/AIAAQgEAAHAAgABAwH8AwAB/AcAAfACAAEBAfgDAAH8BwAB/gMAAfgDAAH4BwAB/AMAAfgD + AAH4BwAB/AMAAfACAAEgAcAHAAH+BgABIAGABgABAQH/AYAFAAEzAYADAAGAAgABAwH/AcAFAAE/AwAB + AQHAAgABDwH/AeACAAEIAgABPwMAAQEBwAIAAv8B8AIAAYACAAF/AwABAwHgAgAC/wH4AgABwAIAAX8D + AAEHAfgBAAEBAv8B/gIAAcACAAH/AwABHwH8AQABAwP/AYABAAHAAgAB/wMAAX8B/wEAAQ8D/wHgAQAB + wAEAAQMB/wIAAT8H/wHgAQABwAEAAQcB/wIAAT8H/wHwAQcBwAEgAR8B/wGAAQAI/wHwAQ8BwAEfAv8B + 8AEBCP8B/AEfAcABHwL/AfABAQr/AfABPwL/AfMB4wr/Afkb/wH4AgABDwH/AgAC/wGDBv8B8AIAAQ8B + /wIAAv8BgAEDAf8B4AL/AfgB8AIAAQ8B/wIAAv8BgAEBAf8B4AH+AX8B8AH4AgABDwH/AgAC/wGAAQEB + /wHBAfwBfwHgAfgCAAEPAf4CAAF/Af8BgAEBAf8BwwH4AX8B4AH4AgABHwH8AgABPwH/AgAB/wHDAfgB + fwHBAfACAAEPAfwCAAE/Af8CAAH/AcMB4AF8AQMB8AIAAQ8B+AIAAR8B/wIAAf8BwwHAAfwBAwHwAgAB + DwH4AgABHwH/AgAB/wHhAQAB/AEHAeACAAEHAfgCAAEfAf4CAAF/AeABAwHwAQ8B4AIAAQcB+AIAAR8B + /AIAAT8B4AIAAQ8B4AIAAQcB+AIAAR8B/AIAAT8DAAEHAeACAAEHAfgCAAEfAfgCAAEfAwABAwHgAgAB + BwH4AgABHwH4AgABHwMAAQMB4AIAAQcB+AIAAR8B+AIAAR8DAAEDAeACAAEHAfgCAAEfAfgCAAEfAwAB + BwHwAgABDwH8AgABPwHwAgABDwMAAT8B8AIAAQ8B/AIAAT8B4AIAAQcDAAE/AfACAAEfAf4CAAF/AeAC + AAEHAcACAAF/AfgCAAEfAf8CAAH/AeACAAEHAcACAAF/AfwCAAE/Af4CAAF/AeACAAEHAYABBgEAAX8B + /gIAAX8B/gIAAX8B4AIAAQ8BgAEGAQABDwH/AgAB/wH+AgABfwHwAgABDwHwAQYBAAEHAf8CAAH/Af4B + CAEAAX8B+AIAAR8B/wEeAQABBwH/AgAB/wH+ATwBBAF/AfwCAAE/Av8BAAEHAf8CAAL/Af4BBwH/Af4C + AAF/Av8BgAEPAf8CAAP/AQcC/wHgAQcD/wGAAR8B/wIAA/8BgwL/AfABDwP/AcABPwH/AgAC/wH4AQcC + /wH+AQ8D/wHwAT8B/wIAAv8B8AEHA/8BDwP/AfwBPwH/AgAC/wHwAQcD/wEHA/8B/AE/Af8CAAL/AfAB + DwP/AYcG/wGAAQEF/wH4AT8C/wH4AT8C/wH4AgABHwT/AfgBHwL/AfgBHwL/AeACAAEHBP8B+AEHAv8B + +AEHAv8B4AIAAQcE/wH4AQMC/wH4AQMC/wHgAgABBwQAAfgBAQL/AfgBAQL/AeACAAEHBAAB/AEAAv8B + /AEAAv8B4AIAAQcEAAH8AQABfwH/AfwBAAF/Af8B4AIAAQcEAAH+AQABPwH/Af4BAAE/Af8B4AIAAQ8E + AAH+AQABHwH/Af4BAAEfAf8B8AIAAQ8EAAH+AQABDwH/Af4BAAEPAf8B8AIAAQ8EAAH/AQABAwL/AQAB + AwH/AfACAAEPBAAB/wEAAQEC/wEAAQEB/wH8AgABPwQAAf8BgAEAAv8BgAEAAv8CAAH/BAAB8AIAAX8B + 8AIAAX8B/wHAAQMB/wQAAfACAAE/AfACAAE/Af8B8AEPAf8EAAHwAgABHwHwAgABHwH/AfABDwH/BAAB + 8AIAAQ8B8AIAAQ8B/wHwAQ8B/wQAAfACAAEPAfACAAEPAf8B4AEHAf8EAAHwAgABDwHwAgABDwH/AeAB + AwH/BAAB8AIAAQ8B8AIAAQ8B/wHAAQMB/wQAAfACAAEPAfACAAEPAf8BwAEDAf8EAAH4AQABDwH/AfgB + AAEPAv8BwAEDAf8EAAH4AQABBwH/AfgBAAEHAv8BwAEDAf8EAAH4AQABAwH/AfgBAAEDAv8BgAEBAf8E + AAH4AQABAwH/AfgBAAEDAv8BgAEBAf8EAAH4AQABAQH/AfgBAAEBAv8BgAEBAf8EAAH4AgAB/wH4AgAC + /wHAAQMB/wQAAfwCAAF/AfwCAAF/Af8BwAEDAf8EAAH8AgABfwH8AgABfwH/AcABAwH/BAAB/AIAAX8B + /AIAAX8B/wHgAQcF/wH8AgABfwH8AgABfwH/AfgBHwX/AfwCAAF/AfwCAAF/Df8BwAEDDv8CAA3/AfwC + AAE/Af8B8QGPCf8B+AIAAR8B/wHxAQ8C/wHBAf8BjwT/AfACAAEPAf8B8QEPAv8BwQH/AY8E/wHgAgAB + BwH/AfEBDwL/AcEB/wGHAfwBfwHxAR8BwAIAAQMB/wHxAQ8C/wHBAf8BhwH4AX8B4AEPAcACAAEDAf8B + 8QEPAv8BwQH/AYcBgAEAAQQBQwGAAgABAQH/AfABDwL/AcEB/wGHAYACAAEBAYACAAEBAf8B8AEfAv8B + wQHPAQcBnAGHAfEBGQQAAf8B8AEfAv8BwAEAAQMBnwGHAf8B+QQAAf8B8AEfAv8BCAEgAQMBnwGHAf8B + +QQAAf8B4AEHAv8BOQH8AckBgAGHAf8B+QQAAf8B4AEHAf8B/gE5Af8BwQHAAYcB/wH5BAAB/wHgAQcB + /wH+AX8B/wHhAcwBhwH/AfkEAAH/AeMBhwH/AfwBfwH/AeEBxAGHAf8B+QQAAf8B4wGPAf8B/AL/AeEB + 4wGHAf8B+QQAAf8B4wGPAf8B+QL/AeEB4AEHAf8B+QQAAf8B4wGPAf8BgQL/AcEB+AIAAQEEAAH/AeMB + jwH/AYEC/wGDAf8B4AEAAQEEAAH/AeMBjwH/AY4BAgEAAR8E/wQAAf8B5wHPAf8BzAIAAT8E/wGAAgAB + AQH/AeABDwH/AcMBJwb/AYACAAEBAf8B8AEfAf8B4AE/Bv8BwAIAAQMB/wH8AX8B/wH4AX8G/wHAAgAB + AwH/AfwBPwn/AeACAAEHAf8B+AE/Cf8B8AIAAQ8B/wH8AT8J/wH4AgABHwH/Af4K/wH8AgABPw3/AgAO + /wHAAQMB/jX/Ad8B/wG/Af8BwQH+AT8J/wGHAfgBDwH/AYAB/AE/Cf8BhwH5AQ8B/wGAAf4BHwX/AeMB + /gEPAfgBBwH5AQ8B/wGAAf4BGwH4AX8B+AF/Af8B4wH8AQ8B8AEDAfkBDwH/AZAB/gETAfABPwHwAT8B + /wHhAfwBLwHyARMB+AFPAf8BwQH+ARMB8wEAARMBLwH/AeEB/AEvAfMBkwHwAQ8B/wHBAf8BBwHzAYAB + BwEPAf8B4QH8AQ8B8AEQATABHwH/AcEB/gEHAfMBBwHDAQcB/wGAAQABAwHwARABAAGfAf8BwQH8AQcB + 8AEIAcABBwH/AQgBAAGDAfgBEwHBAZ8B/wHAAQwBBwHgAQABQAEPAf8BCQH8AYMB/AEzAfkBnwH/AYgB + AAEHAfABAgEgAQ8B/wEJAfkBhwH8AXMB+wGfAf8BHwHhAYcB+AFHATgBTwHAATkB+QHHAfwB8wH7AZ8B + /wE/AfkBhwH8AU8BgAEPAcABeQH5AccB/AL/AZ8B/wE/AfkB5wH+AQ8BgAEfAc4BGQH/AccB/AT/AT8B + +QHnAf4BPwKfAcYBGQH/AecB/AHwAQABPwHGAT8B/wHHAf8BPwGAAR8B5wEfAf8B5wLgAQABHwHCAXwB + BwGPAf8BHwGAAR8B4AEPAf8BhwHAAecB/wGPAcABcAEAAR8B/wGDAv8B4AIAAQ8BzwHHAf8BzwHMAeMB + 8AF/Af8BgwL/AeABMAEAAX8BwwGPAf8BzwHEAeMH/wF/Av8B4AGPAf8BjwHnAYcK/wH4AT8B/wEfAeAB + Dwr/Af4BPwH8AR8B8AE/Cv8B/gF/AfwBfwHwvP8BjwH+AR8B/gF/Af8BPwH/AccBjwE/BP8B+AIAAR8B + +AE/Af4BDwH/AYcBjgEfBP8B8AIAAQEByQIAAU8B+QGHAYYBHwH4AQcB4AE/AfEBhwGGAREBwwGAAQAB + 4wHwAYMBggEfAYACAAEBAfABzwGHATkByQEfAfwBQwHwARMBkAEPAYQB8AEPAQEB0AF/AYcB+QHAAT8B + /gELAfgBAwEQAc8B/AIAAR8BgAIAAQEBwAF/Af8BEwH4AQABEQGPAfwCAAEfAcMCAAEBAcMBwAEAAXMB + /AFnATgBHwL/AT8B/wHBAf8BhwH5AeACAAFnAfwB7wE8AT8B/wHAAQABHwHwAgABAQHwAQ8B/wFnAcAB + /wH8Av8BzgEAAT8B/wGAAQABAwH/AYcB/wFHAYABfwH+Av8BxgE+AT8F/wHhAf8BDwGeAX8B+AL/AeYB + PgF/Bf8B8AEAAR8BjwH/AcABPwH/AeIBPAF/Bf8B/AEAAX8BxAEAAQIBHwH/AfIBOAn/AcABAAEfAZ8B + /wHwATkJ/wHBAv8BzwH/AfgBMQn/AcMC/wHPAf8B+AEzDP8BywH/AfwBIwz/AcEB/wH8AQcM/wHjAf8B + /gEPD/8BDw//AR8P/wEfTv8BvwHzDv8BHwHhDv8BDwHhAv8B/AE/Cv8BBwHgAv8BOAEcCf8B/gEnAcwB + /wH8ATEBjAE/Af4BDwH4AT8B/wH9Av8B/AFnAcwB/wH4AXgBHgEfAfgBBwHwAR8B/wH8AQ8B/wHwAWcB + zAH/AfkB8AEPAZ8B+ALjAY8B/wH+AQ8B/wHBAeMBjAH/AfEB4AEHAY8C8wLHAf8B/gF/Af8B4wHxAZkB + /wHzAeABBwHPAfMB+QHPAecB8AE+AT8B/wHgATgBmQH/AfMB4QGHAc8B8wE4AQABZwHwAQABDwH/AfAB + HAEZAf8B8wHjAccBzwHzATgBAAHnAfABwAEHAv8BjgE5Af8B8wH3Ae8BzwHzAZgBAAHHAfgBeAEDAv8B + xwEZAf8C8wLPAfEBkQEBAY8B/gE4AQEC/wHjAYkB/wHxAeMBxwHPAfgBAwGAAQ8B/wEeAQEC/wHxAcMB + /wH5AfMBxwGfAf4BAwGAAT8B/wGHAQEC/wGAAeMB/wH5Av8BnwH/AYcBwwL/AcMBgQH/AfwBBAFxAf8B + 8AL/AQ8B/wGAAQcC/wHwAcEB/wHgAT4BOAH/AeABfwH+AQcB/wGAAQcC/wH4AQAB/wHBAf8BHAF/AeYB + PwH8AWcB/wGfAcMC/wH+AQABfwHHAcABDgE/Ac8BHwH4AfMB/wGHAcMD/wGEAR8B4AEAAccBPwHPAY8B + 8QHzAf8BhwT/AY8BDwHwAT8B4wE/Ac8BxwHjAfMG/wHEAQ8C/wHwAT8BxwGAAQEB4wb/AcABPwL/AfgB + fwHiARABCAFHBv8B8wX/AfABPwH8AQ8M/wH4Av8BH2z/AfgBAwL/AYABPwP/AYMB4wX/AfgBAwL/AYAB + PwP/AYMB4wH/AfgBPwH4AT8B4AEAAv8BAAE/A/8BgwGAAf8B+AE/AfgBPwHgAQAC/wEAAT8D/wGPAYgB + /wHgAQ8B4AEPAeABAAL/AgAB4AE/Af8BjgEIAf8B4AEPAeABDwHgAQAC/wIAAeABPwH/Af4BCAH/AwAB + AwHgAQABPwH/AwABDwH/Af4BCAH/AwABAwHgAQABPwH/AwABDwH/Af4BCAH/AwABAwHgAgABPwMAAQ8B + /wGIAQgB/wMAAQMB4AIAAT8DAAEPAf8CiAH/AwABAwHgAgABPwMAAQ8B/wGAAQAB/wMAAQMB4AIAAT8D + AAEPAf8BgAEjAf8DAAEDAeACAAE/AwABDwH/AYABIwH/AwABAwH4AgABPwMAAQ8B/wH4AT8B/wMAAQMB + +AIAAQ8DAAEDAf8B+AE/Af8BgAIAAQ8B/gIAAQ8BgAIAAQMB/wH4AT8B/wGAAgABDwH+AgABDwGAAwAB + /wH4AQ8B/wHgAgAB/wH+AgABDwGAAwAB/wH4AY8B/wHgAgAB/wH+AgABDwGAAwAB/wH4AQ8B/wHgAgAB + /wH+AgABDwGAAwAB/wH4AT8B/wHgAgAB/wH+AgABDwGAAwAB/wH4AT8B/wH4AQABAwH/Af4CAAEPAYAD + AAH/AfgBPwH/AfgBAAEDAf8B/gIAAQ8BgAMAAf8B+AE/Bv8BgAEAAQ8B4AMACf8BgAEAAQ8B4AMAAf8B + /gE/Bv8B+AEAAQ8B4AEAAf4BAAH/Af4BPwb/AfgBAAEPAeABAAH+AQAB/wH+AT8G/wH+AQABDwHgAYMB + /wGDCf8B/gEAAQ8B4AGDAf8Bgwr/AYABPyj/AeMC/wH4Bf8B+AEPAv8B4wH+AQ8B4wL/AfgF/wH4AQ8C + /wHjAf4BDwHjAfgB/wHgAv8B+AL/AfgBCAH/Af4BAwH+AQ8B4wH4Af8B4wL/AfgC/wH4ATgB/wH+AQMB + /gEPAeMB4AH/AoMB/wH4Av8B+AEgAT8B/gECAQABDwHjAeAB/wGPAYMB/wH4Av8B+AEiAT8B/gECAQAB + DwHjAYAB/gEPAYAB/gEAAT8B/wGAAQIBAwH+AgABCAHjAY8B/gEPAYAB/gEAAT8B/wGAAQMBgwH+AgAB + CAHgAgABDwGAASABAAEDAf4CAAGDAf4CAAEIAeACAAEPAfgBIAEAAQMB/gIAAYMB/gIAAQgBgAIAAQMB + +AIAAQMB+AIAAYAB/gIAAQgBjgIAAQMB/wGAAQABAwH4AgABgAH+AgABDwGAAgABAwH+AwAB+AMAAfgC + AAEDAYACAAE/Af4DAAH4AgABIwH4AgABAwGAAgABPwH+BgABIwGAAgABAwHgAgAC/wHgBQABPwGAAgAB + AwHgAgAC/wHgBQABPwGAAgABAwHgAQ4BAAL/AeACAAGIAgAB/wGAAgABAwHgAQ4BAAL/AeACAAGIAgAB + /wGAAgABAwH4AQ8BgAL/Af4CAAH+AgAB/wGAAgABDwH4AQ8BgAEPAf8B/gIAAeACAAH/AYACAAEPAv8B + gAEPAv8B4AEAAeABAAEDAf8BgAEAAT8D/wGAAQ8C/wHgAQAB4AEAAQMB/wGAAQABPwP/AYABPwL/AeAB + AwLgAQ8B/wGAAQAE/wGAAT8C/wHgAQMB4AEgAQ8B/wGAAQAE/wHgAT8C/wH+AT8B+AE/Av8B+AEDBP8B + 4AE/Av8B/gE/AfgBPwL/AfgBAzb/AfgCAAEPAf8BgAEAAv8Bgwb/AfgCAAEPAf8BgAEAAv8BgwL/AfgB + PwL/AfgCAAEPAf8BgAEAAv8BgAEDAf8B+AE/Av8B+AIAAQ8B/wGAAQAC/wGAAQMB/wH4AQMC/wH4AgAB + DwH+AgAC/wGAAQAB/wH+AQMC/wH4AgABPwH+AgAC/wKAAf8B/gEAAv8B+AIAAQ8B/gIAAT8B/wGAAQAB + /wH+AQAC/wH4AgABDwH+AgABPwH/AYABAAH/Af4BAAEPAf8B4AIAAQ8B+AIAAT8B/wGAAQAC/wGAAQ8B + /wHgAgABDwH4AgABPwH/AYABAAL/AYABAwH/AeACAAEPAfgCAAE/Af4CAAE/Af8BgAEDAf8B4AIAAQ8B + +AIAAT8B/gIAAT8DAAE/AeACAAEPAfgCAAE/AfgCAAE/AwABPwHgAgABDwH4AgABPwH4AgABPwMAAQ8B + 4AIAAQ8B+AIAAT8B+AIAAT8DAAEPAfgCAAEPAf4CAAE/AfgCAAE/AwABAwH4AgABDwH+AgABPwH4AgAB + DwGAAgABAwH4AgABDwH+AgAB/wH4AgABDwGAAgABAwH4AgABDwH+AgAB/wHgAgABDwGAAQABPwH/Af4C + AAE/Af8BgAEAAf8B4AIAAQ8BgAEAAQ8B/wH+AgABPwH+AgAB/wHgAgABDwHgAQABDwH/Af4CAAH/Af4C + AAH/AfgCAAEPAeABAAEDAf8B/gIAAf8B/gIAAf8B+AIAAQ8B4AEAAQMC/wGAAQAB/wH+AT4BPwH/Af4C + AAE/AeACAAL/AYABAAH/Af4BPgEPAf8B/gIAAT8B4AIAAv8BgAEAA/8BjwL/AeABAwH/AeACAAE/Af8B + gAEAA/8BjwL/AeABAwH/AfgCAAE/Af8BgAEAA/8BjwL/Af4BDwH/AfgCAAEPAf8BgAEAAv8B+AEPAv8B + /gEPAf8B+AIAAQ8B/wGAAQAC/wH4AQ8D/wGPAf8B+AIAAQ8B/wGAAQAC/wH4AQ8D/wGPBv8BgAEDAv8B + +AE/Cv8BgAEDBf8B+Aj/AYABAwX/AfgE/wGAAQMB/wHgAgABDwT/AfgBDwP/AYABAwH/AeACAAEPBP8B + +AEPAv8B+AIAAT8B4AIAAQ8E/wH4AQMC/wH4AgABPwHgAgABDwT/AfgBAwL/AeACAAEPAeACAAEPBAAB + +AEAAT8B/wHgAgABDwHgAgABDwQAAf4BAAE/Af8BgAIAAQ8B4AIAAQ8EAAH+AQABDwH/AYACAAEPAeAC + AAEPBAAB/wGAAQ8B/wGAAgABAwHgAgABDwQAAf8BgAEAAf8BgAIAAQMB+AIAAQ8EAAH/AYABAAH/AwAB + AwH4AgABDwcAAT8DAAEDAf4CAAH/BwABPwMAAQMB/gIAAf8HAAEDAwABAwH/AeABDwH/BwABAwMAAQMB + /wHgAQ8B/wcAAQMDAAEDAf8B4AEPAf8HAAEDAwABAwH/AeABDwH/BwABAwMAAQMB/wHgAQ8B/wQAAYAB + AAEPAf8DAAEDAf8BgAEDAf8EAAGAAQABAwH/AYACAAEPAf8BgAEDAf8EAAGAAQABAwH/AYACAAEPAf8B + gAEDAf8EAAGAAgAB/wGAAgABDwH/AYABAwH/BAAB4AIAAf8BgAIAAQ8B/wGAAQMB/wQAAeACAAH/AeAC + AAE/Af8BgAEDAf8EAAHgAgAB/wHgAgABPwH/AYABAwH/BAAB4AIAAT8B+AIAAv8BgAEDAf8EAAH4AgAB + PwH4AgAC/wGAAQMB/wQAAfgCAAEPAf4BAAEDAv8B4AEDBf8B+AIAAQ8B/gEAAQMC/wHgAQMF/wH4AgAB + DwX/AeABDwn/Cw== + + + + 531, 17 + + + 855, 17 + + + 1264, 17 + + + 1530, 17 diff --git a/src/UI/SightHound.png b/src/UI/SightHound.png new file mode 100644 index 00000000..fe42cb2c Binary files /dev/null and b/src/UI/SightHound.png differ diff --git a/src/UI/TestImage.jpg b/src/UI/TestImage.jpg new file mode 100644 index 00000000..bc4cb71e Binary files /dev/null and b/src/UI/TestImage.jpg differ diff --git a/src/UI/TestVehicleImage.jpg b/src/UI/TestVehicleImage.jpg new file mode 100644 index 00000000..c755d2e3 Binary files /dev/null and b/src/UI/TestVehicleImage.jpg differ diff --git a/src/UI/ThreadSafe.cs b/src/UI/ThreadSafe.cs new file mode 100644 index 00000000..895c1b59 --- /dev/null +++ b/src/UI/ThreadSafe.cs @@ -0,0 +1,1093 @@ +using System; +using System.Diagnostics; +using System.Globalization; +using System.Threading; + +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + + +public class ThreadSafe +{ + //[Serializable] + [DebuggerDisplay("ThreadSafe.Integer: {GetValue()}")] + [JsonConverter(typeof(ThreadSafeConverter))] + public sealed class Integer:IComparable, IComparable + { + [JsonProperty] + private int _value; + + [DebuggerStepThrough] + //[JsonConstructor] + public Integer(int initialValue = 0) + { + _value = initialValue; + } + + public void Decrement(int minValue = int.MinValue) + { + int initialValue, computedValue; + do + { + initialValue = Interlocked.CompareExchange(ref _value, 0, 0); + computedValue = initialValue - 1; + if (computedValue < minValue) + { + computedValue = minValue; + } + } while (Interlocked.CompareExchange(ref _value, computedValue, initialValue) != initialValue); + } + + // IComparable implementation + public int CompareTo(Integer other) + { + if (other == null) return 1; + return GetValue().CompareTo(other.GetValue()); + } + + // IComparable implementation + public int CompareTo(int other) + { + return GetValue().CompareTo(other); + } + + public static Integer operator ++(Integer tsi) + { + Interlocked.Increment(ref tsi._value); + return tsi; + } + + public static Integer operator --(Integer tsi) + { + Interlocked.Decrement(ref tsi._value); + return tsi; + } + + public static Integer operator +(Integer tsi, int value) + { + Interlocked.Add(ref tsi._value, value); + return tsi; + } + + public static Integer operator -(Integer tsi, int value) + { + Interlocked.Add(ref tsi._value, -value); + return tsi; + } + + public int GetValue() + { + return Interlocked.CompareExchange(ref _value, 0, 0); + } + + + public override string ToString() + { + return GetValue().ToString(); + } + + // Implicit conversion from int to ThreadSafeIntegerWithInterlocked + public static implicit operator Integer(int value) + { + return new Integer(value); + } + + // Implicit conversion from ThreadSafeIntegerWithInterlocked to int + public static implicit operator int(Integer tsi) + { + return tsi.GetValue(); + } + + //allow (int)ThreadSafeInteger + //public static explicit operator int(Integer tsi) + //{ + // return tsi.GetValue(); + //} + // Equality check + public override bool Equals(object obj) + { + if (obj is Integer other) + { + return GetValue() == other.GetValue(); + } + return false; + } + + public override int GetHashCode() + { + return GetValue().GetHashCode(); + } + + // Equality operators + public static bool operator ==(Integer left, Integer right) + { + if (ReferenceEquals(left, right)) + { + return true; + } + + if (left is null || right is null) + { + return false; + } + + return left.GetValue() == right.GetValue(); + } + + public static bool operator !=(Integer left, Integer right) + { + return !(left == right); + } + + // Comparison with int + public static bool operator ==(Integer left, int right) + { + if (left is null) + { + return false; + } + + return left.GetValue() == right; + } + + public static bool operator !=(Integer left, int right) + { + return !(left == right); + } + + public static bool operator ==(int left, Integer right) + { + if (right is null) + { + return false; + } + + return left == right.GetValue(); + } + + public static bool operator !=(int left, Integer right) + { + return !(left == right); + } + } + + //=================================================================================================================== + [DebuggerDisplay("ThreadSafe.DateTime: {GetValue()} ({GetValue().Ticks} ticks)")] + [JsonConverter(typeof(ThreadSafeConverter))] + public sealed class DateTime:IComparable, IComparable + { + [JsonProperty] + private long _value; + public static string _dateFormat = "dd.MM.yy, HH:mm:ss"; + [DebuggerStepThrough] + //[JsonConstructor] + public DateTime(System.DateTime initialValue, string dateFormat) + { + //_ticks = initialValue.Ticks; + _value = initialValue.ToBinary(); + _dateFormat = dateFormat; + } + + public DateTime() + { + //_ticks = System.DateTime.Now.Ticks; + _value = System.DateTime.Now.ToBinary(); + } + + [JsonConstructor] + public DateTime(long value) + { + _value = value; + } + + public System.DateTime GetValue() + { + //long ticks = Interlocked.Read(ref _ticks); + //return new System.DateTime(ticks); + return System.DateTime.FromBinary(Interlocked.Read(ref _value)); + } + + public void SetValue(System.DateTime value) + { + //Interlocked.Exchange(ref _ticks, value.Ticks); + Interlocked.Exchange(ref _value, value.ToBinary()); + } + + // IComparable implementation + public int CompareTo(DateTime other) + { + if (other == null) return 1; + return GetValue().CompareTo(other.GetValue()); + } + + // IComparable implementation + public int CompareTo(System.DateTime other) + { + return GetValue().CompareTo(other); + } + public void Add(TimeSpan value) + { + long newBinaryValue, originalBinaryValue; + do + { + originalBinaryValue = Interlocked.Read(ref _value); + var newDateTime = System.DateTime.FromBinary(originalBinaryValue).Add(value); + newBinaryValue = newDateTime.ToBinary(); + } while (Interlocked.CompareExchange(ref _value, newBinaryValue, originalBinaryValue) != originalBinaryValue); + } + + public void AddDays(double value) + { + Add(TimeSpan.FromDays(value)); + } + + public void AddHours(double value) + { + Add(TimeSpan.FromHours(value)); + } + + public void AddMinutes(double value) + { + Add(TimeSpan.FromMinutes(value)); + } + + public void AddSeconds(double value) + { + Add(TimeSpan.FromSeconds(value)); + } + + public override string ToString() + { + return GetValue().ToString(_dateFormat, CultureInfo.InvariantCulture); + } + + public string ToString(string format) + { + return GetValue().ToString(format, CultureInfo.InvariantCulture); + } + + // Implicit conversion from DateTime to ThreadSafeDateTime + public static implicit operator DateTime(System.DateTime value) + { + return new DateTime(value, _dateFormat); + } + + // Implicit conversion from ThreadSafeDateTime to DateTime + public static implicit operator System.DateTime(DateTime tsd) + { + return tsd.GetValue(); + } + + // Operators + // Equality operators between ThreadSafe.DateTime and System.DateTime + public static bool operator ==(DateTime tsd, System.DateTime dt) + { + return tsd.GetValue() == dt; + } + + public static bool operator !=(DateTime tsd, System.DateTime dt) + { + return tsd.GetValue() != dt; + } + + public static bool operator ==(System.DateTime dt, DateTime tsd) + { + return dt == tsd.GetValue(); + } + + public static bool operator !=(System.DateTime dt, DateTime tsd) + { + return dt != tsd.GetValue(); + } + + // Comparison operators between ThreadSafe.DateTime and System.DateTime + public static bool operator <(DateTime tsd, System.DateTime dt) + { + return tsd.GetValue() < dt; + } + + public static bool operator >(DateTime tsd, System.DateTime dt) + { + return tsd.GetValue() > dt; + } + + public static bool operator <=(DateTime tsd, System.DateTime dt) + { + return tsd.GetValue() <= dt; + } + + public static bool operator >=(DateTime tsd, System.DateTime dt) + { + return tsd.GetValue() >= dt; + } + + public static bool operator <(System.DateTime dt, DateTime tsd) + { + return dt < tsd.GetValue(); + } + + public static bool operator >(System.DateTime dt, DateTime tsd) + { + return dt > tsd.GetValue(); + } + + public static bool operator <=(System.DateTime dt, DateTime tsd) + { + return dt <= tsd.GetValue(); + } + + public static bool operator >=(System.DateTime dt, DateTime tsd) + { + return dt >= tsd.GetValue(); + } + + // Subtraction operator between ThreadSafe.DateTime and System.DateTime + public static TimeSpan operator -(ThreadSafe.DateTime tsd, System.DateTime dt) + { + return tsd.GetValue() - dt; + } + public static DateTime operator +(DateTime tsd, TimeSpan value) + { + tsd.Add(value); + return tsd; + } + public static DateTime operator -(DateTime tsd, TimeSpan value) + { + tsd.Add(-value); + return tsd; + } + public static TimeSpan operator -(DateTime tsd1, DateTime tsd2) + { + return tsd1.GetValue() - tsd2.GetValue(); + } + + public static TimeSpan operator -(System.DateTime dt, DateTime tsd) + { + return dt - tsd.GetValue(); + } + public static bool operator ==(DateTime tsd1, DateTime tsd2) + { + return tsd1.GetValue() == tsd2.GetValue(); + } + + public static bool operator !=(DateTime tsd1, DateTime tsd2) + { + return tsd1.GetValue() != tsd2.GetValue(); + } + + + public static bool operator <(DateTime tsd1, DateTime tsd2) + { + return tsd1.GetValue() < tsd2.GetValue(); + } + + public static bool operator >(DateTime tsd1, DateTime tsd2) + { + return tsd1.GetValue() > tsd2.GetValue(); + } + + public static bool operator <=(DateTime tsd1, DateTime tsd2) + { + return tsd1.GetValue() <= tsd2.GetValue(); + } + + public static bool operator >=(DateTime tsd1, DateTime tsd2) + { + return tsd1.GetValue() >= tsd2.GetValue(); + } + + + public override bool Equals(object obj) + { + if (obj is DateTime other) + { + return this == other; + } + if (obj is System.DateTime dateTime) + { + return this.GetValue() == dateTime; + } + return false; + } + + public override int GetHashCode() + { + return GetValue().GetHashCode(); + } + + + + + } + + //=================================================================================================================== + + [DebuggerDisplay("ThreadSafe.Boolean: {GetValue()}")] + [JsonConverter(typeof(ThreadSafeConverter))] + public sealed class Boolean:IComparable, IComparable + { + [JsonProperty] + private int _value; + private const int False = 0; + private const int True = 1; + //[MethodImpl(MethodImplOptions.NoOptimization)] + [DebuggerStepThrough] + public Boolean(bool initialValue = false) + { + _value = initialValue ? True : False; + } + + //[JsonConstructor] + private Boolean(int value) + { + _value = value; + } + //[MethodImpl(MethodImplOptions.NoOptimization)] + public bool GetValue() + { + //when you're just retrieving the value (as in a getter), there's no need to use CompareExchange because you're not changing the value. You just want to get the current value, and it doesn't matter what that value is. + //return _value == True; + //return Volatile.Read(ref _value) == 1; + return Interlocked.CompareExchange(ref _value, 0, 0) == True; + } + + //A multithreaded stress test may fail randomly because the operations of reading the value, negating it, and setting it back are not atomic as a whole. + //While the getter and setter of the ThreadSafeBoolean are thread-safe individually, the combination of these operations(startBoolean.Value = !startBoolean.Value;) is not atomic.This means that another thread can change the value of startBoolean after it has been read and before it has been set, leading to inconsistent results. + //To make the entire operation atomic, you would need to use a lock or a similar synchronization mechanism + //startBoolean = !startBoolean; + //startBoolean = !startBoolean; + public void ToggleValue() + { + int original, newValue; + do + { + original = _value; + newValue = original == 1 ? 0 : 1; + } + while (Interlocked.CompareExchange(ref _value, newValue, original) != original); + } + + + public void SetValue(bool value) + { + Interlocked.Exchange(ref _value, value ? True : False); + } + + public override string ToString() + { + return GetValue().ToString(); + } + + // IComparable implementation + public int CompareTo(Boolean other) + { + if (other == null) return 1; + return GetValue().CompareTo(other.GetValue()); + } + + // IComparable implementation + public int CompareTo(bool other) + { + return GetValue().CompareTo(other); + } + + // Implicit conversion from bool to ThreadSafe.Boolean + public static implicit operator Boolean(bool value) + { + return new Boolean(value); + } + + // Implicit conversion from ThreadSafe.Boolean to bool + public static implicit operator bool(Boolean tsb) + { + return tsb.GetValue(); + } + + // Equality check + public override bool Equals(object obj) + { + if (obj is Boolean other) + { + return GetValue() == other.GetValue(); + } + if (obj is bool boolValue) + { + return GetValue() == boolValue; + } + return false; + } + + public override int GetHashCode() + { + return GetValue().GetHashCode(); + } + + // Equality operators + public static bool operator ==(Boolean left, Boolean right) + { + if (ReferenceEquals(left, right)) + { + return true; + } + + if (left is null || right is null) + { + return false; + } + + return left.GetValue() == right.GetValue(); + } + + public static bool operator !=(Boolean left, Boolean right) + { + return !(left == right); + } + + // Comparison with bool + public static bool operator ==(Boolean left, bool right) + { + if (left is null) + { + return false; + } + + return left.GetValue() == right; + } + + public static bool operator !=(Boolean left, bool right) + { + return !(left == right); + } + + public static bool operator ==(bool left, Boolean right) + { + if (right is null) + { + return false; + } + + return left == right.GetValue(); + } + + public static bool operator !=(bool left, Boolean right) + { + return !(left == right); + } + } + + //=================================================================================================================== + + [DebuggerDisplay("Threadsafe.Long: {GetValue()}")] + [JsonConverter(typeof(ThreadSafeConverter))] + public sealed class Long:IComparable, IComparable + { + [JsonProperty] + private long _value; + + [DebuggerStepThrough] + //[JsonConstructor] + public Long(long initialValue = 0) + { + _value = initialValue; + } + + + public long GetValue() + { + return Interlocked.Read(ref _value); + } + + public void SetValue(long value) + { + Interlocked.Exchange(ref _value, value); + } + + public override string ToString() + { + return GetValue().ToString(); + } + + // IComparable implementation + public int CompareTo(Long other) + { + if (other == null) return 1; + return GetValue().CompareTo(other.GetValue()); + } + + // IComparable implementation + public int CompareTo(long other) + { + return GetValue().CompareTo(other); + } + // Implicit conversion from long to ThreadSafe.Long + public static implicit operator Long(long value) + { + return new Long(value); + } + + // Implicit conversion from ThreadSafe.Long to long + public static implicit operator long(Long tsl) + { + return tsl.GetValue(); + } + + // Equality check + public override bool Equals(object obj) + { + if (obj is Long other) + { + return GetValue() == other.GetValue(); + } + if (obj is long longValue) + { + return GetValue() == longValue; + } + return false; + } + + public override int GetHashCode() + { + return GetValue().GetHashCode(); + } + + // Equality operators + public static bool operator ==(Long left, Long right) + { + if (ReferenceEquals(left, right)) + { + return true; + } + + if (left is null || right is null) + { + return false; + } + + return left.GetValue() == right.GetValue(); + } + + public static bool operator !=(Long left, Long right) + { + return !(left == right); + } + + // Comparison with long + public static bool operator ==(Long left, long right) + { + if (left is null) + { + return false; + } + + return left.GetValue() == right; + } + + public static bool operator !=(Long left, long right) + { + return !(left == right); + } + + public static bool operator ==(long left, Long right) + { + if (right is null) + { + return false; + } + + return left == right.GetValue(); + } + + public static bool operator !=(long left, Long right) + { + return !(left == right); + } + + // Arithmetic operators + public static Long operator +(Long left, long right) + { + Interlocked.Add(ref left._value, right); + return left; + } + + public static Long operator -(Long left, long right) + { + Interlocked.Add(ref left._value, -right); + return left; + } + + public static Long operator *(Long left, long right) + { + long initialValue, computedValue; + do + { + initialValue = left.GetValue(); + computedValue = initialValue * right; + } while (Interlocked.CompareExchange(ref left._value, computedValue, initialValue) != initialValue); + return left; + } + + public static Long operator /(Long left, long right) + { + long initialValue, computedValue; + do + { + initialValue = left.GetValue(); + computedValue = initialValue / right; + } while (Interlocked.CompareExchange(ref left._value, computedValue, initialValue) != initialValue); + return left; + } + + public static Long operator %(Long left, long right) + { + long initialValue, computedValue; + do + { + initialValue = left.GetValue(); + computedValue = initialValue % right; + } while (Interlocked.CompareExchange(ref left._value, computedValue, initialValue) != initialValue); + return left; + } + } + + //=================================================================================================================== + + //[DebuggerDisplay("Threadsafe.Long: {GetValue()}")] + //[JsonConverter(typeof(ThreadSafeConverter))] + //public sealed class Decimal:IComparable, IComparable + //{ + // [JsonProperty] + // private long _high; + // [JsonProperty] + // private long _low; + // [JsonProperty] + // private int _flags; + + // public Decimal(decimal initialValue = 0) + // { + // SetValue(initialValue); + // } + + // public decimal GetValue() + // { + // return DecimalFromLong(_high, _low, _flags); + // } + + // public void SetValue(decimal value) + // { + // (long high, long low, int flags) = LongFromDecimal(value); + // Interlocked.Exchange(ref _high, high); + // Interlocked.Exchange(ref _low, low); + // Interlocked.Exchange(ref _flags, flags); + // } + + // public void Add(decimal value) + // { + // long initialHigh, initialLow, newHigh, newLow; + // int initialFlags, newFlags; + // decimal result; + // do + // { + // initialHigh = Interlocked.Read(ref _high); + // initialLow = Interlocked.Read(ref _low); + // initialFlags = Interlocked.CompareExchange(ref _flags, 0, 0); + // decimal current = DecimalFromLong(initialHigh, initialLow, initialFlags); + // result = current + value; + // (newHigh, newLow, newFlags) = LongFromDecimal(result); + // } while ( + // Interlocked.CompareExchange(ref _high, newHigh, initialHigh) != initialHigh || + // Interlocked.CompareExchange(ref _low, newLow, initialLow) != initialLow || + // Interlocked.CompareExchange(ref _flags, newFlags, initialFlags) != initialFlags); + // } + + // public void Subtract(decimal value) + // { + // long initialHigh, initialLow, newHigh, newLow; + // int initialFlags, newFlags; + // decimal result; + // do + // { + // initialHigh = Interlocked.Read(ref _high); + // initialLow = Interlocked.Read(ref _low); + // initialFlags = Interlocked.CompareExchange(ref _flags, 0, 0); + // decimal current = DecimalFromLong(initialHigh, initialLow, initialFlags); + // result = current - value; + // (newHigh, newLow, newFlags) = LongFromDecimal(result); + // } while ( + // Interlocked.CompareExchange(ref _high, newHigh, initialHigh) != initialHigh || + // Interlocked.CompareExchange(ref _low, newLow, initialLow) != initialLow || + // Interlocked.CompareExchange(ref _flags, newFlags, initialFlags) != initialFlags); + // } + + // private static (long high, long low, int flags) LongFromDecimal(decimal value) + // { + // int[] bits = decimal.GetBits(value); + // long high = ((long)bits[1] << 32) | bits[0]; + // long low = ((long)bits[2] << 32) | bits[3]; + // int flags = bits[3]; + // return (high, low, flags); + // } + + // private static decimal DecimalFromLong(long high, long low, int flags) + // { + // int[] bits = new int[4]; + // bits[0] = (int)(high & 0xFFFFFFFF); + // bits[1] = (int)(high >> 32); + // bits[2] = (int)(low & 0xFFFFFFFF); + // bits[3] = (int)((int)(low >> 32) | (flags & 0x80000000)); + // return new decimal(bits); + // } + + // public override string ToString() + // { + // return GetValue().ToString(); + // } + + // // Implicit conversion from decimal to ThreadSafe.Decimal + // public static implicit operator Decimal(decimal value) + // { + // return new Decimal(value); + // } + + // // Implicit conversion from ThreadSafe.Decimal to decimal + // public static implicit operator decimal(Decimal tsd) + // { + // return tsd.GetValue(); + // } + + // // IComparable implementation + // public int CompareTo(Decimal other) + // { + // if (other == null) return 1; + // return GetValue().CompareTo(other.GetValue()); + // } + + // // IComparable implementation + // public int CompareTo(decimal other) + // { + // return GetValue().CompareTo(other); + // } + + // // Equality check + // public override bool Equals(object obj) + // { + // if (obj is Decimal other) + // { + // return GetValue() == other.GetValue(); + // } + // if (obj is decimal decimalValue) + // { + // return GetValue() == decimalValue; + // } + // return false; + // } + + // public override int GetHashCode() + // { + // return GetValue().GetHashCode(); + // } + + // // Equality operators + // public static bool operator ==(Decimal left, Decimal right) + // { + // if (ReferenceEquals(left, right)) + // { + // return true; + // } + + // if (left is null || right is null) + // { + // return false; + // } + + // return left.GetValue() == right.GetValue(); + // } + + // public static bool operator !=(Decimal left, Decimal right) + // { + // return !(left == right); + // } + + // // Comparison with decimal + // public static bool operator ==(Decimal left, decimal right) + // { + // if (left is null) + // { + // return false; + // } + + // return left.GetValue() == right; + // } + + // public static bool operator !=(Decimal left, decimal right) + // { + // return !(left == right); + // } + + // public static bool operator ==(decimal left, Decimal right) + // { + // if (right is null) + // { + // return false; + // } + + // return left == right.GetValue(); + // } + + // public static bool operator !=(decimal left, Decimal right) + // { + // return !(left == right); + // } + + // // Arithmetic operators + // public static Decimal operator +(Decimal left, decimal right) + // { + // left.Add(right); + // return left; + // } + + // public static Decimal operator -(Decimal left, decimal right) + // { + // left.Subtract(right); + // return left; + // } + + // public static Decimal operator *(Decimal left, decimal right) + // { + // long initialHigh, initialLow, newHigh, newLow; + // int initialFlags, newFlags; + // decimal result; + // do + // { + // initialHigh = Interlocked.Read(ref left._high); + // initialLow = Interlocked.Read(ref left._low); + // initialFlags = Interlocked.CompareExchange(ref left._flags, 0, 0); + // decimal current = DecimalFromLong(initialHigh, initialLow, initialFlags); + // result = current * right; + // (newHigh, newLow, newFlags) = LongFromDecimal(result); + // } while ( + // Interlocked.CompareExchange(ref left._high, newHigh, initialHigh) != initialHigh || + // Interlocked.CompareExchange(ref left._low, newLow, initialLow) != initialLow || + // Interlocked.CompareExchange(ref left._flags, newFlags, initialFlags) != initialFlags); + // return left; + // } + + // public static Decimal operator /(Decimal left, decimal right) + // { + // long initialHigh, initialLow, newHigh, newLow; + // int initialFlags, newFlags; + // decimal result; + // do + // { + // initialHigh = Interlocked.Read(ref left._high); + // initialLow = Interlocked.Read(ref left._low); + // initialFlags = Interlocked.CompareExchange(ref left._flags, 0, 0); + // decimal current = DecimalFromLong(initialHigh, initialLow, initialFlags); + // result = current / right; + // (newHigh, newLow, newFlags) = LongFromDecimal(result); + // } while ( + // Interlocked.CompareExchange(ref left._high, newHigh, initialHigh) != initialHigh || + // Interlocked.CompareExchange(ref left._low, newLow, initialLow) != initialLow || + // Interlocked.CompareExchange(ref left._flags, newFlags, initialFlags) != initialFlags); + // return left; + // } + //} + + +} + +//=================================================================================================================== + +public class ThreadSafeConverter:JsonConverter +{ + public override bool CanConvert(Type objectType) + { + return objectType == typeof(ThreadSafe.Integer) || + objectType == typeof(ThreadSafe.Long) || + objectType == typeof(ThreadSafe.Boolean) || + objectType == typeof(ThreadSafe.DateTime); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + if (value is ThreadSafe.Integer tsi) + { + writer.WriteValue(tsi.GetValue()); + } + else if (value is ThreadSafe.Long tsl) + { + writer.WriteValue(tsl.GetValue()); + } + else if (value is ThreadSafe.Boolean tsb) + { + writer.WriteValue(tsb.GetValue()); + } + else if (value is ThreadSafe.DateTime tsd) + { + writer.WriteValue(tsd.GetValue().ToBinary()); + } + //else if (value is ThreadSafe.Decimal tsc) + //{ + // writer.WriteValue(tsc.GetValue().ToString(CultureInfo.InvariantCulture)); + //} + else + { + throw new JsonSerializationException("Unsupported type"); + } + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.StartObject) + { + // Read the old format (with private fields) + var jsonObject = JObject.Load(reader); + if (objectType == typeof(ThreadSafe.Integer)) + { + var value = jsonObject["_value"].ToObject(); + return new ThreadSafe.Integer(value); + } + else if (objectType == typeof(ThreadSafe.Long)) + { + var value = jsonObject["_value"].ToObject(); + return new ThreadSafe.Long(value); + } + else if (objectType == typeof(ThreadSafe.Boolean)) + { + var value = jsonObject["_value"].ToObject(); + return new ThreadSafe.Boolean(value); + } + else if (objectType == typeof(ThreadSafe.DateTime)) + { + var value = jsonObject["_value"].ToObject(); + return new ThreadSafe.DateTime(System.DateTime.FromBinary(value), "dd.MM.yy, HH:mm:ss"); + } + } + else if (reader.TokenType == JsonToken.Integer || reader.TokenType == JsonToken.Boolean || reader.TokenType == JsonToken.String) + { + // Read the new format (just the value) + if (objectType == typeof(ThreadSafe.Integer)) + { + return new ThreadSafe.Integer(Convert.ToInt32(reader.Value)); + } + else if (objectType == typeof(ThreadSafe.Long)) + { + return new ThreadSafe.Long(Convert.ToInt64(reader.Value)); + } + else if (objectType == typeof(ThreadSafe.Boolean)) + { + return new ThreadSafe.Boolean(Convert.ToBoolean(reader.Value)); + } + else if (objectType == typeof(ThreadSafe.DateTime)) + { + return new ThreadSafe.DateTime(System.DateTime.FromBinary(Convert.ToInt64(reader.Value)), "dd.MM.yy, HH:mm:ss"); + } + //else if (objectType == typeof(ThreadSafe.Decimal)) + //{ + // return new ThreadSafe.Decimal(decimal.Parse(reader.Value.ToString(), CultureInfo.InvariantCulture)); + //} + } + + throw new JsonSerializationException("Unsupported type"); + } +} + diff --git a/src/UI/ThreadSafe_OLD.cs b/src/UI/ThreadSafe_OLD.cs new file mode 100644 index 00000000..5f243da5 --- /dev/null +++ b/src/UI/ThreadSafe_OLD.cs @@ -0,0 +1,467 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Threading; + + +public class ThreadSafe_OLD + +{ + public static int Maximum(ref int target, int value) + { + int currentVal = target, startVal, desiredVal; + do + { + startVal = currentVal; + desiredVal = Math.Max(startVal, value); + currentVal = Interlocked.CompareExchange(ref target, desiredVal, startVal); + } while (startVal != currentVal); + return desiredVal; + } + + public static long Maximum(ref long target, long value) + { + long currentVal = target, startVal, desiredVal; + do + { + startVal = currentVal; + desiredVal = Math.Max(startVal, value); + currentVal = Interlocked.CompareExchange(ref target, desiredVal, startVal); + } while (startVal != currentVal); + return desiredVal; + } + + public static int Minimum(ref int target, int value) + { + int currentVal = target, startVal, desiredVal; + do + { + startVal = currentVal; + desiredVal = Math.Min(startVal, value); + currentVal = Interlocked.CompareExchange(ref target, desiredVal, startVal); + } while (startVal != currentVal); + return desiredVal; + } + + public static long Minimum(ref long target, long value) + { + long currentVal = target, startVal, desiredVal; + do + { + startVal = currentVal; + desiredVal = Math.Min(startVal, value); + currentVal = Interlocked.CompareExchange(ref target, desiredVal, startVal); + } while (startVal != currentVal); + return desiredVal; + } + + /// + /// A Datetime value that is ThreadSafe_OLD - Note only has 20ms resolution because uses Ticks + /// + /// https://stackoverflow.com/questions/1531668/thread-safe-datetime-update-using-interlocked + public class DateTime + { + public long _value; + private string _dateFormat = ""; + [DebuggerStepThrough] + public DateTime(System.DateTime value, string dateFormat = "dd.MM.yy, HH:mm:ss") + { + this._value = value.ToBinary(); + _dateFormat = dateFormat; + } + [DebuggerStepThrough] + public void Write(System.DateTime value) + { + Interlocked.Exchange(ref this._value, value.ToBinary()); + } + [DebuggerStepThrough] + public System.DateTime Read() + { + long lastvalue = Interlocked.CompareExchange(ref this._value, 0, 0); + return System.DateTime.FromBinary(lastvalue); + + } + [DebuggerStepThrough] + public override string ToString() + { + return $"{this.Read().ToString(_dateFormat)}"; + } + } + public class Integer + { + public int _value; + [DebuggerStepThrough] + public Integer(int value) + { + this._value = value; + } + [DebuggerStepThrough] + public int ReadUnfenced() + { + return this._value; + } + + public int ReadAcquireFence() + { + var value = this._value; + Thread.MemoryBarrier(); + return value; + } + [DebuggerStepThrough] + public int ReadFullFence() + { + var value = this._value; + Thread.MemoryBarrier(); + return value; + } + + [MethodImpl(MethodImplOptions.NoOptimization)] + public int ReadCompilerOnlyFence() + { + return this._value; + } + + public void WriteReleaseFence(int newValue) + { + this._value = newValue; + Thread.MemoryBarrier(); + } + [DebuggerStepThrough] + public void WriteFullFence(int newValue) + { + this._value = newValue; + Thread.MemoryBarrier(); + } + + [MethodImpl(MethodImplOptions.NoOptimization)] + public void WriteCompilerOnlyFence(int newValue) + { + this._value = newValue; + } + [DebuggerStepThrough] + public void WriteUnfenced(int newValue) + { + this._value = newValue; + } + + public bool AtomicCompareExchange(int newValue, int comparand) + { + return Interlocked.CompareExchange(ref this._value, newValue, comparand) == comparand; + } + + public int AtomicExchange(int newValue) + { + return Interlocked.Exchange(ref this._value, newValue); + } + + public int AtomicAddAndGet(int delta) + { + return Interlocked.Add(ref this._value, delta); + } + + public int AtomicIncrementAndGet() + { + return Interlocked.Increment(ref this._value); + } + + public int AtomicDecrementAndGet() + { + return Interlocked.Decrement(ref this._value); + } + + public int Maximum(int newValue) + { + return ThreadSafe_OLD.Maximum(ref this._value, newValue); + } + + public int Minimum(int newValue) + { + return ThreadSafe_OLD.Minimum(ref this._value, newValue); + } + + public override string ToString() + { + var value = this.ReadFullFence(); + return value.ToString(); + } + } + + public class Long + { + public long _value; + + public Long(long value) + { + this._value = value; + } + + public long ReadUnfenced() + { + return this._value; + } + + public long ReadAcquireFence() + { + var value = this._value; + Thread.MemoryBarrier(); + return value; + } + + public long ReadFullFence() + { + Thread.MemoryBarrier(); + return this._value; + } + + [MethodImpl(MethodImplOptions.NoOptimization)] + public long ReadCompilerOnlyFence() + { + return this._value; + } + + public void WriteReleaseFence(long newValue) + { + Thread.MemoryBarrier(); + this._value = newValue; + } + + public void WriteFullFence(long newValue) + { + Thread.MemoryBarrier(); + this._value = newValue; + } + + [MethodImpl(MethodImplOptions.NoOptimization)] + public void WriteCompilerOnlyFence(long newValue) + { + this._value = newValue; + } + + public void WriteUnfenced(long newValue) + { + this._value = newValue; + } + + public bool AtomicCompareExchange(long newValue, long comparand) + { + return Interlocked.CompareExchange(ref this._value, newValue, comparand) == comparand; + } + + public long AtomicExchange(long newValue) + { + return Interlocked.Exchange(ref this._value, newValue); + } + + public long AtomicAddAndGet(long delta) + { + return Interlocked.Add(ref this._value, delta); + } + + public long AtomicIncrementAndGet() + { + return Interlocked.Increment(ref this._value); + } + + public long AtomicDecrementAndGet() + { + return Interlocked.Decrement(ref this._value); + } + + public long Maximum(long newValue) + { + return ThreadSafe_OLD.Maximum(ref this._value, newValue); + } + + public long Minimum(long newValue) + { + return ThreadSafe_OLD.Minimum(ref this._value, newValue); + } + + public override string ToString() + { + var value = this.ReadFullFence(); + return value.ToString(); + } + } + + public class Boolean + { + public int _value; + private const int False = 0; + private const int True = 1; + [DebuggerStepThrough] + public Boolean(bool value) + { + this._value = value ? True : False; + } + + + public bool ReadUnfenced() + { + return ToBool(this._value); + } + + public bool ReadAcquireFence() + { + var value = ToBool(this._value); + Thread.MemoryBarrier(); + return value; + } + [DebuggerStepThrough] + public bool ReadFullFence() + { + var value = ToBool(this._value); + Thread.MemoryBarrier(); + return value; + } + + [MethodImpl(MethodImplOptions.NoOptimization)] + public bool ReadCompilerOnlyFence() + { + return ToBool(this._value); + } + + public void WriteReleaseFence(bool newValue) + { + var newValueInt = ToInt(newValue); + Thread.MemoryBarrier(); + this._value = newValueInt; + } + [DebuggerStepThrough] + public void WriteFullFence(bool newValue) + { + var newValueInt = ToInt(newValue); + Thread.MemoryBarrier(); + this._value = newValueInt; + } + + [MethodImpl(MethodImplOptions.NoOptimization)] + public void WriteCompilerOnlyFence(bool newValue) + { + this._value = ToInt(newValue); + } + + public void WriteUnfenced(bool newValue) + { + this._value = ToInt(newValue); + } + + public bool AtomicCompareExchange(bool newValue, bool comparand) + { + var newValueInt = ToInt(newValue); + var comparandInt = ToInt(comparand); + + return Interlocked.CompareExchange(ref this._value, newValueInt, comparandInt) == comparandInt; + } + + public bool AtomicExchange(bool newValue) + { + var newValueInt = ToInt(newValue); + var originalValue = Interlocked.Exchange(ref this._value, newValueInt); + return ToBool(originalValue); + } + [DebuggerStepThrough] + public override string ToString() + { + return $"{this.ReadFullFence()}"; + } + [DebuggerStepThrough] + private static bool ToBool(int value) + { + if (value != False && value != True) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + return value == True; + } + [DebuggerStepThrough] + private static int ToInt(bool value) + { + return value ? True : False; + } + + public bool CompareAndSet(bool comparand, bool newValue) + { + var newValueInt = ToInt(newValue); + var comparandInt = ToInt(comparand); + + return Interlocked.CompareExchange(ref this._value, newValueInt, comparandInt) == comparandInt; + } + } + + public class AtomicReference + where T : class + { + public T _value; + + public AtomicReference(T value) + { + this._value = value; + } + + public T ReadUnfenced() + { + return this._value; + } + + public T ReadAcquireFence() + { + var value = this._value; + Thread.MemoryBarrier(); + return value; + } + + public T ReadFullFence() + { + var value = this._value; + Thread.MemoryBarrier(); + return value; + } + + [MethodImpl(MethodImplOptions.NoOptimization)] + public T ReadCompilerOnlyFence() + { + return this._value; + } + + public void WriteReleaseFence(T newValue) + { + this._value = newValue; + Thread.MemoryBarrier(); + } + + public void WriteFullFence(T newValue) + { + this._value = newValue; + Thread.MemoryBarrier(); + } + + [MethodImpl(MethodImplOptions.NoOptimization)] + public void WriteCompilerOnlyFence(T newValue) + { + this._value = newValue; + } + + public void WriteUnfenced(T newValue) + { + this._value = newValue; + } + + public bool AtomicCompareExchange(T newValue, T comparand) + { + return Interlocked.CompareExchange(ref this._value, newValue, comparand) == comparand; + } + + public T AtomicExchange(T newValue) + { + return Interlocked.Exchange(ref this._value, newValue); + } + + public bool CompareAndSet(T comparand, T newValue) + { + return Interlocked.CompareExchange(ref this._value, newValue, comparand) == comparand; + } + } +} + diff --git a/src/UI/ThrottledHttpClient.cs b/src/UI/ThrottledHttpClient.cs new file mode 100644 index 00000000..76a84260 --- /dev/null +++ b/src/UI/ThrottledHttpClient.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using System; +using System.Collections.Concurrent; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Net.Http.Headers; + +//this class is used to throttle the number of requests to the same server so that multiple requests dont overlap and cause timeout errors +public class ThrottledHttpClient +{ + private readonly HttpClient _httpClient; + private readonly ConcurrentDictionary _semaphores; + private readonly TimeSpan _semaphoreTimeout; + + public ThrottledHttpClient(TimeSpan semaphoreTimeout) + { + _httpClient = new HttpClient(); + _semaphores = new ConcurrentDictionary(); + _semaphoreTimeout = semaphoreTimeout; + } + + public TimeSpan Timeout + { + get => _httpClient.Timeout; + set => _httpClient.Timeout = value; + } + + public HttpRequestHeaders DefaultRequestHeaders => _httpClient.DefaultRequestHeaders; + + public async Task GetAsync(string uri, bool WaitIfInUse = true) + { + if (!Uri.TryCreate(uri, UriKind.Absolute, out Uri parsedUri)) + { + throw new ArgumentException("Invalid URI string.", nameof(uri)); + } + + return await GetAsync(parsedUri, WaitIfInUse); + } + + public async Task GetAsync(Uri uri, bool WaitIfInUse = true) + { + string hostKey = $"{uri.Host}:{uri.Port}"; + var semaphore = _semaphores.GetOrAdd(hostKey, new SemaphoreSlim(1, 1)); + + try + { + if (WaitIfInUse) + { + await semaphore.WaitAsync(_semaphoreTimeout); + } + else if (!semaphore.Wait(0)) + { + return null; // Immediate return with null if semaphore is not immediately available. + } + + // Proceed with the request as the semaphore has been acquired + return await _httpClient.GetAsync(uri); + } + catch + { + throw; + } + finally + { + semaphore.Release(); + } + } +} + +// Usage example: +// var throttledClient = new ThrottledHttpClient(TimeSpan.FromSeconds(10)); +// throttledClient.Timeout = TimeSpan.FromSeconds(30); +// var response = await throttledClient.GetAsync(new Uri("http://example.com")); + diff --git a/src/UI/Trace.cs b/src/UI/Trace.cs new file mode 100644 index 00000000..d5aa0191 --- /dev/null +++ b/src/UI/Trace.cs @@ -0,0 +1,54 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +using AITool; + + +class Trace:IDisposable +{ + private bool disposedValue; + private string _memberName = ""; + private Stopwatch _sw; + [DebuggerStepThrough] + public Trace([CallerMemberName()] string memberName = null) + { + + this._memberName = memberName; + this._sw = Stopwatch.StartNew(); + if (AITOOL.LogMan != null) + AITOOL.LogMan.Enter(memberName); + } + [DebuggerStepThrough] + protected virtual void Dispose(bool disposing) + { + if (!this.disposedValue) + { + if (disposing) + { + // TODO: dispose managed state (managed objects) + if (AITOOL.LogMan != null) + AITOOL.LogMan.Exit(this._memberName, this._sw.ElapsedMilliseconds); + } + + // TODO: free unmanaged resources (unmanaged objects) and override finalizer + // TODO: set large fields to null + this.disposedValue = true; + } + } + + // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources + // ~Trace() + // { + // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + // Dispose(disposing: false); + // } + [DebuggerStepThrough] + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + this.Dispose(disposing: true); + GC.SuppressFinalize(this); + } +} + diff --git a/src/UI/UI.csproj b/src/UI/UI.csproj index 6036c1b9..23f858ee 100644 --- a/src/UI/UI.csproj +++ b/src/UI/UI.csproj @@ -1,193 +1,169 @@ - - - - - Debug - AnyCPU - {B8ACBD49-0830-4E34-B5B6-C876BEAE6F65} - WinExe - WindowsFormsApp2 - aitool - v4.6.1 - 512 - true - true - false - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 2 - 1.67.0.%2a - false - true - true - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - logo.ico - - - CF35D53B9A3FA151692C997D0106616CB25D8869 - - - UI_TemporaryKey.pfx - - - true - - - false - - - - ..\packages\Microsoft.WindowsAPICodePack.Core.1.1.0\lib\Microsoft.WindowsAPICodePack.dll - - - ..\packages\Microsoft.WindowsAPICodePack.Shell.1.1.0\lib\Microsoft.WindowsAPICodePack.Shell.dll - - - ..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll - - - ..\packages\SixLabors.Core.1.0.0-beta0007\lib\netstandard2.0\SixLabors.Core.dll - - - ..\packages\SixLabors.ImageSharp.1.0.0-beta0006\lib\netstandard2.0\SixLabors.ImageSharp.dll - - - - ..\packages\System.Buffers.4.4.0\lib\netstandard2.0\System.Buffers.dll - - - - - ..\packages\System.Memory.4.5.1\lib\netstandard2.0\System.Memory.dll - - - - ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll - - - ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.1\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll - - - - - - - - - - - - - ..\packages\Telegram.Bot.14.11.0\lib\net45\Telegram.Bot.dll - - - - - - Form - - - Shell.cs - - - Form - - - InputForm.cs - - - - - Shell.cs - - - InputForm.cs - - - ResXFileCodeGenerator - Designer - Resources.Designer.cs - - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - True - True - Resources.resx - - - True - Settings.settings - True - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - False - Microsoft .NET Framework 4.6.1 %28x86 and x64%29 - true - - - False - .NET Framework 3.5 SP1 - false - - - + + + net8.0-windows10.0.19041.0 + SystemAware + true + latest + WinExe + AITool + AITool + false + false + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 2 + 1.67.0.%2a + false + true + + false + true + true + + + true + UI.ruleset + false + True + False + True + True + False + None.None.Increment.DeltaDayStamp + False + SettingsVersion + None + None.None.Increment.DeltaDayStamp + + + latest + UI.ruleset + true + + + Logo_old.ico + + + CF35D53B9A3FA151692C997D0106616CB25D8869 + + + UI_TemporaryKey.pfx + + + true + + + false + + + app.manifest + + + AITool.Program + + + + + + + + + Component + + + True + True + Resources.resx + + + True + True + Settings.settings + + + .editorconfig + + + + + + + Always + + + PreserveNewest + + + PreserveNewest + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PublicResXFileCodeGenerator + Resources.Designer.cs + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + PreserveNewest + + + + CALL "$(SolutionDir)AITool.Setup\BUILD.bat" + + + + CALL "$(SolutionDir)UI\CleanOldInstalls.bat" + \ No newline at end of file diff --git a/src/UI/UI.ruleset b/src/UI/UI.ruleset new file mode 100644 index 00000000..578e9544 --- /dev/null +++ b/src/UI/UI.ruleset @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/UI/app.manifest b/src/UI/app.manifest new file mode 100644 index 00000000..f5088afb --- /dev/null +++ b/src/UI/app.manifest @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/UI/packages.config b/src/UI/packages.config index 2ee17fa3..88ff62ee 100644 --- a/src/UI/packages.config +++ b/src/UI/packages.config @@ -1,14 +1,71 @@  + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/UI/sqlite3.dll b/src/UI/sqlite3.dll new file mode 100644 index 00000000..8d3ca0c2 Binary files /dev/null and b/src/UI/sqlite3.dll differ diff --git a/src/UI/tick.wav b/src/UI/tick.wav new file mode 100644 index 00000000..e0c8af1a Binary files /dev/null and b/src/UI/tick.wav differ diff --git a/src/UI/util.cs b/src/UI/util.cs new file mode 100644 index 00000000..e7f8b0a2 --- /dev/null +++ b/src/UI/util.cs @@ -0,0 +1,17 @@ +namespace AITool +{ + public static class ExtensionsForInt32 + { + public static bool Between(this int num, int min, int max, bool inclusive = true) + { + if (min < 0) + { + min = 0; + } + + return inclusive + ? min <= num && num <= max + : min < num && num < max; + } + } +} diff --git a/src/bi-aidetection.NET6.sln b/src/bi-aidetection.NET6.sln new file mode 100644 index 00000000..230fcd3e --- /dev/null +++ b/src/bi-aidetection.NET6.sln @@ -0,0 +1,65 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.1.32228.430 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UI", "UI\UI.csproj", "{B8ACBD49-0830-4E34-B5B6-C876BEAE6F65}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{141E9702-AE33-43B3-9BB2-25AC712C4AB0}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AITool.Service", "AITool.Service\AITool.Service.csproj", "{4FA49B76-97E6-4729-8F87-CFCEBACBAB9F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ObjectListView2022", "..\..\ObjectListView.NET6\ObjectListView\ObjectListView2022.csproj", "{CBD2428F-BD1D-4983-B31D-0DC4F8C5CC16}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThreadSafeTesting", "ThreadSafeTesting\ThreadSafeTesting.csproj", "{C4EDFB03-16E8-42EF-9E49-D9AD9217D198}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B8ACBD49-0830-4E34-B5B6-C876BEAE6F65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B8ACBD49-0830-4E34-B5B6-C876BEAE6F65}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B8ACBD49-0830-4E34-B5B6-C876BEAE6F65}.Debug|x86.ActiveCfg = Debug|Any CPU + {B8ACBD49-0830-4E34-B5B6-C876BEAE6F65}.Debug|x86.Build.0 = Debug|Any CPU + {B8ACBD49-0830-4E34-B5B6-C876BEAE6F65}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B8ACBD49-0830-4E34-B5B6-C876BEAE6F65}.Release|Any CPU.Build.0 = Release|Any CPU + {B8ACBD49-0830-4E34-B5B6-C876BEAE6F65}.Release|x86.ActiveCfg = Release|Any CPU + {B8ACBD49-0830-4E34-B5B6-C876BEAE6F65}.Release|x86.Build.0 = Release|Any CPU + {4FA49B76-97E6-4729-8F87-CFCEBACBAB9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4FA49B76-97E6-4729-8F87-CFCEBACBAB9F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4FA49B76-97E6-4729-8F87-CFCEBACBAB9F}.Debug|x86.ActiveCfg = Debug|Any CPU + {4FA49B76-97E6-4729-8F87-CFCEBACBAB9F}.Debug|x86.Build.0 = Debug|Any CPU + {4FA49B76-97E6-4729-8F87-CFCEBACBAB9F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4FA49B76-97E6-4729-8F87-CFCEBACBAB9F}.Release|Any CPU.Build.0 = Release|Any CPU + {4FA49B76-97E6-4729-8F87-CFCEBACBAB9F}.Release|x86.ActiveCfg = Release|Any CPU + {4FA49B76-97E6-4729-8F87-CFCEBACBAB9F}.Release|x86.Build.0 = Release|Any CPU + {CBD2428F-BD1D-4983-B31D-0DC4F8C5CC16}.Debug|Any CPU.ActiveCfg = Release|Any CPU + {CBD2428F-BD1D-4983-B31D-0DC4F8C5CC16}.Debug|Any CPU.Build.0 = Release|Any CPU + {CBD2428F-BD1D-4983-B31D-0DC4F8C5CC16}.Debug|x86.ActiveCfg = Debug|Any CPU + {CBD2428F-BD1D-4983-B31D-0DC4F8C5CC16}.Debug|x86.Build.0 = Debug|Any CPU + {CBD2428F-BD1D-4983-B31D-0DC4F8C5CC16}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CBD2428F-BD1D-4983-B31D-0DC4F8C5CC16}.Release|Any CPU.Build.0 = Release|Any CPU + {CBD2428F-BD1D-4983-B31D-0DC4F8C5CC16}.Release|x86.ActiveCfg = Release|Any CPU + {CBD2428F-BD1D-4983-B31D-0DC4F8C5CC16}.Release|x86.Build.0 = Release|Any CPU + {C4EDFB03-16E8-42EF-9E49-D9AD9217D198}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C4EDFB03-16E8-42EF-9E49-D9AD9217D198}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C4EDFB03-16E8-42EF-9E49-D9AD9217D198}.Debug|x86.ActiveCfg = Debug|Any CPU + {C4EDFB03-16E8-42EF-9E49-D9AD9217D198}.Debug|x86.Build.0 = Debug|Any CPU + {C4EDFB03-16E8-42EF-9E49-D9AD9217D198}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C4EDFB03-16E8-42EF-9E49-D9AD9217D198}.Release|Any CPU.Build.0 = Release|Any CPU + {C4EDFB03-16E8-42EF-9E49-D9AD9217D198}.Release|x86.ActiveCfg = Release|Any CPU + {C4EDFB03-16E8-42EF-9E49-D9AD9217D198}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {90F77C3F-B8B1-42AE-B6EF-5ED36951BF6E} + EndGlobalSection +EndGlobal diff --git a/src/bi-aidetection.sln b/src/bi-aidetection.sln deleted file mode 100644 index 22019888..00000000 --- a/src/bi-aidetection.sln +++ /dev/null @@ -1,25 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.28307.489 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UI", "UI\UI.csproj", "{B8ACBD49-0830-4E34-B5B6-C876BEAE6F65}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {B8ACBD49-0830-4E34-B5B6-C876BEAE6F65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B8ACBD49-0830-4E34-B5B6-C876BEAE6F65}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B8ACBD49-0830-4E34-B5B6-C876BEAE6F65}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B8ACBD49-0830-4E34-B5B6-C876BEAE6F65}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {70C9FD94-C962-456A-9C9F-796B13AD6FA0} - EndGlobalSection -EndGlobal