using System.ComponentModel;
|
using System.Drawing.Drawing2D;
|
using System.Drawing.Text;
|
using System.Text.RegularExpressions;
|
|
namespace LB_VisionControls
|
{
|
/// <summary>
|
/// Popup menu for autocomplete
|
/// </summary>
|
[Browsable(false)]
|
public class AutocompleteMenu : ToolStripDropDown
|
{
|
AutocompleteListView listView;
|
public ToolStripControlHost host;
|
public Range Fragment { get; internal set; }
|
|
/// <summary>
|
/// Regex pattern for serach fragment around caret
|
/// </summary>
|
public string SearchPattern { get; set; }
|
/// <summary>
|
/// Minimum fragment length for popup
|
/// </summary>
|
public int MinFragmentLength { get; set; }
|
/// <summary>
|
/// User selects item
|
/// </summary>
|
public event EventHandler<SelectingEventArgs> Selecting;
|
/// <summary>
|
/// It fires after item inserting
|
/// </summary>
|
public event EventHandler<SelectedEventArgs> Selected;
|
/// <summary>
|
/// Occurs when popup menu is opening
|
/// </summary>
|
public event EventHandler<CancelEventArgs> Opening;
|
/// <summary>
|
/// Allow TAB for select menu item
|
/// </summary>
|
public bool AllowTabKey { get { return listView.AllowTabKey; } set { listView.AllowTabKey = value; } }
|
/// <summary>
|
/// Interval of menu appear (ms)
|
/// </summary>
|
public int AppearInterval { get { return listView.AppearInterval; } set { listView.AppearInterval = value; } }
|
|
protected override void OnPaint(PaintEventArgs e)
|
{
|
e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
|
e.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(50, 37, 38)), new Rectangle(-1, -1, this.ClientRectangle.Width + 2, this.ClientRectangle.Height + 2));
|
//base.OnPaint(e);
|
}
|
public AutocompleteMenu(FastColoredTextBox tb)
|
{
|
// create a new popup and add the list view to it
|
AutoClose = false;
|
AutoSize = false;
|
Margin = Padding.Empty;
|
Padding = Padding.Empty;
|
listView = new AutocompleteListView(tb);
|
host = new ToolStripControlHost(listView);
|
host.Margin = new Padding(1, 1, 1, 1);
|
host.Padding = Padding.Empty;
|
host.AutoSize = false;
|
host.AutoToolTip = false;
|
CalcSize();
|
base.Items.Add(host);
|
listView.Parent = this;
|
SearchPattern = @"[\w\.]";
|
MinFragmentLength = 2;
|
}
|
|
internal void OnOpening(CancelEventArgs args)
|
{
|
if (Opening != null)
|
Opening(this, args);
|
}
|
|
public new void Close()
|
{
|
listView.toolTip.Hide(listView);
|
base.Close();
|
}
|
|
internal void CalcSize()
|
{
|
host.Size = listView.Size;
|
Size = new System.Drawing.Size(listView.Size.Width, listView.Size.Height);
|
}
|
|
public virtual void OnSelecting()
|
{
|
listView.OnSelecting();
|
}
|
|
public void SelectNext(int shift)
|
{
|
listView.SelectNext(shift);
|
}
|
|
internal void OnSelecting(SelectingEventArgs args)
|
{
|
if (Selecting != null)
|
Selecting(this, args);
|
}
|
|
public void OnSelected(SelectedEventArgs args)
|
{
|
if (Selected != null)
|
Selected(this, args);
|
}
|
|
public new AutocompleteListView Items
|
{
|
get { return listView; }
|
}
|
|
/// <summary>
|
/// Shows popup menu immediately
|
/// </summary>
|
/// <param name="forced">If True - MinFragmentLength will be ignored</param>
|
public void Show(bool forced)
|
{
|
Items.DoAutocomplete(forced);
|
}
|
}
|
|
public class AutocompleteListView : UserControl
|
{
|
internal List<AutocompleteItem> visibleItems;
|
IEnumerable<AutocompleteItem> sourceItems = new List<AutocompleteItem>();
|
int selectedItemIndex = 0;
|
int hoveredItemIndex = -1;
|
int itemHeight;
|
AutocompleteMenu Menu { get { return Parent as AutocompleteMenu; } }
|
int oldItemCount = 0;
|
FastColoredTextBox tb;
|
internal ToolTip toolTip = new ToolTip();
|
System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
|
|
internal bool AllowTabKey { get; set; }
|
public ImageList ImageList { get; set; }
|
internal int AppearInterval { get { return timer.Interval; } set { timer.Interval = value; } }
|
|
internal AutocompleteListView(FastColoredTextBox tb)
|
{
|
this.Width = 210;
|
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint, true);
|
base.Font = new Font(FontFamily.GenericSansSerif, 9);
|
visibleItems = new List<AutocompleteItem>();
|
itemHeight = Font.Height + 2;
|
VerticalScroll.SmallChange = itemHeight;
|
this.BackColor = Color.Transparent; //Color.FromArgb(255, 255, 17, 18);
|
BorderStyle = BorderStyle.None;
|
MaximumSize = new Size(Size.Width, 180);
|
toolTip.ShowAlways = false;
|
AppearInterval = 500;
|
|
timer.Tick += new EventHandler(timer_Tick);
|
|
this.tb = tb;
|
|
tb.KeyDown += new KeyEventHandler(tb_KeyDown);
|
tb.SelectionChanged += new EventHandler(tb_SelectionChanged);
|
tb.KeyPressed += new KeyPressEventHandler(tb_KeyPressed);
|
|
Form form = tb.FindForm();
|
if (form != null)
|
{
|
form.LocationChanged += (o, e) => Menu.Close();
|
form.ResizeBegin += (o, e) => Menu.Close();
|
form.FormClosing += (o, e) => Menu.Close();
|
form.LostFocus += (o, e) => Menu.Close();
|
}
|
|
tb.LostFocus += (o, e) =>
|
{
|
if (!Menu.Focused) Menu.Close();
|
};
|
|
tb.Scroll += (o, e) => Menu.Close();
|
}
|
|
void tb_KeyPressed(object sender, KeyPressEventArgs e)
|
{
|
bool backspaceORdel = e.KeyChar == '\b' || e.KeyChar == 0xff;
|
|
/*
|
if (backspaceORdel)
|
prevSelection = tb.Selection.Start;*/
|
|
if (Menu.Visible && !backspaceORdel)
|
DoAutocomplete(false);
|
else
|
ResetTimer(timer);
|
}
|
|
void timer_Tick(object sender, EventArgs e)
|
{
|
timer.Stop();
|
DoAutocomplete(false);
|
}
|
|
void ResetTimer(System.Windows.Forms.Timer timer)
|
{
|
timer.Stop();
|
timer.Start();
|
}
|
|
internal void DoAutocomplete()
|
{
|
DoAutocomplete(false);
|
}
|
|
internal void DoAutocomplete(bool forced)
|
{
|
if (!Menu.Enabled)
|
{
|
Menu.Close();
|
return;
|
}
|
|
visibleItems.Clear();
|
selectedItemIndex = 0;
|
VerticalScroll.Value = 0;
|
//get fragment around caret
|
Range fragment = tb.Selection.GetFragment(Menu.SearchPattern);
|
string text = fragment.Text;
|
//calc screen point for popup menu
|
Point point = tb.PlaceToPoint(fragment.End);
|
point.Offset(2, tb.CharHeight);
|
//
|
if (forced ||
|
(text.Length >= Menu.MinFragmentLength && tb.Selection.IsEmpty))
|
{
|
Menu.Fragment = fragment;
|
bool foundSelected = false;
|
//build popup menu
|
foreach (var item in sourceItems)
|
{
|
item.Parent = Menu;
|
CompareResult res = item.Compare(text);
|
if (res != CompareResult.Hidden)
|
visibleItems.Add(item);
|
if (res == CompareResult.VisibleAndSelected && !foundSelected)
|
{
|
foundSelected = true;
|
selectedItemIndex = visibleItems.Count - 1;
|
}
|
}
|
|
if (foundSelected)
|
{
|
AdjustScroll();
|
DoSelectedVisible();
|
}
|
}
|
|
//show popup menu
|
if (Count > 0)
|
{
|
if (!Menu.Visible)
|
{
|
CancelEventArgs args = new CancelEventArgs();
|
Menu.OnOpening(args);
|
if (!args.Cancel)
|
Menu.Show(tb, point);
|
}
|
else
|
Invalidate();
|
}
|
else
|
Menu.Close();
|
}
|
|
void tb_SelectionChanged(object sender, EventArgs e)
|
{
|
/*
|
FastColoredTextBox tb = sender as FastColoredTextBox;
|
|
if (Math.Abs(prevSelection.iChar - tb.Selection.Start.iChar) > 1 ||
|
prevSelection.iLine != tb.Selection.Start.iLine)
|
Menu.Close();
|
prevSelection = tb.Selection.Start;*/
|
if (Menu.Visible)
|
{
|
bool needClose = false;
|
|
if (!tb.Selection.IsEmpty)
|
needClose = true;
|
else
|
if (!Menu.Fragment.Contains(tb.Selection.Start))
|
{
|
if (tb.Selection.Start.iLine == Menu.Fragment.End.iLine && tb.Selection.Start.iChar == Menu.Fragment.End.iChar + 1)
|
{
|
//user press key at end of fragment
|
char c = tb.Selection.CharBeforeStart;
|
if (!Regex.IsMatch(c.ToString(), Menu.SearchPattern))//check char
|
needClose = true;
|
}
|
else
|
needClose = true;
|
}
|
|
if (needClose)
|
Menu.Close();
|
}
|
|
}
|
|
void tb_KeyDown(object sender, KeyEventArgs e)
|
{
|
if (Menu.Visible)
|
if (ProcessKey(e.KeyCode, e.Modifiers))
|
e.Handled = true;
|
|
if (!Menu.Visible)
|
if (e.Modifiers == Keys.Control && e.KeyCode == Keys.Space)
|
{
|
DoAutocomplete();
|
e.Handled = true;
|
}
|
}
|
|
void AdjustScroll()
|
{
|
if (oldItemCount == visibleItems.Count)
|
return;
|
|
int needHeight = itemHeight * visibleItems.Count + 1;
|
Height = Math.Min(needHeight, MaximumSize.Height);
|
Menu.CalcSize();
|
|
AutoScrollMinSize = new Size(0, needHeight);
|
oldItemCount = visibleItems.Count;
|
}
|
|
protected override void OnPaint(PaintEventArgs e)
|
{
|
AdjustScroll();
|
e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
|
e.Graphics.TextRenderingHint = TextRenderingHint.AntiAlias;
|
int startI = VerticalScroll.Value / itemHeight - 1;
|
int finishI = (VerticalScroll.Value + ClientSize.Height) / itemHeight + 1;
|
startI = Math.Max(startI, 0);
|
finishI = Math.Min(finishI, visibleItems.Count);
|
int y = 0;
|
int leftPadding = 20;
|
for (int i = startI; i < finishI; i++)
|
{
|
y = i * itemHeight - VerticalScroll.Value;
|
|
//if (ImageList != null && visibleItems[i].ImageIndex >= 0)
|
// e.Graphics.DrawImage(ImageList.Images[visibleItems[i].ImageIndex], 1, y);
|
|
if (i == selectedItemIndex)
|
{
|
Brush selectedBrush = new SolidBrush(Color.Yellow);//new LinearGradientBrush(new Point(0, y - 3), new Point(0, y + itemHeight), Color.GreenYellow, Color.Green);
|
e.Graphics.FillRectangle(selectedBrush, leftPadding, y, ClientSize.Width - 1 - leftPadding, itemHeight - 1);
|
e.Graphics.DrawRectangle(Pens.Yellow, leftPadding, y, ClientSize.Width - 1 - leftPadding, itemHeight - 1);
|
e.Graphics.DrawString(visibleItems[i].ToString(), Font, Brushes.Black, leftPadding, y);
|
}
|
else
|
{
|
e.Graphics.DrawString(visibleItems[i].ToString(), Font, Brushes.LightSkyBlue, leftPadding, y);
|
}
|
if (i == hoveredItemIndex)
|
e.Graphics.DrawRectangle(Pens.Red, leftPadding, y, ClientSize.Width - 1 - leftPadding, itemHeight - 1);
|
}
|
}
|
|
protected override void OnScroll(ScrollEventArgs se)
|
{
|
base.OnScroll(se);
|
Invalidate();
|
}
|
|
protected override void OnMouseClick(MouseEventArgs e)
|
{
|
base.OnMouseClick(e);
|
|
if (e.Button == System.Windows.Forms.MouseButtons.Left)
|
{
|
selectedItemIndex = PointToItemIndex(e.Location);
|
DoSelectedVisible();
|
Invalidate();
|
}
|
}
|
|
protected override void OnMouseDoubleClick(MouseEventArgs e)
|
{
|
base.OnMouseDoubleClick(e);
|
selectedItemIndex = PointToItemIndex(e.Location);
|
Invalidate();
|
OnSelecting();
|
}
|
|
internal virtual void OnSelecting()
|
{
|
if (selectedItemIndex < 0 || selectedItemIndex >= visibleItems.Count)
|
return;
|
tb.TextSource.Manager.BeginAutoUndoCommands();
|
try
|
{
|
AutocompleteItem item = visibleItems[selectedItemIndex];
|
SelectingEventArgs args = new SelectingEventArgs()
|
{
|
Item = item,
|
SelectedIndex = selectedItemIndex
|
};
|
|
Menu.OnSelecting(args);
|
|
if (args.Cancel)
|
{
|
selectedItemIndex = args.SelectedIndex;
|
Invalidate();
|
return;
|
}
|
|
if (!args.Handled)
|
{
|
var fragment = Menu.Fragment;
|
DoAutocomplete(item, fragment);
|
}
|
|
Menu.Close();
|
//
|
SelectedEventArgs args2 = new SelectedEventArgs()
|
{
|
Item = item,
|
Tb = Menu.Fragment.tb
|
};
|
item.OnSelected(Menu, args2);
|
Menu.OnSelected(args2);
|
}
|
finally
|
{
|
tb.TextSource.Manager.EndAutoUndoCommands();
|
}
|
}
|
|
private void DoAutocomplete(AutocompleteItem item, Range fragment)
|
{
|
string newText = item.GetTextForReplace();
|
//replace text of fragment
|
var tb = fragment.tb;
|
if (tb.Selection.ColumnSelectionMode)
|
{
|
var start = tb.Selection.Start;
|
var end = tb.Selection.End;
|
start.iChar = fragment.Start.iChar;
|
end.iChar = fragment.End.iChar;
|
tb.Selection.Start = start;
|
tb.Selection.End = end;
|
}
|
else
|
{
|
tb.Selection.Start = fragment.Start;
|
tb.Selection.End = fragment.End;
|
}
|
tb.InsertText(newText);
|
tb.Focus();
|
}
|
|
int PointToItemIndex(Point p)
|
{
|
return (p.Y + VerticalScroll.Value) / itemHeight;
|
}
|
|
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
|
{
|
ProcessKey(keyData, Keys.None);
|
|
return base.ProcessCmdKey(ref msg, keyData);
|
}
|
|
private bool ProcessKey(Keys keyData, Keys keyModifiers)
|
{
|
if (keyModifiers == Keys.None)
|
switch (keyData)
|
{
|
case Keys.Down:
|
SelectNext(+1);
|
return true;
|
case Keys.PageDown:
|
SelectNext(+10);
|
return true;
|
case Keys.Up:
|
SelectNext(-1);
|
return true;
|
case Keys.PageUp:
|
SelectNext(-10);
|
return true;
|
case Keys.Enter:
|
case Keys.Space:
|
OnSelecting();
|
return true;
|
case Keys.Tab:
|
if (!AllowTabKey)
|
break;
|
OnSelecting();
|
return true;
|
case Keys.Escape:
|
Menu.Close();
|
return true;
|
}
|
|
return false;
|
}
|
|
public void SelectNext(int shift)
|
{
|
selectedItemIndex = Math.Max(0, Math.Min(selectedItemIndex + shift, visibleItems.Count - 1));
|
DoSelectedVisible();
|
//
|
Invalidate();
|
}
|
|
private void DoSelectedVisible()
|
{
|
if (selectedItemIndex >= 0 && selectedItemIndex < visibleItems.Count)
|
SetToolTip(visibleItems[selectedItemIndex]);
|
|
var y = selectedItemIndex * itemHeight - VerticalScroll.Value;
|
if (y < 0)
|
VerticalScroll.Value = selectedItemIndex * itemHeight;
|
if (y > ClientSize.Height - itemHeight)
|
VerticalScroll.Value = Math.Min(VerticalScroll.Maximum, selectedItemIndex * itemHeight - ClientSize.Height + itemHeight);
|
//some magic for update scrolls
|
AutoScrollMinSize -= new Size(1, 0);
|
AutoScrollMinSize += new Size(1, 0);
|
}
|
|
private void SetToolTip(AutocompleteItem autocompleteItem)
|
{
|
var title = visibleItems[selectedItemIndex].ToolTipTitle;
|
var text = visibleItems[selectedItemIndex].ToolTipText;
|
|
if (string.IsNullOrEmpty(title))
|
{
|
toolTip.ToolTipTitle = null;
|
toolTip.SetToolTip(this, null);
|
return;
|
}
|
|
if (string.IsNullOrEmpty(text))
|
{
|
toolTip.ToolTipTitle = null;
|
toolTip.Show(title, this, Width + 3, 0, 3000);
|
}
|
else
|
{
|
toolTip.ToolTipTitle = title;
|
toolTip.Show(text, this, Width + 3, 0, 3000);
|
}
|
}
|
|
public int Count
|
{
|
get { return visibleItems.Count; }
|
}
|
|
public void SetAutocompleteItems(ICollection<string> items)
|
{
|
List<AutocompleteItem> list = new List<AutocompleteItem>(items.Count);
|
foreach (var item in items)
|
list.Add(new AutocompleteItem(item));
|
SetAutocompleteItems(list);
|
}
|
|
public void SetAutocompleteItems(ICollection<AutocompleteItem> items)
|
{
|
sourceItems = items;
|
}
|
}
|
|
public class SelectingEventArgs : EventArgs
|
{
|
public AutocompleteItem Item { get; internal set; }
|
public bool Cancel { get; set; }
|
public int SelectedIndex { get; set; }
|
public bool Handled { get; set; }
|
}
|
|
public class SelectedEventArgs : EventArgs
|
{
|
public AutocompleteItem Item { get; internal set; }
|
public FastColoredTextBox Tb { get; set; }
|
}
|
}
|