diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 1ff0c42..0000000 --- a/.gitattributes +++ /dev/null @@ -1,63 +0,0 @@ -############################################################################### -# Set default behavior to automatically normalize line endings. -############################################################################### -* text=auto - -############################################################################### -# Set default behavior for command prompt diff. -# -# This is need for earlier builds of msysgit that does not have it on by -# default for csharp files. -# Note: This is only used by command line -############################################################################### -#*.cs diff=csharp - -############################################################################### -# Set the merge driver for project and solution files -# -# Merging from the command prompt will add diff markers to the files if there -# are conflicts (Merging from VS is not affected by the settings below, in VS -# the diff markers are never inserted). Diff markers may cause the following -# file extensions to fail to load in VS. An alternative would be to treat -# these files as binary and thus will always conflict and require user -# intervention with every merge. To do so, just uncomment the entries below -############################################################################### -#*.sln merge=binary -#*.csproj merge=binary -#*.vbproj merge=binary -#*.vcxproj merge=binary -#*.vcproj merge=binary -#*.dbproj merge=binary -#*.fsproj merge=binary -#*.lsproj merge=binary -#*.wixproj merge=binary -#*.modelproj merge=binary -#*.sqlproj merge=binary -#*.wwaproj merge=binary - -############################################################################### -# behavior for image files -# -# image files are treated as binary by default. -############################################################################### -#*.jpg binary -#*.png binary -#*.gif binary - -############################################################################### -# diff behavior for common document formats -# -# Convert binary document formats to text before diffing them. This feature -# is only available from the command line. Turn it on by uncommenting the -# entries below. -############################################################################### -#*.doc diff=astextplain -#*.DOC diff=astextplain -#*.docx diff=astextplain -#*.DOCX diff=astextplain -#*.dot diff=astextplain -#*.DOT diff=astextplain -#*.pdf diff=astextplain -#*.PDF diff=astextplain -#*.rtf diff=astextplain -#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore index 4547d9e..9488398 100644 --- a/.gitignore +++ b/.gitignore @@ -362,6 +362,5 @@ MigrationBackup/ # Fody - auto-generated XML schema FodyWeavers.xsd -# Test files -Test/ -Readme images/ \ No newline at end of file +# secret files +secret/ diff --git a/Forms/AskPathForm.Designer.cs b/Forms/AskPathForm.Designer.cs new file mode 100644 index 0000000..e0ed501 --- /dev/null +++ b/Forms/AskPathForm.Designer.cs @@ -0,0 +1,107 @@ +namespace SteamVR_OculusDash_Switcher.Forms +{ + partial class AskPathForm + { + /// + /// 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.lbText = new System.Windows.Forms.Label(); + this.btnApply = new System.Windows.Forms.Button(); + this.textBox1 = new System.Windows.Forms.TextBox(); + this.lbPath = new System.Windows.Forms.Label(); + this.btnBrowse = new System.Windows.Forms.Button(); + this.SuspendLayout(); + // + // lbText + // + this.lbText.AutoSize = true; + this.lbText.Location = new System.Drawing.Point(12, 9); + this.lbText.Name = "lbText"; + this.lbText.Size = new System.Drawing.Size(59, 25); + this.lbText.TabIndex = 0; + this.lbText.Text = "label1"; + // + // btnApply + // + this.btnApply.Dock = System.Windows.Forms.DockStyle.Bottom; + this.btnApply.Location = new System.Drawing.Point(0, 193); + this.btnApply.Name = "btnApply"; + this.btnApply.Size = new System.Drawing.Size(844, 39); + this.btnApply.TabIndex = 0; + this.btnApply.Text = "Apply"; + this.btnApply.UseVisualStyleBackColor = true; + // + // textBox1 + // + this.textBox1.Location = new System.Drawing.Point(68, 51); + this.textBox1.Name = "textBox1"; + this.textBox1.Size = new System.Drawing.Size(356, 31); + this.textBox1.TabIndex = 0; + // + // lbPath + // + this.lbPath.AutoSize = true; + this.lbPath.Location = new System.Drawing.Point(12, 54); + this.lbPath.Name = "lbPath"; + this.lbPath.Size = new System.Drawing.Size(50, 25); + this.lbPath.TabIndex = 1; + this.lbPath.Text = "Path:"; + // + // btnBrowse + // + this.btnBrowse.Location = new System.Drawing.Point(430, 49); + this.btnBrowse.Name = "btnBrowse"; + this.btnBrowse.Size = new System.Drawing.Size(112, 34); + this.btnBrowse.TabIndex = 2; + this.btnBrowse.Text = "Browse"; + this.btnBrowse.UseVisualStyleBackColor = true; + // + // AskPathForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(10F, 25F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(844, 232); + this.Controls.Add(this.btnBrowse); + this.Controls.Add(this.lbText); + this.Controls.Add(this.btnApply); + this.Controls.Add(this.textBox1); + this.Controls.Add(this.lbPath); + this.Name = "AskPathForm"; + this.Text = "AskPath"; + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label lbText; + private System.Windows.Forms.Button btnApply; + private System.Windows.Forms.TextBox textBox1; + private System.Windows.Forms.Label lbPath; + private System.Windows.Forms.Button btnBrowse; + } +} \ No newline at end of file diff --git a/Forms/AskPathForm.cs b/Forms/AskPathForm.cs new file mode 100644 index 0000000..5f5d2d9 --- /dev/null +++ b/Forms/AskPathForm.cs @@ -0,0 +1,21 @@ +using System; +using System.Windows.Forms; + +namespace SteamVR_OculusDash_Switcher.Forms +{ + public partial class AskPathForm : Form + { + /// + /// Not ready. Not used + /// + public AskPathForm() + { + InitializeComponent(); + } + + public DialogResult ShowDialog(string text) + { + throw new NotImplementedException(); + } + } +} diff --git a/Forms/AskPathForm.resx b/Forms/AskPathForm.resx new file mode 100644 index 0000000..f298a7b --- /dev/null +++ b/Forms/AskPathForm.resx @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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/Forms/ControlsTipsForm.cs b/Forms/ControlsTipsForm.cs index e575b7b..62a9587 100644 --- a/Forms/ControlsTipsForm.cs +++ b/Forms/ControlsTipsForm.cs @@ -1,13 +1,5 @@ -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.Drawing; using System.Windows.Forms; -using SteamVR_OculusDash_Switcher.Properties; using SteamVR_OculusDash_Switcher.Properties.Localization; using Titanium; using static Titanium.Forms; diff --git a/Forms/SettingsForm.Designer.cs b/Forms/SettingsForm.Designer.cs index bc69fe2..232d96b 100644 --- a/Forms/SettingsForm.Designer.cs +++ b/Forms/SettingsForm.Designer.cs @@ -164,7 +164,7 @@ private void InitializeComponent() this.gbInterface.Controls.Add(this.comboLanguage); this.gbInterface.Location = new System.Drawing.Point(318, 12); this.gbInterface.Name = "gbInterface"; - this.gbInterface.Size = new System.Drawing.Size(267, 315); + this.gbInterface.Size = new System.Drawing.Size(295, 315); this.gbInterface.TabIndex = 999; this.gbInterface.TabStop = false; this.gbInterface.Text = "Interface"; @@ -181,14 +181,16 @@ private void InitializeComponent() // lbTrayIconColorValue // this.lbTrayIconColorValue.AutoSize = true; + this.lbTrayIconColorValue.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; this.lbTrayIconColorValue.Cursor = System.Windows.Forms.Cursors.Hand; + this.lbTrayIconColorValue.Font = new System.Drawing.Font("DejaVu Sans", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); this.lbTrayIconColorValue.ForeColor = System.Drawing.SystemColors.MenuHighlight; this.lbTrayIconColorValue.Location = new System.Drawing.Point(135, 263); this.lbTrayIconColorValue.Margin = new System.Windows.Forms.Padding(0); this.lbTrayIconColorValue.Name = "lbTrayIconColorValue"; - this.lbTrayIconColorValue.Size = new System.Drawing.Size(105, 25); + this.lbTrayIconColorValue.Size = new System.Drawing.Size(157, 23); this.lbTrayIconColorValue.TabIndex = 1004; - this.lbTrayIconColorValue.Text = "White/Black"; + this.lbTrayIconColorValue.Text = "◄ White/Black ►"; this.lbTrayIconColorValue.Click += new System.EventHandler(this.lbTrayIconColorValue_Click); // // lbTrayIconColor diff --git a/Forms/SettingsForm.cs b/Forms/SettingsForm.cs index 3d61176..4fc4876 100644 --- a/Forms/SettingsForm.cs +++ b/Forms/SettingsForm.cs @@ -1,11 +1,8 @@ using System; -using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Globalization; -using System.Linq; using System.Media; -using System.Resources; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; @@ -13,7 +10,6 @@ using SteamVR_OculusDash_Switcher.Properties; using SteamVR_OculusDash_Switcher.Properties.Localization; using Titanium; -using TitaniumComparator.LogicClasses; using static SteamVR_OculusDash_Switcher.Program; using static Titanium.Forms; @@ -101,6 +97,8 @@ public void InitializeControls() lbSaved.Text = LocalizationStrings.SettingsForm_btnApply_label__Saved; lbSaved.Left = btnApply.Left - MeasureText(lbSaved).Width; //- lbSaved.Margin.Right; lbSaved.ForeColor = this.BackColor; + gbFunctions.Text = LocalizationStrings.SettingsForm__Main_functions; + gbInterface.Text = LocalizationStrings.SettingsForm__Interface; //btnApply.Enabled = false; @@ -128,17 +126,25 @@ public void InitializeControls() #region Обработчики событий //\--------------- - //% SETTINGS APPLY + //! SETTINGS APPLY //\--------------- private void btnApply_Click(object sender, EventArgs e) { try { - bool restartControlBecouseStupidWinformsCantDoSuchBasicThingAsClearCombobox = false; //TODO: Someone plaese say me how to clear combobox and it's work without -1 index exception that if it's possible + bool restartControlBecouseStupidWinformsCantDoSuchBasicThingAsClearCombobox = false; //TODO: Someone plaese say me how to clear combobox and it's work without -1 index exception if it's possible + + //! LANGUAGE CHANGE if (!Equals(Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName, ((Language)comboLanguage.SelectedItem).Culture.TwoLetterISOLanguageName)) { Thread.CurrentThread.CurrentUICulture = ((Language)comboLanguage.SelectedItem).Culture; restartControlBecouseStupidWinformsCantDoSuchBasicThingAsClearCombobox = true; + + ErrorTaskDialog.InitializeDictionary( + LocalizationStrings.ErrorTaskDialog__OpenMicrosoftDocs, + LocalizationStrings.ErrorTaskDialog__Copy_to_Clipboard, + LocalizationStrings.ErrorTaskDialog__Open_Inner_Exception, + LocalizationStrings.Button__Close); } if (comboSteamVRDisableMethod.SelectedItem!= null && Settings.Default.SteamVRDisablingMethod != (SteamVRMethod)comboSteamVRDisableMethod.SelectedItem) @@ -209,9 +215,9 @@ private async void btnCheckOculusKillerUpdates_Click(object sender, EventArgs e) var status = await OculusDash.CheckKiller(true); MessageBox.Show(status switch { - OculusDash.Status.Downloaded => LocalizationStrings.OculusKiller_StatusDiscription_Downloaded, - OculusDash.Status.Updated => LocalizationStrings.OculusKiller_StatusDiscription_Updated, - OculusDash.Status.NoAction => LocalizationStrings.OculusKiller_StatusDiscription_NoAction + GitHub.Status.Downloaded => LocalizationStrings.OculusKiller_StatusDiscription_Downloaded, + GitHub.Status.Updated => LocalizationStrings.OculusKiller_StatusDiscription_Updated, + GitHub.Status.NoAction => LocalizationStrings.OculusKiller_StatusDiscription_NoAction }, "Updated", MessageBoxButtons.OK, MessageBoxIcon.Information); btnCheckOculusKillerUpdates.Text = LocalizationStrings.SettingsForm_btnCheckOculusKillerUpdates; btnCheckOculusKillerUpdates.Enabled = true; @@ -278,7 +284,7 @@ private void lbIconsRealism_Resize(object sender, EventArgs e) private void SetTrayColorValue() { - lbTrayIconColorValue.Text = Settings.Default.BlackMode ? LocalizationStrings.SettingsForm_lbTrayIconColorValue_Black + "►" : "◄" + LocalizationStrings.SettingsForm_lbTrayIconColorValue_White; + lbTrayIconColorValue.Text = Settings.Default.BlackMode ? LocalizationStrings.SettingsForm_lbTrayIconColorValue_Black + " ►" : "◄ " + LocalizationStrings.SettingsForm_lbTrayIconColorValue_White; } private void Change_IconRealism_DiscriptionAndImage() diff --git a/LICENSE b/LICENSE index ad3dccf..ba2400a 100644 --- a/LICENSE +++ b/LICENSE @@ -2,10 +2,6 @@ Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Public L CC BY-NC-SA -Please, note that this licence is not applying to all files under `Titanium` namespace. They may use other licence specified in file (if other licence is not specified, they inherit project's licence (this)). In this project these files are: -Logic/Titanium.cs -Logic/TitaniumForms.cs - Short: You are free to: Share — copy and redistribute the material in any medium or format diff --git a/Logic/AnimationManager.cs b/Logic/AnimationManager.cs index 9377cc2..2ab3001 100644 --- a/Logic/AnimationManager.cs +++ b/Logic/AnimationManager.cs @@ -3,10 +3,7 @@ using System.ComponentModel; using System.Diagnostics; using System.Drawing; -using System.Linq; using System.Reflection; -using System.Text; -using System.Threading.Tasks; namespace SteamVR_OculusDash_Switcher.Logic { diff --git a/Logic/ErrorTaskDialog.cs b/Logic/ErrorTaskDialog.cs deleted file mode 100644 index db66fe6..0000000 --- a/Logic/ErrorTaskDialog.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Windows.Forms; -using SteamVR_OculusDash_Switcher.Properties.Localization; -using TaskDialogButton = Ookii.Dialogs.WinForms.TaskDialogButton; - -namespace TitaniumComparator.LogicClasses -{ - public static class ErrorTaskDialog - { - public static void ShowMessageBox(this Exception? Error) - { - if(Error == null) return; - - Ookii.Dialogs.WinForms.TaskDialog taskDialog = new(); - taskDialog.Content = Error.Message; - //if(Error.Data.Count!=0) taskDialog.CollapsedControlText = Error.Data.ToDictionary().ToStringT(); - if(Error.HelpLink!=null) taskDialog.Footer = $"{LocalizationStrings.ErrorTaskDialog__OpenMicrosoftDocs}"; - TaskDialogButton closeBtn = new(LocalizationStrings.Button__Close), - copyBtn = new(LocalizationStrings.ErrorTaskDialog__Copy_to_Clipboard), - innerExceptionBtn = new(LocalizationStrings.ErrorTaskDialog__Open_Inner_Exception); - innerExceptionBtn.Enabled = Error.InnerException != null; - taskDialog.Buttons.Add(closeBtn); - taskDialog.Buttons.Add(copyBtn); - taskDialog.Buttons.Add(innerExceptionBtn); - - var button = taskDialog.ShowDialog(); - if(button == closeBtn) taskDialog.Dispose(); - if(button == copyBtn) {Thread thread = new(() => Clipboard.SetText(Error.Message)); - thread.SetApartmentState(ApartmentState.STA); - thread.Start(); - thread.Join(); - Error.ShowMessageBox(); - } - if(button == innerExceptionBtn) - Error.InnerException.ShowMessageBox(); - } - } -} diff --git a/Logic/GitHub.cs b/Logic/GitHub.cs new file mode 100644 index 0000000..fac0403 --- /dev/null +++ b/Logic/GitHub.cs @@ -0,0 +1,245 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using ICSharpCode.SharpZipLib.Zip; +using Octokit; +using Octokit.Internal; +using FileMode = System.IO.FileMode; + +namespace Titanium; + +public static class GitHub +{ + public enum Status + { + Downloaded, + Updated, + NoAction + } + + public static string ProxyAddress = "auto"; + + public class UpdateResult + { + public Status Status; + public string? ReleaseDiscription { get; private set; } + public string? ReleaseName { get; private set; } + public Version? Version { get; private set; } + + public UpdateResult(Status Status = Status.NoAction, Version? Version = null, string? ReleaseName = null, string? ReleaseDiscription = null) + { + this.Status = Status; + this.ReleaseDiscription = ReleaseDiscription; + this.ReleaseName = ReleaseName; + this.Version = Version; + } + + public UpdateResult Change(Status? Status = null, Version? Version = null, string? ReleaseName = null, string? ReleaseDiscription = null) + { + if (Status!=null) this.Status = (Status)Status; + if (ReleaseDiscription!=null) this.ReleaseDiscription = ReleaseDiscription; + if (ReleaseName!=null) this.ReleaseName = ReleaseName; + if (Version!=null) this.Version = Version; + return this; + } + } + + + public enum UpdateMode + { + /// Download programm if it's not exist, but don't update it if it's exist + Download, + /// Only checks a new version, but not update it. + Check, + /// Update programm only if it's not installed or older version is installed + Update, + /// Update programm even if the newer version is installed + Replace + } + + /// + /// Updates the exe in specified paths from GitHub releases page + /// + /// GitHub repository link from where updates will be downloaded. In any format from "https://github.com/TuTAH1/xml-js-Parser/releases/tag/1.2.0" to just "TuTAH1/xml-js-Parser" (both variants will give the same result) + /// Path of physical exe file that should be updated + /// Should archives be unpacked while placing in + /// regex of the filename of the release + /// Leave GitHub release files in ./Temp. Don't Forget to DELETE TEMP folder after performing needed operations + /// List of files that shouldn't extracted from downloaded archive. If null, all files will be extracted + /// Turns Blacklist into whitelist if true + /// Function that will be executed if update found. If this function will return false, update will be canceled + /// + public static async Task checkSoftwareUpdatesByLink(UpdateMode UpdateMode, string repositoryLink, string ProgramExePath, string DownloadPath = "Temp", bool ClearDownloadFolder = false, bool Unpack = true, Regex? GitHubFilenameRegex = null, bool ReverseGithubFilenameRegex = false, bool TempFolder = false, Regex[] ArchiveIgnoreFileList = null, bool ReverseArchiveFileList = false, bool KillRelatedProcesses = false, Func AskUpdate = default) + { + string[] ss = repositoryLink.RemoveFrom(TypesFuncs.Side.Start, "https://", "github.com/").Split("/"); + if (ss.Length < 2) throw new ArgumentException("Can't get username and repName from " + repositoryLink); + return await checkSoftwareUpdates(UpdateMode, ss[0], ss[1], ProgramExePath, DownloadPath,ClearDownloadFolder, Unpack: Unpack, GitHubFilenameRegex: GitHubFilenameRegex,ReverseGithubFilenameRegex: ReverseGithubFilenameRegex, TempFolder: TempFolder, ArchiveIgnoreFileList: ArchiveIgnoreFileList, ReverseArchiveFileList: ReverseArchiveFileList, KillRelatedProcesses: KillRelatedProcesses, AskUpdate: AskUpdate).ConfigureAwait(false); + } + + /// + /// Updates the exe in specified paths from GitHub releases page + /// + /// /// + /// + /// Repository author id (example: TuTAH1) + /// Repository name (example: SteamVR-OculusDash-Switcher) + /// Path of physical exe file that should be updated + /// + /// + /// Should archives be unpacked while placing in + /// regex of the filename of the release + /// + /// Leave GitHub release files in ./Temp. Don't Forget to DELETE TEMP folder after performing needed operations + /// List of files that shouldn't extracted from downloaded archive. If null, all files will be extracted + /// Turns Blacklist into whitelist if true + /// + /// Function that will be executed if update found. If this function will return false, update will be canceled + /// + public static async Task checkSoftwareUpdates(UpdateMode UpdateMode, string author, string repName, string ProgramExePath, string DownloadPath = "Temp", bool ClearDownloadFolder = false, bool Unpack = true, Regex GitHubFilenameRegex = null, bool ReverseGithubFilenameRegex = false, bool TempFolder = false, Regex[] ArchiveIgnoreFileList = null, bool ReverseArchiveFileList = false, bool? KillRelatedProcesses = false, Func AskUpdate = default) + + { + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + KillRelatedProcesses ??= !TempFolder; + + //string registyProxyAddress = @"HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings"; + //string? proxyConfigScriptLink = Registry.GetValue(registyProxyAddress,"AutoConfigURL", null)?.ToString(); + //bool proxyEnabled = Registry.GetValue(registyProxyAddress, "ProxyEnable",null)?.ToString() == 1.ToString() || !proxyConfigScriptLink.IsNullOrEmpty(); + + HttpClientHandler clientHandler = new HttpClientHandler(); + clientHandler.ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true; + var webProxy = new WebProxy(); + + clientHandler.Proxy = ProxyAddress switch + { + "auto" => (HttpClient.DefaultProxy) + ,"no" => null + ,_=> new WebProxy(new Uri(ProxyAddress)) + }; + + + var connection = new Connection( + new ProductHeaderValue("Titanium-GithubSoftwareUpdater"), + new HttpClientAdapter(() => clientHandler)); + + var github = new GitHubClient(connection); + var release = await github.Repository.Release.GetLatest(author, repName).ConfigureAwait(false); + Version? relVersion = null; + try + { + relVersion = Version.Parse(new Regex("[^.0-9]").Replace(release.TagName, "")); + } catch (Exception) { } + UpdateResult result = new(Status.NoAction, relVersion, release.Name, release.Body); + + bool fileExist = File.Exists(ProgramExePath); + + if (UpdateMode!= UpdateMode.Check && !fileExist || UpdateMode == UpdateMode.Replace) + { + await DownloadLastest().ConfigureAwait(false); + return result.Change(Status.Downloaded); + } + else if (UpdateMode is UpdateMode.Update or UpdateMode.Check) + { + var currentVersion = FileVersionInfo.GetVersionInfo(ProgramExePath); + if (currentVersion is null) + throw new InvalidOperationException("Product version field is empty"); + + //:If current file's version is higher than in github, don't do anything + if (UpdateMode == UpdateMode.Check || relVersion <= Version.Parse(currentVersion.ProductVersion!)) return result; + + if (AskUpdate == default || !AskUpdate(result)) + return result; + await DownloadLastest().ConfigureAwait(false); + return result.Change(Status.Updated); + } + return result; + + async Task DownloadLastest() + { + + var gitHubFiles = release.Assets; + + if (!gitHubFiles.Any()) throw new ArgumentNullException(nameof(gitHubFiles), "No any files found in the release"); + + gitHubFiles = ( + from file in gitHubFiles + where (GitHubFilenameRegex?.IsMatch(file.Name) ^ ReverseGithubFilenameRegex ?? true) //: Select all files aliased with GitHubFilename regex + select file).ToList(); + + if (!gitHubFiles.Any()) throw new ArgumentNullException(nameof(gitHubFiles),GitHubFilenameRegex==null? "No files found in the release" : $"No files matching \"{GitHubFilenameRegex}\" found in the release"); + + foreach (var file in gitHubFiles) + { + string filepath = $"{DownloadPath}\\{file.Name}"; + + using var client = new HttpClient(); + var s = await client.GetStreamAsync(file.BrowserDownloadUrl).ConfigureAwait(false); + if(ClearDownloadFolder) + try {IO.RemoveAll(DownloadPath, false); } catch (Exception) {} + Directory.CreateDirectory(filepath.Slice(0,"\\")); + var fs = new FileStream(filepath, FileMode.OpenOrCreate); //TODO: Заменить Temp на DownloadPath + s.CopyTo(fs); //TODO: may be done async + fs.Close(); + s.Close(); + + Unpack = Unpack && new FileInfo(filepath).Extension == ".zip"; + if (Unpack) + { + if ((bool)KillRelatedProcesses) + { + var archive = new ZipFile(fs.Name); + foreach (ZipEntry entry in archive) + { + var entryPath = (TempFolder ? "Temp\\" : "") + entry.Name; + var entryName = entryPath.Slice("\\", LastStart: true); + + if ((bool)KillRelatedProcesses && entryName.EndsWith(".exe")) + TypesFuncs.KillProcesses(Path: AppContext.BaseDirectory + entryName, Name: entryName); + } + + archive.Close(); + } + + ZipStrings.CodePage = 866; + new FastZip { EntryFactory = new ZipEntryFactory { IsUnicodeText = true } }.ExtractZip(filepath, (TempFolder? "Temp\\" : ""), null); + File.Delete(filepath); + } + else + { + File.Move(filepath, filepath.RemoveFrom(TypesFuncs.Side.Start, "Temp\\"), true); + } + } + + + //new WebClient().DownloadFile(releasesPage, "OculusDash.exe"); + return true; + } + + + } + + class GitHubFile + { + public string Name; + public string Link; + public Classes.FileSize? Size; + public DateTime? Date; + + public GitHubFile(string Name, string Link, string Size, string Date) + { + this.Name = Name; + this.Link = Link; + this.Size = Classes.FileSize.Get(Size); + try + { this.Date = Convert.ToDateTime(Date); } + catch (Exception) + { this.Date = null; } + + } + } +} \ No newline at end of file diff --git a/Logic/Internet.cs b/Logic/Internet.cs index a941d96..cb20f12 100644 --- a/Logic/Internet.cs +++ b/Logic/Internet.cs @@ -1,17 +1,11 @@ using System; using System.IO; using System.Net; //для работы с интернетом - -using System.Collections.Generic; -using System.Linq; using System.Text; using System.Threading.Tasks; -using System.Data.SqlClient; using AngleSharp; using AngleSharp.Dom; -using AngleSharp.Html.Dom; using AngleSharp.Io; -using static System.Console; namespace DataCollector { diff --git a/Logic/Logic.zip b/Logic/Logic.zip new file mode 100644 index 0000000..4b127be Binary files /dev/null and b/Logic/Logic.zip differ diff --git a/Logic/OculusDash.cs b/Logic/OculusDash.cs index 1a011ff..8441f0a 100644 --- a/Logic/OculusDash.cs +++ b/Logic/OculusDash.cs @@ -1,99 +1,78 @@ using System; -using System.Collections.Generic; using System.Diagnostics; using System.IO; -using System.Linq; using System.Net; using System.Net.Http; -using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Windows.Forms; using AngleSharp.Dom; using DataCollector; using Microsoft.Win32; +using SteamVR_OculusDash_Switcher.Properties.Localization; using Titanium; namespace SteamVR_OculusDash_Switcher.Logic { public static class OculusDash { - private static readonly string _oculusFolderPath = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Oculus")?.GetValue("InstallLocation")?.ToString()?? (Directory.Exists( @"C:\Program Files\Oculus\")? @"C:\Program Files\Oculus\" : null); + private static readonly string _oculusFolderPath; /// OculusDash.exe path - private static readonly string _oculusDashExePath = _oculusFolderPath?.Add("\\" + @"Support\oculus-dash\dash\bin\OculusDash.exe"); + private static readonly string _oculusDashExePath; ///Oculus killere exe's location inside of this program's folder const string _innerOculusKillerPath = @"OculusDashKiller\OculusDash.exe"; private static bool OculusKillerChecked = false; - public static bool IsOculusKillerExist => File.Exists(_innerOculusKillerPath); - public static bool IsOculusExist => _oculusFolderPath != null; - public enum Status + static OculusDash() { - Downloaded, - Updated, - NoAction - } + _oculusFolderPath = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Oculus")?.GetValue("InstallLocation")?.ToString()?? (Directory.Exists( @"C:\Program Files\Oculus\")? @"C:\Program Files\Oculus\" : null); //: Check where Oculus is installed, if not found, set default path - //TODO: Make it async - //:Checks if OculusKiller exe exist and downloads the lastest release if not - private static async Task checkOculusKiller(bool checkUpdates) - { - const string oculusDashDownloadLink = @"https://github.com/ItsKaitlyn03/OculusKiller/releases/latest/download/OculusDash.exe"; - - if (!IsOculusKillerExist) - { - await DownloadLastestOculusKiller(); - return Status.Downloaded; - } - else if (checkUpdates) + if (_oculusFolderPath == null) { - var doc = await Internet.getResponseAsync(@"https://github.com/ItsKaitlyn03/OculusKiller/releases"); - var lastestVersion = doc.QuerySelector(".ml-1.wb-break-all")?.Text(); - if (lastestVersion is null) - throw new ArgumentNullException(nameof(lastestVersion), "Can't get lastest version"); - - var currentVersion = FileVersionInfo.GetVersionInfo(_innerOculusKillerPath); - if (currentVersion is null) - throw new InvalidOperationException("Product version field is empty"); - - lastestVersion = new Regex("[^.0-9]").Replace(lastestVersion, ""); - - /*MessageBox.Show($"Lastest version: {lastestVersion};" + - $"\nParsed: {Version.Parse(lastestVersion)}" + - $"\n Current version: {currentVersion.ProductVersion}" + - $"\n Parsed: {Version.Parse(currentVersion.ProductVersion)}");*/ - - - //:If current file's version is lower than in github, download lastest from github - if (Version.Parse(lastestVersion) > Version.Parse(currentVersion.ProductVersion)) + var drives = DriveInfo.GetDrives(); + foreach (var drive in drives) { - await DownloadLastestOculusKiller(); //! Not checked - return Status.Updated; + string driveLetter = drive.RootDirectory.FullName; + string oculusFolderPath = Path.Combine(driveLetter, @"Oculus\"); + if (!Directory.Exists(oculusFolderPath)) continue; + + _oculusFolderPath = oculusFolderPath; + break; } } - return Status.NoAction; - async Task DownloadLastestOculusKiller() + if (_oculusFolderPath == null) { - using (var client = new HttpClient()) + using var fbd = new FolderBrowserDialog(); + fbd.Description = LocalizationStrings.OculusDash_OculusNotFound__Select_Oculus_folder_path; + DialogResult result = fbd.ShowDialog(); + + if (result == DialogResult.OK && !string.IsNullOrWhiteSpace(fbd.SelectedPath)) { - var s = await client.GetStreamAsync(oculusDashDownloadLink); - Directory.CreateDirectory(_innerOculusKillerPath.Slice(0,"\\")); - var fs = new FileStream(_innerOculusKillerPath, FileMode.OpenOrCreate); - s.CopyTo(fs); //TODO: may be done async + string[] files = Directory.GetFiles(fbd.SelectedPath); } - - new WebClient().DownloadFile(oculusDashDownloadLink, "OculusDash.exe"); - return true; } + + _oculusDashExePath = _oculusFolderPath?.Add("\\" + @"Support\oculus-dash\dash\bin\OculusDash.exe"); + } + + public static bool IsOculusKillerExist => File.Exists(_innerOculusKillerPath); + public static bool IsOculusExist => _oculusFolderPath != null; + + + //:Checks if OculusKiller exe exist and downloads the lastest release if not + private static async Task checkOculusKiller(bool checkUpdates) + { + const string oculusDashRepository = @"ItsKaitlyn03/OculusKiller"; + return(await GitHub.checkSoftwareUpdatesByLink(GitHub.UpdateMode.Update, oculusDashRepository,_innerOculusKillerPath,"OculusDashKiller").ConfigureAwait(false)).Status; } /// /// Checks if OculusKiller exe exist and otherwise downloads the lastest release (also tries to update, if is true) /// - public static async Task CheckKiller(bool checkUpdates = false) + public static async Task CheckKiller(bool checkUpdates = false) { - var status = await checkOculusKiller(checkUpdates); + var status = await checkOculusKiller(checkUpdates).ConfigureAwait(false); OculusKillerChecked = true; //: Sets true if no exceptions return status; } @@ -110,9 +89,18 @@ public static async Task CheckKiller(bool checkUpdates = false) { try { - return (new FileInfo(_oculusDashExePath).Length <= (IsOculusKillerExist? new FileInfo(_innerOculusKillerPath).Length : 1000))? //TODO: Add IsOculusExist check - File.Exists(_oculusDashExePath)? true : null - : false; + var oculusDashExeFileInfo = new FileInfo(_oculusDashExePath); + + if (!oculusDashExeFileInfo.Exists) //: OculusDash.exe exist + { + var backup = new FileInfo(_oculusDashExePath + "_"); + if (!backup.Exists) return null; //: OculusDash.exe_ exist + else return true; + } + else if (oculusDashExeFileInfo.Length <= (IsOculusKillerExist ? new FileInfo(_innerOculusKillerPath).Length : 1000)) //: OculusDash.exe is replaced by killer + return true; + else + return false; //: OculusDash.exe is ok } catch (Exception e) { @@ -123,11 +111,10 @@ public static async Task CheckKiller(bool checkUpdates = false) public static void Break() { - if (!OculusKillerChecked) CheckKiller(); + if (!OculusKillerChecked) CheckKiller().Wait(); if(IsOculusDashKilled()!= false) return; - File.Move(_oculusDashExePath, _oculusDashExePath + "_", true); //: Backup - File.Move(_innerOculusKillerPath, _oculusDashExePath,true); //: Replace with Oculus Killer + File.Copy(_innerOculusKillerPath, _oculusDashExePath,true); //: Replace with Oculus Killer } public static void Restore() diff --git a/Logic/Program.cs b/Logic/Program.cs index 723d8ce..6f9ec7a 100644 --- a/Logic/Program.cs +++ b/Logic/Program.cs @@ -1,31 +1,16 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Drawing; using System.IO; -using System.Linq; -using System.Reflection; -using System.Threading.Tasks; using System.Windows.Forms; using InfoBox; using Microsoft.Win32; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using Titanium; using Icon = System.Drawing.Icon; -using System.Globalization; -using System.Net; -using System.Net.Http; -using System.Resources; -using System.Threading; -using AngleSharp.Dom; -using DataCollector; using SteamVR_OculusDash_Switcher.Forms; using SteamVR_OculusDash_Switcher.Logic; using SteamVR_OculusDash_Switcher.Properties; using SteamVR_OculusDash_Switcher.Properties.Localization; -using TitaniumComparator.LogicClasses; - namespace SteamVR_OculusDash_Switcher { static partial class Program @@ -60,6 +45,12 @@ static void Main() Application.SetCompatibleTextRenderingDefault(true); if (_IconsRealismLevel <0 || _IconsRealismLevel > _MaxIconsRealismLevel) _IconsRealismLevel = 1; + ErrorTaskDialog.InitializeDictionary( + LocalizationStrings.ErrorTaskDialog__OpenMicrosoftDocs, + LocalizationStrings.ErrorTaskDialog__Copy_to_Clipboard, + LocalizationStrings.ErrorTaskDialog__Open_Inner_Exception, + LocalizationStrings.Button__Close); + bool oculusBroken = false; try { @@ -243,8 +234,8 @@ private static void GenerateMenuOptions(this ContextMenuStrip contextMenu) private static void ToggleSteamVR_Click(object sender, EventArgs args) { - if (_SteamVr.IsBroken) RestoreSteamVR(); - else BreakSteamVR(); + if (CurrentMode == Mode.Oculus) ToSteamVR(); + else ToOculus(); } private static void KillSteamVR() @@ -255,7 +246,7 @@ private static void KillSteamVR() { e.ShowMessageBox(); } } - private static void BreakSteamVR() + private static void ToOculus() { try { @@ -264,7 +255,7 @@ private static void BreakSteamVR() { OculusDash.Restore(); } - + CurrentMode = Mode.Oculus; notifyIcon1.Icon = GetIcon(); } catch (Exception e) @@ -273,7 +264,7 @@ private static void BreakSteamVR() } } - private static void RestoreSteamVR() + private static void ToSteamVR() { try { @@ -282,6 +273,9 @@ private static void RestoreSteamVR() { OculusDash.Break(); } + + CurrentMode = Mode.SteamVR; + notifyIcon1.Icon = GetIcon(); } catch (Exception e) diff --git a/Logic/SteamVR.cs b/Logic/SteamVR.cs index de90f9c..fe0700e 100644 --- a/Logic/SteamVR.cs +++ b/Logic/SteamVR.cs @@ -3,18 +3,12 @@ using System.Diagnostics; using System.IO; using System.Linq; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; using System.Windows.Forms; -using AngleSharp.Dom; -using DataCollector; using Microsoft.Win32; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using SteamVR_OculusDash_Switcher.Properties.Localization; using Titanium; -using TitaniumComparator.LogicClasses; namespace SteamVR_OculusDash_Switcher.Logic { @@ -22,7 +16,7 @@ public class SteamVR { public BreakMethod Method; private static string _steamVrFolderPath; - private static string _steamVRexeFolderPath; + private static string _steamVRexeFolderPath => _steamVrFolderPath.Add("\\") + @"bin\win64\"; public bool IsBroken; public static bool IsSteamVRExeFolderExist => Directory.Exists(_steamVRexeFolderPath); private static readonly string[] _steamVRProcesses = @@ -57,7 +51,7 @@ public bool LocateSteamVR() var openvrpaths = $@"c:\Users\{Environment.UserName}\AppData\Local\openvr\openvrpaths.vrpath"; _steamVrFolderPath = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Steam App 250820")?.GetValue("InstallLocation")?.ToString() ?? @"C:\Program Files (x86)\Steam\steamapps\common\SteamVR\"; - if (!Directory.Exists(_steamVrFolderPath)) + if (!Directory.Exists(_steamVRexeFolderPath)) { if (File.Exists(openvrpaths)) { @@ -80,8 +74,6 @@ public bool LocateSteamVR() methodFound = true; } else throw new DirectoryNotFoundException("SteamVR Folder not found"); } - - _steamVRexeFolderPath = _steamVrFolderPath.Add("\\") + @"bin\win64\"; return !methodFound; } diff --git a/Logic/SteamVRMethods.cs b/Logic/SteamVRMethods.cs index 046e7a8..ee7da99 100644 --- a/Logic/SteamVRMethods.cs +++ b/Logic/SteamVRMethods.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using SteamVR_OculusDash_Switcher.Properties.Localization; namespace SteamVR_OculusDash_Switcher.Logic @@ -53,6 +49,7 @@ public override bool Equals(object? obj) => BreakMethod bm => bm == Value, SteamVR svr => svr.Method == Value, SteamVRMethod svrm => Equals(svrm), + DBNull => false, _ => obj != null && (int)obj == (int)Value }; diff --git a/Logic/Titanium.cs b/Logic/Titanium.cs index 6b847b5..6c04636 100644 --- a/Logic/Titanium.cs +++ b/Logic/Titanium.cs @@ -1,13 +1,10 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Globalization; +using System.IO; using System.Linq; -using System.Linq.Expressions; -using System.Reflection; using System.Text; using System.Text.RegularExpressions; @@ -15,15 +12,15 @@ namespace Titanium { /// /// Just my library of small functions that makes c# programming easier. The automatization of automatization instrument - /// Despite of the program license, THIS file is CC BY-SA + /// Despite of the program license, THIS file is CC BY-NC-SA /// /// /// Author /// Титан /// /// - /// - public static class TypesFuncs { //!21.04.2022 + /// + public static class TypesFuncs { //!22.09.2022 #region Parsing @@ -245,13 +242,14 @@ public static string ToStringT(this double Double, int Accuracy = 3) //:10.10.20 } - public static string ToStringT(this T[] Array, string Separator = "") //:21.04.2022 Added separator parametr, replaced [foreach] by [for] + public static string ToStringT(this IEnumerable Array, string Separator = "") //:21.04.2022 Added separator parametr, replaced [foreach] by [for] { string s = ""; - for (int i = 0; i < Array.Length; i++) + var Count = Array.Count(); + for (int i = 0; i < Count; i++) { - s += Array[i]; - if (i != Array.Length - 1) s += Separator; + s += Array.ElementAt(i); + if (i != Count - 1) s += Separator; } return s; @@ -538,109 +536,49 @@ public static bool RandBool(int TrueProbability) { #endregion - #region List - - /// - /// Случайным образом перемешивает массив - /// - public static List RandomShuffle(this IEnumerable list) - { - Random random = new Random(); - var shuffle = new List(list); - for (var i = shuffle.Count() - 1; i >= 1; i--) - { - int j = random.Next(i + 1); - - var tmp = shuffle[j]; - shuffle[j] = shuffle[i]; - shuffle[i] = tmp; - } - return shuffle; - } - - public static List RandomShuffle(this IEnumerable list, Random random) - { - var shuffle = new List(list); - for (var i = shuffle.Count() - 1; i >= 1; i--) - { - int j = random.Next(i + 1); - - var tmp = shuffle[j]; - shuffle[j] = shuffle[i]; - shuffle[i] = tmp; - } - return shuffle; - } - - public static List RandomList(int start, int count) - { - List List = new List(count); - List Empty = new List(); - for (int i = 0; i < count; i++) - { - List.Add(0); - Empty.Add(true); - } - Random Random = new Random(); - - int End = start + count; - for (int i = start; i < End;) - { - int Index = Random.Next(0, count); //C#-повский рандом гавно. Надо заменить чем-то - - if (Empty[Index]) - { - List[Index] = i; - Empty[Index] = false; - i++; - - } - } - - return List; - } - - public static T Pop(this List list) - { - T r = list[^1]; - list.RemoveAt(list.Count-1); - return r; - } - - public static void Swap(this List list, int aIndex, int bIndex) - { - T value = list[aIndex]; - list[aIndex] = list[bIndex]; - list[bIndex] = value; - } - - public static void Swap(this T[] list, int aIndex, int bIndex) - { - T value = list[aIndex]; - list[aIndex] = list[bIndex]; - list[bIndex] = value; - } - - #endregion - - #endregion + #endregion #region OtherTypeFuncs #region Int - static int DivRem(int dividend, int divisor, out int reminder) + public static bool IsOdd(this int value) => value % 2 != 0; + public static bool IsEven(this int value) => value % 2 == 0; + + public static int DivRem(int dividend, int divisor, out int reminder) { int quotient = dividend / divisor; reminder = dividend - divisor * quotient; return quotient; } + + /// + /// Maxes input value be in range between MinValue and MaxValue + /// + public static int Fit(this int i, int MinValue = int.MinValue, int MaxValue = Int32.MaxValue) => i < MinValue ? MinValue : i > MaxValue ? MaxValue : i; + + /// + /// Maxes input value be in range between 0 and MaxValue + /// + public static int FitPositive(this int i, int MaxValue = int.MaxValue) => i < 0 ? 0 : i > MaxValue ? MaxValue : i; + /// + /// Maxes input value be in range between MinValue and 0 + /// + public static int FitNegative(this int i, int MinValue = int.MinValue) => i > 0 ? 0 : i < MinValue ? MinValue : i; + + #endregion #region String + public static bool ContainsAny(this string s, IEnumerable sequence) => sequence.Any(s.Contains); + public static bool ContainsAny(this string s, params string[] sequence) => sequence.Any(s.Contains); //:24.10.2022 IEnumerable replaced with params + public static bool ContainsAll(this string s, IEnumerable sequence) => sequence.All(s.Contains);//:24.10.2022 IEnumerable replaced with params + + public static bool ContainsAll(this string s, params string[] sequence) => sequence.All(s.Contains);//:24.10.2022 IEnumerable replaced with params + public static int SymbolsCount(this string s) { int i = s.Length; @@ -653,213 +591,288 @@ public static int SymbolsCount(this string s) } public static string FormatToString(this double d, int n, Positon pos, char filler = ' ') //:Ленивый и неоптимизированный способ + { + d = Math.Round(d, n); + string s = d.ToString(); + if (s.Length < n) { + switch (pos) { + case Positon.left: { + for (int i = s.Length; i < n; i++) { - d = Math.Round(d, n); - string s = d.ToString(); - if (s.Length < n) { - switch (pos) { - case Positon.left: { - for (int i = s.Length; i < n; i++) - { - s += filler; - } - } break; - case Positon.center: { - int halfString = (n - s.Length) / 2; - for (int i = 0; i < halfString; i++) - { - s=s.Insert(0, filler.ToString()); - } - for (int i = s.Length; i < n; i++) - { - s += filler; - } - }break; - case Positon.right: { - for (int i = 0; i < (n - s.Length); i++) - { - s = s.Insert(0, filler.ToString()); - } - }break; - } - } - else if (s.Length > n) { - int Eindex = s.LastIndexOf('E'); - - if (Eindex > 0) { //если в строке есть Е+хх - string E = s.TrimStart('E'); - s = s.Substring(0, n - E.Length); - s += E; - } - else { - s = s.Substring(0, n); - } - } - return s; + s += filler; } - - public static string FormatString(this string s, int n, Positon pos, char filler = ' ') - { - if (s.Lengthn) + for (int i = s.Length; i < n; i++) { - int Eindex = s.LastIndexOf('E'); - - if (Eindex>0) - { //если в строке есть Е+хх - string E = s.TrimStart('E'); - s=s.Substring(0, n-E.Length); - s+=E; - } - else - { - s=s.Substring(0, n); - } + s += filler; + } + }break; + case Positon.right: { + for (int i = 0; i < (n - s.Length); i++) + { + s = s.Insert(0, filler.ToString()); } - return s; + }break; } + } + else if (s.Length > n) { + int Eindex = s.LastIndexOf('E'); - public enum Positon: byte { left,center,right} + if (Eindex > 0) { //если в строке есть Е+хх + string E = s.TrimStart('E'); + s = s.Substring(0, n - E.Length); + s += E; + } + else { + s = s.Substring(0, n); + } + } + return s; + } /// - /// Slices the string form Start to End not including End + /// Makes s.Length be equal to by adding symbols if it's too short or cutting it if it's too long /// /// - /// - /// + /// + /// The position of if it's too short . If it's too long, it will be always aligned Left + /// + /// Positive is right, negative is left. Will be sliced if out of range /// - public static string Slice(this string s, int Start = 0, int End = Int32.MaxValue) + public static string FormatString(this string s, int FixedLength, Positon Align, char Filter = ' ', int Offset = 0) { - if (Start < 0) throw new ArgumentOutOfRangeException(nameof(Start),Start,null); - if (Start > End) throw new ArgumentOutOfRangeException(nameof(Start),Start,$"start ({Start}) is be bigger than end ({End})"); - if (End > s.Length) End = s.Length; - return s.Substring(Start, End - Start); + if (s.Length0? NumberOfFillerSymbols/2 : NumberOfFillerSymbols/2 +1, + secondHalf = NumberOfFillerSymbols-firstHalf; + if (NumberOfFillerSymbols.IsOdd()) Offset += (Offset > 0) ? -1 : 1; + + firstHalf += Offset; + secondHalf -= Offset; + s = Filter.Multiply(firstHalf) + s + Filter.Multiply(secondHalf); + } + break; + case Positon.right: + { + Offset.FitNegative(NumberOfFillerSymbols); + NumberOfFillerSymbols += Offset; + s = Filter.Multiply(NumberOfFillerSymbols) + s + Filter.Multiply(Offset); + } + break; + } + } + else if (s.Length>FixedLength) + { + int Eindex = s.LastIndexOf('E'); + + if (Eindex>0) + { //если в строке есть Е+хх + string E = s.TrimStart('E'); + s=s.Substring(0, FixedLength-E.Length); + s+=E; + } + else + { + s=s.Substring(0, FixedLength); + } + } + return s; } /// - /// Обрезает строку, начиная с первого появления StartsWith и заканчивая последним появлением EndsWith + /// Inserts between and /// /// - /// Строка, с которой будет происходить срезание - /// Строка, до которой будет происходить срезание - /// Возвращать строку, если начало или конец не найдены (иначе будет возвращен null) - /// Начинать поиск Start с конца - /// Начинать поиск Start с конца + /// + /// + /// /// - public static string Slice(this string s, string StartsWith, string EndsWith, bool AlwaysReturnString = false, bool LastStart = false, bool LastEnd = true) + /// + public static string Insert(this string s, string Value, int Start, int? End = null) { - var start = StartsWith == null? 0 : LastStart? s.LastIndexOfEnd(StartsWith) : s.IndexOfEnd(StartsWith); - if (start < 0) return AlwaysReturnString? s : null; - - s = s.Slice(start); + int end= End?? Start; + if (s.IsNullOrEmpty()) return Value; + if (Start < 0) Start = s.Length - Start; + if (end < 0) end = s.Length - Start; + if (Start > end) Swap(ref Start, ref end); - var end = EndsWith==null? s.Length-1 : LastEnd? s.LastIndexOf(EndsWith) : s.IndexOf(EndsWith); - if (end < 0) return AlwaysReturnString? s : null; + if (Start < 0 || Start > s.Length) throw new ArgumentException("Incorrect start " + Start, nameof(Start)); + if (end < 0 || end > s.Length) throw new ArgumentException("Incorrect end " +end, nameof(end)); - return s.Slice(0,end); + return s.Slice(0,Start) + Value + s.Slice(Start); } - public static string SliceFromEnd(this string s, string StartsWith = null, string EndsWith = null, bool AlwaysReturnString = false, bool LastStart = false, bool LastEnd = true) - { - var end = EndsWith==null? s.Length-1 : LastEnd? s.LastIndexOf(EndsWith) : s.IndexOf(EndsWith); - if (end < 0) return AlwaysReturnString? s : null; + public enum Positon: byte { left,center,right} - s = s.Slice(0, end); + /// + /// Slices the string form to + /// Supported types: , , , . + /// + /// Type of the + /// Type of the + /// + /// Start of the result string + /// + /// /// : 0 (don't cut start) + /// : Start index of the result string (inverse direction if negative) + /// : The string inside that will be the start position of the result + /// : The string inside matches Regex that will be the start position of the result + /// : Условия, которым должны удовлетворять символы начала строки (по функции на 1 символ) + /// + /// End of the result string + /// + /// : Max (don't cut end) + /// : End index of the result string (inverse direction if negative) + /// : The string inside that will be the end position of the result + /// : The string inside matches Regex that will be the end position of the result + /// : Условия, которым должны удовлетворять символы конца строки (по функции на 1 символ) + /// + /// return if or not found (may be half-cutted) + /// if true, the last occurance of the will be searched (doesn't do anything if is ) + /// if true, the last occurance of the will be searched (doesn't do anything if is ) + /// Include symbols (doesn't do anything if is ) + /// Include symbols (doesn't do anything if is ) + /// + /// + public static string Slice(this string s, Ts? Start, Te? End, bool AlwaysReturnString = false, bool LastStart = false, bool LastEnd = true, bool IncludeStart = false, bool IncludeEnd = false) + { + if (s.IsNullOrEmpty()) + if (AlwaysReturnString) + return null; + else + throw new ArgumentNullException(nameof(s)); - var start = StartsWith == null? 0 : LastStart? s.LastIndexOfEnd(StartsWith) : s.IndexOfEnd(StartsWith); - if (start < 0) return AlwaysReturnString? s : null; + int start; + int end; + bool BasicSlice = Start is int or null && End is int or null; - return s.Slice(start); - } - public static string Slice(this string s, int Start, string EndsWith, bool AlwaysReturnString = false, bool LastEnd = true) + switch (Start) { - var end = LastEnd? s.LastIndexOf(EndsWith) : s.IndexOf(EndsWith); - if (end < 0) return AlwaysReturnString? s : null; - - return s.Slice(Start, end); - + case null: + start = 0; + break; + case int startIndex: + start = startIndex; + if (start < 0) start = s.Length + start; //: count from end if negative + if (start < 0 || start >= s.Length) + if (AlwaysReturnString) + start = 0; + else + return null; + break; + case string startsWith: + start = LastStart ? s.LastIndexOfEnd(startsWith) : s.IndexOfEnd(startsWith); + if (start < 0) start = 0; + if (IncludeStart) start += startsWith.Length; + break; + case Regex startRegex: + var match = LastStart? startRegex.Matches(s).Last() : startRegex.Match(s); + start = match.Index>=0? + (match.Index + (IncludeStart ? 0 : match.Length)) : 0; + break; + case Func[] startConditions: + start = startConditions?.Any()==true? + s.IndexOfT(startConditions, IndexOfEnd: !IncludeStart, RightDirection: !LastStart) : 0; + if (start < 0) start = 0; + break; + default: + throw new TypeInitializationException(typeof(Ts).FullName, new ArgumentException($"Type of {nameof(Start)} is not supported")); } - public static string Slice(this string s, string StartsWith, int End = Int32.MaxValue, bool AlwaysReturnString = false, bool LastStart = false) - { - var start = LastStart? s.LastIndexOfEnd(StartsWith) : s.IndexOfEnd(StartsWith); - if (start < 0) return AlwaysReturnString? s : null; - - return s.Slice(start, End); - } + if (!BasicSlice) s = s.Slice(start); - /// - /// - /// - /// - /// Условия, которым должны удовлетворять символы начала строки (по функции на 1 символ) - /// Условия, которым должны удовлетворять символы конца строки (по функции на 1 символ) - /// Возвращать изначальную строку при неудачном поиске - /// - public static string Slice(this string s, Func[] StartConditions, Func[] EndConditions, bool AlwaysReturnString = false) + switch (End) { - if (!StartConditions.Any()) throw new ArgumentNullException(nameof(StartConditions), "Условия начальной строки не заданы"); - if (!EndConditions.Any()) throw new ArgumentNullException(nameof(EndConditions), "Условия конечной строки не заданы"); - - var start = s.IndexOfT(StartConditions,IndexOfEnd:true); - if (start < 0) return AlwaysReturnString? s : null; - - var end =s.IndexOfT(EndConditions,LastOccuarance:true); - if (end < 0) return AlwaysReturnString? s : null; - - return s.Slice(start, end); + case null: + end = s.Length; + break; + case int endIndex: + end = endIndex; + if (end < 0) end = s.Length + end; //: count from end if negative + if (BasicSlice && start > end) Swap(ref start, ref end); + if (end > s.Length) end = s.Length; + break; + case string endsWith: + end = (LastEnd ? s.LastIndexOf(endsWith) : s.IndexOf(endsWith)); + if(end<0) end = s.Length; + if (IncludeEnd) end += endsWith.Length; + break; + case Regex endregex: + var match = LastEnd? endregex.Matches(s).Last() : endregex.Match(s); + end = match.Index>=0? + (match.Index + (LastEnd ? 0 : match.Length)) : 0; + break; + case Func[] endConditions: + end = endConditions?.Any()!=true? + s.IndexOfT(endConditions,IndexOfEnd: IncludeEnd, RightDirection: !LastEnd) : 0; + if (end < 0) + if (AlwaysReturnString) + end = s.Length-1; + else + return null; + break; + default: + throw new TypeInitializationException(typeof(Ts).FullName, new ArgumentException($"Type of {nameof(End)} is not supported")); } - public static string Slice(this string s, int Start, Func[] EndConditions, bool AlwaysReturnString = false) - { - if (!EndConditions.Any()) throw new ArgumentNullException(nameof(EndConditions), "Условия конечной строки не заданы"); - - var end =s.IndexOfT(EndConditions,LastOccuarance:true); - if (end < 0) return AlwaysReturnString? s.Slice(Start) : null; - - return s.Slice(Start, end); - } + return BasicSlice ? + s.Substring(start, (end - start)) : + s.Slice(0, end); + } + + /// + /// Removes symbols from 0 to + /// Supported types: , , , ; + /// + /// Type of the + /// + /// Start of the result string + /// + /// /// : 0 (don't cut start) + /// : Start index of the result string (inverse direction if negative) + /// : The string inside that will be the start position of the result + /// : The string inside matches Regex that will be the start position of the result + /// : Условия, которым должны удовлетворять символы начала строки (по функции на 1 символ) + /// + + /// return if or not found (may be half-cutted) + /// if true, the last occurance of the will be searched (doesn't do anything if is ) + /// Include symbols (doesn't do anything if is ) + + /// + /// + public static string Slice(this string s, Ts? Start, bool AlwaysReturnString = false, bool LastStart = false, bool IncludeStart = false) => + s.Slice(Start, int.MaxValue, AlwaysReturnString, LastStart, false, IncludeStart, false); - public static string Slice(this string s, int Start, Regex EndRegex, bool AlwaysReturnString = false) + public static string SliceFromEnd(this string s, string StartsWith = null, string EndsWith = null, bool AlwaysReturnString = false, bool LastStart = false, bool LastEnd = true, bool IncludeStart = false, bool IncludeEnd = false) //:25.08.2022 includeStart, includeEnd { - var end = EndRegex.Match(s).Index; + var end = EndsWith==null? s.Length-1 : LastEnd? s.LastIndexOf(EndsWith) : s.IndexOf(EndsWith); if (end < 0) return AlwaysReturnString? s : null; - return s.Slice(Start, end); + s = s.Slice(0, end); + + var start = StartsWith == null? 0 : LastStart? s.LastIndexOfEnd(StartsWith) : s.IndexOfEnd(StartsWith); + if (start < 0) return AlwaysReturnString? s : null; + + return IncludeStart? StartsWith : "" + s.Slice(start) + (IncludeEnd? EndsWith : ""); } private static int IndexOfEnd(this string s, string s2) @@ -872,7 +885,7 @@ private static int IndexOfEnd(this string s, string s2) } /// - /// Reports the position of a symbol next to last occurance of a string s2 (position after the end of s2) + /// Reports the position of a symbol next to last occurance of a string (position after the end of ) /// /// /// @@ -893,39 +906,39 @@ public enum DirectionEnum Left } - public static int IndexOfT(this string s, Func[] Conditions, int Start = 0, int End = Int32.MaxValue, DirectionEnum Direction = DirectionEnum.Custom, bool IndexOfEnd = false, bool LastOccuarance = false) + public static int IndexOfT(this string s, Func[] Conditions, int Start = 0, int End = Int32.MaxValue, bool RightDirection = true, bool IndexOfEnd = false) //:22.09.22 Bugfix, deleted useless LastOccurance; Replaced DirectionEnum with bool RightDirection { - if (End == Int32.MaxValue) End = Direction == DirectionEnum.Left ? 0 : s.Length-1; + if (End == Int32.MaxValue) End = s.Length-1; if (Start < 0) Start = s.Length + Start; - if (Start < 0) throw new ArgumentOutOfRangeException(nameof(Start),Start,$"incorrect negative Start ({Start - s.Length}). |Start| should be ≤ s.Length ({s.Length})"); + if (Start < 0) new ArgumentOutOfRangeException(nameof(Start),Start,$"incorrect negative Start ({Start - s.Length}). |Start| should be ≤ s.Length ({s.Length})"); if (End < 0) End = s.Length + End; if (End < 0) throw new ArgumentOutOfRangeException(nameof(End),End,$"incorrect negative End ({End - s.Length}). |End| should be ≤ s.Length ({s.Length})"); if (End == Start) return -2; - if (Direction == DirectionEnum.Custom) - Direction = End > Start ? DirectionEnum.Right : DirectionEnum.Left; - else if ((Direction == DirectionEnum.Left && End > Start) || (Direction == DirectionEnum.Right && End < Start)) + if(RightDirection && End < Start || + !RightDirection && End > Start) Swap(ref Start, ref End); - - bool rightDirection = Direction == DirectionEnum.Right; - int defaultCurMatchPos = rightDirection? 0 : s.Length-1; + + int defaultCurMatchPos = RightDirection? 0 : Conditions.Length-1; int curCondition = defaultCurMatchPos; - int Result = -1; + int Result = -1; - if (rightDirection) + if (RightDirection) for (int i = Start; i < End; i++) { if (Conditions[curCondition](s[i])) { curCondition++; - if (curCondition != Conditions.Length-1) continue; + if (curCondition != Conditions.Length) continue; Result = i; curCondition = defaultCurMatchPos; - if(!LastOccuarance) break; + //if(!LastOccuarance) + break; } else { + i -= curCondition; curCondition = defaultCurMatchPos; } } @@ -938,20 +951,35 @@ public static int IndexOfT(this string s, Func[] Conditions, int Star if (curCondition != 0) continue; Result = i; curCondition = defaultCurMatchPos; - if(!LastOccuarance) break; + //if(!LastOccuarance) + break; } else { + i += ((Conditions.Length-1) - curCondition); curCondition = defaultCurMatchPos; } } - return IndexOfEnd ? Result : Result - Conditions.Length; + return Result = Result == -1 || IndexOfEnd ^ !RightDirection? + Result : (Result - Conditions.Length) +1; } - public static int IndexOfT(this string s, string s2, int Start = 0, int End = Int32.MaxValue, DirectionEnum Direction = DirectionEnum.Custom, bool IndexOfEnd = false, bool LastOccuarance = false) + /// + /// Return an index of (? end : start) of in + /// + /// + /// + /// Start checking from + /// until End + /// Direction of searching + /// Return index of end of s2 (last letter) instead of start (first letter) + /// + /// + /// + public static int IndexOfT(this string s, string s2, int Start = 0, int End = Int32.MaxValue, bool RightDirection = true, bool IndexOfEnd = false) //:22.09.22 Bugfix, deleted useless LastOccurance; Replaced DirectionEnum with bool RightDirection { - if (End == Int32.MaxValue) End = Direction == DirectionEnum.Left ? 0 : s.Length-1; + if (End == Int32.MaxValue) End = s.Length-1; if (Start < 0) Start = s.Length + Start; if (Start < 0) new ArgumentOutOfRangeException(nameof(Start),Start,$"incorrect negative Start ({Start - s.Length}). |Start| should be ≤ s.Length ({s.Length})"); if (End < 0) End = s.Length + End; @@ -959,17 +987,15 @@ public static int IndexOfT(this string s, string s2, int Start = 0, int End = In if (End == Start) return -2; - if (Direction == DirectionEnum.Custom) - Direction = End > Start ? DirectionEnum.Right : DirectionEnum.Left; - else if ((Direction == DirectionEnum.Left && End > Start) || (Direction == DirectionEnum.Right && End < Start)) + if(RightDirection && End < Start || + !RightDirection && End > Start) Swap(ref Start, ref End); - bool rightDirection = Direction == DirectionEnum.Right; - int defaultCurMatchPos = rightDirection? 0 : s.Length-1; + int defaultCurMatchPos = RightDirection? 0 : s2.Length-1; int curMatchPos = defaultCurMatchPos; int Result = -1; - if (rightDirection) + if (RightDirection) for (int i = Start; i < End; i++) { if (s[i] == s2[curMatchPos]) @@ -978,10 +1004,12 @@ public static int IndexOfT(this string s, string s2, int Start = 0, int End = In if (curMatchPos != s2.Length) continue; Result = i; curMatchPos = defaultCurMatchPos; - if(!LastOccuarance) break; + //if(!LastOccuarance) + break; } else { + i -= curMatchPos; curMatchPos = defaultCurMatchPos; } } @@ -994,15 +1022,18 @@ public static int IndexOfT(this string s, string s2, int Start = 0, int End = In if (curMatchPos != 0) continue; Result = i; curMatchPos = defaultCurMatchPos; - if(!LastOccuarance) break; + //if(!LastOccuarance) + break; } else { + i += ((s2.Length-1) - curMatchPos); curMatchPos = defaultCurMatchPos; } } - return IndexOfEnd? Result : Result - s2.Length; + return Result = Result == -1 || IndexOfEnd ^ !RightDirection? + Result : (Result - s2.Length) +1; } @@ -1030,7 +1061,7 @@ public static string Multiply(this char s, int count) /// - /// s[..Start] + NewString + s[End..] + /// [..] + + s[..] /// /// original string /// String from 0 to Start will be added before NewString @@ -1060,6 +1091,7 @@ public static string Replace(this string s, int Start, int? End = null, string N else return s[..Start] + NewString; } + public static string ReplaceFromLast(this string s, string OldString, string NewString, bool Exception = true) { if (OldString is null) @@ -1071,24 +1103,77 @@ public static string ReplaceFromLast(this string s, string OldString, string New return s.Replace(start, start+OldString.Length, NewString, Exception); } + public static string Replace(this string s, Dictionary ReplaceRule) => s.Replace(ReplaceRule.Keys, ReplaceRule.Values); + + public static string Replace(this string s, IEnumerable OldStrings, IEnumerable NewStrings) + { + throw new NotImplementedException(); + } + + /// + /// Escapes string + /// + /// + /// characters need to be escaped + /// + public static string Escape(this string s, string Characters, string EscapeSymbol = "\\") //:26.08.2022 + { + string result = ""; + foreach (var c in s) + { + if (Characters.Contains(c)) result += EscapeSymbol; + result += c; + } + + return result; + } + + public static IEnumerable Add(this IEnumerable strings, string addiction, bool ToEnd = true) + { + var list = strings.ToList(); + for (var index = 0; index < list.Count; index++) + { + list[index] = list[index].Add(addiction, ToEnd); + } + + return list; + } + /// - /// Adds unadded part of to in the end of . "stringTest".Add("Testing") //"stringTesting" will be unchanged if is already added to "Test".Add("st") //"Test". Replacement occuts only in end "Tests".Add("Testing") //"TestsTesting" + /// Добавляет ту часть к , которая не содержится в конце . Например, "Test".Add("stop") = "Testop"; "Test".Add("rest") = "Testrest" /// - /// Initial string - /// String that should be in the end of + /// + /// + /// Добавить к концу строки (иначе -- к началу) /// - public static string Add(this string s, string addiction) + public static string Add(this string s, string addiction, bool ToEnd = true) => ToEnd ? AddToEnd(s, addiction) : AddToStart(s, addiction); //TODO: ToEnd лучше заменить с bool на enum Position{Start,End} + + private static string AddToStart(this string s, string addiction) + { + if (addiction.Length>1) throw new NotImplementedException("AddToEnd with more than 1 character isn't supported yet"); + + if (s[0] != addiction[0]) return addiction + s; + else return s; + } + + private static string AddToEnd(this string s, string addiction) { + if (s.IsNullOrEmpty()) return addiction; int offset = 0; - //:Searches for *addiction* in the end of the *s* for (; offset < addiction.Length; offset++) { - if (s[^1] != addiction[offset]) continue; + if (s[^1] != addiction[offset]) continue; + int aPosition = offset; - for (int sPosition = s.Length-1; sPosition >0;) + for (int sPosition = s.Length-1; sPosition >=0;) { - if (s[sPosition--] != addiction[aPosition--]) break; + + if (s[sPosition--] != addiction[aPosition--]) + { + Debug.Print($"{s[..(sPosition+1)]}|{s[sPosition+1]}|{s[(sPosition+2)..]} ≠ {addiction[..(aPosition+1)]}|{addiction[aPosition+1]}|{addiction[(aPosition+2)..]}"); + break; + } if (aPosition < 0) { return s + addiction[(offset+1)..]; @@ -1108,8 +1193,103 @@ public static string Add(this string s, string addiction) #endregion - #region Array + #region Enumerable + + public static bool ContainsAny(this IEnumerable source, params T[] values) => source.Any(values.Contains); + public static bool ContainsAll(this IEnumerable source, params T[] values) => source.Any(values.Contains); + public static bool Empty(this IEnumerable s) => !s.Any(); + public static Tgt[] ToArray(this IEnumerable source, Func Convert) + { + Tgt[] result = new Tgt[source.Count()]; + int i = 0; + foreach (var s in source) + { + result[i++] = Convert(s); + } + + return result; + } + + /// + /// Случайным образом перемешивает массив + /// + public static List RandomShuffle(this IEnumerable list) + { + Random random = new Random(); + var shuffle = new List(list); + for (var i = shuffle.Count() - 1; i >= 1; i--) + { + int j = random.Next(i + 1); + + var tmp = shuffle[j]; + shuffle[j] = shuffle[i]; + shuffle[i] = tmp; + } + return shuffle; + } + + public static List RandomShuffle(this IEnumerable list, Random random) + { + var shuffle = new List(list); + for (var i = shuffle.Count() - 1; i >= 1; i--) + { + int j = random.Next(i + 1); + + var tmp = shuffle[j]; + shuffle[j] = shuffle[i]; + shuffle[i] = tmp; + } + return shuffle; + } + + public static List RandomList(int start, int count) + { + List List = new List(count); + List Empty = new List(); + for (int i = 0; i < count; i++) + { + List.Add(0); + Empty.Add(true); + } + Random Random = new Random(); + + int End = start + count; + for (int i = start; i < End;) + { + int Index = Random.Next(0, count); //C#-повский рандом гавно. Надо заменить чем-то + + if (Empty[Index]) + { + List[Index] = i; + Empty[Index] = false; + i++; + + } + } + + return List; + } + + public static T Pop(this List list) + { + T r = list[^1]; + list.RemoveAt(list.Count-1); + return r; + } + + public static void Swap(this List list, int aIndex, int bIndex) + { + T value = list[aIndex]; + list[aIndex] = list[bIndex]; + list[bIndex] = value; + } + public static void Swap(this T[] list, int aIndex, int bIndex) + { + T value = list[aIndex]; + list[aIndex] = list[bIndex]; + list[bIndex] = value; + } public static int IndexOf(this T[] array, T value) => Array.IndexOf(array, value); public static T[][] Split(this T[] array, int arraysCount) @@ -1189,7 +1369,7 @@ public static T[] ReduceDimension(this T[][] Arrays) return res; } - public static string ToStringLine(this T[] Array, string Separator = " ") + public static string ToStringLine(this IEnumerable Array, string Separator = " ") { string result = ""; foreach (var el in Array) @@ -1200,7 +1380,7 @@ public static string ToStringLine(this T[] Array, string Separator = " ") return result[..^Separator.Length]; } - public static string ToStringLine(this List Array, string Separator = " ") + public static string ToStringLine(this IEnumerable Array, string Separator, string LastSeparator) { string result = ""; foreach (var el in Array) @@ -1240,13 +1420,35 @@ public static List AddRangeAndGet(this List list, List summand) return list; } - public static string Remove(this string S, string RemovableString, StringComparison ComparisonType = StringComparison.Ordinal) + /// + /// Removes (? first : last) occurance of + /// + /// + /// + /// + /// + /// + public static string Remove(this string S, string RemovableString, bool FromLeft = true, StringComparison ComparisonType = StringComparison.Ordinal) { if (RemovableString is null or "") return S; - int startPos = S.IndexOf(RemovableString, ComparisonType); + int startPos = FromLeft? S.IndexOf(RemovableString) : S.LastIndexOf(RemovableString); return startPos == -1 ? S : S.Remove(startPos, RemovableString.Length); } + public static string RemoveFrom(this string Source, TypesFuncs.Side FromWhere = Side.End, params string[] RemovableStrings) + { + foreach (var rem in RemovableStrings) + Source = Source.RemoveFrom(FromWhere, rem); + return Source; + } + + public static string RemoveFrom(this string Source, Side FromWhere, string RemovableString) + { + if (FromWhere!= Side.End && Source.StartsWith(RemovableString)) Source = Source.Slice(RemovableString.Length); + if (FromWhere!= Side.Start && Source.EndsWith(RemovableString)) Source = Source.Slice(0, -RemovableString.Length); + return Source; + } + public static string RemoveAll(this string S, string RemovableString, StringComparison ComparisonType = StringComparison.Ordinal) { if (RemovableString is null or "") return S; @@ -1260,6 +1462,46 @@ public static string RemoveAll(this string S, string RemovableString, StringComp } } + public static string RemoveAll(this string S, string[] RemovableStrings, StringComparison ComparisonType = StringComparison.Ordinal) + { + foreach (var s in RemovableStrings) + { + S = S.RemoveAll(s, ComparisonType); + } + + return S; + } + + public enum Side + { + Start, + End, + Both + } + + public static string RemoveAllFrom(this string S, string RemovableChars, Side FromWhere = Side.Both, StringComparison ComparisonType = StringComparison.Ordinal) + { + int start = 0, end = 0; + + if (FromWhere != Side.End) + foreach (var C in S) + { + if (RemovableChars.Contains(C)) start++; + else break; + } + + if (FromWhere != Side.Start) + for (int i = S.Length -1; i >=0; i--) + { + if (RemovableChars.Contains(S[i])) end++; + else break; + } + + return S[start..^end]; + } + + public static bool AllEquals(this IEnumerable array) => array.All(x => Equals(array.First(), x)); + #endregion #region Size @@ -1295,21 +1537,59 @@ public enum Dimension } #endregion - #region Image + #region Regex - - public static Image Resize(this Image i, int NewDimensionValue, Dimension FixedDimension = Dimension.Height) + public static bool IsMatchT(this Regex r, string s, int start = 0) => s != null && r.IsMatch(s, start); + public static bool IsMatchAny(this IEnumerable r, string s, int start = 0) => s != null && r.Any(x => x.IsMatch(s)); + public static bool IsMatchAll(this IEnumerable r, string s, int start = 0) => s != null && r.All(x => x.IsMatch(s)); + public static Regex Reverse(this Regex r) + { + var regStr = r.ToString(); + return regStr.StartsWith(@"^((?!") && regStr.EndsWith(@").)*")? new Regex(regStr[5..^4]) : new Regex(@"^((?!" + r + @").)*"); + } + + public static List ReverseRegexes(this List r) + { + for (int i = 0; i < r.Count; i++) + { + r[i] = Reverse(r[i]); + } + + return r; + } + + /// + /// Swaps "equal" mode to "contains" mode (Добавляет $/^ в начало/конец, если их нет; Убирает их, если они есть) + /// + /// + /// + internal static Regex InvertRegex(string S) { - return i == null? null : new Bitmap(i, i.Size.Resize(NewDimensionValue, FixedDimension)); + bool anyStart = S.StartsWith("^"); + bool anyEnd = S.EndsWith("$"); + S = anyStart ? S[1..] : $"^{S}"; + S = anyEnd ? S[..^1] : $"{S}$"; + return new Regex(S); } + #endregion - #region Color + #region Dictionary - static Color Change(this Color c, byte? A = null, byte? R = null, byte? G = null, byte? B = null) => Color.FromArgb(A?? c.A, R??c.R, G??c.G, B??c.B); + /*public static Dictionary Reverse(this Dictionary source) + { + for (int i = 0; i < source.Count; i++) + { + source. + } + }*/ #endregion + #region Color + + static Color Change(this Color c, byte? A = null, byte? R = null, byte? G = null, byte? B = null) => Color.FromArgb(A?? c.A, R??c.R, G??c.G, B??c.B); + #region Universal Type public static void Swap(ref T a, ref T b) @@ -1333,5 +1613,306 @@ public static bool IsDefault(this T value) where T : struct #endregion + #region Process + + public static void KillProcesses(string? Path = null, string? Name = null) + { + List processes; + Name ??= Path?.Slice(new[] { new Func(x => x is '\\' or '/') }, LastStart:true); + try + { + processes = ( + from proc in Name == null ? Process.GetProcesses() : Process.GetProcessesByName(Name) + where Path == null || proc.MainModule.FileName == Path + select proc + ).ToList(); + } + catch (Exception e) + { + throw new Exception("Error while gathering processes", e); + } + + var Exceptions = new List(); + + foreach (var proc in processes) + { + try + { + proc.Kill(); + } + catch (Exception e) + { + Exceptions.Add(new InvalidOperationException($"Can't kill process {proc.ProcessName}", e)); + } + } + + if (Exceptions.Count > 0) throw new AggregateException(Exceptions); + } + + #endregion + + #endregion + } + + public static class IO + { + /// + /// Copies all files, directories, subdirectories and it's contant to the new folder + /// + /// + /// + /// + /// All paths should end on "\" and contains only "\" (not "/) + public static void CopyAll(string SourcePath, string TargetPath, bool KillRelatedProcesses = false, List ExceptList = null, bool DisableSyntaxCheck = false) + { + ExceptList ??= new List(); + var ErrorList = new List(); + if (!DisableSyntaxCheck) + { + SourcePath = SourcePath.Replace("/", "\\").Add("\\"); + TargetPath = TargetPath.IsNullOrEmpty()? "" : TargetPath.Replace("/", "\\").Add("\\"); + } + + //Now Create all of the directories + foreach (string dirPath in Directory.GetDirectories(SourcePath, "*", SearchOption.AllDirectories)) + { + if (ExceptList.IsMatchAny(dirPath)) continue; + try + { + Directory.CreateDirectory(dirPath.Replace(SourcePath, TargetPath)); + } + catch (Exception e) + { + ErrorList.Add(e); + } + + } + + //Copy all the files & Replaces any files with the same name + foreach (string newPath in Directory.GetFiles(SourcePath, "*.*", SearchOption.AllDirectories)) + { + if (ExceptList.IsMatchAny(newPath)) continue; + try + { + var destination = newPath.Replace(SourcePath, TargetPath); + if (KillRelatedProcesses && destination.EndsWith(".exe")) + TypesFuncs.KillProcesses(Path: destination); + File.Copy(newPath, destination , true); + } + catch (Exception e) + { + ErrorList.Add(e); + } + + } + + if (ErrorList.Count > 0) throw new AggregateException("Unable to copy files" ,ErrorList); + } + + public static void RemoveAll(string FolderPath, bool RemoveSelf = true, List? ExceptList = null) + { + var ErrorList = new List(); + + foreach (var dir in Directory.GetDirectories(FolderPath)) + { + try + { + if(ExceptList?.IsMatchAny(dir)?? false) continue; + RemoveAll(dir, ExceptList:ExceptList); + } + catch (Exception e) + { + ErrorList.Add(e); + } + } + + foreach (var file in Directory.GetFiles(FolderPath)) + { + if(ExceptList?.IsMatchAny(file)?? false) continue; + File.Delete(file); + } + + if (RemoveSelf) + try { Directory.Delete(FolderPath, false);} + catch (Exception e) {ErrorList.Add(e);} + + if (ErrorList.Count > 0) throw new AggregateException("Unable to copy files" ,ErrorList); + } + + /// + /// Renames all files in the Directory (not recursive) + /// + /// + /// Function where input is file's name (not path) and the output is new file's name + /// Regular expression of filePATHs that won't be renamed + public static void RenameAll(string FolderPath, Func Rename, List? ExceptList = null) + { + ExceptList ??= new List(); + foreach (var file in Directory.GetFiles(FolderPath)) + { + if(ExceptList.IsMatchAny(file)) continue; + + var fileInfo = new FileInfo(file); + var path = fileInfo.Directory.FullName; + var fileName = fileInfo.Name; + File.Move(file,Path.Combine(path, Rename(fileName))); + } + } + + public static void CopyAllTo(this DirectoryInfo di, string TargetPath, bool KillRelatedProcesses = false, List ExceptList = null, bool DisableSyntaxCheck = false) + { + CopyAll(di.FullName, TargetPath, KillRelatedProcesses, ExceptList, DisableSyntaxCheck); + } + + public static void MoveAllTo(string SourcePath, string TargetPath, bool DeleteSourceDir = true, bool KillRelatedProcesses = false, List ExceptList = null, bool DisableSyntaxCheck = false) => new DirectoryInfo(SourcePath).MoveAllTo(TargetPath, DeleteSourceDir, KillRelatedProcesses, ExceptList, DisableSyntaxCheck); + + public static void MoveAllTo(this DirectoryInfo di, string TargetPath, bool DeleteSourceDir = true, bool KillRelatedProcesses = false, List ExceptList = null, bool DisableSyntaxCheck = false) + { + var sourcePath = di.FullName; + CopyAll(sourcePath,TargetPath,KillRelatedProcesses, ExceptList, DisableSyntaxCheck); + if (DeleteSourceDir) Directory.Delete(sourcePath, true); + else + { + foreach (var dir in di.GetDirectories()) + { + if (ExceptList.IsMatchAny(dir.FullName)) + dir.Delete(true); + } + } + } + } + + public static class ClassicFuncs + { + public static bool IsNullOrEmpty(this string s) => string.IsNullOrEmpty(s); + + public static bool IsNullOrWhiteSpace(this string s) => string.IsNullOrWhiteSpace(s); + /// + /// Retrieves reference to specified string + /// + /// + /// A reference to s if it is in the common language runtime intern pool; otherwise null + public static string IsInterned(this string s) => string.IsInterned(s); + + } + + public static class Classes + { + public class FileSize + { + private long longSize; + + public double Size; + public SizeUnit Unit; + + public enum SizeUnit + { + Bit, + Byte, + KiloByte, + MegaByte, + GigaByte, + TeraByte, + PetaByte, + ExaByte, + ZettaByte, + YottaByte, + KiloBit, + MegaBit, + GigaBit, + TeraBit, + PetaBit, + ExaBit, + ZettaBit, + YottaBit + } + + private static string[][] UnitNames = new[] + { + new[] { "bit" }, + new[] { "byte", "b" }, + new[] { "kilobyte", "kb" }, + new[] { "megabyte", "mb" }, + new[] { "gigabyte", "gb" }, + new[] { "terabyte", "tb" }, + new[] { "petabyte", "pb" }, + new[] { "exabyte", "eb" }, + new[] { "zettabyte", "zb" }, + new[] { "yottabyte", "yb" }, + new[] { "kilobit", "kbit" }, + new[] { "megabit", "mbit" }, + new[] { "gigabit", "gbit" }, + new[] { "terabit", "tbit" }, + new[] { "petabit", "pbit" }, + new[] { "exabit", "ebit" }, + new[] { "zettabit", "zbit" }, + new[] { "yottabit", "ybit" } + }; + + private void CalculateLongSize() + { + int intUnit = (int)Unit; + longSize = (long)( + (SizeUnit)(Size * (double)Unit) == + SizeUnit.Bit ? 1 : + Unit == SizeUnit.Byte ? 8 : + Math.Pow(2, (intUnit - 2) % 8 + 1) * (Unit > SizeUnit.YottaByte ? 1 : 8) + ); + } + + private static string getUnitName(SizeUnit SU) => UnitNames[(int)SU][0]; + + private static SizeUnit getSizeUnit(string UnitName, bool strictlyEqual = false) + { + var lowerUnitName = UnitName.ToLower(); + + for (int i = UnitNames.Length-1; i >=0; i--) + { + //Debug.Write(UnitNames[i].ToStringT(", ")); + if (strictlyEqual? UnitNames[i].Contains(lowerUnitName) : UnitNames[i].Any(lowerUnitName.Contains)) + return (SizeUnit)i; + } + + throw new ArgumentOutOfRangeException(nameof(UnitName), "can't find unit named " + UnitName); + } + + public FileSize(long BitsCount) + { + longSize = BitsCount; + Size = BitsCount; + Unit = 0; + if (BitsCount < 8) return; + Size /= 8; + Unit++; + for (; Size > 1024 && Unit - /// Just my library of small functions that makes c# programming easier. The automatization of automatization instrument - /// Despite of the program license, THIS file is CC BY-SA + /// Just my library of small functions that makes c# WinForms programming easier. + /// Despite of the program license, THIS file is CC BY-NC-SA /// /// /// Author @@ -23,1315 +21,393 @@ namespace Titanium { /// /// /// - public static class TypesFuncs { //!21.04.2022 - - - #region Parsing - - #region IsType - public static bool IsDigit(this char c) => c >= '0' && c <= '9'; - - public static bool IsDoubleT(this char c) => (c >= '0' && c <= '9')||( c == '.' || c == ','||c=='-'); - #endregion - - #region ToType - #region Int - - - /// - /// Преобразует число в char в Int - /// - /// - /// Число, если содержимое Char – число: - /// -1 в противном случае - public static int ToIntT(this char ch) => - ch is >= '0' and <= '9'? - ch-'0' : -1; - - /// - /// "534 Убирает все лишние символы и читает Int. 04" => 53404. Максимально оптимизирован, даже лучше стандартного - /// - /// - /// Может ли число быть отрицательным (если нет, то - будет игнорироваться, иначе он будет учитываться только если стоит рядом с числом) - /// If true, stops parsing if int already found, but current symbol isn't a number - /// Возвращаемое значение при исключении (если не указывается, то при исключении... вызывается исключение - /// Числа, если они содержаться в строке - public static int ToIntT(this string str, bool CanBeNegative = true, bool StopIfNumberEnded = false, int? ExceptionValue = null) //:16.05.2022 removed reduntant ischar check - { - int Number = 0; - bool isContainsNumber = false; - bool negative = false; - foreach (var ch in str) - { - if (CanBeNegative&&!isContainsNumber) //:multiple ifs each cycle isn't affects perfomance; CanBeNegative не проверялось - { - if (ch == '-') negative = true; - else if (ch != ' ') negative = false; - } - if (ch.IsDigit()) - { - isContainsNumber = true; - if (Number > int.MaxValue / 10) return negative ? int.MinValue : int.MaxValue; //:none or positive perfomance effect - //TODO: make options for ValueOutOfRange ↑ – exceptoin; custom value; min/max value (current); overflow (system default) - Number = Number * 10 + (ch - '0'); - } else if(StopIfNumberEnded && isContainsNumber) break; - } - - if (!isContainsNumber) - { - if (ExceptionValue == null) - { - throw new ArgumentException("Строка не содержит числа"); - } - else return (int)ExceptionValue; - } - - return negative? -Number:Number; - } - - public static long ToLongT(this string str, bool CanBeNegative = true, bool StopIfNumberEnded = false, long? ExceptionValue = null) - { - long Number = 0; - bool isContainsNumber = false; - bool negative = false; - foreach (var ch in str) - { - if (CanBeNegative&&!isContainsNumber) //:multiple ifs each cycle isn't affects perfomance; CanBeNegative не проверялось - { - if (ch == '-') negative = true; - else if (ch != ' ') negative = false; - } - if (ch.IsDigit()) - { - isContainsNumber = true; - if (Number > long.MaxValue / 10) return negative ? long.MinValue : long.MaxValue; //:none or positive perfomance effect - //TODO: make options for ValueOutOfRange ↑ – exceptoin; custom value; min/max value (current); overflow (system default) - Number = Number * 10 + (ch - '0'); - } else if(StopIfNumberEnded && isContainsNumber) break; - } - - if (!isContainsNumber) - { - if (ExceptionValue == null) - { - throw new ArgumentException("Строка не содержит числа"); - } - else return (long)ExceptionValue; - } - - return negative? -Number:Number; - } - - - public static int? ToNIntT(this string str, bool CanBeNegative = true, bool StopIfNumberEnded = false) //:08.10.2021 new func - { - try - { - return ToIntT(str, CanBeNegative,StopIfNumberEnded); - } - catch (Exception) - { - return null; - } - } - - - /// - /// Searches for INT in string. Throws IndexOutOfRangeException if no any int found in this string. - /// - /// - /// - /// first INT number found in string - public static int FindInt(this string str, char thousandSeparator = ' ') //! legacy Obsolete - { - int end = 0, start = 0; - bool minus = false; - while (!str[start].IsDigit()) - { - minus = str[start] == '-'; - start++; - } - - end = minus ? start - 1 : start; - - do - { - end++; - } while ((str[end].IsDigit() || str[end] == thousandSeparator) && end < str.Length - 1); - - return Convert.ToInt32(str.Substring(start,end-start).Replace(thousandSeparator.ToString(), ""), new CultureInfo("en-US", true)); - } - public static int FindIntBetween(this string MainStr, string LeftStr, string RightStr = null) - { - if (RightStr == null) RightStr = LeftStr; - int right = MainStr.IndexOf(RightStr), - left = MainStr.LastIndexOf(LeftStr, 0, right - 1); - int number; - - do - { //TODO: Сначала искать IndexOf Right, потом LastIndexOf Left, - if (int.TryParse(MainStr.Substring(left+1,right-(left+1)), out number)) return number; - right = MainStr.IndexOf(RightStr); - left = MainStr.LastIndexOf(LeftStr, left, (right - left) - 1); - } while (left > 0 && right > 0); - - return -1; - } - - #endregion - - #region String - - /// - /// Int to subscript numbers string - /// - /// - /// - public static string ToIndex(this int number) - { - string num = number.ToString(); - string index = ""; - - for (int i = 0; i < num.Length; i++) { - if (num[i] == '1') index += '₁'; - if (num[i] == '2') index += '₂'; - if (num[i] == '3') index += '₃'; - if (num[i] == '4') index += '₄'; - if (num[i] == '5') index += '₅'; - if (num[i] == '6') index += '₆'; - if (num[i] == '7') index += '₇'; - if (num[i] == '8') index += '₈'; - if (num[i] == '9') index += '₉'; - if (num[i] == '0') index += '₀'; - } - return index; - } - - public static string ToVisibleString(this string s) //!03.10.2021 - { - string a = ""; - foreach (char c in s) - { - if (!char.IsControl(c)) a += c; - } - - return a; - } - - - /// - /// Преобразовывает string в double, обрезая дробную часть до момента, когда начинаются нули - /// - /// - /// - /// - public static string ToStringT(this double Double, int Accuracy = 3) //:10.10.2021 Created - { - var String = Double.ToString(CultureInfo.InvariantCulture); //: Пока я не умею преобразовывать double в String с минимально возможными затратами самостоятельно - var temp = String.Split('.'); - if (temp.Length == 1) - { - return temp[0]; - } - else - { - string intPart = temp[0], decPart = temp[1]; - int superfluity = decPart.IndexOf('0'.Multiply(Accuracy)); //TODO: вместо нулей можно пробегать по строке, пока символ не начнет повторятся несколько раз - if (superfluity == -1) return String; - - decPart = decPart.Slice(0, superfluity); - return intPart + "." + decPart; - } - - } - - public static string ToStringT(this T[] Array, string Separator = "") //:21.04.2022 Added separator parametr, replaced [foreach] by [for] - { - string s = ""; - for (int i = 0; i < Array.Length; i++) - { - s += Array[i]; - if (i != Array.Length - 1) s += Separator; - } - - return s; - } - - public static string ToStringT (this IDictionary Dictionary, bool Vertical = true, string ItemSeparator = default, string PairSeparator = "\n", string BeforePair = null, string AfterPair = null) - { - ItemSeparator??= Vertical? " = " : ", "; - - if(Vertical) return BeforePair + string.Join(PairSeparator, Dictionary.Select(kv => kv.Key + ItemSeparator + kv.Value).ToArray()) + AfterPair; - else - { - string result = BeforePair; - foreach (var key in Dictionary.Keys) - { - result += key + ItemSeparator; - } - - result = result.Replace(-ItemSeparator.Length,null,AfterPair + PairSeparator + BeforePair); - - foreach (var value in Dictionary.Values) - { - result += value + ItemSeparator; - } - - return result + AfterPair; - } - - } - - - #region Bytes - - /// - /// Reads String and parses to Short until meets a letter - /// - /// - /// - - public static string ToHex(this byte[] bytes) - { - char[] c = new char[bytes.Length * 2]; - - byte b; - - for (int bx = 0, cx = 0; bx < bytes.Length; ++bx, ++cx) - { - b = ((byte)(bytes[bx] >> 4)); - c[cx] = (char)(b > 9 ? b + 0x37 + 0x20 : b + 0x30); - - b = ((byte)(bytes[bx] & 0x0F)); - c[++cx] = (char)(b > 9 ? b + 0x37 + 0x20 : b + 0x30); - } - - return new string(c); - } + public static class Forms + { + /// + /// source + /// SmirnoFF.Oleg + /// + public static T Clone(this T controlToClone) + where T : Control + { + PropertyInfo[] controlProperties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance); - #endregion + T instance = Activator.CreateInstance(); - #endregion - - #region Double - - /// - /// "534 Убирает все лишние символы и читает double. 0.4" => 5340.4 - /// - /// - /// - /// - /// Может ли нуль целой части быть опущен (".23" вместо "0.23") - /// - /// Разделитель целой и дробной части - /// - /// - public static double ToDoubleT(this string str, bool CanBeNegative = true, bool StopIfNumberEnded = false, bool CanBeShortcuted = true, bool DotShouldBeAttachedToNumber = true, char Separator = '.', double? ExceptionValue = Double.NaN) //:23.05.2022 Behaviour merged from ToIntT() - { - double Double = 0; - bool? IntPart = true; - int FractionalPart = -1; - bool isContainsDouble = false; - bool negative = false; - foreach (var ch in str) - { - if (IntPart == true) - { - if (CanBeNegative&&!isContainsDouble && ch == '-') negative = true; //:multiple ifs per cycle isn't affects perfomance; CanBeNegative не проверялось - else if (!isContainsDouble && ch != ' ') negative = false; - else if (ch.IsDigit()) - { - if (Double > double.MaxValue / 10) return negative ? double.MinValue : double.MaxValue; - //TODO: make options for ValueOutOfRange ↑ – exceptoin; custom value; min/max value (current); overflow (system default) - Double = Double * 10 + (ch - '0'); - isContainsDouble = true; - } - else if (ch == Separator) IntPart = null; //: Состояние квантовой запутанности. Целая часть и закончилась или нет одновременно. Ну а на самом деле просто чтобы не парсил точку или запятую в предложении как разделитель - else if(StopIfNumberEnded && isContainsDouble) break; - } - else - { - if (ch.IsDigit()) - { - if(FractionalPart==15) break; - IntPart = false; - //:на случай строки вроде ".23" (=="0.23") - if (!isContainsDouble && !CanBeShortcuted) - { - Double = Double * 10 + ch.ToIntT(); - isContainsDouble = true; - IntPart = true; - continue; - } - Double += ch.ToIntT()*Math.Pow(10, FractionalPart--); - } - else if (IntPart == null && DotShouldBeAttachedToNumber) IntPart = true; - else if(StopIfNumberEnded && isContainsDouble) break; - } - } - - if (!isContainsDouble) - { - if (ExceptionValue == null) - { - throw new ArgumentException("Строка не содержит числа"); - } - else return (int)ExceptionValue; - } - return negative? -Double:Double; - } - - public static double? ToNDoubleT(this string str, char Separator = '.', bool CanBeNegative = true, bool CanBeShortcuted = true, bool DotShouldBeAttachedToNumber = true, bool StopIfNumberEnded = false) //:08.10.2021 new func + foreach (PropertyInfo propInfo in controlProperties) + { + if (propInfo.CanWrite) { - try - { - return ToDoubleT(str, CanBeNegative, StopIfNumberEnded, CanBeShortcuted, DotShouldBeAttachedToNumber, Separator); - } - catch (Exception) - { - return null; - } + if(propInfo.Name != "WindowTarget") + propInfo.SetValue(instance, propInfo.GetValue(controlToClone, null), null); } + } + return instance; + } - #endregion - - #region Float - - public static float FindFloat(this string str, char DecimalSeparator = '.', char thousandSeparator = ' ') - { - int start = 0; - bool minus = false; - while (!str[start].IsDigit()) - { - minus = str[start] == '-'; - start++; - } - - int end = minus ? start - 1 : start; - do - { - end++; - } while ((str[end].IsDigit() || str[end] == thousandSeparator || str[end] == DecimalSeparator) && end < str.Length - 1); - - return Convert.ToSingle(str.Substring(start, end - start).Replace(DecimalSeparator, '.').Replace(thousandSeparator.ToString(), ""), new CultureInfo("en-US", true)); - } - - - #endregion - - #region Short - - public static short ReadShortUntilLetter(this string str) - { - short Short = 0; - foreach (var ch in str) - { - if (ch.IsDigit()) - { - Short = (short)(Short* 10 + (short)ch); - } - else break; - } - - return Short; - } - - #endregion - - #region Bool - - public static bool ToBool(this string S) - { - return S.ToLower() is "true" or "yes" or "да" or "1"; - } - - public static string ToRuString(this bool Bool) - { - return Bool? "Да" : "Нет"; - } - - public static bool RandBool(int TrueProbability) { - Random rand = new Random((int)DateTime.Now.Ticks); - return rand.Next() <= TrueProbability; - } - - #endregion - - #region Array - - public static double[,] ToDoubleT(this string[,] Strings, char Separator = '.') - { - int d0 = Strings.GetLength(0), d1 = Strings.GetLength(1); - double[,] doubles = new double[d0, d1]; - for (int i = 0; i < d0; i++) - { - for (int j = 0; j < d1; j++) - { - doubles[i, j] = Strings[i, j].ToDoubleT(Separator:Separator); - } - } - - return doubles; - } - - public static string[,] ToStringT(this double[,] Doubles, string Format) - { - int d0 = Doubles.GetLength(0), d1 = Doubles.GetLength(1); - string[,] strings = new string[d0, d1]; - for (int i = 0; i < d0; i++) - { - for (int j = 0; j < d1; j++) - { - strings[i, j] = Doubles[i, j].ToString(Format); - } - } - - return strings; - } - - public static string[,] ToStringMatrix(this double[,] Doubles) - { - int d0 = Doubles.GetLength(0), d1 = Doubles.GetLength(1); - string[,] strings = new string[d0, d1]; - for (int i = 0; i < d0; i++) - { - for (int j = 0; j < d1; j++) - { - strings[i, j] = Doubles[i, j].ToString(); - } - } - - return strings; - } - - public static T[,,] ToSingleArray(this T[][,] list) - { - int[] dimensions = new[] { list.Length, list[0].GetLength(0), list[0].GetLength(1) }; - - T[,,] result = new T[dimensions[0],dimensions[1],dimensions[2]]; - - try - { - - for (int i = 0; i < dimensions[0]; i++) - { - for (int j = 0; j < dimensions[1]; j++) - { - for (int k = 0; k < dimensions[2]; k++) - { - result[i, j, k] = list[i][j, k]; - } - } - } - - } - catch (ArgumentOutOfRangeException e) - { - throw new ArgumentException("iternal arrays' dimensions should be the same", e); - } - - return result; - } - - #endregion - - #endregion - - #region List - - /// - /// Случайным образом перемешивает массив - /// - public static List RandomShuffle(this IEnumerable list) - { - Random random = new Random(); - var shuffle = new List(list); - for (var i = shuffle.Count() - 1; i >= 1; i--) - { - int j = random.Next(i + 1); - - var tmp = shuffle[j]; - shuffle[j] = shuffle[i]; - shuffle[i] = tmp; - } - return shuffle; - } - - public static List RandomShuffle(this IEnumerable list, Random random) - { - var shuffle = new List(list); - for (var i = shuffle.Count() - 1; i >= 1; i--) - { - int j = random.Next(i + 1); - - var tmp = shuffle[j]; - shuffle[j] = shuffle[i]; - shuffle[i] = tmp; - } - return shuffle; - } - - public static List RandomList(int start, int count) - { - List List = new List(count); - List Empty = new List(); - for (int i = 0; i < count; i++) - { - List.Add(0); - Empty.Add(true); - } - Random Random = new Random(); - - int End = start + count; - for (int i = start; i < End;) - { - int Index = Random.Next(0, count); //C#-повский рандом гавно. Надо заменить чем-то - - if (Empty[Index]) - { - List[Index] = i; - Empty[Index] = false; - i++; - - } - } - - return List; - } - - public static T Pop(this List list) - { - T r = list[^1]; - list.RemoveAt(list.Count-1); - return r; - } - public static void Swap(this List list, int aIndex, int bIndex) - { - T value = list[aIndex]; - list[aIndex] = list[bIndex]; - list[bIndex] = value; - } - - public static void Swap(this T[] list, int aIndex, int bIndex) - { - T value = list[aIndex]; - list[aIndex] = list[bIndex]; - list[bIndex] = value; - } - - #endregion - - #endregion - - - #region OtherTypeFuncs - - #region Int - - static int DivRem(int dividend, int divisor, out int reminder) + /// + /// source + /// Stuart Helwig + /// + public static void CopyPropertiesTo(this Control sourceControl, Control targetControl) + { + // make sure these are the same + if (sourceControl.GetType() != targetControl.GetType()) { - int quotient = dividend / divisor; - reminder = dividend - divisor * quotient; - return quotient; + throw new ArgumentException("Incorrect control types"); } - #endregion - - #region String - - public static int SymbolsCount(this string s) + foreach (PropertyInfo sourceProperty in sourceControl.GetType().GetProperties()) { - int i = s.Length; - foreach (var c in s) + object newValue = sourceProperty.GetValue(sourceControl, null); + + MethodInfo mi = sourceProperty.GetSetMethod(true); + if (mi != null) { - if (char.IsControl(c)) i--; + sourceProperty.SetValue(targetControl, newValue, null); } - - return i; } + } - public static string FormatToString(this double d, int n, Positon pos, char filler = ' ') //:Ленивый и неоптимизированный способ - { - d = Math.Round(d, n); - string s = d.ToString(); - if (s.Length < n) { - switch (pos) { - case Positon.left: { - for (int i = s.Length; i < n; i++) - { - s += filler; - } - } break; - case Positon.center: { - int halfString = (n - s.Length) / 2; - for (int i = 0; i < halfString; i++) - { - s=s.Insert(0, filler.ToString()); - } - for (int i = s.Length; i < n; i++) - { - s += filler; - } - }break; - case Positon.right: { - for (int i = 0; i < (n - s.Length); i++) - { - s = s.Insert(0, filler.ToString()); - } - }break; - } - } - else if (s.Length > n) { - int Eindex = s.LastIndexOf('E'); - - if (Eindex > 0) { //если в строке есть Е+хх - string E = s.TrimStart('E'); - s = s.Substring(0, n - E.Length); - s += E; - } - else { - s = s.Substring(0, n); - } - } - return s; - } - - public static string FormatString(this string s, int n, Positon pos, char filler = ' ') - { - if (s.Lengthn) - { - int Eindex = s.LastIndexOf('E'); - - if (Eindex>0) - { //если в строке есть Е+хх - string E = s.TrimStart('E'); - s=s.Substring(0, n-E.Length); - s+=E; - } - else - { - s=s.Substring(0, n); - } - } - return s; - } - - public enum Positon: byte { left,center,right} - - /// - /// Slices the string form Start to End not including End - /// - /// - /// - /// - /// - public static string Slice(this string s, int Start = 0, int End = Int32.MaxValue) - { - if (Start < 0) throw new ArgumentOutOfRangeException(nameof(Start),Start,null); - if (Start > End) throw new ArgumentOutOfRangeException(nameof(Start),Start,$"start ({Start}) is be bigger than end ({End})"); - if (End > s.Length) End = s.Length; - return s.Substring(Start, End - Start); - } + public static int GetHeaderHeight(this Form F) + { + Rectangle screenRectangle = F.RectangleToScreen(F.ClientRectangle); + return screenRectangle.Top - F.Top; + } - /// - /// Обрезает строку, начиная с первого появления StartsWith и заканчивая последним появлением EndsWith - /// - /// - /// Строка, с которой будет происходить срезание - /// Строка, до которой будет происходить срезание - /// Возвращать строку, если начало или конец не найдены (иначе будет возвращен null) - /// Начинать поиск Start с конца - /// Начинать поиск Start с конца - /// - public static string Slice(this string s, string StartsWith, string EndsWith, bool AlwaysReturnString = false, bool LastStart = false, bool LastEnd = true) - { - var start = StartsWith == null? 0 : LastStart? s.LastIndexOfEnd(StartsWith) : s.IndexOfEnd(StartsWith); - if (start < 0) return AlwaysReturnString? s : null; - - s = s.Slice(start); + public static int GetWindowPadding(this Form F, Orientation Side + ) => (int)((Side == Orientation.Horizontal? 6 : 5) * (F.DeviceDpi/96.0)); - var end = EndsWith==null? s.Length-1 : LastEnd? s.LastIndexOf(EndsWith) : s.IndexOf(EndsWith); - if (end < 0) return AlwaysReturnString? s : null; + public static Size MeasureText(Label l) => TextRenderer.MeasureText(l.Text,l.Font); - return s.Slice(0,end); - } + #region Image - public static string SliceFromEnd(this string s, string StartsWith = null, string EndsWith = null, bool AlwaysReturnString = false, bool LastStart = false, bool LastEnd = true) + public static Image? Resize(this Image i, int NewDimensionValue, TypesFuncs.Dimension FixedDimension = TypesFuncs.Dimension.Height) { - var end = EndsWith==null? s.Length-1 : LastEnd? s.LastIndexOf(EndsWith) : s.IndexOf(EndsWith); - if (end < 0) return AlwaysReturnString? s : null; - - s = s.Slice(0, end); - - var start = StartsWith == null? 0 : LastStart? s.LastIndexOfEnd(StartsWith) : s.IndexOfEnd(StartsWith); - if (start < 0) return AlwaysReturnString? s : null; - - return s.Slice(start); - } - public static string Slice(this string s, int Start, string EndsWith, bool AlwaysReturnString = false, bool LastEnd = true) - { - var end = LastEnd? s.LastIndexOf(EndsWith) : s.IndexOf(EndsWith); - if (end < 0) return AlwaysReturnString? s : null; - - return s.Slice(Start, end); - + return i == null? null : new Bitmap(i, i.Size.Resize(NewDimensionValue, FixedDimension)); } - public static string Slice(this string s, string StartsWith, int End = Int32.MaxValue, bool AlwaysReturnString = false, bool LastStart = false) - { - var start = LastStart? s.LastIndexOfEnd(StartsWith) : s.IndexOfEnd(StartsWith); - if (start < 0) return AlwaysReturnString? s : null; - return s.Slice(start, End); + #endregion + /*public static async Task ForegroundColorAnimation(this Control c, Color EndColor, int TimeMs) + { + double R = EndColor.R, G = EndColor.G, B = EndColor.B, A = EndColor.A; + double R_ = c.ForeColor.R, G_ = c.ForeColor.G, B_ = c.ForeColor.B, A_ = c.ForeColor.A; + int TimeNs = (TimeMs * 1000) / 256; + Timer timer = new Timer(); + timer.Interval = + timer.Tick+=Timer_Tick(null); + }*/ + + /// + /// Class that makes some *Action* on all depedent controlls if one/all text of depedent controls is/not valid + /// + public class DependentControls + { + private readonly LogicType _logic; + private List _controls; + public bool isValid { get; private set; } + private delegate void ControlValidHandler(DependentControl sender, bool isValid); + public delegate void ValidHandler(bool isValid); + public event ValidHandler ValidChanged; + public Action DefaultValidFalseAction; + public Action DefaultValidTrueAction; + public Func DefaultValidCheck; + public enum LogicType + { + AND, + OR } /// - /// + /// All parametrs describes a default behavior for new added control /// - /// - /// Условия, которым должны удовлетворять символы начала строки (по функции на 1 символ) - /// Условия, которым должны удовлетворять символы конца строки (по функции на 1 символ) - /// Возвращать изначальную строку при неудачном поиске - /// - public static string Slice(this string s, Func[] StartConditions, Func[] EndConditions, bool AlwaysReturnString = false) - { - if (!StartConditions.Any()) throw new ArgumentNullException(nameof(StartConditions), "Условия начальной строки не заданы"); - if (!EndConditions.Any()) throw new ArgumentNullException(nameof(EndConditions), "Условия конечной строки не заданы"); - - var start = s.IndexOfT(StartConditions,IndexOfEnd:true); - if (start < 0) return AlwaysReturnString? s : null; - - var end =s.IndexOfT(EndConditions,LastOccuarance:true); - if (end < 0) return AlwaysReturnString? s : null; - - return s.Slice(start, end); - } - - public static string Slice(this string s, int Start, Func[] EndConditions, bool AlwaysReturnString = false) + /// Action that executes when control.text is NOT valid + /// Action that executes when control.text IS valid + /// Function that checks is control.text valid + public DependentControls(LogicType Logic, Action ValidFalseAction = null, Action ValidTrueAction = null, Func ValidCheck = null) { - if (!EndConditions.Any()) throw new ArgumentNullException(nameof(EndConditions), "Условия конечной строки не заданы"); - - var end =s.IndexOfT(EndConditions,LastOccuarance:true); - if (end < 0) return AlwaysReturnString? s.Slice(Start) : null; + _controls = new List(); + _logic = Logic; + DefaultValidFalseAction = ValidFalseAction; + DefaultValidTrueAction = ValidTrueAction; + DefaultValidCheck = ValidCheck; - return s.Slice(Start, end); } - public static string Slice(this string s, int Start, Regex EndRegex, bool AlwaysReturnString = false) - { - var end = EndRegex.Match(s).Index; - if (end < 0) return AlwaysReturnString? s : null; - return s.Slice(Start, end); - } - - private static int IndexOfEnd(this string s, string s2) + public void Add(Control Control, Action ValidFalseAction= null, Action ValidTrueAction= null, Func ValidCheck = null) { - if (s == null) - if (s2.Length == 0) - return 0; - int i = s.IndexOf(s2); - return i == -1 ? -1 : i + s2.Length; - } + ValidFalseAction ??= DefaultValidFalseAction ?? (_ => {}); + ValidTrueAction ??= DefaultValidTrueAction; + ValidCheck ??= DefaultValidCheck; + var newControl = new DependentControl(_logic, Control, ValidFalseAction, ValidTrueAction, ValidCheck); + _controls.Add(newControl); + newControl.ValidChanged += ValidChangeReaction; - /// - /// Reports the position of a symbol next to last occurance of a string s2 (position after the end of s2) - /// - /// - /// - /// - private static int LastIndexOfEnd(this string s, string s2) - { - if (s == null) - if (s2.Length == 0) return 0; - int i = s.LastIndexOf(s2); - if (i == -1) return -1; - else return i + s2.Length; + //newControl.CheckValidity(); } - public enum DirectionEnum - { - Custom, - Right, - Left - } + public void AddRange(List Controls, Action ValidFalseAction = null, Action ValidTrueAction = null, Func ValidCheck = null) => + Controls.ForEach(Control => this.Add(Control, ValidFalseAction, ValidTrueAction, ValidCheck)); - public static int IndexOfT(this string s, Func[] Conditions, int Start = 0, int End = Int32.MaxValue, DirectionEnum Direction = DirectionEnum.Custom, bool IndexOfEnd = false, bool LastOccuarance = false) + private void ValidChangeReaction(object _sender, bool value) { - if (End == Int32.MaxValue) End = Direction == DirectionEnum.Left ? 0 : s.Length-1; - if (Start < 0) Start = s.Length + Start; - if (Start < 0) throw new ArgumentOutOfRangeException(nameof(Start),Start,$"incorrect negative Start ({Start - s.Length}). |Start| should be ≤ s.Length ({s.Length})"); - if (End < 0) End = s.Length + End; - if (End < 0) throw new ArgumentOutOfRangeException(nameof(End),End,$"incorrect negative End ({End - s.Length}). |End| should be ≤ s.Length ({s.Length})"); - - if (End == Start) return -2; - - if (Direction == DirectionEnum.Custom) - Direction = End > Start ? DirectionEnum.Right : DirectionEnum.Left; - else if ((Direction == DirectionEnum.Left && End > Start) || (Direction == DirectionEnum.Right && End < Start)) - Swap(ref Start, ref End); - - bool rightDirection = Direction == DirectionEnum.Right; - int defaultCurMatchPos = rightDirection? 0 : s.Length-1; - int curCondition = defaultCurMatchPos; - int Result = -1; - - if (rightDirection) - for (int i = Start; i < End; i++) - { - if (Conditions[curCondition](s[i])) - { - curCondition++; - if (curCondition != Conditions.Length-1) continue; - Result = i; - curCondition = defaultCurMatchPos; - if(!LastOccuarance) break; - } - else - { - curCondition = defaultCurMatchPos; - } - } - else - for (int i = Start; i >= End; i--) + var sender = _sender as DependentControl; + switch (_logic) + { + case LogicType.AND: { - if (Conditions[curCondition](s[i])) + if (value) { - curCondition--; - if (curCondition != 0) continue; - Result = i; - curCondition = defaultCurMatchPos; - if(!LastOccuarance) break; - } - else - { - curCondition = defaultCurMatchPos; - } - } + sender.ExecuteValidTrueAction(); + bool temp = _controls.All(x => x.IsValid); + if(temp!= isValid) ValidChanged?.Invoke(temp); + isValid = temp; - return IndexOfEnd ? Result : Result - Conditions.Length; - } - - public static int IndexOfT(this string s, string s2, int Start = 0, int End = Int32.MaxValue, DirectionEnum Direction = DirectionEnum.Custom, bool IndexOfEnd = false, bool LastOccuarance = false) - { - if (End == Int32.MaxValue) End = Direction == DirectionEnum.Left ? 0 : s.Length-1; - if (Start < 0) Start = s.Length + Start; - if (Start < 0) new ArgumentOutOfRangeException(nameof(Start),Start,$"incorrect negative Start ({Start - s.Length}). |Start| should be ≤ s.Length ({s.Length})"); - if (End < 0) End = s.Length + End; - if (End < 0) throw new ArgumentOutOfRangeException(nameof(End),End,$"incorrect negative End ({End - s.Length}). |End| should be ≤ s.Length ({s.Length})"); - - if (End == Start) return -2; - if (Direction == DirectionEnum.Custom) - Direction = End > Start ? DirectionEnum.Right : DirectionEnum.Left; - else if ((Direction == DirectionEnum.Left && End > Start) || (Direction == DirectionEnum.Right && End < Start)) - Swap(ref Start, ref End); - - bool rightDirection = Direction == DirectionEnum.Right; - int defaultCurMatchPos = rightDirection? 0 : s.Length-1; - int curMatchPos = defaultCurMatchPos; - int Result = -1; - - if (rightDirection) - for (int i = Start; i < End; i++) - { - if (s[i] == s2[curMatchPos]) - { - curMatchPos++; - if (curMatchPos != s2.Length) continue; - Result = i; - curMatchPos = defaultCurMatchPos; - if(!LastOccuarance) break; } else { - curMatchPos = defaultCurMatchPos; + sender.ExecuteValidFalseAction(); + if(isValid) ValidChanged?.Invoke(false); + isValid = false; + } - } - else - for (int i = Start; i >= End; i--) + } break; + + case LogicType.OR: { - if (s[i] == s2[curMatchPos]) + if (value) { - curMatchPos--; - if (curMatchPos != 0) continue; - Result = i; - curMatchPos = defaultCurMatchPos; - if(!LastOccuarance) break; + if (!isValid) ValidChanged?.Invoke(true); + isValid = true; + foreach (var c in _controls) + c.ExecuteValidTrueAction(); } - else + else { - curMatchPos = defaultCurMatchPos; + bool temp = false; + foreach (var c in _controls) + { + if (c.IsValid) {temp = true; break;} + } + if(!temp) foreach (var c in _controls) + c.ExecuteValidFalseAction(); + if(temp!=isValid) ValidChanged?.Invoke(temp); + isValid = temp; } - } - - return IndexOfEnd? Result : Result - s2.Length; - } - - - public static string Multiply(this string s, int count) - { - StringBuilder sb = new StringBuilder(s.Length*count); - for (int i = 0; i < count; i++) - { - sb.Append(s); - } - - return sb.ToString(); - } - - public static string Multiply(this char s, int count) - { - StringBuilder sb = new StringBuilder(count); - for (int i = 0; i < count; i++) - { - sb.Append(s); + } break; } - - return sb.ToString(); - } - - - /// - /// s[..Start] + NewString + s[End..] - /// - /// original string - /// String from 0 to Start will be added before NewString - /// String from End to s.Length-1 will be added after NewString. If null, End = NewString.Length, or null if there's no NewString - /// String that will be between Start and End. If null, you'll just cut the text between Start and End - /// if false, its returns original string instead of exception - /// - public static string Replace(this string s, int Start, int? End = null, string NewString = null, bool Exception = true) - { - if (Start < 0) Start = s.Length - Start; - if (Start < 0 || Start > s.Length) - if (Exception) throw new ArgumentOutOfRangeException($"There's no position {Start} in string s[{s.Length}]"); - else return s; - - if (End != null) - { - if (End < 0) End = s.Length - End; - if (End < 0 || End > s.Length) - if (Exception) throw new ArgumentOutOfRangeException($"There's no position {End} in string s[{s.Length}]"); - else if (End > s.Length) End = s.Length - 1; - else return s; - } - else End = Start + NewString?.Length?? 0; - - if (Start == End && string.IsNullOrEmpty(NewString)) return s; - if (End < s.Length) return s[..Start] + NewString + s[(int)End..]; - else return s[..Start] + NewString; } - public static string ReplaceFromLast(this string s, string OldString, string NewString, bool Exception = true) + class DependentControl { - if (OldString is null) - { - if (Exception) throw new ArgumentNullException(nameof(OldString)); - else return s; - } - var start = s.LastIndexOf(OldString, StringComparison.Ordinal); - return s.Replace(start, start+OldString.Length, NewString, Exception); - } + private LogicType _logic; + internal Control Control; + private readonly Control InitialControl; - /// - /// Adds unadded part of to in the end of . "stringTest".Add("Testing") //"stringTesting" will be unchanged if is already added to "Test".Add("st") //"Test". Replacement occuts only in end "Tests".Add("Testing") //"TestsTesting" - /// - /// Initial string - /// String that should be in the end of - /// - public static string Add(this string s, string addiction) - { - int offset = 0; + public bool IsValid { get; private set; } + private event ControlValidHandler _validChanged; - //:Searches for *addiction* in the end of the *s* - for (; offset < addiction.Length; offset++) + public event ControlValidHandler ValidChanged { - if (s[^1] != addiction[offset]) continue; - int aPosition = offset; - for (int sPosition = s.Length-1; sPosition >0;) + add { - if (s[sPosition--] != addiction[aPosition--]) break; - if (aPosition < 0) - { - return s + addiction[(offset+1)..]; - } + _validChanged += value; + CheckValidity(); + _validChanged?.Invoke(this, IsValid); } + remove => _validChanged -= value; } - return s+addiction; - } - - - #endregion - - #region Double - - public static bool isEven(this int number) => number % 2 == 0; - - #endregion - - #region Array - - public static int IndexOf(this T[] array, T value) => Array.IndexOf(array, value); - - public static T[][] Split(this T[] array, int arraysCount) - { - var arraysSize = DivRem(array.Length,arraysCount,out int lastArraySize); - if (lastArraySize == 0) lastArraySize = arraysSize; + private Func _validCheck; + private Action _validFalseAction; + private Action _validTrueAction; - T[][] resultArrays = new T[arraysCount][]; - int k = 0; - for (int i = 0; i < arraysCount-1; i++) + public DependentControl(LogicType logic, Control Control, Action ValidFalseAction, Action ValidTrueAction = null, Func ValidCheck = null) { - resultArrays[i] = new T[arraysSize]; - for (int j = 0; j < arraysSize; j++) - { - resultArrays[i][j] = array[k++]; - } + _logic = logic; + this.Control = Control; + InitialControl = Control.Clone(); + _validCheck = ValidCheck ?? (x => x != ""); + IsValid = _validCheck(Control.Text); + _validFalseAction = ValidFalseAction; + _validTrueAction = ValidTrueAction?? ((_control) => InitialControl.CopyPropertiesTo(_control)); + + Control.TextChanged +=Control_TextChanged; } - resultArrays[^1] = new T[lastArraySize]; - for (int j = 0; j < lastArraySize; j++) + private void Control_TextChanged(object sender, EventArgs e) //TODO: можно сделать с таймером для оптимизации { - resultArrays[^1][j] = array[k++]; + CheckValidity(); } - return resultArrays; - } - public static int GetMaxIndex(this double[] array) - { - int MaxIndex = 0; - double MaxValue = array[0]; + internal void ExecuteValidTrueAction() => _validTrueAction(Control); + internal void ExecuteValidFalseAction() => _validFalseAction(Control); - for (int i = 1; i < array.Length; i++) + public bool CheckValidity() { - if (array[i] > MaxValue) + bool old = IsValid; + IsValid = _validCheck(Control.Text); + if (old != IsValid) { - MaxValue = array[i]; - MaxIndex = i; + _validChanged?.Invoke(this, IsValid); } - } - - return MaxIndex; - } - public static T[] Concat(T[] Array1, T[] Array2) - { - T[] res = new T[Array1.Length + Array2.Length]; - for (int i = 0; i < Array1.Length; i++) - { - res[i] = Array1[i]; - } - for (int i = 0; i < Array2.Length; i++) - { - res[i + Array1.Length] = Array2[i]; + return (bool)IsValid; } - - return res; } + } - public static T[] ReduceDimension(this T[][] Arrays) - { - int arraySize = 0; - for (int i = 0; i < Arrays.Length; i++) - { - arraySize += Arrays[i].Length; - } - - T[] res = new T[arraySize]; - int k = 0; - foreach (var array in Arrays) - { - foreach (var item in array) - { - res[k++] = item; - } - } + /// + /// Just Picture and text to the right to the picture. Not a standalone control because I don't really want to mess up with learning to how to render it, phew + /// + internal class PictureText : Control + { + private readonly PictureBox Picture; + private readonly Label Label; - return res; - } - - public static string ToStringLine(this T[] Array, string Separator = " ") + public PictureText(Point Location, Bitmap Image, int ImageHeight, string Text, int Spacing, VerticalAlignment TextAlignment = VerticalAlignment.Center) { - string result = ""; - foreach (var el in Array) - { - result += el + Separator; - } + this.Location = Location; - return result[..^Separator.Length]; - } + Picture = new PictureBox(); + Picture.Location = Location; + Picture.BackgroundImage = Image; + Picture.Size = Image.Size.Resize(ImageHeight); + Picture.BackgroundImageLayout = ImageLayout.Zoom; - public static string ToStringLine(this List Array, string Separator = " ") - { - string result = ""; - foreach (var el in Array) - { - result += el + Separator; - } + Label = new Label(); + Label.Text = Text; + Label.Padding = Padding.Empty; + Label.Margin = Padding.Empty; + Label.Size = MeasureText(Label)+ new Size(4,0); + Label.Location = new Point( + Location.X + Spacing + Picture.Width, + TextAlignment switch + { + VerticalAlignment.Top => Location.Y, + VerticalAlignment.Center => Location.Y + ImageHeight/2 - Label.Size.Height/2, + VerticalAlignment.Bottom => Location.Y + ImageHeight - Label.Size.Height, + _ => throw new ArgumentOutOfRangeException(nameof(TextAlignment), TextAlignment, null) + }); - return result[..^Separator.Length]; + Size = new(Picture.Width + Spacing + Label.Width, Math.Max(Picture.Height, Label.Height)); } - public static T[] FillAndGet(this T[] source, T value) + public void AddTo(Control control) { - for (int i = 0; i < source.Length; i++) - source[i] = value; - - return source; + control.Controls.AddRange(new Control[]{Picture, Label}); } + } - public static T[] AddRangeAndGet(this T[] array, T[] summand) - { - var newArray = new T[array.Length + summand.Length]; - for (int i = 0; i < array.Length; i++) - { - newArray[i] = array[i]; - } - for (int i = 0; i < summand.Length; i++) - { - newArray[i + array.Length] = summand[i]; - } + public class PictureTextCollection + { + // ReSharper disable once FieldCanBeMadeReadOnly.Local + private List _items; - return newArray; - } + //public List Items => _items; + public Point Location { get; } + public int Count => _items.Count; + public int ImageHeight; + public int ImageToTextDistance; + public int ImageToImageDistance; + public VerticalAlignment TextAlignment; + public Size Size = Size.Empty; + public int Width => Size.Width; + public int Height => Size.Height; + public Panel ParentPanel; - public static List AddRangeAndGet(this List list, List summand) + public PictureTextCollection(Point Location, int ImageHeight, int ImageToTextDistance, int ImageToImageDistance, Panel ParentPanel, VerticalAlignment TextAlignment = VerticalAlignment.Center) { - list.AddRange(summand); - return list; - } + this.ImageHeight = ImageHeight; + this.ImageToTextDistance = ImageToTextDistance; + this.Location = Location; + this.ImageToImageDistance = ImageToImageDistance; + this.ParentPanel = ParentPanel; + this.TextAlignment = TextAlignment; - public static string Remove(this string S, string RemovableString, StringComparison ComparisonType = StringComparison.Ordinal) - { - if (RemovableString is null or "") return S; - int startPos = S.IndexOf(RemovableString, ComparisonType); - return startPos == -1 ? S : S.Remove(startPos, RemovableString.Length); + _items = new List(); } - public static string RemoveAll(this string S, string RemovableString, StringComparison ComparisonType = StringComparison.Ordinal) + public void Add(Bitmap Image, string Text) { - if (RemovableString is null or "") return S; - - while (true) - { - int startPos = S.IndexOf(RemovableString,ComparisonType); - if (startPos == -1) return S; - - S = S.Remove(startPos, RemovableString.Length); - } + Point newLocation = Count == 0 ? Location : new Point(Location.X, _items.Last().Location.Y + _items.Last().Size.Height + ImageToImageDistance); + var item = new PictureText(newLocation, Image, ImageHeight, Text, ImageToTextDistance); + _items.Add(item); + item.AddTo(ParentPanel); + Size = new Size( + Math.Max(Width, item.Width), + Height == 0? (item.Height) : (Height + ImageToImageDistance + item.Height)); } - #endregion - - #region Size - - // returns the highest dimension - public static int Max(this Size s) => Math.Max(s.Height, s.Width); - // returns the lowest dimension - public static int Min(this Size s) => Math.Min(s.Height, s.Width); - - /// - /// Returns the new Size with same aspect ratio - /// - /// original size - /// - /// Dimension you just wrote - /// - public static Size Resize(this Size s, int NewDimensionValue, Dimension FixedDimension = Dimension.Height) - { - if ((FixedDimension is Dimension.Height && s.Height == 0) || (FixedDimension is Dimension.Width && s.Width == 0)) throw new ArgumentOutOfRangeException(nameof(s), $"original {FixedDimension} can't be 0"); - - return FixedDimension switch - { - Dimension.Height => new Size((s.Width * NewDimensionValue) / s.Height, NewDimensionValue), - Dimension.Width => new Size(NewDimensionValue, (s.Height * NewDimensionValue) / s.Width), - _ => throw new ArgumentOutOfRangeException(nameof(FixedDimension), FixedDimension, null) - }; - } - - public enum Dimension + public void AddRange(Bitmap[] Images, string[] Texts) + { + if (Images.Length != Texts.Length) throw new ArgumentException("Arrays should have same length"); + for (int i = 0; i < Images.Length; i++) { - Height, - Width + Add(Images[i], Texts[i]); } - #endregion - - #region Image - - - public static Image Resize(this Image i, int NewDimensionValue, Dimension FixedDimension = Dimension.Height) - { - return i == null? null : new Bitmap(i, i.Size.Resize(NewDimensionValue, FixedDimension)); } - #endregion - - #region Color - - static Color Change(this Color c, byte? A = null, byte? R = null, byte? G = null, byte? B = null) => Color.FromArgb(A?? c.A, R??c.R, G??c.G, B??c.B); - - #endregion - - #region Universal Type - - public static void Swap(ref T a, ref T b) - { - T c = a; - a = b; - b = c; - } - - public static bool IsDefault(this T value) where T : struct - { - bool isDefault = value.Equals(default(T)); - - return isDefault; - } - - public static IEnumerable ToDefault_IfNot(this IEnumerable s, Func predicate) => s?.Any(predicate) is true ? s : null; - public static T ToDefault_IfNot(this T s, Func predicate) where T : struct => predicate(s)? s : default; - - #endregion - - #endregion + } + } + public static class ErrorTaskDialog + { + public static string Language = "English"; + private static bool DictionaryInitialized = false; + + private static class ETDLocalizaton + { + internal static string OpenMicrosoftDocs; + internal static string Close; + internal static string Copy_to_Clipboard; + internal static string Open_Inner_Exception; + internal static string Title; + } + + public static void InitializeDictionary(string OpenMicrosoftDocs = "Open Microsoft documentation", string Copy_to_Clipboard = "Copy to Clipboard", string Open_Inner_Exception = "Open inner exception", string Close = "Close", string Title = "Error") + { + DictionaryInitialized = true; + + ETDLocalizaton.OpenMicrosoftDocs = OpenMicrosoftDocs; + ETDLocalizaton.Close = Close; + ETDLocalizaton.Copy_to_Clipboard = Copy_to_Clipboard; + ETDLocalizaton.Open_Inner_Exception = Open_Inner_Exception; + ETDLocalizaton.Title = Title; + } + public static void ShowMessageBox(this Exception? Error, string? Title = null) + { + if(Error == null) return; + if(!DictionaryInitialized) InitializeDictionary(); + + + Ookii.Dialogs.WinForms.TaskDialog taskDialog = new(); + taskDialog.WindowTitle = Title ?? ETDLocalizaton.Title; + taskDialog.Content = Error.Message; + //if(Error.Data.Count!=0) taskDialog.CollapsedControlText = Error.Data.ToDictionary().ToStringT(); + if(Error.HelpLink!=null) taskDialog.Footer = $"{ETDLocalizaton.OpenMicrosoftDocs}"; + TaskDialogButton closeBtn = new(ETDLocalizaton.Close), + copyBtn = new(ETDLocalizaton.Copy_to_Clipboard), + innerExceptionBtn = new(ETDLocalizaton.Open_Inner_Exception); + innerExceptionBtn.Enabled = Error.InnerException != null; + taskDialog.Buttons.Add(closeBtn); + taskDialog.Buttons.Add(copyBtn); + taskDialog.Buttons.Add(innerExceptionBtn); + + var button = taskDialog.ShowDialog(); + if(button == closeBtn) taskDialog.Dispose(); + if(button == copyBtn) {Thread thread = new(() => Clipboard.SetText(Error.Message)); + thread.SetApartmentState(ApartmentState.STA); + thread.Start(); + thread.Join(); + Error.ShowMessageBox(); + } + if(button == innerExceptionBtn) + Error.InnerException.ShowMessageBox(); + } } } diff --git a/Properties/Localization/LocalizationStrings.Designer.cs b/Properties/Localization/LocalizationStrings.Designer.cs index aa35292..49345f5 100644 --- a/Properties/Localization/LocalizationStrings.Designer.cs +++ b/Properties/Localization/LocalizationStrings.Designer.cs @@ -19,7 +19,7 @@ namespace SteamVR_OculusDash_Switcher.Properties.Localization { // с помощью такого средства, как ResGen или Visual Studio. // Чтобы добавить или удалить член, измените файл .ResX и снова запустите ResGen // с параметром /str или перестройте свой проект VS. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class LocalizationStrings { @@ -106,7 +106,7 @@ internal static string ControlsTipsForm__TitleText { } /// - /// Ищет локализованную строку, похожую на Break/Restore SteamVR. + /// Ищет локализованную строку, похожую на Switch SteamVR ↔ Oculus. /// internal static string ControlsTipsForm_tip__LMB { get { @@ -304,6 +304,15 @@ internal static string MessageBox_Title__Error { } } + /// + /// Ищет локализованную строку, похожую на Select Oculus folder path. + /// + internal static string OculusDash_OculusNotFound__Select_Oculus_folder_path { + get { + return ResourceManager.GetString("OculusDash_OculusNotFound__Select_Oculus_folder_path", resourceCulture); + } + } + /// /// Ищет локализованную строку, похожую на Newest Oculus Killer was downloaded successfully. /// @@ -331,6 +340,24 @@ internal static string OculusKiller_StatusDiscription_Updated { } } + /// + /// Ищет локализованную строку, похожую на Interface. + /// + internal static string SettingsForm__Interface { + get { + return ResourceManager.GetString("SettingsForm__Interface", resourceCulture); + } + } + + /// + /// Ищет локализованную строку, похожую на General functions. + /// + internal static string SettingsForm__Main_functions { + get { + return ResourceManager.GetString("SettingsForm__Main_functions", resourceCulture); + } + } + /// /// Ищет локализованную строку, похожую на Saved!. /// diff --git a/Properties/Localization/LocalizationStrings.resx b/Properties/Localization/LocalizationStrings.resx index f2336ba..2648dda 100644 --- a/Properties/Localization/LocalizationStrings.resx +++ b/Properties/Localization/LocalizationStrings.resx @@ -196,7 +196,7 @@ Open context menu - Break/Restore SteamVR + Switch SteamVR ↔ Oculus Kill SteamVR @@ -277,4 +277,13 @@ Checking updates... + + Select Oculus folder path + + + General functions + + + Interface + \ No newline at end of file diff --git a/Properties/Localization/LocalizationStrings.ru.resx b/Properties/Localization/LocalizationStrings.ru.resx index d69bac8..9642b6e 100644 --- a/Properties/Localization/LocalizationStrings.ru.resx +++ b/Properties/Localization/LocalizationStrings.ru.resx @@ -196,7 +196,7 @@ Открыть контекстное меню - Сломать/Восстановить SteamVR + Переключить SteamVR ↔ Oculus Убить SteamVR @@ -271,4 +271,13 @@ Проверка обновлений... + + Выберите путь папки Oculus + + + Основные функции + + + Интерфейс + \ No newline at end of file diff --git a/Readme images/Settings_1.jpg b/Readme images/Settings_1.jpg new file mode 100644 index 0000000..23b27eb Binary files /dev/null and b/Readme images/Settings_1.jpg differ diff --git a/Readme images/SteamVR modes_1.jpg b/Readme images/SteamVR modes_1.jpg new file mode 100644 index 0000000..eb7c98c Binary files /dev/null and b/Readme images/SteamVR modes_1.jpg differ diff --git a/Readme images/Tray menu_1.jpg b/Readme images/Tray menu_1.jpg new file mode 100644 index 0000000..617a628 Binary files /dev/null and b/Readme images/Tray menu_1.jpg differ diff --git a/SteamVR-OculusDash Switcher.csproj b/SteamVR-OculusDash Switcher.csproj index c7f538a..8dab0da 100644 --- a/SteamVR-OculusDash Switcher.csproj +++ b/SteamVR-OculusDash Switcher.csproj @@ -15,6 +15,7 @@ https://github.com/TuTAH1/SteamVR-OculusDash-Switcher en-US true + 10 @@ -39,7 +40,9 @@ + + diff --git a/Test/Form1.Designer.cs b/Test/Form1.Designer.cs new file mode 100644 index 0000000..089e680 --- /dev/null +++ b/Test/Form1.Designer.cs @@ -0,0 +1,60 @@ + +namespace SteamVR_OculusDash_Switcher.Forms +{ + partial class Form1 + { + /// + /// 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.SuspendLayout(); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(12, 9); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(59, 25); + this.label1.TabIndex = 0; + this.label1.Text = "label1"; + // + // Form1 + // + this.AutoScaleDimensions = new System.Drawing.SizeF(10F, 25F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(800, 450); + this.Controls.Add(this.label1); + this.Name = "Form1"; + this.Text = "Form1"; + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label label1; + } +} \ No newline at end of file diff --git a/Test/Form1.cs b/Test/Form1.cs new file mode 100644 index 0000000..d1dcea7 --- /dev/null +++ b/Test/Form1.cs @@ -0,0 +1,22 @@ +using System.Diagnostics; +using System.Windows.Forms; + +namespace SteamVR_OculusDash_Switcher.Forms +{ + public partial class Form1 : Form + { + public Form1() + { + InitializeComponent(); + Stopwatch test = Stopwatch.StartNew(); + var lab = new Label(); + this.Controls.Add(lab); + //label1.CopyPropertiesTo(lab); + lab.Text = "Test"; + lab.Top = 20; + //lab.Top += lab.Height + lab.Padding.Top; + test.Stop(); + MessageBox.Show(test.ElapsedMilliseconds.ToString()); + } + } +} diff --git a/Test/Form1.resx b/Test/Form1.resx new file mode 100644 index 0000000..f298a7b --- /dev/null +++ b/Test/Form1.resx @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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