first commit
This commit is contained in:
@@ -0,0 +1,214 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Gui.CompletionWindow
|
||||
{
|
||||
/// <summary>
|
||||
/// Description of AbstractCompletionWindow.
|
||||
/// </summary>
|
||||
public abstract class AbstractCompletionWindow : System.Windows.Forms.Form
|
||||
{
|
||||
protected TextEditorControl control;
|
||||
protected Size drawingSize;
|
||||
Rectangle workingScreen;
|
||||
Form parentForm;
|
||||
|
||||
protected AbstractCompletionWindow(Form parentForm, TextEditorControl control)
|
||||
{
|
||||
workingScreen = Screen.GetWorkingArea(parentForm);
|
||||
// SetStyle(ControlStyles.Selectable, false);
|
||||
this.parentForm = parentForm;
|
||||
this.control = control;
|
||||
|
||||
SetLocation();
|
||||
StartPosition = FormStartPosition.Manual;
|
||||
FormBorderStyle = FormBorderStyle.None;
|
||||
ShowInTaskbar = false;
|
||||
MinimumSize = new Size(1, 1);
|
||||
Size = new Size(1, 1);
|
||||
}
|
||||
|
||||
protected virtual void SetLocation()
|
||||
{
|
||||
TextArea textArea = control.ActiveTextAreaControl.TextArea;
|
||||
TextLocation caretPos = textArea.Caret.Position;
|
||||
|
||||
int xpos = textArea.TextView.GetDrawingXPos(caretPos.Y, caretPos.X);
|
||||
int rulerHeight = textArea.TextEditorProperties.ShowHorizontalRuler ? textArea.TextView.FontHeight : 0;
|
||||
Point pos = new Point(textArea.TextView.DrawingPosition.X + xpos,
|
||||
textArea.TextView.DrawingPosition.Y + (textArea.Document.GetVisibleLine(caretPos.Y)) * textArea.TextView.FontHeight
|
||||
- textArea.TextView.TextArea.VirtualTop.Y + textArea.TextView.FontHeight + rulerHeight);
|
||||
|
||||
Point location = control.ActiveTextAreaControl.PointToScreen(pos);
|
||||
|
||||
// set bounds
|
||||
Rectangle bounds = new Rectangle(location, drawingSize);
|
||||
|
||||
if (!workingScreen.Contains(bounds)) {
|
||||
if (bounds.Right > workingScreen.Right) {
|
||||
bounds.X = workingScreen.Right - bounds.Width;
|
||||
}
|
||||
if (bounds.Left < workingScreen.Left) {
|
||||
bounds.X = workingScreen.Left;
|
||||
}
|
||||
if (bounds.Top < workingScreen.Top) {
|
||||
bounds.Y = workingScreen.Top;
|
||||
}
|
||||
if (bounds.Bottom > workingScreen.Bottom) {
|
||||
bounds.Y = bounds.Y - bounds.Height - control.ActiveTextAreaControl.TextArea.TextView.FontHeight;
|
||||
if (bounds.Bottom > workingScreen.Bottom) {
|
||||
bounds.Y = workingScreen.Bottom - bounds.Height;
|
||||
}
|
||||
}
|
||||
}
|
||||
Bounds = bounds;
|
||||
}
|
||||
|
||||
protected override CreateParams CreateParams {
|
||||
get {
|
||||
CreateParams p = base.CreateParams;
|
||||
AddShadowToWindow(p);
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
static int shadowStatus;
|
||||
|
||||
/// <summary>
|
||||
/// Adds a shadow to the create params if it is supported by the operating system.
|
||||
/// </summary>
|
||||
public static void AddShadowToWindow(CreateParams createParams)
|
||||
{
|
||||
if (shadowStatus == 0) {
|
||||
// Test OS version
|
||||
shadowStatus = -1; // shadow not supported
|
||||
if (Environment.OSVersion.Platform == PlatformID.Win32NT) {
|
||||
Version ver = Environment.OSVersion.Version;
|
||||
if (ver.Major > 5 || ver.Major == 5 && ver.Minor >= 1) {
|
||||
shadowStatus = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (shadowStatus == 1) {
|
||||
createParams.ClassStyle |= 0x00020000; // set CS_DROPSHADOW
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool ShowWithoutActivation {
|
||||
get {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
protected void ShowCompletionWindow()
|
||||
{
|
||||
Owner = parentForm;
|
||||
Enabled = true;
|
||||
this.Show();
|
||||
|
||||
control.Focus();
|
||||
|
||||
if (parentForm != null) {
|
||||
parentForm.LocationChanged += new EventHandler(this.ParentFormLocationChanged);
|
||||
}
|
||||
|
||||
control.ActiveTextAreaControl.VScrollBar.ValueChanged += new EventHandler(ParentFormLocationChanged);
|
||||
control.ActiveTextAreaControl.HScrollBar.ValueChanged += new EventHandler(ParentFormLocationChanged);
|
||||
control.ActiveTextAreaControl.TextArea.DoProcessDialogKey += new DialogKeyProcessor(ProcessTextAreaKey);
|
||||
control.ActiveTextAreaControl.Caret.PositionChanged += new EventHandler(CaretOffsetChanged);
|
||||
control.ActiveTextAreaControl.TextArea.LostFocus += new EventHandler(this.TextEditorLostFocus);
|
||||
control.Resize += new EventHandler(ParentFormLocationChanged);
|
||||
|
||||
foreach (Control c in Controls) {
|
||||
c.MouseMove += ControlMouseMove;
|
||||
}
|
||||
}
|
||||
|
||||
void ParentFormLocationChanged(object sender, EventArgs e)
|
||||
{
|
||||
SetLocation();
|
||||
}
|
||||
|
||||
public virtual bool ProcessKeyEvent(char ch)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected virtual bool ProcessTextAreaKey(Keys keyData)
|
||||
{
|
||||
if (!Visible) {
|
||||
return false;
|
||||
}
|
||||
switch (keyData) {
|
||||
case Keys.Escape:
|
||||
Close();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected virtual void CaretOffsetChanged(object sender, EventArgs e)
|
||||
{
|
||||
}
|
||||
|
||||
protected void TextEditorLostFocus(object sender, EventArgs e)
|
||||
{
|
||||
if (!control.ActiveTextAreaControl.TextArea.Focused && !this.ContainsFocus) {
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnClosed(EventArgs e)
|
||||
{
|
||||
base.OnClosed(e);
|
||||
|
||||
// take out the inserted methods
|
||||
parentForm.LocationChanged -= new EventHandler(ParentFormLocationChanged);
|
||||
|
||||
foreach (Control c in Controls) {
|
||||
c.MouseMove -= ControlMouseMove;
|
||||
}
|
||||
|
||||
if (control.ActiveTextAreaControl.VScrollBar != null) {
|
||||
control.ActiveTextAreaControl.VScrollBar.ValueChanged -= new EventHandler(ParentFormLocationChanged);
|
||||
}
|
||||
if (control.ActiveTextAreaControl.HScrollBar != null) {
|
||||
control.ActiveTextAreaControl.HScrollBar.ValueChanged -= new EventHandler(ParentFormLocationChanged);
|
||||
}
|
||||
|
||||
control.ActiveTextAreaControl.TextArea.LostFocus -= new EventHandler(this.TextEditorLostFocus);
|
||||
control.ActiveTextAreaControl.Caret.PositionChanged -= new EventHandler(CaretOffsetChanged);
|
||||
control.ActiveTextAreaControl.TextArea.DoProcessDialogKey -= new DialogKeyProcessor(ProcessTextAreaKey);
|
||||
control.Resize -= new EventHandler(ParentFormLocationChanged);
|
||||
Dispose();
|
||||
}
|
||||
|
||||
protected override void OnMouseMove(MouseEventArgs e)
|
||||
{
|
||||
base.OnMouseMove(e);
|
||||
ControlMouseMove(this, e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the mouse moves over this form or any child control.
|
||||
/// Shows the mouse cursor on the text area if it has been hidden.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Derived classes should attach this handler to the MouseMove event
|
||||
/// of all created controls which are not added to the Controls
|
||||
/// collection.
|
||||
/// </remarks>
|
||||
protected void ControlMouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
control.ActiveTextAreaControl.TextArea.ShowHiddenCursor(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Gui.CompletionWindow
|
||||
{
|
||||
/// <summary>
|
||||
/// Description of CodeCompletionListView.
|
||||
/// </summary>
|
||||
public class CodeCompletionListView : System.Windows.Forms.UserControl
|
||||
{
|
||||
ICompletionData[] completionData;
|
||||
int firstItem = 0;
|
||||
int selectedItem = -1;
|
||||
ImageList imageList;
|
||||
|
||||
public ImageList ImageList {
|
||||
get {
|
||||
return imageList;
|
||||
}
|
||||
set {
|
||||
imageList = value;
|
||||
}
|
||||
}
|
||||
|
||||
public int FirstItem {
|
||||
get {
|
||||
return firstItem;
|
||||
}
|
||||
set {
|
||||
if (firstItem != value) {
|
||||
firstItem = value;
|
||||
OnFirstItemChanged(EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ICompletionData SelectedCompletionData {
|
||||
get {
|
||||
if (selectedItem < 0) {
|
||||
return null;
|
||||
}
|
||||
return completionData[selectedItem];
|
||||
}
|
||||
}
|
||||
|
||||
public int ItemHeight {
|
||||
get {
|
||||
return Math.Max(imageList.ImageSize.Height, (int)(Font.Height * 1.25));
|
||||
}
|
||||
}
|
||||
|
||||
public int MaxVisibleItem {
|
||||
get {
|
||||
return Height / ItemHeight;
|
||||
}
|
||||
}
|
||||
|
||||
public CodeCompletionListView(ICompletionData[] completionData)
|
||||
{
|
||||
Array.Sort(completionData, DefaultCompletionData.Compare);
|
||||
this.completionData = completionData;
|
||||
|
||||
// this.KeyDown += new System.Windows.Forms.KeyEventHandler(OnKey);
|
||||
// SetStyle(ControlStyles.Selectable, false);
|
||||
// SetStyle(ControlStyles.UserPaint, true);
|
||||
// SetStyle(ControlStyles.DoubleBuffer, false);
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
if (completionData != null) {
|
||||
Array.Clear(completionData, 0, completionData.Length);
|
||||
}
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
public void SelectIndex(int index)
|
||||
{
|
||||
int oldSelectedItem = selectedItem;
|
||||
int oldFirstItem = firstItem;
|
||||
|
||||
index = Math.Max(0, index);
|
||||
selectedItem = Math.Max(0, Math.Min(completionData.Length - 1, index));
|
||||
if (selectedItem < firstItem) {
|
||||
FirstItem = selectedItem;
|
||||
}
|
||||
if (firstItem + MaxVisibleItem <= selectedItem) {
|
||||
FirstItem = selectedItem - MaxVisibleItem + 1;
|
||||
}
|
||||
if (oldSelectedItem != selectedItem) {
|
||||
if (firstItem != oldFirstItem) {
|
||||
Invalidate();
|
||||
} else {
|
||||
int min = Math.Min(selectedItem, oldSelectedItem) - firstItem;
|
||||
int max = Math.Max(selectedItem, oldSelectedItem) - firstItem;
|
||||
Invalidate(new Rectangle(0, 1 + min * ItemHeight, Width, (max - min + 1) * ItemHeight));
|
||||
}
|
||||
OnSelectedItemChanged(EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
public void CenterViewOn(int index)
|
||||
{
|
||||
int oldFirstItem = this.FirstItem;
|
||||
int firstItem = index - MaxVisibleItem / 2;
|
||||
if (firstItem < 0)
|
||||
this.FirstItem = 0;
|
||||
else if (firstItem >= completionData.Length - MaxVisibleItem)
|
||||
this.FirstItem = completionData.Length - MaxVisibleItem;
|
||||
else
|
||||
this.FirstItem = firstItem;
|
||||
if (this.FirstItem != oldFirstItem) {
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearSelection()
|
||||
{
|
||||
if (selectedItem < 0)
|
||||
return;
|
||||
int itemNum = selectedItem - firstItem;
|
||||
selectedItem = -1;
|
||||
Invalidate(new Rectangle(0, itemNum * ItemHeight, Width, (itemNum + 1) * ItemHeight + 1));
|
||||
Update();
|
||||
OnSelectedItemChanged(EventArgs.Empty);
|
||||
}
|
||||
|
||||
public void PageDown()
|
||||
{
|
||||
SelectIndex(selectedItem + MaxVisibleItem);
|
||||
}
|
||||
|
||||
public void PageUp()
|
||||
{
|
||||
SelectIndex(selectedItem - MaxVisibleItem);
|
||||
}
|
||||
|
||||
public void SelectNextItem()
|
||||
{
|
||||
SelectIndex(selectedItem + 1);
|
||||
}
|
||||
|
||||
public void SelectPrevItem()
|
||||
{
|
||||
SelectIndex(selectedItem - 1);
|
||||
}
|
||||
|
||||
public void SelectItemWithStart(string startText)
|
||||
{
|
||||
if (startText == null || startText.Length == 0) return;
|
||||
string originalStartText = startText;
|
||||
startText = startText.ToLower();
|
||||
int bestIndex = -1;
|
||||
int bestQuality = -1;
|
||||
// Qualities: 0 = match start
|
||||
// 1 = match start case sensitive
|
||||
// 2 = full match
|
||||
// 3 = full match case sensitive
|
||||
double bestPriority = 0;
|
||||
for (int i = 0; i < completionData.Length; ++i) {
|
||||
string itemText = completionData[i].Text;
|
||||
string lowerText = itemText.ToLower();
|
||||
if (lowerText.StartsWith(startText)) {
|
||||
double priority = completionData[i].Priority;
|
||||
int quality;
|
||||
if (lowerText == startText) {
|
||||
if (itemText == originalStartText)
|
||||
quality = 3;
|
||||
else
|
||||
quality = 2;
|
||||
} else if (itemText.StartsWith(originalStartText)) {
|
||||
quality = 1;
|
||||
} else {
|
||||
quality = 0;
|
||||
}
|
||||
bool useThisItem;
|
||||
if (bestQuality < quality) {
|
||||
useThisItem = true;
|
||||
} else {
|
||||
if (bestIndex == selectedItem) {
|
||||
useThisItem = false;
|
||||
} else if (i == selectedItem) {
|
||||
useThisItem = bestQuality == quality;
|
||||
} else {
|
||||
useThisItem = bestQuality == quality && bestPriority < priority;
|
||||
}
|
||||
}
|
||||
if (useThisItem) {
|
||||
bestIndex = i;
|
||||
bestPriority = priority;
|
||||
bestQuality = quality;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bestIndex < 0) {
|
||||
ClearSelection();
|
||||
} else {
|
||||
if (bestIndex < firstItem || firstItem + MaxVisibleItem <= bestIndex) {
|
||||
SelectIndex(bestIndex);
|
||||
CenterViewOn(bestIndex);
|
||||
} else {
|
||||
SelectIndex(bestIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPaint(PaintEventArgs pe)
|
||||
{
|
||||
float yPos = 1;
|
||||
float itemHeight = ItemHeight;
|
||||
// Maintain aspect ratio
|
||||
int imageWidth = (int)(itemHeight * imageList.ImageSize.Width / imageList.ImageSize.Height);
|
||||
|
||||
int curItem = firstItem;
|
||||
Graphics g = pe.Graphics;
|
||||
while (curItem < completionData.Length && yPos < Height) {
|
||||
RectangleF drawingBackground = new RectangleF(1, yPos, Width - 2, itemHeight);
|
||||
if (drawingBackground.IntersectsWith(pe.ClipRectangle)) {
|
||||
// draw Background
|
||||
if (curItem == selectedItem) {
|
||||
g.FillRectangle(SystemBrushes.Highlight, drawingBackground);
|
||||
} else {
|
||||
g.FillRectangle(SystemBrushes.Window, drawingBackground);
|
||||
}
|
||||
|
||||
// draw Icon
|
||||
int xPos = 0;
|
||||
if (imageList != null && completionData[curItem].ImageIndex < imageList.Images.Count) {
|
||||
g.DrawImage(imageList.Images[completionData[curItem].ImageIndex], new RectangleF(1, yPos, imageWidth, itemHeight));
|
||||
xPos = imageWidth;
|
||||
}
|
||||
|
||||
// draw text
|
||||
if (curItem == selectedItem) {
|
||||
g.DrawString(completionData[curItem].Text, Font, SystemBrushes.HighlightText, xPos, yPos);
|
||||
} else {
|
||||
g.DrawString(completionData[curItem].Text, Font, SystemBrushes.WindowText, xPos, yPos);
|
||||
}
|
||||
}
|
||||
|
||||
yPos += itemHeight;
|
||||
++curItem;
|
||||
}
|
||||
g.DrawRectangle(SystemPens.Control, new Rectangle(0, 0, Width - 1, Height - 1));
|
||||
}
|
||||
|
||||
protected override void OnMouseDown(System.Windows.Forms.MouseEventArgs e)
|
||||
{
|
||||
float yPos = 1;
|
||||
int curItem = firstItem;
|
||||
float itemHeight = ItemHeight;
|
||||
|
||||
while (curItem < completionData.Length && yPos < Height) {
|
||||
RectangleF drawingBackground = new RectangleF(1, yPos, Width - 2, itemHeight);
|
||||
if (drawingBackground.Contains(e.X, e.Y)) {
|
||||
SelectIndex(curItem);
|
||||
break;
|
||||
}
|
||||
yPos += itemHeight;
|
||||
++curItem;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPaintBackground(PaintEventArgs pe)
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual void OnSelectedItemChanged(EventArgs e)
|
||||
{
|
||||
if (SelectedItemChanged != null) {
|
||||
SelectedItemChanged(this, e);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnFirstItemChanged(EventArgs e)
|
||||
{
|
||||
if (FirstItemChanged != null) {
|
||||
FirstItemChanged(this, e);
|
||||
}
|
||||
}
|
||||
|
||||
public event EventHandler SelectedItemChanged;
|
||||
public event EventHandler FirstItemChanged;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,364 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Diagnostics;
|
||||
using System.Windows.Forms;
|
||||
using ICSharpCode.TextEditor.Document;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Gui.CompletionWindow
|
||||
{
|
||||
public class CodeCompletionWindow : AbstractCompletionWindow
|
||||
{
|
||||
ICompletionData[] completionData;
|
||||
CodeCompletionListView codeCompletionListView;
|
||||
VScrollBar vScrollBar = new VScrollBar();
|
||||
ICompletionDataProvider dataProvider;
|
||||
IDocument document;
|
||||
bool showDeclarationWindow = true;
|
||||
bool fixedListViewWidth = true;
|
||||
const int ScrollbarWidth = 16;
|
||||
const int MaxListLength = 10;
|
||||
|
||||
int startOffset;
|
||||
int endOffset;
|
||||
DeclarationViewWindow declarationViewWindow = null;
|
||||
Rectangle workingScreen;
|
||||
|
||||
public static CodeCompletionWindow ShowCompletionWindow(Form parent, TextEditorControl control, string fileName, ICompletionDataProvider completionDataProvider, char firstChar)
|
||||
{
|
||||
return ShowCompletionWindow(parent, control, fileName, completionDataProvider, firstChar, true, true);
|
||||
}
|
||||
|
||||
public static CodeCompletionWindow ShowCompletionWindow(Form parent, TextEditorControl control, string fileName, ICompletionDataProvider completionDataProvider, char firstChar, bool showDeclarationWindow, bool fixedListViewWidth)
|
||||
{
|
||||
ICompletionData[] completionData = completionDataProvider.GenerateCompletionData(fileName, control.ActiveTextAreaControl.TextArea, firstChar);
|
||||
if (completionData == null || completionData.Length == 0) {
|
||||
return null;
|
||||
}
|
||||
CodeCompletionWindow codeCompletionWindow = new CodeCompletionWindow(completionDataProvider, completionData, parent, control, showDeclarationWindow, fixedListViewWidth);
|
||||
codeCompletionWindow.CloseWhenCaretAtBeginning = firstChar == '\0';
|
||||
codeCompletionWindow.ShowCompletionWindow();
|
||||
return codeCompletionWindow;
|
||||
}
|
||||
|
||||
CodeCompletionWindow(ICompletionDataProvider completionDataProvider, ICompletionData[] completionData, Form parentForm, TextEditorControl control, bool showDeclarationWindow, bool fixedListViewWidth) : base(parentForm, control)
|
||||
{
|
||||
this.dataProvider = completionDataProvider;
|
||||
this.completionData = completionData;
|
||||
this.document = control.Document;
|
||||
this.showDeclarationWindow = showDeclarationWindow;
|
||||
this.fixedListViewWidth = fixedListViewWidth;
|
||||
|
||||
workingScreen = Screen.GetWorkingArea(Location);
|
||||
startOffset = control.ActiveTextAreaControl.Caret.Offset + 1;
|
||||
endOffset = startOffset;
|
||||
if (completionDataProvider.PreSelection != null) {
|
||||
startOffset -= completionDataProvider.PreSelection.Length + 1;
|
||||
endOffset--;
|
||||
}
|
||||
|
||||
codeCompletionListView = new CodeCompletionListView(completionData);
|
||||
codeCompletionListView.ImageList = completionDataProvider.ImageList;
|
||||
codeCompletionListView.Dock = DockStyle.Fill;
|
||||
codeCompletionListView.SelectedItemChanged += new EventHandler(CodeCompletionListViewSelectedItemChanged);
|
||||
codeCompletionListView.DoubleClick += new EventHandler(CodeCompletionListViewDoubleClick);
|
||||
codeCompletionListView.Click += new EventHandler(CodeCompletionListViewClick);
|
||||
Controls.Add(codeCompletionListView);
|
||||
|
||||
if (completionData.Length > MaxListLength) {
|
||||
vScrollBar.Dock = DockStyle.Right;
|
||||
vScrollBar.Minimum = 0;
|
||||
vScrollBar.Maximum = completionData.Length - 1;
|
||||
vScrollBar.SmallChange = 1;
|
||||
vScrollBar.LargeChange = MaxListLength;
|
||||
codeCompletionListView.FirstItemChanged += new EventHandler(CodeCompletionListViewFirstItemChanged);
|
||||
Controls.Add(vScrollBar);
|
||||
}
|
||||
|
||||
this.drawingSize = GetListViewSize();
|
||||
SetLocation();
|
||||
|
||||
if (declarationViewWindow == null) {
|
||||
declarationViewWindow = new DeclarationViewWindow(parentForm);
|
||||
}
|
||||
SetDeclarationViewLocation();
|
||||
declarationViewWindow.ShowDeclarationViewWindow();
|
||||
declarationViewWindow.MouseMove += ControlMouseMove;
|
||||
control.Focus();
|
||||
CodeCompletionListViewSelectedItemChanged(this, EventArgs.Empty);
|
||||
|
||||
if (completionDataProvider.DefaultIndex >= 0) {
|
||||
codeCompletionListView.SelectIndex(completionDataProvider.DefaultIndex);
|
||||
}
|
||||
|
||||
if (completionDataProvider.PreSelection != null) {
|
||||
CaretOffsetChanged(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
vScrollBar.ValueChanged += VScrollBarValueChanged;
|
||||
document.DocumentAboutToBeChanged += DocumentAboutToBeChanged;
|
||||
}
|
||||
|
||||
bool inScrollUpdate;
|
||||
|
||||
void CodeCompletionListViewFirstItemChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (inScrollUpdate) return;
|
||||
inScrollUpdate = true;
|
||||
vScrollBar.Value = Math.Min(vScrollBar.Maximum, codeCompletionListView.FirstItem);
|
||||
inScrollUpdate = false;
|
||||
}
|
||||
|
||||
void VScrollBarValueChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (inScrollUpdate) return;
|
||||
inScrollUpdate = true;
|
||||
codeCompletionListView.FirstItem = vScrollBar.Value;
|
||||
codeCompletionListView.Refresh();
|
||||
control.ActiveTextAreaControl.TextArea.Focus();
|
||||
inScrollUpdate = false;
|
||||
}
|
||||
|
||||
void SetDeclarationViewLocation()
|
||||
{
|
||||
// This method uses the side with more free space
|
||||
int leftSpace = Bounds.Left - workingScreen.Left;
|
||||
int rightSpace = workingScreen.Right - Bounds.Right;
|
||||
Point pos;
|
||||
// The declaration view window has better line break when used on
|
||||
// the right side, so prefer the right side to the left.
|
||||
if (rightSpace * 2 > leftSpace) {
|
||||
declarationViewWindow.FixedWidth = false;
|
||||
pos = new Point(Bounds.Right, Bounds.Top);
|
||||
if (declarationViewWindow.Location != pos) {
|
||||
declarationViewWindow.Location = pos;
|
||||
}
|
||||
} else {
|
||||
declarationViewWindow.Width = declarationViewWindow.GetRequiredLeftHandSideWidth(new Point(Bounds.Left, Bounds.Top));
|
||||
declarationViewWindow.FixedWidth = true;
|
||||
if (Bounds.Left < declarationViewWindow.Width) {
|
||||
pos = new Point(0, Bounds.Top);
|
||||
} else {
|
||||
pos = new Point(Bounds.Left - declarationViewWindow.Width, Bounds.Top);
|
||||
}
|
||||
if (declarationViewWindow.Location != pos) {
|
||||
declarationViewWindow.Location = pos;
|
||||
}
|
||||
declarationViewWindow.Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void SetLocation()
|
||||
{
|
||||
base.SetLocation();
|
||||
if (declarationViewWindow != null) {
|
||||
SetDeclarationViewLocation();
|
||||
}
|
||||
}
|
||||
|
||||
Util.MouseWheelHandler mouseWheelHandler = new Util.MouseWheelHandler();
|
||||
|
||||
public void HandleMouseWheel(MouseEventArgs e)
|
||||
{
|
||||
int scrollDistance = mouseWheelHandler.GetScrollAmount(e);
|
||||
if (scrollDistance == 0)
|
||||
return;
|
||||
if (control.TextEditorProperties.MouseWheelScrollDown)
|
||||
scrollDistance = -scrollDistance;
|
||||
int newValue = vScrollBar.Value + vScrollBar.SmallChange * scrollDistance;
|
||||
vScrollBar.Value = Math.Max(vScrollBar.Minimum, Math.Min(vScrollBar.Maximum - vScrollBar.LargeChange + 1, newValue));
|
||||
}
|
||||
|
||||
void CodeCompletionListViewSelectedItemChanged(object sender, EventArgs e)
|
||||
{
|
||||
ICompletionData data = codeCompletionListView.SelectedCompletionData;
|
||||
if (showDeclarationWindow && data != null && data.Description != null && data.Description.Length > 0) {
|
||||
declarationViewWindow.Description = data.Description;
|
||||
SetDeclarationViewLocation();
|
||||
} else {
|
||||
declarationViewWindow.Description = null;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool ProcessKeyEvent(char ch)
|
||||
{
|
||||
switch (dataProvider.ProcessKey(ch)) {
|
||||
case CompletionDataProviderKeyResult.BeforeStartKey:
|
||||
// increment start+end, then process as normal char
|
||||
++startOffset;
|
||||
++endOffset;
|
||||
return base.ProcessKeyEvent(ch);
|
||||
case CompletionDataProviderKeyResult.NormalKey:
|
||||
// just process normally
|
||||
return base.ProcessKeyEvent(ch);
|
||||
case CompletionDataProviderKeyResult.InsertionKey:
|
||||
return InsertSelectedItem(ch);
|
||||
default:
|
||||
throw new InvalidOperationException("Invalid return value of dataProvider.ProcessKey");
|
||||
}
|
||||
}
|
||||
|
||||
void DocumentAboutToBeChanged(object sender, DocumentEventArgs e)
|
||||
{
|
||||
// => startOffset test required so that this startOffset/endOffset are not incremented again
|
||||
// for BeforeStartKey characters
|
||||
if (e.Offset >= startOffset && e.Offset <= endOffset) {
|
||||
if (e.Length > 0) { // length of removed region
|
||||
endOffset -= e.Length;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(e.Text)) {
|
||||
endOffset += e.Text.Length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When this flag is set, code completion closes if the caret moves to the
|
||||
/// beginning of the allowed range. This is useful in Ctrl+Space and "complete when typing",
|
||||
/// but not in dot-completion.
|
||||
/// </summary>
|
||||
public bool CloseWhenCaretAtBeginning { get; set; }
|
||||
|
||||
protected override void CaretOffsetChanged(object sender, EventArgs e)
|
||||
{
|
||||
int offset = control.ActiveTextAreaControl.Caret.Offset;
|
||||
if (offset == startOffset) {
|
||||
if (CloseWhenCaretAtBeginning)
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
if (offset < startOffset || offset > endOffset) {
|
||||
Close();
|
||||
} else {
|
||||
codeCompletionListView.SelectItemWithStart(control.Document.GetText(startOffset, offset - startOffset));
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool ProcessTextAreaKey(Keys keyData)
|
||||
{
|
||||
if (!Visible) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (keyData) {
|
||||
case Keys.Home:
|
||||
codeCompletionListView.SelectIndex(0);
|
||||
return true;
|
||||
case Keys.End:
|
||||
codeCompletionListView.SelectIndex(completionData.Length-1);
|
||||
return true;
|
||||
case Keys.PageDown:
|
||||
codeCompletionListView.PageDown();
|
||||
return true;
|
||||
case Keys.PageUp:
|
||||
codeCompletionListView.PageUp();
|
||||
return true;
|
||||
case Keys.Down:
|
||||
codeCompletionListView.SelectNextItem();
|
||||
return true;
|
||||
case Keys.Up:
|
||||
codeCompletionListView.SelectPrevItem();
|
||||
return true;
|
||||
case Keys.Tab:
|
||||
InsertSelectedItem('\t');
|
||||
return true;
|
||||
case Keys.Return:
|
||||
InsertSelectedItem('\n');
|
||||
return true;
|
||||
}
|
||||
return base.ProcessTextAreaKey(keyData);
|
||||
}
|
||||
|
||||
void CodeCompletionListViewDoubleClick(object sender, EventArgs e)
|
||||
{
|
||||
InsertSelectedItem('\0');
|
||||
}
|
||||
|
||||
void CodeCompletionListViewClick(object sender, EventArgs e)
|
||||
{
|
||||
control.ActiveTextAreaControl.TextArea.Focus();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing) {
|
||||
document.DocumentAboutToBeChanged -= DocumentAboutToBeChanged;
|
||||
if (codeCompletionListView != null) {
|
||||
codeCompletionListView.Dispose();
|
||||
codeCompletionListView = null;
|
||||
}
|
||||
if (declarationViewWindow != null) {
|
||||
declarationViewWindow.Dispose();
|
||||
declarationViewWindow = null;
|
||||
}
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
bool InsertSelectedItem(char ch)
|
||||
{
|
||||
document.DocumentAboutToBeChanged -= DocumentAboutToBeChanged;
|
||||
ICompletionData data = codeCompletionListView.SelectedCompletionData;
|
||||
bool result = false;
|
||||
if (data != null) {
|
||||
control.BeginUpdate();
|
||||
|
||||
try {
|
||||
if (endOffset - startOffset > 0) {
|
||||
control.Document.Remove(startOffset, endOffset - startOffset);
|
||||
}
|
||||
Debug.Assert(startOffset <= document.TextLength);
|
||||
result = dataProvider.InsertAction(data, control.ActiveTextAreaControl.TextArea, startOffset, ch);
|
||||
} finally {
|
||||
control.EndUpdate();
|
||||
}
|
||||
}
|
||||
Close();
|
||||
return result;
|
||||
}
|
||||
|
||||
Size GetListViewSize()
|
||||
{
|
||||
int height = codeCompletionListView.ItemHeight * Math.Min(MaxListLength, completionData.Length);
|
||||
int width = codeCompletionListView.ItemHeight * 10;
|
||||
if (!fixedListViewWidth) {
|
||||
width = GetListViewWidth(width, height);
|
||||
}
|
||||
return new Size(width, height);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list view width large enough to handle the longest completion data
|
||||
/// text string.
|
||||
/// </summary>
|
||||
/// <param name="defaultWidth">The default width of the list view.</param>
|
||||
/// <param name="height">The height of the list view. This is
|
||||
/// used to determine if the scrollbar is visible.</param>
|
||||
/// <returns>The list view width to accommodate the longest completion
|
||||
/// data text string; otherwise the default width.</returns>
|
||||
int GetListViewWidth(int defaultWidth, int height)
|
||||
{
|
||||
float width = defaultWidth;
|
||||
using (Graphics graphics = codeCompletionListView.CreateGraphics()) {
|
||||
for (int i = 0; i < completionData.Length; ++i) {
|
||||
float itemWidth = graphics.MeasureString(completionData[i].Text.ToString(), codeCompletionListView.Font).Width;
|
||||
if(itemWidth > width) {
|
||||
width = itemWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float totalItemsHeight = codeCompletionListView.ItemHeight * completionData.Length;
|
||||
if (totalItemsHeight > height) {
|
||||
width += ScrollbarWidth; // Compensate for scroll bar.
|
||||
}
|
||||
return (int)width;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
using ICSharpCode.TextEditor.Util;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Gui.CompletionWindow
|
||||
{
|
||||
public interface IDeclarationViewWindow
|
||||
{
|
||||
string Description {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
void ShowDeclarationViewWindow();
|
||||
void CloseDeclarationViewWindow();
|
||||
}
|
||||
|
||||
public class DeclarationViewWindow : Form, IDeclarationViewWindow
|
||||
{
|
||||
string description = string.Empty;
|
||||
bool fixedWidth;
|
||||
|
||||
public string Description {
|
||||
get {
|
||||
return description;
|
||||
}
|
||||
set {
|
||||
description = value;
|
||||
if (value == null && Visible) {
|
||||
Visible = false;
|
||||
} else if (value != null) {
|
||||
if (!Visible) ShowDeclarationViewWindow();
|
||||
Refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool FixedWidth {
|
||||
get {
|
||||
return fixedWidth;
|
||||
}
|
||||
set {
|
||||
fixedWidth = value;
|
||||
}
|
||||
}
|
||||
|
||||
public int GetRequiredLeftHandSideWidth(Point p) {
|
||||
if (description != null && description.Length > 0) {
|
||||
using (Graphics g = CreateGraphics()) {
|
||||
Size s = TipPainterTools.GetLeftHandSideDrawingSizeHelpTipFromCombinedDescription(this, g, Font, null, description, p);
|
||||
return s.Width;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public bool HideOnClick;
|
||||
|
||||
public DeclarationViewWindow(Form parent)
|
||||
{
|
||||
SetStyle(ControlStyles.Selectable, false);
|
||||
StartPosition = FormStartPosition.Manual;
|
||||
FormBorderStyle = FormBorderStyle.None;
|
||||
Owner = parent;
|
||||
ShowInTaskbar = false;
|
||||
Size = new Size(0, 0);
|
||||
base.CreateHandle();
|
||||
}
|
||||
|
||||
protected override CreateParams CreateParams {
|
||||
get {
|
||||
CreateParams p = base.CreateParams;
|
||||
AbstractCompletionWindow.AddShadowToWindow(p);
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool ShowWithoutActivation {
|
||||
get {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnClick(EventArgs e)
|
||||
{
|
||||
base.OnClick(e);
|
||||
if (HideOnClick) Hide();
|
||||
}
|
||||
|
||||
public void ShowDeclarationViewWindow()
|
||||
{
|
||||
Show();
|
||||
}
|
||||
|
||||
public void CloseDeclarationViewWindow()
|
||||
{
|
||||
Close();
|
||||
Dispose();
|
||||
}
|
||||
|
||||
protected override void OnPaint(PaintEventArgs pe)
|
||||
{
|
||||
if (description != null && description.Length > 0) {
|
||||
if (fixedWidth) {
|
||||
TipPainterTools.DrawFixedWidthHelpTipFromCombinedDescription(this, pe.Graphics, Font, null, description);
|
||||
} else {
|
||||
TipPainterTools.DrawHelpTipFromCombinedDescription(this, pe.Graphics, Font, null, description);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPaintBackground(PaintEventArgs pe)
|
||||
{
|
||||
pe.Graphics.FillRectangle(SystemBrushes.Info, pe.ClipRectangle);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Gui.CompletionWindow
|
||||
{
|
||||
public interface ICompletionData
|
||||
{
|
||||
int ImageIndex {
|
||||
get;
|
||||
}
|
||||
|
||||
string Text {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
string Description {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a priority value for the completion data item.
|
||||
/// When selecting items by their start characters, the item with the highest
|
||||
/// priority is selected first.
|
||||
/// </summary>
|
||||
double Priority {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Insert the element represented by the completion data into the text
|
||||
/// editor.
|
||||
/// </summary>
|
||||
/// <param name="textArea">TextArea to insert the completion data in.</param>
|
||||
/// <param name="ch">Character that should be inserted after the completion data.
|
||||
/// \0 when no character should be inserted.</param>
|
||||
/// <returns>Returns true when the insert action has processed the character
|
||||
/// <paramref name="ch"/>; false when the character was not processed.</returns>
|
||||
bool InsertAction(TextArea textArea, char ch);
|
||||
}
|
||||
|
||||
public class DefaultCompletionData : ICompletionData
|
||||
{
|
||||
string text;
|
||||
string description;
|
||||
int imageIndex;
|
||||
|
||||
public int ImageIndex {
|
||||
get {
|
||||
return imageIndex;
|
||||
}
|
||||
}
|
||||
|
||||
public string Text {
|
||||
get {
|
||||
return text;
|
||||
}
|
||||
set {
|
||||
text = value;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual string Description {
|
||||
get {
|
||||
return description;
|
||||
}
|
||||
}
|
||||
|
||||
double priority;
|
||||
|
||||
public double Priority {
|
||||
get {
|
||||
return priority;
|
||||
}
|
||||
set {
|
||||
priority = value;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool InsertAction(TextArea textArea, char ch)
|
||||
{
|
||||
textArea.InsertString(text);
|
||||
return false;
|
||||
}
|
||||
|
||||
public DefaultCompletionData(string text, int imageIndex)
|
||||
{
|
||||
this.text = text;
|
||||
this.imageIndex = imageIndex;
|
||||
}
|
||||
|
||||
public DefaultCompletionData(string text, string description, int imageIndex)
|
||||
{
|
||||
this.text = text;
|
||||
this.description = description;
|
||||
this.imageIndex = imageIndex;
|
||||
}
|
||||
|
||||
public static int Compare(ICompletionData a, ICompletionData b)
|
||||
{
|
||||
if (a == null)
|
||||
throw new ArgumentNullException("a");
|
||||
if (b == null)
|
||||
throw new ArgumentNullException("b");
|
||||
return string.Compare(a.Text, b.Text, StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace ICSharpCode.TextEditor.Gui.CompletionWindow
|
||||
{
|
||||
public interface ICompletionDataProvider
|
||||
{
|
||||
ImageList ImageList {
|
||||
get;
|
||||
}
|
||||
string PreSelection {
|
||||
get;
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the index of the element in the list that is chosen by default.
|
||||
/// </summary>
|
||||
int DefaultIndex {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes a keypress. Returns the action to be run with the key.
|
||||
/// </summary>
|
||||
CompletionDataProviderKeyResult ProcessKey(char key);
|
||||
|
||||
/// <summary>
|
||||
/// Executes the insertion. The provider should set the caret position and then
|
||||
/// call data.InsertAction.
|
||||
/// </summary>
|
||||
bool InsertAction(ICompletionData data, TextArea textArea, int insertionOffset, char key);
|
||||
|
||||
/// <summary>
|
||||
/// Generates the completion data. This method is called by the text editor control.
|
||||
/// </summary>
|
||||
ICompletionData[] GenerateCompletionData(string fileName, TextArea textArea, char charTyped);
|
||||
}
|
||||
|
||||
public enum CompletionDataProviderKeyResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Normal key, used to choose an entry from the completion list
|
||||
/// </summary>
|
||||
NormalKey,
|
||||
/// <summary>
|
||||
/// This key triggers insertion of the completed expression
|
||||
/// </summary>
|
||||
InsertionKey,
|
||||
/// <summary>
|
||||
/// Increment both start and end offset of completion region when inserting this
|
||||
/// key. Can be used to insert whitespace (or other characters) in front of the expression
|
||||
/// while the completion window is open.
|
||||
/// </summary>
|
||||
BeforeStartKey
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user