diff --git a/Navferty.Common/Controls/CheckedListBoxEx.cs b/Navferty.Common/Controls/CheckedListBoxEx.cs index b6d3ef9..0107c98 100644 --- a/Navferty.Common/Controls/CheckedListBoxEx.cs +++ b/Navferty.Common/Controls/CheckedListBoxEx.cs @@ -2,6 +2,9 @@ using System.Drawing; using System.Windows.Forms; +using Navferty.Common.WinAPI; +using Navferty.Common.WinAPI.GDI; + #nullable enable namespace Navferty.Common.Controls @@ -47,19 +50,18 @@ protected override void WndProc(ref Message m) base.WndProc(ref m); //Now do our job. - switch ((WinAPI.WindowMessages)m.Msg) + switch ((Windows.WindowMessages)m.Msg) { - case WinAPI.WindowMessages.WM_PAINT: RePaint(); break; + case Windows.WindowMessages.WM_PAINT: RePaint(); break; } } private void RePaint() { if (Items.Count > 0 || string.IsNullOrWhiteSpace(emptyText)) - return;//We paint over only if ListBox noes not have any items and and EmptyText + return; //We paint over only if ListBox noes not have any items and and EmptyText - //var rcClient = WinAPI.GetClientRect(this); - using (var dc = new WinAPI.DC(this)) + using (var dc = this.GetDC()) using (var g = dc.CreateGraphics()) { g.PageUnit = GraphicsUnit.Pixel; diff --git a/Navferty.Common/Controls/ErrorDialog.cs b/Navferty.Common/Controls/ErrorDialog.cs new file mode 100644 index 0000000..9169c14 --- /dev/null +++ b/Navferty.Common/Controls/ErrorDialog.cs @@ -0,0 +1,214 @@ +using System; +using System.Drawing; + +using Navferty.Common.Feedback; + +#nullable enable + +namespace Navferty.Common.Controls +{ + internal class ErrorDialog : FormEx + { + private System.Windows.Forms.Button btnOk; + private System.Windows.Forms.Label lblMessage; + private System.Windows.Forms.PictureBox picIcon; + private System.Windows.Forms.TableLayoutPanel tlpMessage; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; + private System.Windows.Forms.LinkLabel llSendErrorReport; + private System.Windows.Forms.Label lblHeader; + private System.Windows.Forms.TableLayoutPanel tlpMain; + + internal ErrorDialog() : base() + { + InitializeComponent(); + + picIcon!.Image = SystemIcons.Exclamation.ToBitmap(); + lblMessage!.Text = string.Empty; + lblMessage!.BackColor = SystemColors.Window; + llSendErrorReport!.Text = Localization.UIStrings.Feedback_SendFeedback; + btnOk!.Text = Localization.UIStrings.ErrorWindow_OkButton; + } + + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ErrorDialog)); + this.tlpMain = new System.Windows.Forms.TableLayoutPanel(); + this.lblHeader = new System.Windows.Forms.Label(); + this.tlpMessage = new System.Windows.Forms.TableLayoutPanel(); + this.lblMessage = new System.Windows.Forms.Label(); + this.picIcon = new System.Windows.Forms.PictureBox(); + this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); + this.btnOk = new System.Windows.Forms.Button(); + this.llSendErrorReport = new System.Windows.Forms.LinkLabel(); + this.tlpMain.SuspendLayout(); + this.tlpMessage.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.picIcon)).BeginInit(); + this.tableLayoutPanel1.SuspendLayout(); + this.SuspendLayout(); + // + // tlpMain + // + this.tlpMain.AutoSize = true; + this.tlpMain.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + this.tlpMain.ColumnCount = 1; + this.tlpMain.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tlpMain.Controls.Add(this.lblHeader, 0, 0); + this.tlpMain.Controls.Add(this.tlpMessage, 0, 1); + this.tlpMain.Controls.Add(this.tableLayoutPanel1, 0, 2); + this.tlpMain.Dock = System.Windows.Forms.DockStyle.Fill; + this.tlpMain.Location = new System.Drawing.Point(0, 0); + this.tlpMain.Name = "tlpMain"; + this.tlpMain.RowCount = 3; + this.tlpMain.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tlpMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tlpMain.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tlpMain.Size = new System.Drawing.Size(384, 161); + this.tlpMain.TabIndex = 0; + // + // lblHeader + // + this.lblHeader.AutoSize = true; + this.lblHeader.Dock = System.Windows.Forms.DockStyle.Top; + this.lblHeader.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.lblHeader.ForeColor = System.Drawing.SystemColors.HotTrack; + this.lblHeader.Location = new System.Drawing.Point(3, 0); + this.lblHeader.Name = "lblHeader"; + this.lblHeader.Padding = new System.Windows.Forms.Padding(8); + this.lblHeader.Size = new System.Drawing.Size(378, 35); + this.lblHeader.TabIndex = 5; + this.lblHeader.Text = "label1"; + this.lblHeader.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // tlpMessage + // + this.tlpMessage.AutoSize = true; + this.tlpMessage.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + this.tlpMessage.BackColor = System.Drawing.SystemColors.Window; + this.tlpMessage.ColumnCount = 3; + this.tlpMessage.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 8F)); + this.tlpMessage.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tlpMessage.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tlpMessage.Controls.Add(this.lblMessage, 2, 0); + this.tlpMessage.Controls.Add(this.picIcon, 1, 0); + this.tlpMessage.Dock = System.Windows.Forms.DockStyle.Fill; + this.tlpMessage.Location = new System.Drawing.Point(3, 38); + this.tlpMessage.Name = "tlpMessage"; + this.tlpMessage.RowCount = 1; + this.tlpMessage.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tlpMessage.Size = new System.Drawing.Size(378, 66); + this.tlpMessage.TabIndex = 3; + // + // lblMessage + // + this.lblMessage.AutoSize = true; + this.lblMessage.BackColor = System.Drawing.SystemColors.Info; + this.lblMessage.Dock = System.Windows.Forms.DockStyle.Left; + this.lblMessage.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.lblMessage.Location = new System.Drawing.Point(47, 0); + this.lblMessage.Name = "lblMessage"; + this.lblMessage.Padding = new System.Windows.Forms.Padding(8); + this.lblMessage.Size = new System.Drawing.Size(68, 66); + this.lblMessage.TabIndex = 1; + this.lblMessage.Text = "label1"; + this.lblMessage.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // picIcon + // + this.picIcon.Dock = System.Windows.Forms.DockStyle.Left; + this.picIcon.Image = ((System.Drawing.Image)(resources.GetObject("picIcon.Image"))); + this.picIcon.Location = new System.Drawing.Point(11, 3); + this.picIcon.Name = "picIcon"; + this.picIcon.Size = new System.Drawing.Size(30, 60); + this.picIcon.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.picIcon.TabIndex = 2; + this.picIcon.TabStop = false; + // + // tableLayoutPanel1 + // + this.tableLayoutPanel1.ColumnCount = 2; + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel1.Controls.Add(this.btnOk, 1, 0); + this.tableLayoutPanel1.Controls.Add(this.llSendErrorReport, 0, 0); + this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Bottom; + this.tableLayoutPanel1.Location = new System.Drawing.Point(3, 110); + this.tableLayoutPanel1.Name = "tableLayoutPanel1"; + this.tableLayoutPanel1.Padding = new System.Windows.Forms.Padding(8); + this.tableLayoutPanel1.RowCount = 1; + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel1.Size = new System.Drawing.Size(378, 48); + this.tableLayoutPanel1.TabIndex = 4; + // + // btnOk + // + this.btnOk.DialogResult = System.Windows.Forms.DialogResult.OK; + this.btnOk.Dock = System.Windows.Forms.DockStyle.Right; + this.btnOk.Location = new System.Drawing.Point(285, 11); + this.btnOk.Name = "btnOk"; + this.btnOk.Size = new System.Drawing.Size(82, 26); + this.btnOk.TabIndex = 0; + this.btnOk.Text = "Close"; + this.btnOk.UseVisualStyleBackColor = true; + // + // llSendErrorReport + // + this.llSendErrorReport.AutoSize = true; + this.llSendErrorReport.Dock = System.Windows.Forms.DockStyle.Bottom; + this.llSendErrorReport.Location = new System.Drawing.Point(11, 27); + this.llSendErrorReport.Name = "llSendErrorReport"; + this.llSendErrorReport.Size = new System.Drawing.Size(268, 13); + this.llSendErrorReport.TabIndex = 1; + this.llSendErrorReport.TabStop = true; + this.llSendErrorReport.Text = "Send error report"; + this.llSendErrorReport.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.llSendErrorReport_LinkClicked); + // + // ErrorForm + // + this.AcceptButton = this.btnOk; + this.AutoSize = true; + this.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + this.ClientSize = new System.Drawing.Size(384, 161); + this.Controls.Add(this.tlpMain); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.MinimumSize = new System.Drawing.Size(400, 200); + this.Name = "ErrorForm"; + this.ShowIcon = false; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.tlpMain.ResumeLayout(false); + this.tlpMain.PerformLayout(); + this.tlpMessage.ResumeLayout(false); + this.tlpMessage.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.picIcon)).EndInit(); + this.tableLayoutPanel1.ResumeLayout(false); + this.tableLayoutPanel1.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + private readonly Exception? Err = null; + private readonly bool AllowErrorReporting = false; + + internal ErrorDialog(Exception ex, string? title, bool allowErrorReporting = true) : this() + { + Err = ex; + AllowErrorReporting = allowErrorReporting && (null != Err); + Text = Localization.UIStrings.ErrorWindow_Title; + bool HasTitle = !string.IsNullOrWhiteSpace(title); + { + lblHeader.Visible = HasTitle; + if (HasTitle) lblHeader.Text = title; + } + lblMessage.Text = (null == Err) ? "Unknown Error!" : ex.Message; + llSendErrorReport.Visible = allowErrorReporting; + } + + private void llSendErrorReport_LinkClicked(object sender, System.Windows.Forms.LinkLabelLinkClickedEventArgs e) + { + if (AllowErrorReporting) FeedbackManager.ShowFeedbackUI(Err?.Message); + } + } +} diff --git a/Navferty.Common/Controls/ErrorDialog.resx b/Navferty.Common/Controls/ErrorDialog.resx new file mode 100644 index 0000000..89b4a3c --- /dev/null +++ b/Navferty.Common/Controls/ErrorDialog.resx @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + + + iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO + vAAADrwBlbxySQAACNxJREFUWEfFV2lsVNcV5l8gJJAEkkgRQmxOAjisYTGLMWFx2KFlKYsS/0hoFKRE + +ZEGEqJUQipUVIkaERpq4w3HC96wwfUA9ixexzNjj+0Zz3jswZ4ZL9gem63UBWPe1+88v3EtQltQVfVI + R8+ed+/5vrPe+0Y9qRz6+OMwagz1FNVI9VPvaSp/y2/yTtaEadv+e6GxqN98/nn8kcOHbyckJPQbjcaH + TU1NuNHXhwcPHqgqf8tv8k7WyFrZI3s1M08v3Dzt6NGjP/7+xIlbBoPhYW8wqAQ9jUp7cbHiTUuH+4fT + cP7uBGq+/gb1J/8AT3IS/Hqj0uPxKL09PYrskb1iQ2xpZp9MuGHNsWPHGnWFhQM3e3uVjpJS+HOy0XU2 + Hr5PP4X/wAEEfrET/h3b0bptG5qpru07YN+xA+b334cjKUnxmSuV6+3titgQW2JTM//vhQu3njh+vMfh + cCi9tbVozTiP3uRktH34IToJ0E6w9i3b4NuyBa1UL7V5w2Y0btwI14ZoOKOjYd2wAaWbNqH222/RZauG + 2BKbYluDebwIS1no9/sRKChAe2oaOr44jA4NOLB1qwrcsnkzvARoJpD73XfRQFDHunWoo9rXrIHtnXdg + XbUKJfz7L+vWw5udDbGpkXh8JPhimoRK2Krg8fEIHPw1OrcTdP16NK1cCe/atUPABPVEE5i/O9asRR2B + 7AStXrMa5mXLUD53PkoXLULpihXQc1/Bqih4s7LUSGjp+HlNSLFIviTszd99h7aPPlI9bo6KRDfD//f8 + fLTzWb88Am4COwkaArZGRcFCj8sWLoSVewIkb99/AFdmzUTR4sXQLY1AXuQqdFutEAzB0mCHhIyipGKl + 4FynT6Ptq6/g28Tcrl6NThYVrl+HKt3d8O7bBwuBQsBV9NAcuRIl8+ahipG5ZjSi5UYvfHSkcucuXHzz + TVxasAB5b7+Nwl/uhGAIlmBq8KNGSc/q9fqHnZYqNB/5Eq3MsYD79uwBenqGwENyvQvu/ftRMWcOqiIj + Uc6Qm+aEw7xhI66VlKCprQ3OhgY4OBc8Vguusj6yw8KQNTscOQsWojHlJ0iLCqYKTiZhMjiC3d1KU1oa + mmNi0By9Di7mr2XPr4A7dzTkf8q9ri44SaKY3hXPnoVyFmNzaSma2tvRQPDamhp0cM3du3eRy3epU6ci + ddYsZJCEbs8uyJwQTMEWAjEyvXo8TbAfOgQ3C83FYnJIUTFszR98ANy8qQIPDg6iv78fd/m85Q/Atn07 + TEyDgDeyyhucTtjt9iFwEk/muzPPPYfk8eORSD1LzZg7D111dUpiYmK/YAuBUzJC/VeKFfuu3XAy9PUs + qBqqjfktnRUOx3vvoT8YxABJ3L59Wx293bduobO+Hq7iYjXcAmypqkKgowN/5ZpEpuZHgp8bOxYJ1HhN + UyZNgiMuDiaT6aFgCwGjzPGG2DilmuGqXRmJmpUrYGEKpKUqI5arobZx+vXReB/D2slnS0sLPF4vHB4P + qqurYTab4Q+Bk7x4nvLss0giaKKmQiR5wgQYGWnBFGwh4BePrL/9BrbVbCcN2LxsKcqWLkUJ28gYEYHC + 6VNRwmnXwlby+nxwuVxwMAI1BK+i5/5AAHcIfo6pC4GHVKIgKmSSxo3DBc4OwRRsIXBPTjT93r2wLF+u + gpctWYISDhIjwfXSx9OnI2/MGKTRmJ5RchK8zuEY9ry0rAx9NHiRLSthl3WpI1RIhJ4pJJH61lvqKSrY + KoH79+/DdPDgkMcCzOLT81nE3r7EkGUTPJ2bL06ejNrz51FNcJvNNgTOAjSx/QwkYeWwujR7tgok60Uz + HlUhMH8+BgYGhgn4gz1BVB79GgYS0Av44kXQsW0uEDhLA89j8dQSwOZ2w8o0mCsrUSoEKipgMhhQzGIs + Ki9HjU6Hgpkz1T2yV8jnjtALjFAuZ0Nfb+9wCozuRo9Sd+YMrpKAjM7C8HB1ozAWb3IJbs/Lg5Wht9Lz + SoL72fM3aKSQM75IIqDXy4UEFeyGBpMJujfeUAnka3pJdPRoFLAGDJ98Ag/vDYItBE6R/QPv1SsojFgG + 3dy5w+CSt6yXX4adp5mAW+h5BT0W8Js3biCJhJOnTYMuMwtGRkOiInXh4UzwW8womjFDBb1MvUK9Ss0c + Nx4NyUkKyQ4KthCIORt/tr/d7cJFFmD2Sy+p4RPPzzP/9szMIXCLRQX3aeChVhOSmWxTA9NT09iIRq71 + sUv6/taPvro6GCdNhv6ZZ2CgGqnnp0xFd4ODgyhheBCpo7ijrU3J5ymXPnGiCi59m8ERamFuzTxYyplf + P+f8TVZ7PM8AqXYBDxXaBRZfNVMQ4NkR5NC6pyhqq8kMKSNw5ZjRKOMktJ44DrnefXnkyNAoFpGDQQ6I + hthYpDLfSTQuQ+PP1DR2g573g9bOThU8jnPiT3wvJIWAgEvKpGALXn8dLTwHHsjEZJSu8HQsYtiruaaG + tjJffBE+ox4G44jDSIRMho/jPN5qUl55BbHcICE+TU3m8Vt7+TJil0XgB/4vA0UISL9nUgVccl1ILWEN + BXJyYODFRUJfx7UO7rFNmIg8EnrscSwSupB0MdfnZoQhgfkXTwVQ9I/aU+a5pEcISPjFe6lyHcHFW8mz + PMv5FGD388+jjnWVNmUKgizQyzrdzy8kImQ0fCXzpKcjhRtiSeJ7DVjISERGElBzr3kvFS7gFfzNxt8E + vJHgDtpIf+01tGTnwOl0/usrmQhfDF9Km0gikSM4jm34PQ0JiZEE1BZ9xPtSahXX1FJd3GMneAbbtCU3 + 9z9fSkPCBcPXckmHHBxnXn0VcSwgqXypjUcJSI9Lm5nHjEUNgW1cm/vCC8jnLSnIwfTE1/KQCEsJVejD + pJXtVfbFESROnYZY5jOFxtNJJoskLlEvU/UEzmObpRJct3s3r+I5asE99YdJSLhh+NOMk3JQrmzdHJ/X + CgqU6pMnlcJ9e5EesQQp4bORR09Nn30GNz/PunhQBeXTTK8flL1iQ2xpZp9euPn/83H6qNDY/+DzfNSo + fwDWYGmBKz70PQAAAABJRU5ErkJggg== + + + \ No newline at end of file diff --git a/Navferty.Common/Extensions/ControlsExtensions.cs b/Navferty.Common/Extensions/ControlsExtensions.cs index f237c66..a648a64 100644 --- a/Navferty.Common/Extensions/ControlsExtensions.cs +++ b/Navferty.Common/Extensions/ControlsExtensions.cs @@ -1,10 +1,11 @@ using System; using System.Diagnostics; using System.Drawing; -using System.Drawing.Drawing2D; using System.Runtime.CompilerServices; using System.Windows.Forms; +using Navferty.Common.WinAPI; + #nullable enable namespace Navferty.Common @@ -16,9 +17,9 @@ public static class ControlsExtensions public static void SetVistaCueBanner(this TextBox ctl, string? BannerText = null) { _ = ctl ?? throw new ArgumentNullException(nameof(ctl)); - ctl.RunWhenHandleReady(tb => WinAPI.SendMessage( + ctl.RunWhenHandleReady(tb => Windows.SendMessage( tb.Handle, - WinAPI.WindowMessages.EM_SETCUEBANNER, + Windows.WindowMessages.EM_SETCUEBANNER, 0, BannerText)); } @@ -75,6 +76,15 @@ public static void RunDelayed(this Action DelayedAction, int DelayInterval = 100 private const int DEFAULT_TEXT_EDIT_DELAY = 1000; private const string DEFAULT_FILTER_CUEBANNER = "Filter"; + /// + /// Attaches a deferred text change event handler that makes it possible to react to text changes with some delay, + /// allowing the user to correct erroneous input, + /// or complete input, rather than reacting immediately to each letter. + /// + /// TextChanged Handler + /// Delay (ms.) during which repeated input will not call the handler + /// Vista cueabanner text + /// Sets the background color for textbox to Systemcolors.Info [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AttachDelayedFilter( this TextBox txtCtl, @@ -97,13 +107,13 @@ public static void AttachDelayedFilter( }; txtCtl.TextChanged += (s, e) => { - //Перезапускаем таймер + //Restart timer... TMR.Stop(); TMR.Start(); }; } - + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AttachDelayedFilter( this TextBox txtCtl, @@ -120,28 +130,6 @@ public static void AttachDelayedFilter( SetBackColorAsSystemTipColor); } - - /* - - - Friend Sub AttachDelayedFilter(TB As System.Windows.Forms.ToolStripTextBox, - TextChangedCallBack As Action, - Optional iDelay_ms As Integer = 1000, - Optional VistaCueBanner As String = C_DEFAULT_FILTER_TEXTBOX_CUE_BANNER, - Optional SetBackColorAsSystemTipColor As Boolean = True) - - - With TB - - Call.TextBox.AttachDelayedFilter(TextChangedCallBack, iDelay_ms, VistaCueBanner, SetBackColorAsSystemTipColor) - - If(SetBackColorAsSystemTipColor) Then.BackColor = SystemColors.Info - End With - End Sub - */ - - - #endregion } diff --git a/Navferty.Common/Extensions/DelegatesExtensions.cs b/Navferty.Common/Extensions/DelegatesExtensions.cs new file mode 100644 index 0000000..c5d603d --- /dev/null +++ b/Navferty.Common/Extensions/DelegatesExtensions.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +using NLog; + +#nullable enable + +namespace Navferty.Common +{ + [DebuggerStepThrough] + public static class DelegatesExtensions + { + public static bool TryCatch( + this Action a, + bool displayErrorMessage = true, + string? errorTitle = null, + ILogger? logger = null, + string? loggerTitle = null, + bool allowErrorReporting = true + ) + { + try + { + a.Invoke(); + return true; + } + catch (Exception ex) + { + errorTitle ??= Application.ProductName; + loggerTitle ??= errorTitle; + + if (displayErrorMessage) + { + ex.ShowErrorUI(errorTitle, logger, loggerTitle, allowErrorReporting); + //MessageBox.Show(ex.Message, errorTitle!, MessageBoxButtons.OK, MessageBoxIcon.Error); + } + else + { + logger?.Error(ex, loggerTitle); + } + } + return false; + } + } +} diff --git a/Navferty.Common/Extensions/ErrorsExtensions.cs b/Navferty.Common/Extensions/ErrorsExtensions.cs new file mode 100644 index 0000000..3b86c2f --- /dev/null +++ b/Navferty.Common/Extensions/ErrorsExtensions.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +using Navferty.Common.Controls; + +using NLog; + +#nullable enable + +namespace Navferty.Common +{ + [DebuggerStepThrough] + public static class ErrorsExtensions + { + /// Displays error message box to user, with ability to send feedback, and wites error to the Log + /// Some description about error to display to the user + /// + /// Some description about error to write to Log. If null - use errorTitle + /// Allow user to send bug report + public static void ShowErrorUI( + this Exception ex, + string? errorTitle = null, + ILogger? logger = null, + string? loggerTitle = null, + bool allowErrorReporting = true) + { + loggerTitle ??= errorTitle ?? "[Error description empty]"; + logger ??= NLog.LogManager.GetCurrentClassLogger(); + logger?.Error(ex, loggerTitle); + + using var ef = new ErrorDialog(ex, errorTitle, allowErrorReporting); + ef.ShowDialog(); + } + } +} diff --git a/Navferty.Common/Extensions/RangeExtensions.cs b/Navferty.Common/Extensions/RangeExtensions.cs new file mode 100644 index 0000000..dbff9ab --- /dev/null +++ b/Navferty.Common/Extensions/RangeExtensions.cs @@ -0,0 +1,38 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +using Microsoft.Office.Interop.Excel; + +namespace Navferty.Common +{ + [DebuggerStepThrough] + public static class RangeExtensions + { + public const Int64 DEFAULT_MAX_ALLOWED_CELLS = 1_000_000L; + + public class TooManyCellsException : Exception + { + public TooManyCellsException() : base(Localization.UIStrings.Error_TooManyCellsSelected) { } + } + + /// Throws error if cells count in specifed range more than 'maxAllowedCellsInRange' (DEFAULT_MAX_ALLOWED_CELLS_IN_RANGE) + /// + /// + /// + + [DebuggerStepThrough] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Int64 ThrowIfTooManyCellsSelected( + this Range? sel, + Int64 maxAllowedCellsInRange = DEFAULT_MAX_ALLOWED_CELLS) + { + Int64 iCellsSelected = sel?.Cells?.Count ?? 0; + + if (iCellsSelected > DEFAULT_MAX_ALLOWED_CELLS) + throw new TooManyCellsException(); + + return iCellsSelected; + } + } +} diff --git a/Navferty.Common/Extensions/StringExtensions.cs b/Navferty.Common/Extensions/StringExtensions.cs index d63c277..5ad1199 100644 --- a/Navferty.Common/Extensions/StringExtensions.cs +++ b/Navferty.Common/Extensions/StringExtensions.cs @@ -1,5 +1,6 @@ using System.Linq; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Text.RegularExpressions; #nullable enable @@ -32,5 +33,14 @@ public static int CountChars(this string value, char c) { return value.Count(x => x == c); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string LimitLength( + this string text, + int MaxLength) + { + if (text.Length > MaxLength) text = new string(text.Take(MaxLength).ToArray()); + return text; + } } } diff --git a/Navferty.Common/Feedback/FeedbackManager.cs b/Navferty.Common/Feedback/FeedbackManager.cs new file mode 100644 index 0000000..1f14077 --- /dev/null +++ b/Navferty.Common/Feedback/FeedbackManager.cs @@ -0,0 +1,253 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Windows.Forms; + +using Microsoft.Win32; + +using Navferty.Common.WinAPI.Networking.Mail; + +using NLog; + +#nullable enable + +namespace Navferty.Common.Feedback +{ + public static class FeedbackManager + { + private const string GITHUB_BUGTRACKER_URL = @"https://github.com/navferty/NavfertyExcelAddIn/issues"; + private const string DEVELOPER_MAIL = @"navferty@ymail.com"; + private const string MAIL_SUBJECT = @"NavfertyExcelAddin Bug report from user!"; + internal const int MAX_USER_TEXT_LENGH = 1_000; + + private static FileInfo[] GetScreenshotsAsFiles(ImageFormat fmt, string fileExt = "jpg") + => System.Windows.Forms.Screen.AllScreens.ToList().Select(scr => + { + using (Bitmap bmCapt = new(scr.Bounds.Width, scr.Bounds.Height, PixelFormat.Format32bppArgb)) + { + var rcCapt = scr.Bounds; + using (Graphics g = Graphics.FromImage(bmCapt)) + g.CopyFromScreen(rcCapt.Left, rcCapt.Top, 0, 0, rcCapt.Size); + + var sBitmapFile = Path.Combine(Path.GetTempPath(), (Guid.NewGuid().ToString() + '.'.ToString() + fileExt)); + bmCapt.Save(sBitmapFile, fmt); + return new System.IO.FileInfo(sBitmapFile); + } + }).ToArray(); + + /// This used for debug! + //If you want to send error reports to custom email, + //create new string value 'Navferty_ExcelAddIn_Feedback_Email' in root of 'HKEY_CURRENT_USER' and set you mail + /// + internal static string GetDeveloperMail() + { + try + { + string mail = Registry.CurrentUser.GetValue("Navferty_ExcelAddIn_Feedback_Email").ToString().Trim(); + if (string.IsNullOrWhiteSpace(mail)) mail = DEVELOPER_MAIL; + return mail; + } + catch { return DEVELOPER_MAIL; } + } + + /// Create and Send feedback mail using MAPI. + /// + /// In fact, the email is not sent immediately, but is cached in the mail client, + /// and will be sent when the user launches the mail client. + /// If the mail client is already running, the email will be sent at the next synchronization. + /// + /// Some mail body text + /// Takes and sends Screenshots of each monitor + /// This window will be hidden to take screenshots + internal static bool SendFeedbackMail( + string userText, + bool sendScreenshots = true, + Form? parentWindow = null + ) + { + + var logger = LogManager.GetCurrentClassLogger(); + logger.Debug("Start SendFeedEMail Task..."); + + string developerMail = GetDeveloperMail(); + logger.Debug($"developerMail: '{developerMail}'"); + + List lFilesToAttach = new(); + userText = userText.LimitLength(MAX_USER_TEXT_LENGH); + + string sysInfo = GetSystemInfo().Trim(); + logger.Debug($"System Info Dump:\n{sysInfo}\n\n********\nUser message: '{userText}'\n"); + + StringBuilder sbMessageBody = new(); + sbMessageBody.Append("User message: "); + sbMessageBody.AppendLine((string.IsNullOrWhiteSpace(userText) ? "[NONE]" : ('"' + userText + '"'))); + string messageBody = sbMessageBody.ToString(); + + if (sendScreenshots) + { + if (parentWindow != null) + { + //Temporary hide feedback UI to make clear screenshots + parentWindow.Opacity = 0; + parentWindow.Refresh(); + Application.DoEvents(); + } + try + { + //Create screenshots to temp dir + var screenshotFiles = GetScreenshotsAsFiles(ImageFormat.Jpeg); + lFilesToAttach.AddRange(screenshotFiles); + string screenshotFileNames = string.Join(", ", screenshotFiles.Select(fi => fi.FullName)); + logger.Debug($"Screenshot Files ({lFilesToAttach.Count}): '{screenshotFileNames}'"); + } + finally + { + if (parentWindow != null) + { + //Restore feedback UI + parentWindow.Opacity = 1; + parentWindow.Refresh(); + Application.DoEvents(); + } + } + } + + var fiNLogFile = GetNLogFile(); + if (null != fiNLogFile && fiNLogFile.Exists) + { + logger.Debug($"Log File Found: '{fiNLogFile.FullName}', Exist: {fiNLogFile.Exists}"); + FileInfo fiLogFileInTempDir = new(Path.Combine( + Path.GetTempPath(), + (Guid.NewGuid().ToString() + "_" + fiNLogFile.Name))); + + //Drop last NLog cache data to disk + { + //logger.Factory.Flush(); + LogManager.Flush(); + Thread.Sleep(1000); //Waiting NLog flush task to finish + } + + //Copy NLog file to temp file + fiNLogFile.CopyTo(fiLogFileInTempDir.FullName); + + //Attach temp NLog file to email + if (fiLogFileInTempDir.Exists) lFilesToAttach.Add(fiLogFileInTempDir); + } + + logger.Debug($"Total Files To Attach: '{lFilesToAttach.Count}'"); + try + { + //Send mail + var bSend = MAPI.SendMail( + developerMail, + MAIL_SUBJECT, + messageBody, + MAPI.UIFlags.SendMailDirectNoUI, + parentWindow, + lFilesToAttach.Select(fi => fi.FullName).ToArray() + ); + + logger.Debug($"Send result: {bSend}"); + return bSend; + } + finally + { + //Cleanup Temp files + lFilesToAttach.ForEach(fi => + { + try { fi.Delete(); } + catch { } + }); + } + } + + /// Collect some debug information about system to help resolve errors + private static string GetSystemInfo() + { + Func DumpAssemmbly = new((asm, title) => + { + StringBuilder sbAsm = new(); + sbAsm.AppendLine($"*** {title ?? string.Empty} Assembly '{asm.FullName}'"); + sbAsm.AppendLine($"Location: '{asm.Location}'"); + sbAsm.AppendLine($"ImageRuntimeVersion: '{asm.ImageRuntimeVersion}'"); + sbAsm.AppendLine($"Trusted: '{asm.IsFullyTrusted}'"); + sbAsm.AppendLine($"EntryPoint: '{asm.EntryPoint}'"); + return sbAsm.ToString(); + }); + + Func DumpAssemmblyName = new((asmn, title) => + { + StringBuilder sbAsm = new(); + sbAsm.AppendLine($"*** {title ?? string.Empty} Assembly '{asmn.FullName}'"); + sbAsm.AppendLine($"CodeBase: '{asmn.CodeBase}'"); + sbAsm.AppendLine($"ContentType: '{asmn.ContentType}'"); + sbAsm.AppendLine($"Culture: '{asmn.CultureInfo.DisplayName}'"); + sbAsm.AppendLine($"ProcessorArchitecture: '{asmn.ProcessorArchitecture}'"); + return sbAsm.ToString(); + }); + + var dtNow = DateTime.Now; + var asm = Assembly.GetExecutingAssembly(); + StringBuilder sbSysInfo = new(); + sbSysInfo.AppendLine("*** Product:"); + sbSysInfo.AppendLine($"Name: '{Application.ProductName}' v'{Application.ProductVersion}'"); + sbSysInfo.AppendLine($"Path: '{Application.ExecutablePath}'"); + sbSysInfo.AppendLine(); + sbSysInfo.AppendLine(DumpAssemmbly(Assembly.GetExecutingAssembly(), "Executing")); + sbSysInfo.AppendLine(DumpAssemmbly(Assembly.GetCallingAssembly(), "Calling")); + + Assembly.GetExecutingAssembly() + .GetReferencedAssemblies() + .OrderBy(asmn => asmn.FullName) + .ToList() + .ForEach(asmn => { sbSysInfo.AppendLine(DumpAssemmblyName(asmn, "Referenced")); }); + + sbSysInfo.AppendLine("*** TimeZone:"); + sbSysInfo.AppendLine(dtNow.Kind.ToString() + $": {dtNow}"); + sbSysInfo.AppendLine($"Utc: {dtNow.ToUniversalTime()}"); + sbSysInfo.AppendLine($"UtcOffset: {TimeZone.CurrentTimeZone.GetUtcOffset(dtNow)}"); + sbSysInfo.AppendLine(); + sbSysInfo.AppendLine("*** Culture:"); + sbSysInfo.AppendLine($"CultureInfo.CurrentCulture: {CultureInfo.CurrentCulture}"); + sbSysInfo.AppendLine($"CultureInfo.CurrentUICulture: {CultureInfo.CurrentUICulture}"); + sbSysInfo.AppendLine($"Application.CurrentCulture: {Application.CurrentCulture}"); + sbSysInfo.AppendLine($"InputLanguage: {Application.CurrentInputLanguage.Culture} (Layout: {Application.CurrentInputLanguage.LayoutName})"); + sbSysInfo.AppendLine(); + sbSysInfo.AppendLine($"VisualStyleState: {Application.VisualStyleState}"); + return sbSysInfo.ToString(); + } + + /// Return NLog engine Log file path on the disk + private static FileInfo? GetNLogFile() + { + LogManager.Flush(); //Write NLog cache to disk if this still in RAM + var logFileName = LogManagement.GetTargetFilename(null); + if (logFileName != null) return new(logFileName); + return null; + } + + /// Displays user Feedback UI dialog + /// some text to prefill user comment textbox + public static void ShowFeedbackUI(string? message = null) + { + using var fui = new frmFeedbackUI(message); + fui.ShowDialog(); + } + + /// Opens Github issues page in Web browser + public static void ShowGithub() + => System.Diagnostics.Process.Start(GITHUB_BUGTRACKER_URL); + + /// Opens NLog engine file in text editor + public static void ShowLogFile() + => System.Diagnostics.Process.Start(GetNLogFile()!.FullName); + + } +} diff --git a/Navferty.Common/Feedback/frmFeedbackUI.Designer.cs b/Navferty.Common/Feedback/frmFeedbackUI.Designer.cs new file mode 100644 index 0000000..2fea728 --- /dev/null +++ b/Navferty.Common/Feedback/frmFeedbackUI.Designer.cs @@ -0,0 +1,167 @@ +namespace Navferty.Common.Feedback +{ + partial class frmFeedbackUI + { + /// + /// 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.btnSend = new System.Windows.Forms.Button(); + this.lblMessage = new System.Windows.Forms.Label(); + this.chkIncludeScreenshots = new System.Windows.Forms.CheckBox(); + this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); + this.txtUserMessage = new System.Windows.Forms.TextBox(); + this.lblSummary = new System.Windows.Forms.LinkLabel(); + this.llGotoGithub = new System.Windows.Forms.LinkLabel(); + this.tableLayoutPanel1.SuspendLayout(); + this.SuspendLayout(); + // + // btnSend + // + this.btnSend.Dock = System.Windows.Forms.DockStyle.Right; + this.btnSend.Location = new System.Drawing.Point(428, 231); + this.btnSend.Name = "btnSend"; + this.tableLayoutPanel1.SetRowSpan(this.btnSend, 2); + this.btnSend.Size = new System.Drawing.Size(113, 34); + this.btnSend.TabIndex = 1; + this.btnSend.Text = "Send"; + this.btnSend.UseVisualStyleBackColor = true; + this.btnSend.Click += new System.EventHandler(this.OnSend); + // + // lblMessage + // + this.lblMessage.AutoSize = true; + this.tableLayoutPanel1.SetColumnSpan(this.lblMessage, 2); + this.lblMessage.Dock = System.Windows.Forms.DockStyle.Top; + this.lblMessage.Location = new System.Drawing.Point(7, 4); + this.lblMessage.Name = "lblMessage"; + this.lblMessage.Size = new System.Drawing.Size(534, 13); + this.lblMessage.TabIndex = 1; + this.lblMessage.Text = "message"; + // + // chkIncludeScreenshots + // + this.chkIncludeScreenshots.AutoSize = true; + this.tableLayoutPanel1.SetColumnSpan(this.chkIncludeScreenshots, 2); + this.chkIncludeScreenshots.Dock = System.Windows.Forms.DockStyle.Top; + this.chkIncludeScreenshots.Location = new System.Drawing.Point(7, 208); + this.chkIncludeScreenshots.Name = "chkIncludeScreenshots"; + this.chkIncludeScreenshots.Size = new System.Drawing.Size(534, 17); + this.chkIncludeScreenshots.TabIndex = 2; + this.chkIncludeScreenshots.Text = "include screenshot"; + this.chkIncludeScreenshots.UseVisualStyleBackColor = true; + // + // tableLayoutPanel1 + // + this.tableLayoutPanel1.ColumnCount = 2; + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel1.Controls.Add(this.btnSend, 1, 3); + this.tableLayoutPanel1.Controls.Add(this.lblMessage, 0, 0); + this.tableLayoutPanel1.Controls.Add(this.txtUserMessage, 0, 1); + this.tableLayoutPanel1.Controls.Add(this.chkIncludeScreenshots, 0, 2); + this.tableLayoutPanel1.Controls.Add(this.lblSummary, 0, 3); + this.tableLayoutPanel1.Controls.Add(this.llGotoGithub, 0, 4); + this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill; + this.tableLayoutPanel1.Location = new System.Drawing.Point(8, 8); + this.tableLayoutPanel1.Name = "tableLayoutPanel1"; + this.tableLayoutPanel1.Padding = new System.Windows.Forms.Padding(4); + this.tableLayoutPanel1.RowCount = 5; + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel1.Size = new System.Drawing.Size(548, 272); + this.tableLayoutPanel1.TabIndex = 0; + // + // txtUserMessage + // + this.tableLayoutPanel1.SetColumnSpan(this.txtUserMessage, 2); + this.txtUserMessage.Dock = System.Windows.Forms.DockStyle.Fill; + this.txtUserMessage.HideSelection = false; + this.txtUserMessage.Location = new System.Drawing.Point(7, 20); + this.txtUserMessage.MaxLength = 1000; + this.txtUserMessage.Multiline = true; + this.txtUserMessage.Name = "txtUserMessage"; + this.txtUserMessage.ScrollBars = System.Windows.Forms.ScrollBars.Both; + this.txtUserMessage.Size = new System.Drawing.Size(534, 182); + this.txtUserMessage.TabIndex = 0; + // + // lblSummary + // + this.lblSummary.AutoSize = true; + this.lblSummary.Dock = System.Windows.Forms.DockStyle.Top; + this.lblSummary.Location = new System.Drawing.Point(7, 228); + this.lblSummary.Name = "lblSummary"; + this.lblSummary.Size = new System.Drawing.Size(415, 13); + this.lblSummary.TabIndex = 4; + this.lblSummary.TabStop = true; + this.lblSummary.Text = "summary"; + // + // llGotoGithub + // + this.llGotoGithub.AutoSize = true; + this.llGotoGithub.Dock = System.Windows.Forms.DockStyle.Bottom; + this.llGotoGithub.Location = new System.Drawing.Point(7, 255); + this.llGotoGithub.Name = "llGotoGithub"; + this.llGotoGithub.Size = new System.Drawing.Size(415, 13); + this.llGotoGithub.TabIndex = 2; + this.llGotoGithub.TabStop = true; + this.llGotoGithub.Text = "goto github"; + this.llGotoGithub.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.OnGotoGithub); + // + // frmFeedbackUI + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(564, 288); + this.Controls.Add(this.tableLayoutPanel1); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "frmFeedbackUI"; + this.Padding = new System.Windows.Forms.Padding(8); + this.ShowIcon = false; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "frmFeedbackUI"; + this.tableLayoutPanel1.ResumeLayout(false); + this.tableLayoutPanel1.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Button btnSend; + private System.Windows.Forms.Label lblMessage; + private System.Windows.Forms.CheckBox chkIncludeScreenshots; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; + private System.Windows.Forms.TextBox txtUserMessage; + private System.Windows.Forms.LinkLabel lblSummary; + private System.Windows.Forms.LinkLabel llGotoGithub; + } +} diff --git a/Navferty.Common/Feedback/frmFeedbackUI.cs b/Navferty.Common/Feedback/frmFeedbackUI.cs new file mode 100644 index 0000000..9a1c92d --- /dev/null +++ b/Navferty.Common/Feedback/frmFeedbackUI.cs @@ -0,0 +1,95 @@ +using System; +using System.Windows.Forms; + +using Navferty.Common.Controls; +//using Navferty.Common.DelegatesExtensions; + +using NLog; + +#nullable enable + +namespace Navferty.Common.Feedback +{ + internal partial class frmFeedbackUI : FormEx + { + private readonly ILogger logger = LogManager.GetCurrentClassLogger(); + + [Obsolete("Just for Designer!", true)] + public frmFeedbackUI() : base() + { + InitializeComponent(); + } + + public frmFeedbackUI(string? message = null) : base() + { + InitializeComponent(); + + Text = Localization.UIStrings.Feedback_Title; + lblMessage.Text = String.Format(Localization.UIStrings.Feedback_Message, FeedbackManager.MAX_USER_TEXT_LENGH); + txtUserMessage.MaxLength = FeedbackManager.MAX_USER_TEXT_LENGH; + + if (!string.IsNullOrWhiteSpace(message)) + { + message = message!.LimitLength(FeedbackManager.MAX_USER_TEXT_LENGH); + txtUserMessage.Text = message; + } + + + chkIncludeScreenshots.Text = Localization.UIStrings.Feedback_IncludeScreenshots; + chkIncludeScreenshots.Checked = true; + + llGotoGithub.Text = Localization.UIStrings.Feedback_GotoGithub; + btnSend.Text = Localization.UIStrings.Feedback_Send; + + //Create ink to show NLog Log file + { + string fullSummaryText = string.Format(Localization.UIStrings.Feedback_Summary_Template, Localization.UIStrings.Feedback_Summary_Loglink); + lblSummary.Text = fullSummaryText; + var la = new LinkArea(fullSummaryText.IndexOf(Localization.UIStrings.Feedback_Summary_Loglink), Localization.UIStrings.Feedback_Summary_Loglink.Length); + lblSummary.LinkArea = la; + lblSummary.LinkClicked += (s, e) => OnShowLog(); + } + } + + private void OnSend(object sender, EventArgs e) + { + new Action(() => + { + if (FeedbackManager.SendFeedbackMail( + txtUserMessage.Text.Trim(), + chkIncludeScreenshots.Checked, + this)) + + DialogResult = DialogResult.OK; + + }).TryCatch(true, + Localization.UIStrings.Feedback_ErrorTitle, + logger, "Failed to send feedback mail!"); + } + + + private void OnGotoGithub(object sender, LinkLabelLinkClickedEventArgs e) + { + new Action(() => { FeedbackManager.ShowGithub(); }) + .TryCatch(true, + Localization.UIStrings.Feedback_ErrorTitle, + logger, "Failed to open Github bugtracker!"); + } + private void OnShowLog() + { + + new Action(() => + { + /* + var EEE = new Exception("Test"); + throw EEE; + */ + FeedbackManager.ShowLogFile(); + }) + .TryCatch(true, + Localization.UIStrings.Feedback_ErrorTitle, + logger, "Failed to show NLog log file!"); + + } + } +} diff --git a/Navferty.Common/Feedback/frmFeedbackUI.resx b/Navferty.Common/Feedback/frmFeedbackUI.resx new file mode 100644 index 0000000..29dcb1b --- /dev/null +++ b/Navferty.Common/Feedback/frmFeedbackUI.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Navferty.Common/Interfaces/IDialogService.cs b/Navferty.Common/Interfaces/IDialogService.cs index d755c89..cb731b2 100644 --- a/Navferty.Common/Interfaces/IDialogService.cs +++ b/Navferty.Common/Interfaces/IDialogService.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; + +using NLog; #nullable enable @@ -6,7 +9,9 @@ namespace Navferty.Common { public interface IDialogService { + [Obsolete("Use ShowError(Exception, ILogger) instead", true)] void ShowError(string message); + void ShowError(Exception e, ILogger? logger = null); void ShowInfo(string message); bool Ask(string message, string caption); void ShowVersion(); diff --git a/Navferty.Common/Localization/UIStrings.Designer.cs b/Navferty.Common/Localization/UIStrings.Designer.cs new file mode 100644 index 0000000..fa82011 --- /dev/null +++ b/Navferty.Common/Localization/UIStrings.Designer.cs @@ -0,0 +1,180 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Navferty.Common.Localization { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class UIStrings { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal UIStrings() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Navferty.Common.Localization.UIStrings", typeof(UIStrings).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Too many cells selected!. + /// + internal static string Error_TooManyCellsSelected { + get { + return ResourceManager.GetString("Error_TooManyCellsSelected", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Close. + /// + internal static string ErrorWindow_OkButton { + get { + return ResourceManager.GetString("ErrorWindow_OkButton", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An error has occurred!. + /// + internal static string ErrorWindow_Title { + get { + return ResourceManager.GetString("ErrorWindow_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error!. + /// + internal static string Feedback_Error { + get { + return ResourceManager.GetString("Feedback_Error", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Failed to send feedback email!. + /// + internal static string Feedback_ErrorTitle { + get { + return ResourceManager.GetString("Feedback_ErrorTitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Bug tracker and feature request. + /// + internal static string Feedback_GotoGithub { + get { + return ResourceManager.GetString("Feedback_GotoGithub", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Include screenshot. + /// + internal static string Feedback_IncludeScreenshots { + get { + return ResourceManager.GetString("Feedback_IncludeScreenshots", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Briefly describe the error that occurs (no more than {0} characters):. + /// + internal static string Feedback_Message { + get { + return ResourceManager.GetString("Feedback_Message", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Send. + /// + internal static string Feedback_Send { + get { + return ResourceManager.GetString("Feedback_Send", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Send Feedback. + /// + internal static string Feedback_SendFeedback { + get { + return ResourceManager.GetString("Feedback_SendFeedback", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to an error log. + /// + internal static string Feedback_Summary_Loglink { + get { + return ResourceManager.GetString("Feedback_Summary_Loglink", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The report will include {0}.. + /// + internal static string Feedback_Summary_Template { + get { + return ResourceManager.GetString("Feedback_Summary_Template", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Feedback. + /// + internal static string Feedback_Title { + get { + return ResourceManager.GetString("Feedback_Title", resourceCulture); + } + } + } +} diff --git a/Navferty.Common/Localization/UIStrings.resx b/Navferty.Common/Localization/UIStrings.resx new file mode 100644 index 0000000..de6f28d --- /dev/null +++ b/Navferty.Common/Localization/UIStrings.resx @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Close + + + An error has occurred! + + + Too many cells selected! + + + Error! + + + Failed to send feedback email! + + + Bug tracker and feature request + + + Include screenshot + + + Briefly describe the error that occurs (no more than {0} characters): + + + Send + + + Send Feedback + + + an error log + + + The report will include {0}. + + + Feedback + + \ No newline at end of file diff --git a/Navferty.Common/Localization/UIStrings.ru-RU.resx b/Navferty.Common/Localization/UIStrings.ru-RU.resx new file mode 100644 index 0000000..8704f1e --- /dev/null +++ b/Navferty.Common/Localization/UIStrings.ru-RU.resx @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Закрыть + + + Произошла ошибка! + + + Выбрано слишком много ячеек! + + + Ошибка! + + + Не удалось отправить отчёт разработчику! + + + Трекер ошибок и запроса новых функций + + + Приложить скриншот + + + Кратко опишите возникающую ошибку (не более {0} символов): + + + Отправить + + + Сообщить разработчикам + + + журнал ошибок + + + Отчёт будет включать в себя {0}. + + + Обратная связь + + \ No newline at end of file diff --git a/Navferty.Common/LogManagement.cs b/Navferty.Common/LogManagement.cs new file mode 100644 index 0000000..45df900 --- /dev/null +++ b/Navferty.Common/LogManagement.cs @@ -0,0 +1,73 @@ + +using System.Linq; + +using NLog; +using NLog.Layouts; +using NLog.Targets; + +#nullable enable + +namespace Navferty.Common +{ + internal static class LogManagement + { + //private const string DefaultTargetName = "AllTargets"; + + public static string? GetTargetFilename(string? targetName) + { + FileTarget? target = null; + if (string.IsNullOrWhiteSpace(targetName)) + { + var fileTargets = GetFileTargets(); + target = fileTargets.FirstOrDefault(); + } + else + { + target = GetTarget(targetName!); + } + if (null == target) return null; + + var layout = target.FileName as SimpleLayout; + if (null == layout) return null; + + // layout.Text provides the filename "template" + // LogEventInfo is required; might make sense for a log line template but not really filename + //var filename = layout.Render(new LogEventInfo()).Replace(@"/", @""); + var filename = layout.Render(new LogEventInfo()).Replace(@"/", @"\").Replace(@"\\", @"\"); ; + return filename; + } + + private static T? GetTarget(string targetName) + where T : Target + { + if (null == LogManager.Configuration) return null; + var target = LogManager.Configuration.FindTargetByName(targetName) as T; + return target; + } + + private static T[]? GetTargets() + where T : Target + { + if (null == LogManager.Configuration) return null; + + var targets = LogManager.Configuration.AllTargets?.Where(trg => trg.GetType() == typeof(T)).ToArray(); + return (T[]?)targets; + } + private static FileTarget[]? GetFileTargets() + { + if (null == LogManager.Configuration) return null; + + var targets = LogManager.Configuration.AllTargets? + .Select(trg => + { + FileTarget? ft = null; + if (trg is FileTarget ft2) ft = ft2; + return ft; + }) + .Where(ft => ft != null) + .Select(ft => (FileTarget)ft!) + .ToArray(); + return targets; + } + } +} diff --git a/Navferty.Common/Navferty.Common.csproj b/Navferty.Common/Navferty.Common.csproj index b7c906c..ade78cc 100644 --- a/Navferty.Common/Navferty.Common.csproj +++ b/Navferty.Common/Navferty.Common.csproj @@ -41,6 +41,9 @@ Component + + Form + Component @@ -50,24 +53,81 @@ Form + + + + + UIStrings.resx + True + True + + + + + Form + + + frmFeedbackUI.cs + - + + True + True + Resources.resx + + + + + + + + True + + + ..\packages\NLog.4.6.7\lib\net45\NLog.dll + + + + + + + + + + + + + + ErrorDialog.cs + + + frmFeedbackUI.cs + + + ResXFileCodeGenerator + UIStrings.Designer.cs + Designer + + + + ResXFileCodeGenerator + Resources.Designer.cs + - diff --git a/Navferty.Common/Properties/Resources.Designer.cs b/Navferty.Common/Properties/Resources.Designer.cs new file mode 100644 index 0000000..ed15fa3 --- /dev/null +++ b/Navferty.Common/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Navferty.Common.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Navferty.Common.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/Navferty.Common/Properties/Resources.resx b/Navferty.Common/Properties/Resources.resx new file mode 100644 index 0000000..29dcb1b --- /dev/null +++ b/Navferty.Common/Properties/Resources.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Navferty.Common/WinAPI/WinAPI.Core.cs b/Navferty.Common/WinAPI/WinAPI.Core.cs new file mode 100644 index 0000000..7912012 --- /dev/null +++ b/Navferty.Common/WinAPI/WinAPI.Core.cs @@ -0,0 +1,21 @@ +#nullable enable + +#region Structures to interoperate with the Windows API + +using word = System.UInt16; +using dword = System.UInt32; +using hwnd = System.IntPtr; +using large_int = System.Int64; +using ulong_ptr = System.IntPtr; + +#endregion + +namespace Navferty.Common.WinAPI +{ + + internal static class Core + { + public const string WINDLL_USER = "user32.dll"; + } + +} diff --git a/Navferty.Common/WinAPI/WinAPI.GDI.cs b/Navferty.Common/WinAPI/WinAPI.GDI.cs new file mode 100644 index 0000000..c7322b5 --- /dev/null +++ b/Navferty.Common/WinAPI/WinAPI.GDI.cs @@ -0,0 +1,58 @@ +using System; +using System.ComponentModel; +using System.Drawing; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +using Microsoft.Win32.SafeHandles; + +#region Structures to interoperate with the Windows API + +using word = System.UInt16; +using dword = System.UInt32; +using hwnd = System.IntPtr; +using large_int = System.Int64; +using ulong_ptr = System.IntPtr; + +#endregion + +#nullable enable + +namespace Navferty.Common.WinAPI.GDI +{ + internal class DC : SafeHandleZeroOrMinusOneIsInvalid + { + [DllImport(Core.WINDLL_USER)] + private static extern IntPtr GetDC(hwnd hwnd); + + [DllImport(Core.WINDLL_USER)] + private static extern bool ReleaseDC(hwnd hwnd, IntPtr hdc); + + internal hwnd hWnd = IntPtr.Zero; + + public DC(hwnd WindowHandle) : base(true) + { + var hdc = GetDC(WindowHandle); + if (hdc == IntPtr.Zero) throw new Win32Exception(); + hWnd = WindowHandle; + SetHandle(hdc); + } + + public DC(IWin32Window Window) : this(Window.Handle) { } + + protected override bool ReleaseHandle() + { + if (IsInvalid) return true; + bool bResult = ReleaseDC(hWnd, handle); + SetHandle(IntPtr.Zero); + return bResult; + } + + public Graphics CreateGraphics() => Graphics.FromHdc(DangerousGetHandle()); + } + + internal static class WinAPI_Extensions + { + public static DC GetDC(this IWin32Window wnd) => new(wnd.Handle); + } +} diff --git a/Navferty.Common/WinAPI/WinAPI.Networking.Mail.MAPI.cs b/Navferty.Common/WinAPI/WinAPI.Networking.Mail.MAPI.cs new file mode 100644 index 0000000..6cdeb4c --- /dev/null +++ b/Navferty.Common/WinAPI/WinAPI.Networking.Mail.MAPI.cs @@ -0,0 +1,357 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +#nullable enable + +namespace Navferty.Common.WinAPI.Networking.Mail +{ + /// + /// https://docs.microsoft.com/ru-ru/previous-versions/windows/desktop/windowsmapi/mapi32-dll-stub-registry-settings?redirectedfrom=MSDN + /// + internal static class MAPI + { + public const int MAX_ATTACHMENTS = 20; + + public enum MAPI_ERRORS : int + { + [Description("OK")] + OK = 0, + + [Description("User abort")] + UserAbort = 1, + + [Description("General MAPI failure")] + GeneralMAPIFailure = 2, + + [Description("MAPI login failure")] + MAPILoginFailure = 3, + + [Description("Disk full")] + DiskFull = 4, + + [Description("Insufficient memory")] + InsufficientMemory = 5, + + [Description("Access denied [6")] + AccessDenied = 6, + + [Description("Unknown")] + Unknown = 7, + + [Description("Too many sessions")] + TooManySessions = 8, + + [Description("Too many files were specified")] + TooManyFilesWereSpecified = 9, + + [Description("Too many recipients were specified")] + ToomanyRecipientsWereSpecified = 10, + + [Description("A specified attachment was not found")] + SpecifiedAttachmentWasNotFound = 11, + + [Description("Attachment open failure")] + AttachmentOpenFailure = 12, + + [Description("Attachment write failure")] + AttachmentWriteFailure = 13, + + [Description("Unknown recipient")] + UnknownRecipient = 14, + + [Description("Bad recipient type")] + BadRecipientType = 15, + + [Description("No messages")] + NoMessages = 16, + + [Description("Invalid message")] + InvalidMessage = 17, + + [Description("Text too large")] + TextTooLarge = 18, + + [Description("Invalid session")] + InvalidSession = 19, + + [Description("Type not supported")] + TypeNotSupported = 20, + + [Description("A recipient was specified ambiguously")] + RecipientWasSpecifiedAmbiguously = 21, + + [Description("Message in use")] + MessageInUse = 22, + + [Description("Network failure")] + NetworkFailure = 23, + + [Description("Invalid edit fields")] + InvalidEditFields = 24, + + [Description("Invalid recipients")] + InvalidRecipients = 25, + + [Description("Not supported")] + NotSupported = 26, + + //[EditorBrowsable(EditorBrowsableState.Never)] + Last + } + + public class MAPIException : System.Exception + { + public readonly MAPI_ERRORS ErrorCode = MAPI_ERRORS.OK; + internal MAPIException(MAPI_ERRORS err) : base(GetErrorMessageForCode(err)) + { + ErrorCode = err; + } + public override string Message => GetErrorMessageForCode(ErrorCode); + + public static string GetErrorMessageForCode(MAPI_ERRORS Err) + { + if (Err >= MAPI_ERRORS.Last) return $"MAPI Error {Err}"; + return Err.ToString(); + } + } + + public enum SendToFlags : int + { + MAPI_ORIG = 0, + MAPI_TO, + MAPI_CC, + MAPI_BCC + }; + + public enum UIFlags : uint + { + /// Display Send Mail UI dialog + PopupUI = 0, + + /// Send mail without displaying the mail UI dialog. + /// for safety reason, MSOutlook displays warning UI that allows user to allow or block sending this mail (mail message itself is not visible to user) + SendMailDirectNoUI + } + + #region API + + private enum MAPI_FLAGS : uint + { + MAPI_LOGON_UI = 1, // Показать интерфейс входа в систему + MAPI_NEW_SESSION = 2, // Не использовать общий сеанс + MAPI_DIALOG = 8, + //MAPI_ALLOW_OTHERS = 8; исходное шестнадцатеричное значение 0x00000008 ; Сделать это общим сеансом + MAPI_EXPLICIT_PROFILE = 16, //Не использовать профиль по умолчанию + MAPI_EXTENDED = 32, //Расширенный вход MAPI + MAPI_FORCE_DOWNLOAD = 4096, //Получить новую почту перед возвратом + MAPI_SERVICE_UI_ALWAYS = 8192, //Выполнить вход в систему во всех провайдерах + MAPI_NO_MAIL = 32768, //Не активировать транспорт + MAPI_PASSWORD_UI = 131072, //Отображать только пользовательский интерфейс пароля + MAPI_TIMEOUT_SHORT = 1048576, //Минимальное ожидание ресурсов входа в систему + MAPI_UNICODE = 0x80000000, + MAPI_USE_DEFAULT = 0x00000040 + } + + /// + /// If you're application is running with Elevated Privileges (i.e. as Administrator) and Outlook isn't, the send will fail. + /// You will have to tell the user to close all running instances of Outlook and try again. + /// + [DllImport("MAPI32.DLL", CharSet = CharSet.Ansi)] + private static extern MAPI_ERRORS MAPISendMail( + IntPtr lhSession, + IntPtr hWnd, + MapiMessage message, + MAPI_FLAGS flg, + int rsv); + + #endregion + + + /// Create and Send mail message. + /// + /// In fact, the email is not sent immediately, but is cached in the mail client, + /// and will be sent when the user launches the mail client. + /// If the mail client is already running, the email will be sent at the next synchronization. + /// + public static bool SendMail( + IEnumerable recipients, + string strSubject, + string strBody, + UIFlags UIflags = UIFlags.PopupUI, + IWin32Window? parentWindow = null, + params string[] attachFiles + ) + { + using (MapiMessage msg = new() + { + subject = strSubject, + noteText = strBody + }) + { + (msg.hRecips, msg.recipCount) = RecipientsToBuffer(recipients); + (msg.hFiles, msg.fileCount) = AttachmentsToBuffer(attachFiles); + + MAPI_FLAGS how = (UIflags == UIFlags.PopupUI) ? (MAPI_FLAGS.MAPI_LOGON_UI | MAPI_FLAGS.MAPI_DIALOG) : (MAPI_FLAGS.MAPI_LOGON_UI); + how |= MAPI_FLAGS.MAPI_NEW_SESSION; + + IntPtr hWnd = (parentWindow == null) ? IntPtr.Zero : parentWindow.Handle; + + var Err = MAPISendMail( + new IntPtr(0), + hWnd, + msg, + how, + 0); + + switch (Err) + { + case MAPI_ERRORS.OK: return true; + case MAPI_ERRORS.UserAbort: return false; + case MAPI_ERRORS.NotSupported: return false;//If send with no UI, and user denied send, this occurs. + default: throw new MAPIException(Err); + } + } + } + + /// + public static bool SendMail( + string sendTo, + string strSubject, + string strBody, + UIFlags uiFlags = UIFlags.PopupUI, + IWin32Window? parentWindow = null, + params string[] attachFiles) + => SendMail( + new MapiRecipDesc[] { CreateRecipient(sendTo, SendToFlags.MAPI_TO) }, + strSubject, strBody, uiFlags, parentWindow, attachFiles); + + + public static MapiRecipDesc CreateRecipient(string email, SendToFlags howTo = SendToFlags.MAPI_TO) + => new(email, howTo); + + private static (IntPtr hMem, int Count) RecipientsToBuffer(IEnumerable recipients) + { + if (!recipients.Any()) throw new ArgumentNullException(nameof(recipients)); + + var lRecipients = recipients.ToList(); + + int size = Marshal.SizeOf(typeof(MapiRecipDesc)); + IntPtr hMem = Marshal.AllocHGlobal(lRecipients.Count * size); + IntPtr hWrite = hMem; + lRecipients.ForEach(mapiDesc => + { + Marshal.StructureToPtr(mapiDesc, hWrite, false); + hWrite += size; + }); + return (hMem, lRecipients.Count); + } + + private static (IntPtr hMem, int fileCount) AttachmentsToBuffer(IEnumerable? attachments = null) + { + if (attachments == null || !attachments.Any()) return (IntPtr.Zero, 0); + if (attachments.Count() > MAX_ATTACHMENTS) throw new ArgumentOutOfRangeException($"{nameof(attachments)}.Count > {MAX_ATTACHMENTS}"); + + List lAttachments = attachments.ToList(); + + int size = Marshal.SizeOf(typeof(MapiFileDesc)); + IntPtr hMem = Marshal.AllocHGlobal(lAttachments.Count * size); + + MapiFileDesc mapiFileDesc = new(); + mapiFileDesc.position = -1; + + IntPtr ptr = hMem; + lAttachments.ForEach(path => + { + mapiFileDesc.name = Path.GetFileName(path); + mapiFileDesc.path = path; + Marshal.StructureToPtr(mapiFileDesc, ptr, false); + ptr += size; + }); + return (hMem, lAttachments.Count); + } + + + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + internal class MapiMessage : IDisposable + { + public int reserved; + public string subject; + public string noteText; + public string messageType; + public string dateReceived; + public string conversationID; + public int flags; + public IntPtr hOriginator; + public int recipCount; + public IntPtr hRecips; + public int fileCount; + public IntPtr hFiles; + + public void Dispose() + { + if (hRecips != IntPtr.Zero) + { + int size = Marshal.SizeOf(typeof(MapiRecipDesc)); + IntPtr ptr = hRecips; + for (int i = 0; i < recipCount; i++) + { + Marshal.DestroyStructure(ptr, typeof(MapiRecipDesc)); + ptr += size; + } + Marshal.FreeHGlobal(hRecips); + } + + if (hFiles != IntPtr.Zero) + { + int size = Marshal.SizeOf(typeof(MapiFileDesc)); + IntPtr ptr = hFiles; + for (int i = 0; i < fileCount; i++) + { + Marshal.DestroyStructure((IntPtr)ptr, + typeof(MapiFileDesc)); + ptr += size; + } + Marshal.FreeHGlobal(hFiles); + } + } + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + internal class MapiFileDesc + { + public int reserved; + public int flags; + public int position; + public string path; + public string name; + public IntPtr hType; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + public class MapiRecipDesc + { + public int reserved = 0; + public SendToFlags recipClass = SendToFlags.MAPI_TO; + + /// MAPISendMail hanging with Outlook because the mail addresses had trailing spaces! + public string name = String.Empty; + public string address = String.Empty; + public int eIDSize = 0; + public IntPtr hEntryID = IntPtr.Zero; + + private MapiRecipDesc() { } + + public MapiRecipDesc(string email, SendToFlags howTo = SendToFlags.MAPI_TO) : this() + { + recipClass = howTo; + name = email.Trim(); + } + } + } +} diff --git a/Navferty.Common/WinAPI/WinAPI.Windows.cs b/Navferty.Common/WinAPI/WinAPI.Windows.cs new file mode 100644 index 0000000..b642c2d --- /dev/null +++ b/Navferty.Common/WinAPI/WinAPI.Windows.cs @@ -0,0 +1,48 @@ +using System; +using System.Drawing; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +#region Structures to interoperate with the Windows API + +using word = System.UInt16; +using dword = System.UInt32; +using hwnd = System.IntPtr; +using large_int = System.Int64; +using ulong_ptr = System.IntPtr; + +#endregion + +#nullable enable + +namespace Navferty.Common.WinAPI +{ + internal static class Windows + { + public enum WindowMessages : int + { + WM_PAINT = 0xF, + EM_SETCUEBANNER = 0x1501 + } + + [DllImport(Core.WINDLL_USER, SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.Winapi)] + internal static extern IntPtr SendMessage( + [In] hwnd hwnd, + [In, MarshalAs(UnmanagedType.I4)] WindowMessages wMsg, + [In] int wParam, + [In, MarshalAs(UnmanagedType.LPTStr)] string? lParam); + + [DllImport(Core.WINDLL_USER)] + private static extern int GetClientRect( + [In] hwnd hwnd, + [In, Out] ref Rectangle rc); + + public static Rectangle GetClientRect(this IWin32Window wind) + { + var rcClient = new Rectangle(); + GetClientRect(wind.Handle, ref rcClient); + return rcClient; + } + } + +} diff --git a/Navferty.Common/WinAPI/WinAPI.cs b/Navferty.Common/WinAPI/WinAPI.cs deleted file mode 100644 index 8440e6f..0000000 --- a/Navferty.Common/WinAPI/WinAPI.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.ComponentModel; -using System.Drawing; -using System.Runtime.InteropServices; -using System.Windows.Forms; - -using Microsoft.Win32.SafeHandles; - -#nullable enable - -namespace Navferty.Common -{ - internal static class WinAPI - { - public enum WindowMessages : int - { - WM_PAINT = 0xF, - EM_SETCUEBANNER = 0x1501 - } - - internal const string WINDLL_USER = "user32.dll"; - - [DllImport(WINDLL_USER, SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.Winapi)] - internal static extern IntPtr SendMessage( - [In] IntPtr hwnd, - [In, MarshalAs(UnmanagedType.I4)] WinAPI.WindowMessages wMsg, - [In] int wParam, - [In, MarshalAs(UnmanagedType.LPTStr)] string? lParam); - - [DllImport(WINDLL_USER)] - private static extern int GetClientRect( - [In] IntPtr hwnd, - [In, Out] ref System.Drawing.Rectangle rc); - - public static Rectangle GetClientRect(IWin32Window wind) - { - var rcClient = new Rectangle(); - GetClientRect(wind.Handle, ref rcClient); - return rcClient; - } - - - internal class DC : SafeHandleZeroOrMinusOneIsInvalid - { - [DllImport(WINDLL_USER)] - private static extern IntPtr GetDC(IntPtr hwnd); - - [DllImport(WINDLL_USER)] - private static extern bool ReleaseDC(IntPtr hwnd, IntPtr hdc); - - internal IntPtr hWnd = IntPtr.Zero; - - public DC(IntPtr WindowHandle) : base(true) - { - var hdc = GetDC(WindowHandle); - if (hdc == IntPtr.Zero) throw new Win32Exception(); - hWnd = WindowHandle; - SetHandle(hdc); - } - - public DC(IWin32Window Window) : this(Window.Handle) { } - - protected override bool ReleaseHandle() - { - if (IsInvalid) return true; - bool bResult = ReleaseDC(hWnd, handle); - SetHandle(IntPtr.Zero); - return bResult; - } - - public Graphics CreateGraphics() => Graphics.FromHdc(DangerousGetHandle()); - } - } -} diff --git a/Navferty.Common/packages.config b/Navferty.Common/packages.config new file mode 100644 index 0000000..754300b --- /dev/null +++ b/Navferty.Common/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Navferty.ExcelAddIn.Web/CurrencyExchangeRates/Manager.cs b/Navferty.ExcelAddIn.Web/CurrencyExchangeRates/Manager.cs index c390acd..cb4fe05 100644 --- a/Navferty.ExcelAddIn.Web/CurrencyExchangeRates/Manager.cs +++ b/Navferty.ExcelAddIn.Web/CurrencyExchangeRates/Manager.cs @@ -9,7 +9,6 @@ namespace Navferty.ExcelAddIn.Web.CurrencyExchangeRates { public static class Manager { - public static ExchangeRateRecord? SelectExchageRate(IDialogService dialogService) { using (var f = new frmExchangeRates(dialogService)) diff --git a/Navferty.ExcelAddIn.Web/CurrencyExchangeRates/frmExchangeRates.cs b/Navferty.ExcelAddIn.Web/CurrencyExchangeRates/frmExchangeRates.cs index c354959..6e84cc2 100644 --- a/Navferty.ExcelAddIn.Web/CurrencyExchangeRates/frmExchangeRates.cs +++ b/Navferty.ExcelAddIn.Web/CurrencyExchangeRates/frmExchangeRates.cs @@ -254,7 +254,7 @@ private void OnPasteResultToWorkSheet() var selRows = gridResult.SelectedRowsAsEnumerable(); if (selRows.Count() != 1) { - dialogService?.ShowError(UIStrings.CurrencyExchangeRates_Error_CanSelectOnlyOneRow); + dialogService?.ShowError(new Exception(UIStrings.CurrencyExchangeRates_Error_CanSelectOnlyOneRow)); return; } @@ -265,7 +265,7 @@ private void OnPasteResultToWorkSheet() } catch (Exception ex) { - dialogService?.ShowError(ex.Message); + dialogService?.ShowError(ex); } } diff --git a/Navferty.ExcelAddIn.Web/NLog.config b/Navferty.ExcelAddIn.Web/NLog.config deleted file mode 100644 index 349d7b8..0000000 --- a/Navferty.ExcelAddIn.Web/NLog.config +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - diff --git a/Navferty.ExcelAddIn.Web/Navferty.ExcelAddIn.Web.csproj b/Navferty.ExcelAddIn.Web/Navferty.ExcelAddIn.Web.csproj index de58509..ed4809c 100644 --- a/Navferty.ExcelAddIn.Web/Navferty.ExcelAddIn.Web.csproj +++ b/Navferty.ExcelAddIn.Web/Navferty.ExcelAddIn.Web.csproj @@ -105,9 +105,6 @@ WCF Proxy Generator Reference.cs - - PreserveNewest - Designer @@ -138,6 +135,7 @@ + diff --git a/NavfertyExcelAddIn.UnitTests/WorksheetCellsEditing/EmptySpaceTrimmerTests.cs b/NavfertyExcelAddIn.UnitTests/WorksheetCellsEditing/EmptySpaceTrimmerTests.cs index cf94531..3e36b95 100644 --- a/NavfertyExcelAddIn.UnitTests/WorksheetCellsEditing/EmptySpaceTrimmerTests.cs +++ b/NavfertyExcelAddIn.UnitTests/WorksheetCellsEditing/EmptySpaceTrimmerTests.cs @@ -11,14 +11,14 @@ namespace NavfertyExcelAddIn.UnitTests.WorksheetCellsEditing [TestClass] public class EmptySpaceTrimmerTests : TestsBase { - private EmptySpaceTrimmer emptySpaceTrimmer; + private TextTrimmer emptySpaceTrimmer; [TestInitialize] public void BeforeEachTest() { SetRangeExtentionsStub(); - emptySpaceTrimmer = new EmptySpaceTrimmer(); + emptySpaceTrimmer = new TextTrimmer(); } [TestMethod] diff --git a/NavfertyExcelAddIn/Commons/DialogService.cs b/NavfertyExcelAddIn/Commons/DialogService.cs index 1cd788c..9d127c6 100644 --- a/NavfertyExcelAddIn/Commons/DialogService.cs +++ b/NavfertyExcelAddIn/Commons/DialogService.cs @@ -8,8 +8,10 @@ using Navferty.Common; -using NavfertyExcelAddIn.Localization; - +using NavfertyExcelAddIn.Localization; + +using NLog; + using Application = Microsoft.Office.Interop.Excel.Application; namespace NavfertyExcelAddIn.Commons @@ -29,10 +31,14 @@ private static readonly Dictionary ExtensionFilte {FileType.Pdf, new FileExtensionFilter("PDF files", "*.pdf")} }; + [Obsolete("Use ShowError(Exception, ILogger) instead", true)] public void ShowError(string message) - { - MessageBox.Show(message, UIStrings.Error, MessageBoxButtons.OK, MessageBoxIcon.Error); + { + //MessageBox.Show(message, UIStrings.Error, MessageBoxButtons.OK, MessageBoxIcon.Error); + ShowError(new Exception(message)); } + public void ShowError(Exception e, ILogger? logger = null) + => e.ShowErrorUI(null, logger); public void ShowInfo(string message) { diff --git a/NavfertyExcelAddIn/Commons/ExceptionLogger.cs b/NavfertyExcelAddIn/Commons/ExceptionLogger.cs index 2d99f2c..c0ccb83 100644 --- a/NavfertyExcelAddIn/Commons/ExceptionLogger.cs +++ b/NavfertyExcelAddIn/Commons/ExceptionLogger.cs @@ -30,8 +30,7 @@ public void Intercept(IInvocation invocation) } catch (Exception ex) { - logger.Error(ex); - dialogService.ShowError(string.Format(UIStrings.ErrorMessage, ex.Message)); + dialogService.ShowError(ex, logger); throw; } } diff --git a/NavfertyExcelAddIn/Feedback/FeedbackBuilder.cs b/NavfertyExcelAddIn/Feedback/FeedbackBuilder.cs new file mode 100644 index 0000000..6852555 --- /dev/null +++ b/NavfertyExcelAddIn/Feedback/FeedbackBuilder.cs @@ -0,0 +1,22 @@ + +using Navferty.Common; + +#nullable enable + +namespace NavfertyExcelAddIn.Feedback +{ + public class FeedbackBuilder : IFeedback + { + internal readonly IDialogService dialogService; + private Microsoft.Office.Interop.Excel.Application App => Globals.ThisAddIn.Application; + + public FeedbackBuilder(IDialogService dialogService) + => this.dialogService = dialogService; + + + public void DisplayFeedbackUI() + { + Navferty.Common.Feedback.FeedbackManager.ShowFeedbackUI(); + } + } +} diff --git a/NavfertyExcelAddIn/Feedback/IFeedback.cs b/NavfertyExcelAddIn/Feedback/IFeedback.cs new file mode 100644 index 0000000..72052bc --- /dev/null +++ b/NavfertyExcelAddIn/Feedback/IFeedback.cs @@ -0,0 +1,7 @@ +namespace NavfertyExcelAddIn.Feedback +{ + public interface IFeedback + { + void DisplayFeedbackUI(); + } +} diff --git a/NavfertyExcelAddIn/Localization/RibbonLabels.Designer.cs b/NavfertyExcelAddIn/Localization/RibbonLabels.Designer.cs index c4502fd..9666620 100644 --- a/NavfertyExcelAddIn/Localization/RibbonLabels.Designer.cs +++ b/NavfertyExcelAddIn/Localization/RibbonLabels.Designer.cs @@ -123,6 +123,15 @@ internal static string CutNames { } } + /// + /// Looks up a localized string similar to Feedback. + /// + internal static string Feedback { + get { + return ResourceManager.GetString("Feedback", resourceCulture); + } + } + /// /// Looks up a localized string similar to Find Errors. /// @@ -303,6 +312,15 @@ internal static string TrimSpaces { } } + /// + /// Looks up a localized string similar to Trim Text. + /// + internal static string TrimTextByLength { + get { + return ResourceManager.GetString("TrimTextByLength", resourceCulture); + } + } + /// /// Looks up a localized string similar to Undo Last Action. /// diff --git a/NavfertyExcelAddIn/Localization/RibbonLabels.resx b/NavfertyExcelAddIn/Localization/RibbonLabels.resx index 205170b..c04d8be 100644 --- a/NavfertyExcelAddIn/Localization/RibbonLabels.resx +++ b/NavfertyExcelAddIn/Localization/RibbonLabels.resx @@ -237,4 +237,10 @@ Web tools + + Feedback + + + Trim Text + \ No newline at end of file diff --git a/NavfertyExcelAddIn/Localization/RibbonLabels.ru-RU.resx b/NavfertyExcelAddIn/Localization/RibbonLabels.ru-RU.resx index 341a4ab..c1d0c22 100644 --- a/NavfertyExcelAddIn/Localization/RibbonLabels.ru-RU.resx +++ b/NavfertyExcelAddIn/Localization/RibbonLabels.ru-RU.resx @@ -237,4 +237,10 @@ Защита данных + + Обратная связь + + + Обрезать текст + \ No newline at end of file diff --git a/NavfertyExcelAddIn/Localization/RibbonSupertips.Designer.cs b/NavfertyExcelAddIn/Localization/RibbonSupertips.Designer.cs index 50bc47b..319cfb3 100644 --- a/NavfertyExcelAddIn/Localization/RibbonSupertips.Designer.cs +++ b/NavfertyExcelAddIn/Localization/RibbonSupertips.Designer.cs @@ -96,6 +96,15 @@ internal static string CutNames { } } + /// + /// Looks up a localized string similar to Send a bug report or feature request to the developer. + /// + internal static string Feedback { + get { + return ResourceManager.GetString("Feedback", resourceCulture); + } + } + /// /// Looks up a localized string similar to Find all cells in the specified range that have any errors (#).. /// @@ -259,6 +268,15 @@ internal static string TrimSpaces { } } + /// + /// Looks up a localized string similar to Trim cell text by specifed length. + /// + internal static string TrimTextByLength { + get { + return ResourceManager.GetString("TrimTextByLength", resourceCulture); + } + } + /// /// Looks up a localized string similar to Undo the latest action done with this add-in. The undo is possible for certain functions in the sections 'Converting Values' and 'Formatting Values' and only in case the cells' values have not been changed after that action.. /// diff --git a/NavfertyExcelAddIn/Localization/RibbonSupertips.resx b/NavfertyExcelAddIn/Localization/RibbonSupertips.resx index dfbccb4..0cd31c3 100644 --- a/NavfertyExcelAddIn/Localization/RibbonSupertips.resx +++ b/NavfertyExcelAddIn/Localization/RibbonSupertips.resx @@ -196,4 +196,10 @@ Allows rewriting values in range -999 billion to 999 billion, with precision up Show or hide worksheet tabs + + Send a bug report or feature request to the developer + + + Trim cell text by specifed length + \ No newline at end of file diff --git a/NavfertyExcelAddIn/Localization/RibbonSupertips.ru-RU.resx b/NavfertyExcelAddIn/Localization/RibbonSupertips.ru-RU.resx index 2b455cc..bfc9a78 100644 --- a/NavfertyExcelAddIn/Localization/RibbonSupertips.ru-RU.resx +++ b/NavfertyExcelAddIn/Localization/RibbonSupertips.ru-RU.resx @@ -196,4 +196,10 @@ Отобразить или скрыть ярлычки листов + + Отправить отчёт об ошибке или пожелание новой функциональности разработчику + + + Обрезать текст по заданной длинне + \ No newline at end of file diff --git a/NavfertyExcelAddIn/Localization/UIStrings.Designer.cs b/NavfertyExcelAddIn/Localization/UIStrings.Designer.cs index 11661df..0e597d4 100644 --- a/NavfertyExcelAddIn/Localization/UIStrings.Designer.cs +++ b/NavfertyExcelAddIn/Localization/UIStrings.Designer.cs @@ -267,6 +267,42 @@ internal static string SuccessfullyValidatedMessage { } } + /// + /// Looks up a localized string similar to Specify the allowed maximum text length, or 0 for no truncation.. + /// + internal static string TrimTextByLength_TextLen { + get { + return ResourceManager.GetString("TrimTextByLength_TextLen", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Text clipping. + /// + internal static string TrimTextByLength_Title { + get { + return ResourceManager.GetString("TrimTextByLength_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Delete lines consisting of only spaces. + /// + internal static string TrimTextByLength_TrimFullSpaced { + get { + return ResourceManager.GetString("TrimTextByLength_TrimFullSpaced", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Trim spaces at the beginning and end of a string. + /// + internal static string TrimTextByLength_TrimStartEnd { + get { + return ResourceManager.GetString("TrimTextByLength_TrimStartEnd", resourceCulture); + } + } + /// /// Looks up a localized string similar to Any unsaved changes will be lost. Active workbook may become corrupted. It is strongly recommended to create backup. Continue?. /// diff --git a/NavfertyExcelAddIn/Localization/UIStrings.resx b/NavfertyExcelAddIn/Localization/UIStrings.resx index 9e56c84..b18a8f7 100644 --- a/NavfertyExcelAddIn/Localization/UIStrings.resx +++ b/NavfertyExcelAddIn/Localization/UIStrings.resx @@ -186,6 +186,18 @@ Successfully validated, no errors or warnings detected + + Specify the allowed maximum text length, or 0 for no truncation. + + + Text clipping + + + Delete lines consisting of only spaces + + + Trim spaces at the beginning and end of a string + Any unsaved changes will be lost. Active workbook may become corrupted. It is strongly recommended to create backup. Continue? diff --git a/NavfertyExcelAddIn/Localization/UIStrings.ru-RU.resx b/NavfertyExcelAddIn/Localization/UIStrings.ru-RU.resx index 70c804b..bbac600 100644 --- a/NavfertyExcelAddIn/Localization/UIStrings.ru-RU.resx +++ b/NavfertyExcelAddIn/Localization/UIStrings.ru-RU.resx @@ -186,6 +186,18 @@ Валидация успешна, ошибок не обнаружено + + Укажите допустимую максимальную длинну текста, или 0 чтобы не обрезать. + + + Обрезка текста + + + Удалять строки состоящие из одних пробелов + + + Обрезать пробелы в начале и в конце строки + Несохранённые изменения будут утеряны. Книга может оказаться повреждённой: настоятельно рекомендуется предварительно создать копию. Продолжить? diff --git a/NavfertyExcelAddIn/NLog.config b/NavfertyExcelAddIn/NLog.config index 9bd8602..9c7fd0a 100644 --- a/NavfertyExcelAddIn/NLog.config +++ b/NavfertyExcelAddIn/NLog.config @@ -13,6 +13,8 @@ diff --git a/NavfertyExcelAddIn/NavfertyExcelAddIn.csproj b/NavfertyExcelAddIn/NavfertyExcelAddIn.csproj index be280f2..ae984d3 100644 --- a/NavfertyExcelAddIn/NavfertyExcelAddIn.csproj +++ b/NavfertyExcelAddIn/NavfertyExcelAddIn.csproj @@ -240,6 +240,12 @@ + + Form + + + frmTrimParams.cs + @@ -266,8 +272,10 @@ - + + + @@ -291,9 +299,9 @@ - + - + @@ -393,6 +401,9 @@ InteractiveRangeReportForm.cs Designer + + frmTrimParams.cs + ResXFileCodeGenerator RibbonSupertips.Designer.cs diff --git a/NavfertyExcelAddIn/NavfertyRibbon.cs b/NavfertyExcelAddIn/NavfertyRibbon.cs index eb50693..890640a 100644 --- a/NavfertyExcelAddIn/NavfertyRibbon.cs +++ b/NavfertyExcelAddIn/NavfertyRibbon.cs @@ -175,31 +175,31 @@ public void Transliterate(IRibbonControl ribbonControl) public void UnprotectWorkbook(IRibbonControl ribbonControl) { - var wb = App.ActiveWorkbook; - var path = wb.FullName; + try + { + var wb = App.ActiveWorkbook; + var path = wb.FullName; - logger.Debug($"UnprotectWorkbook {path}"); + logger.Debug($"UnprotectWorkbook {path}"); - var extension = path.Split('.').LastOrDefault(); + var extension = path.Split('.').LastOrDefault(); - if (extension != "xlsx" && extension != "xlsm") - { - dialogService.ShowError(UIStrings.CannotUnlockPleaseSaveAsXml); - return; - } + if (extension != "xlsx" && extension != "xlsm") + throw new Exception(UIStrings.CannotUnlockPleaseSaveAsXml); - if (!dialogService.Ask(UIStrings.UnsavedChangesWillBeLostPrompt, UIStrings.Warning)) - { - return; - } - - wb.Close(false); + if (!dialogService.Ask(UIStrings.UnsavedChangesWillBeLostPrompt, UIStrings.Warning)) + { + return; + } + wb.Close(false); - var wbUnprotector = GetService(); - wbUnprotector.UnprotectWorkbookWithAllWorksheets(path); + var wbUnprotector = GetService(); + wbUnprotector.UnprotectWorkbookWithAllWorksheets(path); - App.Workbooks.Open(path); + App.Workbooks.Open(path); + } + catch (Exception ex) { dialogService.ShowError(ex); } } public void ProtectUnprotectWorksheets(IRibbonControl ribbonControl) @@ -262,15 +262,9 @@ public void TrimSpaces(IRibbonControl ribbonControl) public void TrimExtraSpaces(IRibbonControl ribbonControl) { var range = GetSelectionOrUsedRange(App.ActiveSheet); - - if (range == null) - return; - + if (range == null) return; logger.Debug($"{nameof(TrimExtraSpaces)}. Range selected is {range.Address}"); - - - var trimmer = GetService(); - trimmer.TrimExtraSpaces(range); + GetService().TrimExtraSpaces(range); } public void RemoveAllSpaces(IRibbonControl ribbonControl) @@ -283,10 +277,19 @@ public void RemoveAllSpaces(IRibbonControl ribbonControl) logger.Debug($"{nameof(RemoveAllSpaces)}. Range selected is {range.Address}"); - var trimmer = GetService(); + var trimmer = GetService(); trimmer.RemoveAllSpaces(range); } + public void TrimTextByLength(IRibbonControl ribbonControl) + { + Range range = GetSelectionOrUsedRange(App.ActiveSheet); + if (range == null) return; + logger.Debug($"{nameof(TrimTextByLength)}. Range selected is {range.Address}"); + GetService().TrimTextByLengthUIDisplay(range); + } + + public void RepairConditionalFormat(IRibbonControl ribbonControl) { var range = GetSelectionOrUsedRange(App.ActiveSheet); @@ -299,7 +302,6 @@ public void RepairConditionalFormat(IRibbonControl ribbonControl) formatFixer.FillRange(range); } - public void UnmergeCells(IRibbonControl ribbonControl) { var range = GetSelectionOrUsedRange(App.ActiveSheet); @@ -317,29 +319,30 @@ public void UnmergeCells(IRibbonControl ribbonControl) public void ValidateValues(IRibbonControl ribbonControl) { - var activeSheet = (Worksheet)App.ActiveSheet; - var range = GetSelectionOrUsedRange(activeSheet); + try + { + var activeSheet = (Worksheet)App.ActiveSheet; + var range = GetSelectionOrUsedRange(activeSheet); - if (range == null) - return; + if (range == null) + return; - logger.Debug($"ValidateValues. Range selected is {range.Address}"); + logger.Debug($"ValidateValues. Range selected is {range.Address}"); - if (!validationTypeByButtonId.TryGetValue(ribbonControl.Id, out var validationType)) - { - dialogService.ShowError($"Invalid control id '{ribbonControl.Id}'"); - throw new ArgumentOutOfRangeException($"Invalid control id '{ribbonControl.Id}'"); - } + if (!validationTypeByButtonId.TryGetValue(ribbonControl.Id, out var validationType)) + throw new ArgumentOutOfRangeException($"Invalid control id '{ribbonControl.Id}'"); - logger.Debug($"ValidateValues. Range selected is {range.Address}, validation type {validationType}"); + logger.Debug($"ValidateValues. Range selected is {range.Address}, validation type {validationType}"); - IReadOnlyCollection results; + IReadOnlyCollection results; - var validator = GetService(); - results = validator.Validate(range, validationType); + var validator = GetService(); + results = validator.Validate(range, validationType); - form = new InteractiveRangeReportForm(results, activeSheet); - form.Show(); + form = new InteractiveRangeReportForm(results, activeSheet); + form.Show(); + } + catch (Exception ex) { dialogService.ShowError(ex, logger); } } public void FindErrors(IRibbonControl ribbonControl) @@ -423,6 +426,13 @@ public void CurrencyExchangeRatesSelect(IRibbonControl ribbonControl) } #endregion + #region Feedback + public void FeedbackStart(IRibbonControl ribbonControl) + { + GetService().DisplayFeedbackUI(); + } + #endregion + #endregion #region Utils diff --git a/NavfertyExcelAddIn/NavfertyRibbon.xml b/NavfertyExcelAddIn/NavfertyRibbon.xml index e0f0b67..a0271fa 100644 --- a/NavfertyExcelAddIn/NavfertyRibbon.xml +++ b/NavfertyExcelAddIn/NavfertyRibbon.xml @@ -38,6 +38,10 @@ onAction="NumberToWordsFrench" keytip="SF" getScreentip="GetLabel" getSupertip="GetSupertip"/> + +