diff --git a/OneMore/Commands/Settings/GeneralSheet.Designer.cs b/OneMore/Commands/Settings/GeneralSheet.Designer.cs index b29ffd1808..1785b81e51 100644 --- a/OneMore/Commands/Settings/GeneralSheet.Designer.cs +++ b/OneMore/Commands/Settings/GeneralSheet.Designer.cs @@ -31,6 +31,7 @@ private void InitializeComponent() { this.introBox = new River.OneMoreAddIn.UI.MoreMultilineLabel(); this.layoutPanel = new System.Windows.Forms.Panel(); + this.sequentialBox = new River.OneMoreAddIn.UI.MoreCheckBox(); this.themeBox = new System.Windows.Forms.ComboBox(); this.themeLabel = new System.Windows.Forms.Label(); this.advancedGroup = new River.OneMoreAddIn.UI.MoreGroupBox(); @@ -59,6 +60,7 @@ private void InitializeComponent() // // layoutPanel // + this.layoutPanel.Controls.Add(this.sequentialBox); this.layoutPanel.Controls.Add(this.themeBox); this.layoutPanel.Controls.Add(this.themeLabel); this.layoutPanel.Controls.Add(this.advancedGroup); @@ -72,6 +74,22 @@ private void InitializeComponent() this.layoutPanel.Size = new System.Drawing.Size(772, 416); this.layoutPanel.TabIndex = 4; // + // sequentialBox + // + this.sequentialBox.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(240)))), ((int)(((byte)(240)))), ((int)(((byte)(240))))); + this.sequentialBox.Cursor = System.Windows.Forms.Cursors.Hand; + this.sequentialBox.ForeColor = System.Drawing.SystemColors.ControlText; + this.sequentialBox.Location = new System.Drawing.Point(25, 159); + this.sequentialBox.Margin = new System.Windows.Forms.Padding(0, 10, 0, 0); + this.sequentialBox.Name = "sequentialBox"; + this.sequentialBox.Size = new System.Drawing.Size(461, 25); + this.sequentialBox.StylizeImage = false; + this.sequentialBox.TabIndex = 7; + this.sequentialBox.Text = "Allow nonsequential name matching in Command Palettes"; + this.sequentialBox.ThemedBack = null; + this.sequentialBox.ThemedFore = null; + this.sequentialBox.UseVisualStyleBackColor = true; + // // themeBox // this.themeBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; @@ -103,11 +121,11 @@ private void InitializeComponent() this.advancedGroup.Controls.Add(this.experimentalBox); this.advancedGroup.Controls.Add(this.verboseBox); this.advancedGroup.ForeColor = System.Drawing.SystemColors.ControlText; - this.advancedGroup.Location = new System.Drawing.Point(7, 274); + this.advancedGroup.Location = new System.Drawing.Point(7, 289); this.advancedGroup.Name = "advancedGroup"; this.advancedGroup.Padding = new System.Windows.Forms.Padding(15, 10, 10, 10); this.advancedGroup.ShowOnlyTopEdge = true; - this.advancedGroup.Size = new System.Drawing.Size(762, 139); + this.advancedGroup.Size = new System.Drawing.Size(762, 124); this.advancedGroup.TabIndex = 4; this.advancedGroup.TabStop = false; this.advancedGroup.Text = "Advanced Options"; @@ -170,7 +188,7 @@ private void InitializeComponent() this.checkUpdatesBox.CheckState = System.Windows.Forms.CheckState.Checked; this.checkUpdatesBox.Cursor = System.Windows.Forms.Cursors.Hand; this.checkUpdatesBox.ForeColor = System.Drawing.SystemColors.ControlText; - this.checkUpdatesBox.Location = new System.Drawing.Point(25, 176); + this.checkUpdatesBox.Location = new System.Drawing.Point(25, 194); this.checkUpdatesBox.Margin = new System.Windows.Forms.Padding(0, 10, 0, 0); this.checkUpdatesBox.Name = "checkUpdatesBox"; this.checkUpdatesBox.Size = new System.Drawing.Size(456, 25); @@ -211,5 +229,6 @@ private void InitializeComponent() private UI.MoreCheckBox experimentalBox; private System.Windows.Forms.ComboBox themeBox; private System.Windows.Forms.Label themeLabel; + private UI.MoreCheckBox sequentialBox; } } diff --git a/OneMore/Commands/Settings/GeneralSheet.cs b/OneMore/Commands/Settings/GeneralSheet.cs index a0c6883ad2..edd51c8d4b 100644 --- a/OneMore/Commands/Settings/GeneralSheet.cs +++ b/OneMore/Commands/Settings/GeneralSheet.cs @@ -33,6 +33,7 @@ public GeneralSheet(SettingsProvider provider) : base(provider) "themeLabel", "themeBox", "langLabel=word_Language", + "sequentialBox", "checkUpdatesBox", "advancedGroup=phrase_AdvancedOptions", "verboseBox", @@ -54,6 +55,7 @@ public GeneralSheet(SettingsProvider provider) : base(provider) } } + sequentialBox.Checked = settings.Get("nonseqMatching", false); checkUpdatesBox.Checked = settings.Get("checkUpdates", false); experimentalBox.Checked = settings.Get("experimental", false); @@ -131,6 +133,11 @@ public override bool CollectSettings() var lang = ((CultureInfo)(langBox.SelectedItem)).Name; var updated = settings.Add("language", lang); + // does not require a restart + save = sequentialBox.Checked + ? settings.Add("nonseqMatching", "true") || save + : settings.Remove("nonseqMatching") || save; + // does not require a restart save = checkUpdatesBox.Checked ? settings.Add("checkUpdates", true) || save diff --git a/OneMore/Commands/Tagging/HashtagDialog.cs b/OneMore/Commands/Tagging/HashtagDialog.cs index f8bae9875d..273b9191e0 100644 --- a/OneMore/Commands/Tagging/HashtagDialog.cs +++ b/OneMore/Commands/Tagging/HashtagDialog.cs @@ -78,8 +78,11 @@ public HashtagDialog() searchButton.NotifyDefault(true); - experimental = new SettingsProvider() - .GetCollection("GeneralSheet").Get("experimental"); + var setprovider = new SettingsProvider(); + var general = setprovider.GetCollection(nameof(GeneralSheet)); + + palette.NonsequentialMatching = general.Get("nonseqMatching"); + experimental = general.Get("experimental"); scanLink.Left = lastScanLabel.Left; scanLink.Visible = false; @@ -87,7 +90,7 @@ public HashtagDialog() ShowScanTimes(); CheckForNewNotebooks(); - ShowOfflineNotebooks = new SettingsProvider() + ShowOfflineNotebooks = setprovider .GetCollection(SettingsKey) .Get("showOffline", true); diff --git a/OneMore/Commands/Tools/CommandPaletteDialog.cs b/OneMore/Commands/Tools/CommandPaletteDialog.cs index f3faf7864b..89f8969eb7 100644 --- a/OneMore/Commands/Tools/CommandPaletteDialog.cs +++ b/OneMore/Commands/Tools/CommandPaletteDialog.cs @@ -4,6 +4,7 @@ namespace River.OneMoreAddIn.Commands { + using River.OneMoreAddIn.Settings; using River.OneMoreAddIn.UI; using System; using System.Text.RegularExpressions; @@ -29,8 +30,13 @@ public CommandPaletteDialog(string title, string intro, bool showClearOption) { InitializeComponent(); + var settings = new SettingsProvider().GetCollection(nameof(GeneralSheet)); + var nonseq = settings.Get("nonseqMatching", false); + palette = new MoreAutoCompleteList { + // allow nonsequential character matching + NonsequentialMatching = nonseq, // keeping this as false will eliminate flicker on startup ShowPopupOnStartup = false }; diff --git a/OneMore/Properties/Resources.Designer.cs b/OneMore/Properties/Resources.Designer.cs index faa6b1890a..397cdb437d 100644 --- a/OneMore/Properties/Resources.Designer.cs +++ b/OneMore/Properties/Resources.Designer.cs @@ -3338,6 +3338,15 @@ internal static string GeneralSheet_introBox_Text { } } + /// + /// Looks up a localized string similar to Allow nonsequential name matching in Command Palettes. + /// + internal static string GeneralSheet_sequentialBox_Text { + get { + return ResourceManager.GetString("GeneralSheet_sequentialBox.Text", resourceCulture); + } + } + /// /// Looks up a localized string similar to System ///Light diff --git a/OneMore/Properties/Resources.ar-SA.resx b/OneMore/Properties/Resources.ar-SA.resx index a48d99601a..77b1e49ff0 100644 --- a/OneMore/Properties/Resources.ar-SA.resx +++ b/OneMore/Properties/Resources.ar-SA.resx @@ -1260,6 +1260,10 @@ تخصيص السلوك العام لـ OneMore textbox + + السماح بمطابقة الأسماء غير المتسلسلة في لوحات الأوامر + checkbox + نظام ضوء diff --git a/OneMore/Properties/Resources.de-DE.resx b/OneMore/Properties/Resources.de-DE.resx index c60fcf808d..89beeca26a 100644 --- a/OneMore/Properties/Resources.de-DE.resx +++ b/OneMore/Properties/Resources.de-DE.resx @@ -1258,6 +1258,10 @@ Verwandte Bereiche: A1: Zelle (0, -1) Passe das Gesamtverhalten von OneMore an textbox + + Erlauben Sie den nichtsequentiellen Namensabgleich in Befehlspaletten + checkbox + System Licht diff --git a/OneMore/Properties/Resources.es-ES.resx b/OneMore/Properties/Resources.es-ES.resx index 9760f46db5..5b9dbee7c5 100644 --- a/OneMore/Properties/Resources.es-ES.resx +++ b/OneMore/Properties/Resources.es-ES.resx @@ -1260,6 +1260,10 @@ Rangos relativos: A1: Cell (0, -1) Personalice el comportamiento general de OneMore textbox + + Permitir coincidencias de nombres no secuenciales en las paletas de comandos + checkbox + Sistema Luz diff --git a/OneMore/Properties/Resources.fr-FR.resx b/OneMore/Properties/Resources.fr-FR.resx index d76f402d3c..3b8cb5b37c 100644 --- a/OneMore/Properties/Resources.fr-FR.resx +++ b/OneMore/Properties/Resources.fr-FR.resx @@ -1260,6 +1260,10 @@ Plages relatives: a1: cellule (0, -1) Personnalisez le comportement général de OneMore textbox + + Autoriser la correspondance de noms non séquentielle dans les palettes de commandes + checkbox + Système Lumière diff --git a/OneMore/Properties/Resources.he-IL.resx b/OneMore/Properties/Resources.he-IL.resx index bd290ecdf3..69cc41b4db 100644 --- a/OneMore/Properties/Resources.he-IL.resx +++ b/OneMore/Properties/Resources.he-IL.resx @@ -1261,6 +1261,10 @@ Total Row Font התאם אישית את ההתנהגות הכוללת של OneMore textbox + + אפשר התאמת שמות לא רציפים בפלטות פקודות + checkbox + מערכת אוֹר diff --git a/OneMore/Properties/Resources.ja-JP.resx b/OneMore/Properties/Resources.ja-JP.resx index 86bbd8d2cc..b45ee88343 100644 --- a/OneMore/Properties/Resources.ja-JP.resx +++ b/OneMore/Properties/Resources.ja-JP.resx @@ -1263,6 +1263,10 @@ OneNoteファイル (*.one) OneMoreの全体的な動作をカスタマイズ textbox + + コマンド パレットで連続しない名前の一致を許可する + checkbox + システム ライト diff --git a/OneMore/Properties/Resources.nl-NL.resx b/OneMore/Properties/Resources.nl-NL.resx index 3bfbe73680..7052df0e7e 100644 --- a/OneMore/Properties/Resources.nl-NL.resx +++ b/OneMore/Properties/Resources.nl-NL.resx @@ -1261,6 +1261,10 @@ Relatieve bereiken: A1: Cell (0, -1) Pas het algemene gedrag van OneMore aan textbox + + Sta niet-opeenvolgende namen toe in opdrachtpaletten + checkbox + Systeem Licht diff --git a/OneMore/Properties/Resources.pl-PL.resx b/OneMore/Properties/Resources.pl-PL.resx index ffdc3733a2..ceb61d4a60 100644 --- a/OneMore/Properties/Resources.pl-PL.resx +++ b/OneMore/Properties/Resources.pl-PL.resx @@ -1261,6 +1261,10 @@ Zakresy względne: A1: komórka (0, -1) Dostosuj ogólne zachowanie OneMore textbox + + Zezwalaj na niesekwencyjne dopasowywanie nazw w paletach poleceń + checkbox + System Światło diff --git a/OneMore/Properties/Resources.pt-BR.resx b/OneMore/Properties/Resources.pt-BR.resx index 4be51a1433..158598161a 100644 --- a/OneMore/Properties/Resources.pt-BR.resx +++ b/OneMore/Properties/Resources.pt-BR.resx @@ -1261,6 +1261,10 @@ Intervalos relativos: a1: célula (0, -1) Personalize o comportamento geral do OneMore textbox + + Permitir correspondência de nomes não sequenciais em paletas de comandos + checkbox + Sistema Luz diff --git a/OneMore/Properties/Resources.resx b/OneMore/Properties/Resources.resx index 1965da35a3..3c6d1605ba 100644 --- a/OneMore/Properties/Resources.resx +++ b/OneMore/Properties/Resources.resx @@ -1261,6 +1261,10 @@ Relative ranges: A1:cell(0,-1) Customize the overall behavior of OneMore textbox + + Allow nonsequential name matching in Command Palettes + checkbox + System Light diff --git a/OneMore/Properties/Resources.zh-CN.resx b/OneMore/Properties/Resources.zh-CN.resx index 79ddcf450c..331af0d324 100644 --- a/OneMore/Properties/Resources.zh-CN.resx +++ b/OneMore/Properties/Resources.zh-CN.resx @@ -1259,6 +1259,10 @@ OneNote 文件 (*.one) 自定义OneMore的整体行为 textbox + + 允许命令面板中的非连续名称匹配 + checkbox + 系统 光 diff --git a/OneMore/UI/MoreAutoCompleteList.cs b/OneMore/UI/MoreAutoCompleteList.cs index 1501ab4bf8..7d23a972aa 100644 --- a/OneMore/UI/MoreAutoCompleteList.cs +++ b/OneMore/UI/MoreAutoCompleteList.cs @@ -13,6 +13,7 @@ namespace River.OneMoreAddIn.UI using System.ComponentModel; using System.Diagnostics; using System.Drawing; + using System.Globalization; using System.Linq; using System.Text.RegularExpressions; using System.Windows.Forms; @@ -39,13 +40,13 @@ internal class MoreAutoCompleteList : ListView, IExtenderProvider // The 'commands' field maintains the original list of available Cmds // The 'matches' field maintains the current matched Cmds based on Owner input - private ToolStripDropDown popup; // invisible host control of this ListtView - private readonly Font highFont; // font of matched substring + private ToolStripDropDown popup; // invisible host control of this ListtView private readonly List commands; // original list of commands private readonly List matches; // dynamic list of matched commands private readonly ThemeManager manager; // color manager private string boxtext; // the current/previous text in the Owner TextBox + // each command name is described by a Cmd entry private sealed class Cmd { @@ -57,6 +58,169 @@ private sealed class Cmd public bool Recent; // true if in "recently used" category } + #region HighlightedItemPainter + private sealed class HighlightedItemPainter : IDisposable + { + private const char Space = ' '; + + private readonly ThemeManager manager; + private readonly ListViewItem item; + private readonly Rectangle bounds; + private readonly Graphics graphics; + + private readonly Font highFont; + private readonly Brush fore; + private readonly Brush high; + private bool disposed; + + public HighlightedItemPainter(ThemeManager manager, DrawListViewSubItemEventArgs e) + { + this.manager = manager; + item = e.Item; + bounds = e.Bounds; + graphics = e.Graphics; + + highFont = new Font(item.Font, item.Font.Style | FontStyle.Bold); + + if (item.Selected) + { + fore = new SolidBrush(manager.GetColor("HighlightText")); + high = new SolidBrush(manager.GetColor("GradientInactiveCaption")); + } + else + { + fore = new SolidBrush(manager.GetColor("ControlText")); + high = new SolidBrush(manager.GetColor("Highlight")); + } + } + + + public void Dispose() + { + if (!disposed) + { + highFont?.Dispose(); + fore?.Dispose(); + high?.Dispose(); + disposed = true; + } + } + + + public bool NonsequentialMatching { get; set; } + + + public void PaintBackground() + { + using var back = new SolidBrush( + manager.GetColor(item.Selected ? "Highlight" : "ListView")); + + graphics.FillRectangle(back, + bounds.X, bounds.Y + 1, + bounds.Width, bounds.Height - 2); + } + + + public void PaintCategory(string text) + { + // this was used for recent and other annotations; difference? + //var size = e.Graphics.MeasureString(annotation, Font); + + var size = TextRenderer.MeasureText(text, item.Font); + var x = bounds.Width - size.Width - 5; + graphics.DrawString(text, item.Font, high, x, bounds.Y); + } + + + public void PaintDivider() + { + graphics.DrawLine(Pens.Silver, // yes, this pen is hard-coded + bounds.X, bounds.Y, bounds.Width, bounds.Y); + } + + + public void PaintItem(string input, string commandName) + { + var inputIndex = 0; + float x = bounds.X; + SizeF size; + + (int, int) range; + if (NonsequentialMatching) + { + range = (int.MaxValue, int.MinValue); + } + else + { + var index = commandName.IndexOf(input, StringComparison.InvariantCultureIgnoreCase); + range = (index, index + input.Length - 1); + } + + bool InRange(int v) => v >= range.Item1 && v <= range.Item2; + + for (int i = 0; i < commandName.Length; i++) + { + var ch = commandName[i]; + var text = $"{ch}"; + + if (ch == Space) + { + size = graphics.MeasureString(text, item.Font, new PointF(x, bounds.Y), + // GenericDefault will measure space but GenericTypographic will not + StringFormat.GenericDefault); + + x += size.Width; + } + else + { + Font font; + Brush brush; + + if (InRange(i) || + NonsequentialMatching && + (inputIndex < input.Length && + char.ToLower(ch, CultureInfo.InvariantCulture) == + char.ToLower(input[inputIndex], CultureInfo.InvariantCulture))) + { + font = highFont; + brush = high; + inputIndex++; + } + else + { + font = item.Font; + brush = fore; + } + + var format = StringFormat.GenericTypographic; + graphics.DrawString(text, font, brush, x, bounds.Y, format); + size = graphics.MeasureString(text, font, new PointF(x, bounds.Y), format); + x += size.Width; + } + } + } + + + public void PaintKeys(string keys) + { + using var cap = item.Selected + ? new SolidBrush(manager.GetColor("GradientInactiveCaption")) + : new SolidBrush(manager.GetColor("ActiveCaption")); + + var size = graphics.MeasureString(keys, item.Font); + var x = bounds.Width - size.Width - 5; + + graphics.DrawString(keys, item.Font, cap, x, bounds.Y); + } + + + public void PaintPlainText(string text) + { + graphics.DrawString(text, item.Font, fore, bounds); + } + } + #endregion + /// /// Initialize a new instance. The instance should be bound to a TextBox using @@ -81,7 +245,6 @@ public MoreAutoCompleteList() SetStyle(ControlStyles.DoubleBuffer | ControlStyles.OptimizedDoubleBuffer, true); Font = new Font("Segoe UI", 9); - highFont = new Font(Font, Font.Style | FontStyle.Bold); commands = new List(); matches = new List(); @@ -131,6 +294,14 @@ public MoreAutoCompleteList() public bool IsPopupVisible => popup?.Visible == true; + /// + /// Gets or sets whether characters in input text is allowed to match nonsequential + /// characters in item text value, e.g. "olf" could match "Open Log File". Otherwise, + /// input text must match an explicit substring. + /// + public bool NonsequentialMatching { get; set; } + + /// /// Sets the subtitle shown for all non-recent commands. /// The default is similar to "other commands" @@ -395,111 +566,33 @@ protected override void OnClientSizeChanged(EventArgs e) protected override void OnDrawSubItem(DrawListViewSubItemEventArgs e) { - Brush back; - Brush fore; - Brush high; - - if (e.Item.Selected) - { - back = new SolidBrush(manager.GetColor("Highlight")); - fore = new SolidBrush(manager.GetColor("HighlightText")); - high = new SolidBrush(manager.GetColor("GradientInactiveCaption")); - } - else + using var painter = new HighlightedItemPainter(manager, e) { - back = new SolidBrush(manager.GetColor("ListView")); - fore = new SolidBrush(manager.GetColor("ControlText")); - high = new SolidBrush(manager.GetColor("Highlight")); - } + NonsequentialMatching = this.NonsequentialMatching + }; - e.Graphics.FillRectangle(back, - e.Bounds.X, e.Bounds.Y + 1, - e.Bounds.Width, e.Bounds.Height - 2); + painter.PaintBackground(); var source = matches.Any() ? matches : commands; var command = source[e.Item.Index]; - var drawn = false; - float x; - - if (!string.IsNullOrWhiteSpace(Owner.Text)) + if (!string.IsNullOrWhiteSpace(Owner.Text) && IsMatch(Owner.Text, command.Name)) { - // draw name in three parts: unmatched start, matched middle, unmatched end... - - var text = command.Name; - - // Owner is the bound TextBox - var index = text.IndexOf(Owner.Text, StringComparison.InvariantCultureIgnoreCase); - if (index >= 0) - { - string phrase; - SizeF size; - StringFormat format; - - // track x-offset of each phrase - x = e.Bounds.X; - - // phrase is in middle so draw prior phrase - if (index > 0) - { - phrase = text.Substring(0, index); - - // when phrase is substring of word, GenericTypographic doesn't measure - // trailing space and when it is prefaced by a space, GenericDefault - // removes that space. So choose appropriate format carefully here - format = index < text.Length - 1 && text[index - 1] == ' ' - ? StringFormat.GenericDefault - : StringFormat.GenericTypographic; - - e.Graphics.DrawString(phrase, Font, fore, x, e.Bounds.Y, format); - size = e.Graphics.MeasureString(phrase, Font, new PointF(x, e.Bounds.Y), format); - x += size.Width; - } - - // draw matched phrase - phrase = text.Substring(index, Owner.Text.Length); - - format = phrase[phrase.Length - 1] == ' ' - ? StringFormat.GenericDefault - : StringFormat.GenericTypographic; - - e.Graphics.DrawString(phrase, highFont, high, x, e.Bounds.Y, format); - size = e.Graphics.MeasureString(phrase, highFont, new PointF(x, e.Bounds.Y), format); - x += size.Width; - - // draw remaining phrase - index += Owner.Text.Length; - if (index < text.Length) - { - phrase = text.Substring(index); - e.Graphics.DrawString(phrase, Font, fore, - x, e.Bounds.Y, StringFormat.GenericTypographic); - } - - drawn = true; - } + // paint item text with matching + painter.PaintItem(Owner.Text, command.Name); } - - if (!drawn) + else { - // if no matched part then draw entire name normally... - - e.Graphics.DrawString(command.Name, e.Item.Font, fore, e.Bounds); + // paint item text without matching + painter.PaintPlainText(command.Name); } - // leave margin from right side to end of key sequence text - x = e.Bounds.Width - 5; - // did we match any Recent items at all? if (source[0].Recent) { if (e.ItemIndex == 0) { - var annotation = RecentKicker; - var size = e.Graphics.MeasureString(annotation, Font); - // push key sequence positioning over to the left - x -= size.Width; - e.Graphics.DrawString(annotation, e.Item.Font, high, x, e.Bounds.Y); + painter.PaintCategory(RecentKicker); } // index of first common command found after all recent commands @@ -509,20 +602,13 @@ protected override void OnDrawSubItem(DrawListViewSubItemEventArgs e) common++; } - // divider line - if (common < source.Count && e.ItemIndex == common - 1) + if (common < source.Count && e.ItemIndex == common) { - e.Graphics.DrawLine(Pens.Silver, // yes, this pen is hard-coded - e.Bounds.X, e.Bounds.Y + e.Bounds.Height - 1, - e.Bounds.Width, e.Bounds.Y + e.Bounds.Height - 1); + painter.PaintDivider(); } else if (common == e.ItemIndex) { - var annotation = OtherKicker; - var size = e.Graphics.MeasureString(annotation, Font); - // push key sequence positioning over to the left - x -= size.Width; - e.Graphics.DrawString(annotation, e.Item.Font, high, x, e.Bounds.Y); + painter.PaintCategory(OtherKicker); } } @@ -534,27 +620,16 @@ protected override void OnDrawSubItem(DrawListViewSubItemEventArgs e) { if (e.Item.Index > 0) { - // divider line - e.Graphics.DrawLine(Pens.Silver, // yes, this pen is hard-coded - e.Bounds.X, e.Bounds.Y, e.Bounds.Width, e.Bounds.Y); + painter.PaintDivider(); } - var size = TextRenderer.MeasureText(command.Category, e.Item.Font); - x -= size.Width; - e.Graphics.DrawString(command.Category, e.Item.Font, high, x, e.Bounds.Y); + painter.PaintCategory(command.Category); } } - // key sequence + // settle for key sequence else if (!string.IsNullOrWhiteSpace(command.Keys)) { - var size = e.Graphics.MeasureString(command.Keys, Font); - x -= size.Width + 5; - - var cap = e.Item.Selected - ? new SolidBrush(manager.GetColor("GradientInactiveCaption")) - : new SolidBrush(manager.GetColor("ActiveCaption")); - - e.Graphics.DrawString(command.Keys, e.Item.Font, cap, x, e.Bounds.Y); + painter.PaintKeys(command.Keys); } } @@ -595,7 +670,8 @@ private void DoTextChanged(object sender, EventArgs e) commands.ForEach(cmd => { - if (cmd.Name.ContainsICIC(text)) + //if (cmd.Name.ContainsICIC(text)) + if (IsMatch(text, cmd.Name)) { matches.Add(cmd); } @@ -661,6 +737,29 @@ private string ClosestWord(out int start) } + // suggusted by nhwCoder here: https://github.com/stevencohn/OneMore/issues/1680 + // allows sequential character searching such as "olf" = "Open Log File" + private bool IsMatch(string input, string command) + { + if (!NonsequentialMatching) + { + return command.ContainsICIC(input); + } + + int inputIndex = 0; + foreach (var ch in command) + { + if (inputIndex < input.Length && + char.ToLower(ch, CultureInfo.InvariantCulture) == + char.ToLower(input[inputIndex], CultureInfo.InvariantCulture)) + { + inputIndex++; + } + } + return inputIndex == input.Length; + } + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Keyboard handlers