#region Disclaimer / License
// Copyright (C) 2012, Jackie Ng
// http://trac.osgeo.org/mapguide/wiki/maestro, jumpinjackie@gmail.com
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ICSharpCode.TextEditor;
using ICSharpCode.TextEditor.Actions;
using System.Windows.Forms;
using System.ComponentModel;
using ICSharpCode.TextEditor.Document;
using Maestro.Shared.UI;
using ICSharpCode.TextEditor.Gui.CompletionWindow;
using Maestro.Editors.Generic.XmlEditor;
using System.Drawing;
using Maestro.Editors.Generic.XmlEditor.AutoCompletion;
namespace Maestro.Editors.Generic
{
///
/// An extension of TextEditorControl for use by the generic XML editor control
///
[ToolboxItem(false)]
public class XmlTextEditorControl : TextEditorControl
{
///
/// Initializes a new instance
///
public XmlTextEditorControl()
{
XmlFormattingStrategy strategy = new XmlFormattingStrategy();
Document.FormattingStrategy = (IFormattingStrategy)strategy;
Document.HighlightingStrategy = HighlightingManager.Manager.FindHighlighter("XML");
Document.FoldingManager.FoldingStrategy = new XmlFoldingStrategy();
}
///
/// Applies the given text editor properties
///
///
public void ApplySettings(ITextEditorProperties props)
{
this.TextEditorProperties = props;
}
internal void RegisterAction(Keys k, IEditAction action)
{
editactions[k] = action;
}
///
/// Forces the editor to update its folds.
///
internal void UpdateFolding()
{
this.Document.FoldingManager.UpdateFoldings(String.Empty, null);
RefreshMargin();
}
///
/// Repaints the folds in the margin.
///
internal void RefreshMargin()
{
Action action = () =>
{
this.ActiveTextAreaControl.TextArea.Refresh(this.ActiveTextAreaControl.TextArea.FoldMargin);
};
if (this.InvokeRequired)
this.BeginInvoke(action);
else
action();
}
#region XML auto-completion stuff
///
///
///
///
protected override void InitializeTextAreaControl(TextAreaControl newControl)
{
base.InitializeTextAreaControl(newControl);
//primaryTextAreaControl = newControl;
newControl.TextArea.KeyEventHandler += new ICSharpCode.TextEditor.KeyEventHandler(HandleKeyPress);
/*
newControl.ContextMenuStrip = contextMenuStrip;
newControl.SelectionManager.SelectionChanged += new EventHandler(SelectionChanged);
newControl.Document.DocumentChanged += new DocumentEventHandler(DocumentChanged);
newControl.TextArea.ClipboardHandler.CopyText += new CopyTextEventHandler(ClipboardHandlerCopyText);
newControl.MouseWheel += new MouseEventHandler(TextAreaMouseWheel);
newControl.DoHandleMousewheel = false;
*/
}
CodeCompletionWindow codeCompletionWindow;
XmlSchemaCompletionDataCollection schemaCompletionDataItems = new XmlSchemaCompletionDataCollection();
XmlSchemaCompletionData defaultSchemaCompletionData = null;
///
/// Gets the schemas that the xml editor will use.
///
///
/// Probably should NOT have a 'set' property, but allowing one
/// allows us to share the completion data amongst multiple
/// xml editor controls.
///
internal XmlSchemaCompletionDataCollection SchemaCompletionDataItems
{
get
{
return schemaCompletionDataItems;
}
set
{
schemaCompletionDataItems = value;
}
}
///
/// Gets or sets the default schema completion data associated with this
/// view.
///
internal XmlSchemaCompletionData DefaultSchemaCompletionData
{
get
{
return defaultSchemaCompletionData;
}
set
{
defaultSchemaCompletionData = value;
}
}
char GetCharacterBeforeCaret()
{
string text = Document.GetText(ActiveTextAreaControl.TextArea.Caret.Offset - 1, 1);
if (text.Length > 0)
{
return text[0];
}
return '\0';
}
bool IsCaretAtDocumentStart
{
get
{
return ActiveTextAreaControl.TextArea.Caret.Offset == 0;
}
}
///
/// Called when the user hits Ctrl+Space.
///
public void ShowCompletionWindow()
{
if (!IsCaretAtDocumentStart)
{
// Find character before cursor.
char ch = GetCharacterBeforeCaret();
HandleKeyPress(ch);
}
}
///
/// Captures the user's key presses.
///
///
/// The code completion window ProcessKeyEvent is not perfect
/// when typing xml. If enter a space or ':' the text is
/// autocompleted when it should not be.
/// The code completion window has one predefined width,
/// which cuts off any long namespaces that we show.
/// The above issues have been resolved by duplicating
/// the code completion window and fixing the problems in the
/// duplicated class.
///
protected bool HandleKeyPress(char ch)
{
if (IsCodeCompletionWindowOpen)
{
if (codeCompletionWindow.ProcessKeyEvent(ch))
{
return false;
}
}
try
{
switch (ch)
{
case '<':
case ' ':
case '=':
ShowCompletionWindow(ch);
return false;
default:
if (XmlParser.IsAttributeValueChar(ch))
{
if (IsInsideQuotes(ActiveTextAreaControl.TextArea))
{
// Have to insert the character ourselves since
// it is not actually inserted yet. If it is not
// inserted now the code completion will not work
// since the completion data provider attempts to
// include the key typed as the pre-selected text.
InsertCharacter(ch);
ShowCompletionWindow(ch);
return true;
}
}
break;
}
}
catch (Exception e)
{
ErrorDialog.Show(e);
}
return false;
}
bool IsCodeCompletionEnabled
{
get
{
return true;
}
}
///
/// Checks whether the caret is inside a set of quotes (" or ').
///
bool IsInsideQuotes(TextArea textArea)
{
bool inside = false;
LineSegment line = textArea.Document.GetLineSegment(textArea.Document.GetLineNumberForOffset(textArea.Caret.Offset));
if (line != null)
{
if ((line.Offset + line.Length > textArea.Caret.Offset) &&
(line.Offset < textArea.Caret.Offset))
{
char charAfter = textArea.Document.GetCharAt(textArea.Caret.Offset);
char charBefore = textArea.Document.GetCharAt(textArea.Caret.Offset - 1);
if (((charBefore == '\'') && (charAfter == '\'')) ||
((charBefore == '\"') && (charAfter == '\"')))
{
inside = true;
}
}
}
return inside;
}
///
/// Inserts a character into the text editor at the current offset.
///
///
/// This code is copied from the TextArea.SimulateKeyPress method. This
/// code is needed to handle an issue with code completion. What if
/// we want to include the character just typed as the pre-selected text
/// for autocompletion? If we do not insert the character before
/// displaying the autocompletion list we cannot set the pre-selected text
/// because it is not actually inserted yet. The autocompletion window
/// checks the offset of the pre-selected text and closes the window
/// if the range is wrong. The offset check is always wrong since the text
/// does not actually exist yet. The check occurs in
/// CodeCompletionWindow.CaretOffsetChanged:
/// [[!CDATA[ int offset = control.ActiveTextAreaControl.Caret.Offset;
///
/// if (offset < startOffset || offset > endOffset) {
/// Close();
/// } else {
/// codeCompletionListView.SelectItemWithStart(control.Document.GetText(startOffset, offset - startOffset));
/// }]]
///
/// The Close method is called because the offset is out of the range.
///
void InsertCharacter(char ch)
{
ActiveTextAreaControl.TextArea.BeginUpdate();
Document.UndoStack.StartUndoGroup();
switch (ActiveTextAreaControl.TextArea.Caret.CaretMode)
{
case CaretMode.InsertMode:
ActiveTextAreaControl.TextArea.InsertChar(ch);
break;
case CaretMode.OverwriteMode:
ActiveTextAreaControl.TextArea.ReplaceChar(ch);
break;
}
int currentLineNr = ActiveTextAreaControl.TextArea.Caret.Line;
Document.FormattingStrategy.FormatLine(ActiveTextAreaControl.TextArea, currentLineNr, Document.PositionToOffset(ActiveTextAreaControl.TextArea.Caret.Position), ch);
ActiveTextAreaControl.TextArea.EndUpdate();
Document.UndoStack.EndUndoGroup();
}
void CodeCompletionWindowClosed(object sender, EventArgs e)
{
codeCompletionWindow.Closed -= new EventHandler(CodeCompletionWindowClosed);
codeCompletionWindow.Dispose();
codeCompletionWindow = null;
}
bool IsCodeCompletionWindowOpen
{
get
{
return ((codeCompletionWindow != null) && (!codeCompletionWindow.IsDisposed));
}
}
void ShowCompletionWindow(char ch)
{
if (IsCodeCompletionWindowOpen)
{
codeCompletionWindow.Close();
}
if (IsCodeCompletionEnabled)
{
XmlCompletionDataProvider completionDataProvider = new XmlCompletionDataProvider(schemaCompletionDataItems, defaultSchemaCompletionData, string.Empty /* defaultNamespacePrefix */);
codeCompletionWindow = CodeCompletionWindow.ShowCompletionWindow(ParentForm, this, FileName, completionDataProvider, ch, true /* showDeclarationWindow */, false);
if (codeCompletionWindow != null)
{
codeCompletionWindow.Closed += new EventHandler(CodeCompletionWindowClosed);
}
}
}
#endregion
}
internal class TextEditorProperties : ITextEditorProperties
{
public bool CaretLine
{
get;
set;
}
public bool AutoInsertCurlyBracket
{
get;
set;
}
public bool HideMouseCursor
{
get;
set;
}
public bool IsIconBarVisible
{
get;
set;
}
public bool AllowCaretBeyondEOL
{
get;
set;
}
public bool ShowMatchingBracket
{
get;
set;
}
public bool CutCopyWholeLine
{
get;
set;
}
public System.Drawing.Text.TextRenderingHint TextRenderingHint
{
get;
set;
}
public bool MouseWheelScrollDown
{
get;
set;
}
public bool MouseWheelTextZoom
{
get;
set;
}
public string LineTerminator
{
get;
set;
}
public LineViewerStyle LineViewerStyle
{
get;
set;
}
public bool ShowInvalidLines
{
get;
set;
}
public int VerticalRulerRow
{
get;
set;
}
public bool ShowSpaces
{
get;
set;
}
public bool ShowTabs
{
get;
set;
}
public bool ShowEOLMarker
{
get;
set;
}
public bool ConvertTabsToSpaces
{
get;
set;
}
public bool ShowHorizontalRuler
{
get;
set;
}
public bool ShowVerticalRuler
{
get;
set;
}
public Encoding Encoding
{
get;
set;
}
public bool EnableFolding
{
get;
set;
}
public bool ShowLineNumbers
{
get;
set;
}
public int TabIndent
{
get;
set;
}
public int IndentationSize
{
get;
set;
}
public IndentStyle IndentStyle
{
get;
set;
}
public DocumentSelectionMode DocumentSelectionMode
{
get;
set;
}
public System.Drawing.Font Font
{
get;
set;
}
public FontContainer FontContainer
{
get;
private set;
}
public BracketMatchingStyle BracketMatchingStyle
{
get;
set;
}
public bool SupportReadOnlySegments
{
get;
set;
}
public static ITextEditorProperties CreateDefault(Font font)
{
return new TextEditorProperties()
{
EnableFolding = true,
ShowLineNumbers = true,
ShowHorizontalRuler = false,
ShowVerticalRuler = false,
ShowSpaces = true,
ShowTabs = true,
ShowInvalidLines = true,
ShowMatchingBracket = true,
IsIconBarVisible = true,
IndentStyle = ICSharpCode.TextEditor.Document.IndentStyle.Smart,
IndentationSize = 2,
DocumentSelectionMode = ICSharpCode.TextEditor.Document.DocumentSelectionMode.Normal,
LineViewerStyle = ICSharpCode.TextEditor.Document.LineViewerStyle.FullRow,
ConvertTabsToSpaces = true,
MouseWheelScrollDown = true,
MouseWheelTextZoom = false,
FontContainer = new FontContainer(font),
Encoding = Encoding.UTF8
};
}
}
}