#region Disclaimer / License
// Copyright (C) 2009, Kenneth Skovhede
// http://www.hexad.dk, opensource@hexad.dk
//
// 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 Disclaimer / License
using ICSharpCode.TextEditor;
using Maestro.Editors.Common.Expression;
using Maestro.Editors.Generic.XmlEditor;
using Maestro.Editors.LayerDefinition.Vector.Thematics;
using Maestro.Shared.UI;
using OSGeo.FDO.Expressions;
using OSGeo.MapGuide.MaestroAPI;
using OSGeo.MapGuide.MaestroAPI.Exceptions;
using OSGeo.MapGuide.MaestroAPI.Schema;
using OSGeo.MapGuide.MaestroAPI.Services;
using OSGeo.MapGuide.ObjectModels.Capabilities;
using OSGeo.MapGuide.ObjectModels.FeatureSource;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace Maestro.Editors.Common
{
///
/// An expression editor dialog
///
public partial class ExpressionEditor : Form, IExpressionEditor, ITextInserter, IExpressionErrorSource
{
private ClassDefinition _cls;
private IFeatureService _featSvc;
private IEditorService _edSvc;
private string m_featureSource = null;
private IFdoProviderCapabilities _caps;
private ITextEditor _editor;
///
/// Initializes a new instance of the class.
///
internal ExpressionEditor()
{
InitializeComponent();
ExpressionText.SetHighlighting("FDO");
_editor = TextEditorFactory.CreateEditor(ExpressionText);
_editor.KeyPress += OnEditorKeyPress;
_editor.DialogKeyPress += OnEditorDialogKeyPress;
_contextualBuffer = new StringBuilder();
this.Disposed += OnDisposed;
}
private void OnDisposed(object sender, EventArgs e)
{
this.Disposed -= OnDisposed;
_editor?.Dispose();
_editor = null;
}
///
/// Raises the System.Windows.Forms.Form.Load event.
///
///
protected override void OnLoad(EventArgs e)
{
_editor.SetParent(ExpressionText);
base.OnLoad(e);
}
///
/// Gets or sets the expression.
///
/// The expression.
public string Expression
{
get { return ExpressionText.Text; }
set { ExpressionText.Text = value; }
}
private ExpressionEditorMode _mode;
public void Initialize(IServerConnection conn, ClassDefinition cls, string featureSourceId, ExpressionEditorMode mode)
{
IFeatureSource fs = (IFeatureSource)conn.ResourceService.GetResource(featureSourceId);
IFdoProviderCapabilities caps = conn.FeatureService.GetProviderCapabilities(fs.Provider);
//This is normally set by the Editor Service, but we don't have that so do it here
_featSvc = conn.FeatureService;
this.Initialize(null, caps, cls, featureSourceId, mode, false);
}
///
/// Initializes the dialog.
///
/// The editor service.
/// The provider capabilities.
/// The class definition.
/// The FeatureSource id.
/// The editor mode
/// if set to true stylization functions are also attached
public void Initialize(IEditorService edSvc, IFdoProviderCapabilities caps, ClassDefinition cls, string featureSourceId, ExpressionEditorMode mode, bool attachStylizationFunctions)
{
try
{
_mode = mode;
_cls = cls;
_edSvc = edSvc;
if (_edSvc != null)
{
_featSvc = _edSvc.CurrentConnection.FeatureService;
insertThemeExpressionToolStripMenuItem.Enabled = true;
}
else
{
insertThemeExpressionToolStripMenuItem.Enabled = false;
}
m_featureSource = featureSourceId;
_caps = caps;
btnTools.Enabled = attachStylizationFunctions;
SortedList sortedCols = new SortedList();
foreach (var col in _cls.Properties)
{
sortedCols.Add(col.Name, col);
}
ColumnName.Items.Clear();
ColumnName.Tag = sortedCols;
foreach (var col in sortedCols.Values)
{
string name = col.Name;
ToolStripButton btn = new ToolStripButton();
btn.Name = name;
btn.Text = name;
btn.Click += delegate
{
InsertText(name);
};
btnProperties.DropDown.Items.Add(btn);
ColumnName.Items.Add(name);
}
if (ColumnName.Items.Count > 0)
ColumnName.SelectedIndex = 0;
LoadFunctions(caps, attachStylizationFunctions);
}
catch
{
}
}
internal static IFdoFunctionDefintionSignature[] MakeUniqueSignatures(IFdoFunctionDefintion func)
{
var dict = new Dictionary();
foreach (var sig in func.Signatures)
{
List args = new List();
foreach (var argDef in sig.Arguments)
{
args.Add(argDef.Name.Trim());
}
string expr = $"{func.Name}({FdoExpressionCompletionDataProvider.StringifyFunctionArgs(args)})"; //NOXLATE
if (!dict.ContainsKey(expr))
dict[expr] = sig;
}
return dict.Values.ToArray();
}
private void LoadFunctions(IFdoProviderCapabilities caps, bool attachStylizationFunctions)
{
//Functions
var sortedFuncs = new SortedList();
foreach (var func in caps.Expression.SupportedFunctions)
{
sortedFuncs.Add(func.Name, func);
}
if (attachStylizationFunctions)
{
foreach (var func in Utility.GetStylizationFunctions())
{
sortedFuncs.Add(func.Name, func);
}
}
foreach (var func in sortedFuncs.Values)
{
string name = func.Name;
string desc = func.Description;
ToolStripItemCollection parent = btnFunctions.DropDown.Items;
var sigs = MakeUniqueSignatures(func);
if (sigs.Length > 1)
{
ToolStripMenuItem btn = new ToolStripMenuItem();
btn.Name = string.Format(Strings.MultiSigFunction, name, sigs.Length);
btn.Text = string.Format(Strings.MultiSigFunction, name, sigs.Length);
btn.ToolTipText = desc;
btnFunctions.DropDown.Items.Add(btn);
parent = btn.DropDown.Items;
}
foreach (var sig in sigs)
{
ToolStripMenuItem btn = new ToolStripMenuItem();
btn.Name = name;
btn.ToolTipText = desc;
List args = new List();
foreach (var argDef in sig.Arguments)
{
args.Add(argDef.Name.Trim());
}
string expr = $"{name}({FdoExpressionCompletionDataProvider.StringifyFunctionArgs(args)})";
btn.Text = expr;
btn.Click += (s, e) =>
{
InsertText(expr);
};
parent.Add(btn);
}
}
//Spatial Operators
foreach (var op in caps.Filter.SpatialOperations)
{
string name = op.ToUpper();
ToolStripButton btn = new ToolStripButton();
btn.Name = btn.Text = btn.ToolTipText = op;
btn.Click += (s, e) =>
{
InsertSpatialFilter(name);
};
btnSpatial.DropDown.Items.Add(btn);
}
//Distance Operators
foreach (var op in caps.Filter.DistanceOperations)
{
string name = op.ToUpper();
ToolStripButton btn = new ToolStripButton();
btn.Name = btn.Text = btn.ToolTipText = op;
btn.Click += (s, e) =>
{
InsertSpatialFilter(name);
};
btnDistance.DropDown.Items.Add(btn);
}
//Conditional Operators
foreach (var op in caps.Filter.ConditionTypes)
{
string name = op.ToUpper();
ToolStripButton btn = new ToolStripButton();
btn.Name = btn.Text = btn.ToolTipText = op;
btn.Click += (s, e) =>
{
InsertSpatialFilter(name);
};
btnCondition.DropDown.Items.Add(btn);
}
}
private void OKBtn_Click(object sender, EventArgs e)
{
this.DialogResult = DialogResult.OK;
this.Close();
}
private bool OnEditorDialogKeyPress(Keys keyData)
{
if (_editor.ProcessKeyPress(keyData))
return true;
if (keyData == Keys.Back)
StripKey();
return false;
}
private bool OnEditorKeyPress(char ch)
{
if (Char.IsLetter(ch))
{
ShowAutoComplete(ch);
}
return false;
}
private StringBuilder _contextualBuffer;
private void StripKey()
{
if (_contextualBuffer.Length == 0)
{
//this.HideBox();
}
else
{
_contextualBuffer.Remove(_contextualBuffer.Length - 1, 1);
System.Diagnostics.Debug.WriteLine("Contextual buffer: " + _contextualBuffer);
//if (_contextualBuffer.Length == 0)
//this.HideBox();
}
}
internal void AppendKey(Keys keyData)
{
_contextualBuffer.Append(Convert.ToChar((int)keyData));
System.Diagnostics.Debug.WriteLine("Contextual buffer: " + _contextualBuffer);
}
private void ShowAutoComplete(char ch)
{
using (var provider = new FdoExpressionCompletionDataProvider(_cls, _caps))
{
_editor.ShowCompletionWindow(provider, ch);
}
}
private void ColumnName_Click(object sender, EventArgs e)
{
}
private void ColumnName_SelectedIndexChanged(object sender, EventArgs e)
{
ColumnValue.Enabled = false;
LookupValues.Enabled = ColumnName.SelectedIndex >= 0;
}
const string UNIQ_VALS = nameof(UNIQ_VALS);
private void LookupValues_Click(object sender, EventArgs e)
{
//Use UNIQUE() method first. This should work in most cases
using (new WaitCursor(this))
{
string filter = null;
var expr = $"UNIQUE({ColumnName.Text})"; //NOXLATE
bool bFallback = false;
ColumnValue.Items.Clear();
ColumnValue.Tag = null;
try
{
using (var rdr = _featSvc.AggregateQueryFeatureSource(m_featureSource, _cls.QualifiedName, filter, new System.Collections.Specialized.NameValueCollection() {
{ UNIQ_VALS, expr }
}))
{
ColumnValue.Tag = rdr.GetPropertyType(UNIQ_VALS);
while (rdr.ReadNext())
{
if (!rdr.IsNull(UNIQ_VALS))
{
object value = rdr[UNIQ_VALS];
ColumnValue.Items.Add(value);
}
}
rdr.Close();
}
}
catch
{
ColumnValue.Items.Clear();
bFallback = true;
}
if (!bFallback)
{
ColumnValue.Enabled = true;
ColumnValue.SelectedIndex = -1;
ColumnValue.DroppedDown = true;
return;
}
try
{
SortedList cols = (SortedList)ColumnName.Tag;
PropertyDefinition col = cols[ColumnName.Text];
bool retry = true;
Exception rawEx = null;
SortedList values = new SortedList();
bool hasNull = false;
while (retry)
{
try
{
retry = false;
using (var rd = _featSvc.QueryFeatureSource(m_featureSource, _cls.QualifiedName, filter, new string[] { ColumnName.Text }))
{
while (rd.ReadNext())
{
if (rd.IsNull(ColumnName.Text))
hasNull = true;
else
values[Convert.ToString(rd[ColumnName.Text], System.Globalization.CultureInfo.InvariantCulture)] = null;
}
rd.Close();
}
}
catch (Exception ex)
{
if (filter == null && ex.Message.IndexOf("MgNullPropertyValueException") >= 0) //NOXLATE
{
hasNull = true;
rawEx = ex;
retry = true;
filter = $"{ColumnName.Text} != NULL"; //NOXLATE
}
else if (rawEx != null)
throw rawEx;
else
throw;
}
}
ColumnValue.Items.Clear();
if (hasNull)
ColumnValue.Items.Add("NULL"); //NOXLATE
foreach (string s in values.Keys)
ColumnValue.Items.Add(s);
ColumnValue.Tag = col.Type;
if (ColumnValue.Items.Count == 0)
MessageBox.Show(this, Strings.NoColumnValuesError, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Information);
else
{
ColumnValue.Enabled = true;
ColumnValue.SelectedIndex = -1;
ColumnValue.DroppedDown = true;
}
}
catch (Exception ex)
{
string msg = NestedExceptionMessageProcessor.GetFullMessage(ex);
MessageBox.Show(this, string.Format(Strings.ColumnValueError, msg), Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
}
private void ColumnValue_SelectedIndexChanged(object sender, EventArgs e)
{
if (ColumnValue.SelectedIndex >= 0)
{
var tag = ColumnValue.Tag;
if (tag != null)
{
if (ColumnValue.Tag as Type == typeof(string) && (ColumnValue.SelectedIndex != 0 || ColumnValue.Text != "NULL")) //NOXLATE
{
InsertText($"'{ColumnValue.Text}'"); //NOXLATE
}
else
{
if (tag is PropertyValueType && (PropertyValueType)tag == PropertyValueType.String)
InsertText($"'{ColumnValue.Text}'"); //NOXLATE
else
InsertText(ColumnValue.Text);
}
}
else
{
InsertText(ColumnValue.Text);
}
}
}
private void InsertSpatialFilter(string text)
{
InsertText($"[geometry] {text} GeomFromText('geometry wkt')"); //NOXLATE
}
private void InsertText(string text)
{
ExpressionText.ActiveTextAreaControl.TextArea.InsertString(text);
}
private void insertThemeExpressionToolStripMenuItem_Click(object sender, EventArgs e)
{
using (var theme = new ThemeCreator(_edSvc, _cls, this))
{
theme.ShowDialog();
}
}
void ITextInserter.InsertText(string text)
{
this.InsertText(text);
}
private void insertARGBColorExpressionToolStripMenuItem_Click(object sender, EventArgs e)
{
using (var picker = new ColorDialog())
{
if (picker.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
var c = picker.Color;
this.InsertText($"ARGB({c.A}, {c.R}, {c.G}, {c.B})"); //NOXLATE
}
}
}
private void insertHTMLCOLORExpressionToolStripMenuItem_Click(object sender, EventArgs e)
{
using (var picker = new ColorDialog())
{
if (picker.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
var c = picker.Color;
this.InsertText($"HTMLCOLOR({c.R}, {c.G}, {c.B})"); //NOXLATE
}
}
}
private void buildAndInsertLOOKUPExpressionToolStripMenuItem_Click(object sender, EventArgs e)
{
List propNames = new List();
foreach (var prop in _cls.Properties)
{
propNames.Add(prop.Name);
}
using (var picker = new LookupExpressionBuilder(propNames.ToArray()))
{
if (picker.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
this.InsertText(picker.GetExpression());
}
}
}
private void buildAndInsertRANGEExpressionToolStripMenuItem_Click(object sender, EventArgs e)
{
List propNames = new List();
foreach (var prop in _cls.Properties)
{
propNames.Add(prop.Name);
}
using (var picker = new RangeExpressionBuilder(propNames.ToArray()))
{
if (picker.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
this.InsertText(picker.GetExpression());
}
}
}
void IExpressionErrorSource.SetCursor(int line, int col)
{
ExpressionText.ActiveTextAreaControl.Caret.Line = line;
ExpressionText.ActiveTextAreaControl.Caret.Column = col;
}
void IExpressionErrorSource.HighlightToken(string token)
{
bool silent = false;
if (string.IsNullOrEmpty(token))
{
if (!silent)
MessageBox.Show(Strings.TextNoStringSpecifiedToLookFor);
return;
}
var search = new TextEditorSearcher();
search.Document = ExpressionText.Document;
search.LookFor = token; // txtLookFor.Text;
search.MatchCase = false;
search.MatchWholeWordOnly = true;
bool bLoopedAround;
TextRange range = search.FindNext(0, false, out bLoopedAround);
if (range != null)
SelectResult(range);
else if (!silent)
MessageBox.Show(Strings.TextNoStringSpecifiedToLookFor);
}
private void SelectResult(TextRange range)
{
TextLocation p1 = ExpressionText.Document.OffsetToPosition(range.Offset);
TextLocation p2 = ExpressionText.Document.OffsetToPosition(range.Offset + range.Length);
ExpressionText.ActiveTextAreaControl.SelectionManager.SetSelection(p1, p2);
ExpressionText.ActiveTextAreaControl.ScrollTo(p1.Line, p1.Column);
// Also move the caret to the end of the selection, because when the user
// presses F3, the caret is where we start searching next time.
ExpressionText.ActiveTextAreaControl.Caret.Position =
ExpressionText.Document.OffsetToPosition(range.Offset + range.Length);
}
private FdoExpressionValidator _validator = new FdoExpressionValidator();
private void btnValidate_Click(object sender, EventArgs e)
{
try
{
if (_mode == ExpressionEditorMode.Filter)
{
FdoFilter filter = FdoFilter.Parse(ExpressionText.Text);
_validator.ValidateFilter(filter, _cls, _caps);
MessageBox.Show(Strings.FilterIsValid);
}
else //Expression
{
FdoExpression expr = FdoExpression.Parse(ExpressionText.Text);
_validator.ValidateExpression(expr, _cls, _caps);
MessageBox.Show(Strings.ExprIsValid);
}
}
catch (FdoExpressionValidationException ex)
{
new ExpressionParseErrorDialog(ex, this).ShowDialog();
}
catch (FdoMalformedExpressionException ex)
{
new MalformedExpressionDialog(ex, this).ShowDialog();
}
}
private void viewParsedExpressionFilterToolStripMenuItem_Click(object sender, EventArgs e)
{
try
{
if (_mode == ExpressionEditorMode.Filter)
{
FdoFilter filter = FdoFilter.Parse(ExpressionText.Text);
new ExpressionDisplayDialog(filter).ShowDialog();
}
else //Expression
{
FdoExpression expr = FdoExpression.Parse(ExpressionText.Text);
new ExpressionDisplayDialog(expr).ShowDialog();
}
}
catch (FdoParseException ex)
{
MessageBox.Show(ex.Message);
}
}
}
}