diff options
| author | MichaelTheShifter <[email protected]> | 2016-07-20 09:40:36 -0400 |
|---|---|---|
| committer | MichaelTheShifter <[email protected]> | 2016-07-20 09:40:36 -0400 |
| commit | d40fed5ce2bc806a91245adb18039634eac13ed0 (patch) | |
| tree | f1d7168aee6db109ac2c738ad18c9db667a6ba69 /source/ShiftUI/Widgets | |
| parent | f1856e8ed30ed882229fd3fa2a4038122a5fb441 (diff) | |
| download | shiftos-c--d40fed5ce2bc806a91245adb18039634eac13ed0.tar.gz shiftos-c--d40fed5ce2bc806a91245adb18039634eac13ed0.tar.bz2 shiftos-c--d40fed5ce2bc806a91245adb18039634eac13ed0.zip | |
Move ShiftUI source code to ShiftOS
This'll be a lot easier to work on.
Diffstat (limited to 'source/ShiftUI/Widgets')
56 files changed, 48360 insertions, 0 deletions
diff --git a/source/ShiftUI/Widgets/Button.cs b/source/ShiftUI/Widgets/Button.cs new file mode 100644 index 0000000..1b38e99 --- /dev/null +++ b/source/ShiftUI/Widgets/Button.cs @@ -0,0 +1,200 @@ +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2004-2005 Novell, Inc. +// +// Authors: +// Peter Bartok [email protected] +// + +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Text; +using System.Runtime.InteropServices; +using System; + +namespace ShiftUI { + [ClassInterface (ClassInterfaceType.AutoDispatch)] + [ComVisible (true)] + //[Designer ("ShiftUI.Design.ButtonBaseDesigner, " + Consts.AssemblySystem_Design, + //"System.ComponentModel.Design.IDesigner")] + [ToolboxWidget] + public class Button : ButtonBase, IButtonWidget { + #region Local variables + DialogResult dialog_result; + #endregion // Local variables + + #region Public Constructors + public Button () + { + dialog_result = DialogResult.None; + SetStyle (Widgetstyles.StandardDoubleClick, false); + } + #endregion // Public Constructors + + #region Public Properties + [Browsable (true)] + [Localizable (true)] + [DefaultValue (AutoSizeMode.GrowOnly)] + [MWFCategory("Layout")] + public AutoSizeMode AutoSizeMode { + get { return base.GetAutoSizeMode (); } + set { base.SetAutoSizeMode (value); } + } + + [DefaultValue (DialogResult.None)] + [MWFCategory("Behavior")] + public virtual DialogResult DialogResult { // IButtonControl + get { return dialog_result; } + set { dialog_result = value; } + } + #endregion // Public Properties + + #region Protected Properties + protected override CreateParams CreateParams { + get { return base.CreateParams; } + } + #endregion // Protected Properties + + #region Public Methods + public virtual void NotifyDefault (bool value) // IButtonControl + { + this.IsDefault = value; + } + + public void PerformClick () // IButtonControl + { + if (CanSelect) + OnClick (EventArgs.Empty); + } + + public override string ToString () + { + return base.ToString () + ", Text: " + this.Text; + } + #endregion // Public Methods + + #region Protected Methods + protected override void OnClick (EventArgs e) + { + if (dialog_result != DialogResult.None) { + Form p = FindForm (); + + if (p != null) + p.DialogResult = dialog_result; + } + + base.OnClick (e); + } + + protected override void OnFontChanged (EventArgs e) + { + base.OnFontChanged (e); + } + + protected override void OnMouseEnter (EventArgs e) + { + base.OnMouseEnter (e); + } + + protected override void OnMouseLeave (EventArgs e) + { + base.OnMouseLeave (e); + } + + protected override void OnMouseUp (MouseEventArgs mevent) + { + base.OnMouseUp (mevent); + } + + protected override void OnTextChanged (EventArgs e) + { + base.OnTextChanged (e); + } + + protected override bool ProcessMnemonic (char charCode) + { + if (this.UseMnemonic && IsMnemonic (charCode, Text) == true) { + PerformClick (); + return true; + } + + return base.ProcessMnemonic (charCode); + } + + protected override void WndProc (ref Message m) + { + base.WndProc (ref m); + } + #endregion // Protected Methods + + #region Events + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Advanced)] + public new event EventHandler DoubleClick { + add { base.DoubleClick += value; } + remove { base.DoubleClick -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Advanced)] + public new event MouseEventHandler MouseDoubleClick { + add { base.MouseDoubleClick += value; } + remove { base.MouseDoubleClick -= value; } + } + #endregion // Events + + #region Internal methods + internal override void Draw (PaintEventArgs pevent) + { + // System style does not use any of the new 2.0 stuff + if (this.FlatStyle == FlatStyle.System) { + base.Draw (pevent); + return; + } + + // FIXME: This should be called every time something that can affect it + // is changed, not every paint. Can only change so many things at a time. + + // Figure out where our text and image should go + Rectangle text_rectangle; + Rectangle image_rectangle; + + ThemeEngine.Current.CalculateButtonTextAndImageLayout (pevent.Graphics, this, out text_rectangle, out image_rectangle); + + // Draw our button + if (this.FlatStyle == FlatStyle.Standard) + ThemeEngine.Current.DrawButton (pevent.Graphics, this, text_rectangle, image_rectangle, pevent.ClipRectangle); + else if (this.FlatStyle == FlatStyle.Flat) + ThemeEngine.Current.DrawFlatButton (pevent.Graphics, this, text_rectangle, image_rectangle, pevent.ClipRectangle); + else if (this.FlatStyle == FlatStyle.Popup) + ThemeEngine.Current.DrawPopupButton (pevent.Graphics, this, text_rectangle, image_rectangle, pevent.ClipRectangle); + } + + internal override Size GetPreferredSizeCore (Size proposedSize) + { + if (this.AutoSize) + return ThemeEngine.Current.CalculateButtonAutoSize (this); + + return base.GetPreferredSizeCore (proposedSize); + } + #endregion // Internal methods + } +} diff --git a/source/ShiftUI/Widgets/CaptionButton.cs b/source/ShiftUI/Widgets/CaptionButton.cs new file mode 100644 index 0000000..d643327 --- /dev/null +++ b/source/ShiftUI/Widgets/CaptionButton.cs @@ -0,0 +1,37 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2004 Novell, Inc. +// +// Authors: +// Peter Bartok [email protected] +// + + +// COMPLETE + +namespace ShiftUI { + public enum CaptionButton { + Close = 0, + Minimize = 1, + Maximize = 2, + Restore = 3, + Help = 4 + } +} diff --git a/source/ShiftUI/Widgets/CategoryGridEntry.cs b/source/ShiftUI/Widgets/CategoryGridEntry.cs new file mode 100644 index 0000000..cb13f16 --- /dev/null +++ b/source/ShiftUI/Widgets/CategoryGridEntry.cs @@ -0,0 +1,67 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2004-2005 Novell, Inc. +// +// Authors: +// Jonathan Chambers ([email protected]) +// + +using System; +using System.Drawing; + +namespace ShiftUI.PropertyGridInternal +{ + /// <summary> + /// Summary description for CategoryGridEntry + /// </summary> + internal class CategoryGridEntry : GridEntry + { + private string label; + public CategoryGridEntry (PropertyGrid owner, string category, GridEntry parent) + : base (owner, parent) + { + label = category; + } + + public override GridItemType GridItemType { + get { return GridItemType.Category; } + } + + public override bool Expandable { + get { return GridItems.Count > 0; } + } + + public override string Label { + get { return label; } + } + + public override bool IsReadOnly { + get { return true; } + } + + public override bool IsEditable { + get { return false; } + } + + public override bool IsResetable { + get { return false; } + } + } +} diff --git a/source/ShiftUI/Widgets/CheckBox.cs b/source/ShiftUI/Widgets/CheckBox.cs new file mode 100644 index 0000000..6ed09f5 --- /dev/null +++ b/source/ShiftUI/Widgets/CheckBox.cs @@ -0,0 +1,404 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2004-2005 Novell, Inc. +// +// Authors: +// Dennis Hayes [email protected] +// Peter Bartok [email protected] +// + +using System; +using System.ComponentModel; +using System.Drawing; +using System.Runtime.InteropServices; + +namespace ShiftUI { + [DefaultProperty("Checked")] + [DefaultEvent("CheckedChanged")] + [ComVisible (true)] + [ClassInterface (ClassInterfaceType.AutoDispatch)] + [DefaultBindingProperty ("CheckState")] + [ToolboxItem ("ShiftUI.Design.AutoSizeToolboxItem," + Consts.AssemblySystem_Design)] + [ToolboxWidget] + public class CheckBox : ButtonBase { + #region Local Variables + internal Appearance appearance; + internal bool auto_check; + internal ContentAlignment check_alignment; + internal CheckState check_state; + internal bool three_state; + #endregion // Local Variables + + #region CheckBoxAccessibleObject Subclass + [ComVisible(true)] + public class CheckBoxAccessibleObject : ButtonBaseAccessibleObject { + #region CheckBoxAccessibleObject Local Variables + private new CheckBox owner; + #endregion // CheckBoxAccessibleObject Local Variables + + #region CheckBoxAccessibleObject Constructors + public CheckBoxAccessibleObject(Widget owner) : base(owner) { + this.owner = (CheckBox)owner; + } + #endregion // CheckBoxAccessibleObject Constructors + + #region CheckBoxAccessibleObject Properties + public override string DefaultAction { + get { + return "Select"; + } + } + + public override AccessibleRole Role { + get { + return AccessibleRole.CheckButton; + } + } + + public override AccessibleStates State { + get { + AccessibleStates retval; + + retval = AccessibleStates.Default; + + if (owner.check_state == CheckState.Checked) { + retval |= AccessibleStates.Checked; + } + + if (owner.Focused) { + retval |= AccessibleStates.Focused; + } + + if (owner.CanFocus) { + retval |= AccessibleStates.Focusable; + } + + return retval; + } + } + #endregion // CheckBoxAccessibleObject Properties + + #region CheckBoxAccessibleObject Methods + public override void DoDefaultAction () + { + owner.Checked = !owner.Checked; + } + #endregion // CheckBoxAccessibleObject Methods + } + #endregion // CheckBoxAccessibleObject Sub-class + + #region Public Constructors + public CheckBox() { + appearance = Appearance.Normal; + auto_check = true; + check_alignment = ContentAlignment.MiddleLeft; + TextAlign = ContentAlignment.MiddleLeft; + SetStyle(Widgetstyles.StandardDoubleClick, false); + SetAutoSizeMode (AutoSizeMode.GrowAndShrink); + } + #endregion // Public Constructors + + #region Internal Methods + internal override void Draw (PaintEventArgs pe) { + // FIXME: This should be called every time something that can affect it + // is changed, not every paint. Can only change so many things at a time. + + // Figure out where our text and image should go + Rectangle glyph_rectangle; + Rectangle text_rectangle; + Rectangle image_rectangle; + + ThemeEngine.Current.CalculateCheckBoxTextAndImageLayout (this, Point.Empty, out glyph_rectangle, out text_rectangle, out image_rectangle); + + // Draw our button + if (FlatStyle != FlatStyle.System) + ThemeEngine.Current.DrawCheckBox (pe.Graphics, this, glyph_rectangle, text_rectangle, image_rectangle, pe.ClipRectangle); + else + ThemeEngine.Current.DrawCheckBox (pe.Graphics, this.ClientRectangle, this); + } + + internal override Size GetPreferredSizeCore (Size proposedSize) + { + if (this.AutoSize) + return ThemeEngine.Current.CalculateCheckBoxAutoSize (this); + + return base.GetPreferredSizeCore (proposedSize); + } + + internal override void HaveDoubleClick() { + if (DoubleClick != null) DoubleClick(this, EventArgs.Empty); + } + #endregion // Internal Methods + + #region Public Instance Properties + [DefaultValue(Appearance.Normal)] + [Localizable(true)] + public Appearance Appearance { + get { + return appearance; + } + + set { + if (value != appearance) { + appearance = value; + OnAppearanceChanged (EventArgs.Empty); + + if (Parent != null) + Parent.PerformLayout (this, "Appearance"); + Invalidate(); + } + } + } + + [DefaultValue(true)] + public bool AutoCheck { + get { + return auto_check; + } + + set { + auto_check = value; + } + } + + [Bindable(true)] + [Localizable(true)] + [DefaultValue(ContentAlignment.MiddleLeft)] + public ContentAlignment CheckAlign { + get { + return check_alignment; + } + + set { + if (value != check_alignment) { + check_alignment = value; + if (Parent != null) + Parent.PerformLayout (this, "CheckAlign"); + Invalidate(); + } + } + } + + [Bindable(true)] + [RefreshProperties(RefreshProperties.All)] + [DefaultValue(false)] + [SettingsBindable (true)] + public bool Checked { + get { + if (check_state != CheckState.Unchecked) { + return true; + } + return false; + } + + set { + if (value && (check_state != CheckState.Checked)) { + check_state = CheckState.Checked; + Invalidate(); + OnCheckedChanged(EventArgs.Empty); + } else if (!value && (check_state != CheckState.Unchecked)) { + check_state = CheckState.Unchecked; + Invalidate(); + OnCheckedChanged(EventArgs.Empty); + } + } + } + + [DefaultValue(CheckState.Unchecked)] + [RefreshProperties(RefreshProperties.All)] + [Bindable(true)] + public CheckState CheckState { + get { + return check_state; + } + + set { + if (value != check_state) { + bool was_checked = (check_state != CheckState.Unchecked); + + check_state = value; + + if (was_checked != (check_state != CheckState.Unchecked)) { + OnCheckedChanged(EventArgs.Empty); + } + + OnCheckStateChanged(EventArgs.Empty); + Invalidate(); + } + } + } + + [DefaultValue(ContentAlignment.MiddleLeft)] + [Localizable(true)] + public override ContentAlignment TextAlign { + get { return base.TextAlign; } + set { base.TextAlign = value; } + } + + + [DefaultValue(false)] + public bool ThreeState { + get { + return three_state; + } + + set { + three_state = value; + } + } + #endregion // Public Instance Properties + + #region Protected Instance Properties + protected override CreateParams CreateParams { + get { + return base.CreateParams; + } + } + + protected override Size DefaultSize { + get { + return new Size(104, 24); + } + } + #endregion // Protected Instance Properties + + #region Public Instance Methods + public override string ToString() { + return base.ToString() + ", CheckState: " + (int)check_state; + } + #endregion // Public Instance Methods + + #region Protected Instance Methods + protected override AccessibleObject CreateAccessibilityInstance() { + AccessibleObject ao; + + ao = base.CreateAccessibilityInstance (); + ao.role = AccessibleRole.CheckButton; + + return ao; + } + + protected virtual void OnAppearanceChanged(EventArgs e) { + EventHandler eh = (EventHandler)(Events [AppearanceChangedEvent]); + if (eh != null) + eh (this, e); + } + + protected virtual void OnCheckedChanged(EventArgs e) { + EventHandler eh = (EventHandler)(Events [CheckedChangedEvent]); + if (eh != null) + eh (this, e); + } + + protected virtual void OnCheckStateChanged(EventArgs e) { + EventHandler eh = (EventHandler)(Events [CheckStateChangedEvent]); + if (eh != null) + eh (this, e); + } + + protected override void OnClick(EventArgs e) { + if (auto_check) { + switch(check_state) { + case CheckState.Unchecked: { + if (three_state) { + CheckState = CheckState.Indeterminate; + } else { + CheckState = CheckState.Checked; + } + break; + } + + case CheckState.Indeterminate: { + CheckState = CheckState.Checked; + break; + } + + case CheckState.Checked: { + CheckState = CheckState.Unchecked; + break; + } + } + } + + base.OnClick (e); + } + + protected override void OnHandleCreated(EventArgs e) { + base.OnHandleCreated (e); + } + + protected override void OnKeyDown (KeyEventArgs e) + { + base.OnKeyDown (e); + } + + protected override void OnMouseUp(MouseEventArgs mevent) { + base.OnMouseUp (mevent); + } + + protected override bool ProcessMnemonic(char charCode) { + if (IsMnemonic(charCode, Text) == true) { + Select(); + OnClick(EventArgs.Empty); + return true; + } + + return base.ProcessMnemonic(charCode); + } + #endregion // Protected Instance Methods + + #region Events + static object AppearanceChangedEvent = new object (); + static object CheckedChangedEvent = new object (); + static object CheckStateChangedEvent = new object (); + + public event EventHandler AppearanceChanged { + add { Events.AddHandler (AppearanceChangedEvent, value); } + remove { Events.RemoveHandler (AppearanceChangedEvent, value); } + } + + public event EventHandler CheckedChanged { + add { Events.AddHandler (CheckedChangedEvent, value); } + remove { Events.RemoveHandler (CheckedChangedEvent, value); } + } + + public event EventHandler CheckStateChanged { + add { Events.AddHandler (CheckStateChangedEvent, value); } + remove { Events.RemoveHandler (CheckStateChangedEvent, value); } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event MouseEventHandler MouseDoubleClick { + add { base.MouseDoubleClick += value; } + remove { base.MouseDoubleClick -= value; } + } + #endregion // Events + + #region Events + // XXX have a look at this and determine if it + // manipulates base.DoubleClick, and see if + // HaveDoubleClick can just call OnDoubleClick. + [Browsable(false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler DoubleClick; + #endregion // Events + } +} diff --git a/source/ShiftUI/Widgets/CheckedListBox.cs b/source/ShiftUI/Widgets/CheckedListBox.cs new file mode 100644 index 0000000..5bcdf5d --- /dev/null +++ b/source/ShiftUI/Widgets/CheckedListBox.cs @@ -0,0 +1,655 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2004-2006 Novell, Inc. +// +// Authors: +// Jordi Mas i Hernandez, [email protected] +// Mike Kestner <[email protected]> +// +// + +using System; +using System.Drawing; +using System.Collections; +using System.ComponentModel; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace ShiftUI +{ + [ClassInterface (ClassInterfaceType.AutoDispatch)] + [ComVisible (true)] + [LookupBindingPropertiesAttribute ()] + [ToolboxWidget] + public class CheckedListBox : ListBox + { + private CheckedIndexCollection checked_indices; + private CheckedItemCollection checked_items; + private Hashtable check_states = new Hashtable (); + private bool check_onclick = false; + private bool three_dcheckboxes = false; + + public CheckedListBox () + { + checked_indices = new CheckedIndexCollection (this); + checked_items = new CheckedItemCollection (this); + SetStyle (Widgetstyles.ResizeRedraw, true); + } + + #region events + static object ItemCheckEvent = new object (); + + [Browsable (true)] + //[EditorBrowsable (EditorBrowsableState.Always)] + public new event EventHandler Click { + add { base.Click += value; } + remove { base.Click -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler DataSourceChanged { + add { base.DataSourceChanged += value; } + remove { base.DataSourceChanged -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler DisplayMemberChanged { + add { base.DisplayMemberChanged += value; } + remove { base.DisplayMemberChanged -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event DrawItemEventHandler DrawItem { + add { base.DrawItem += value; } + remove { base.DrawItem -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event MeasureItemEventHandler MeasureItem { + add { base.MeasureItem += value; } + remove { base.MeasureItem -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler ValueMemberChanged { + add { base.ValueMemberChanged += value; } + remove { base.ValueMemberChanged -= value; } + } + + public event ItemCheckEventHandler ItemCheck { + add { Events.AddHandler (ItemCheckEvent, value); } + remove { Events.RemoveHandler (ItemCheckEvent, value); } + } + + [Browsable (true)] + //[EditorBrowsable (EditorBrowsableState.Always)] + public new event MouseEventHandler MouseClick { + add { base.MouseClick += value; } + remove { base.MouseClick -= value; } + } + #endregion Events + + #region Public Properties + + [Browsable (false)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public CheckedListBox.CheckedIndexCollection CheckedIndices { + get {return checked_indices; } + } + + [Browsable (false)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public CheckedListBox.CheckedItemCollection CheckedItems { + get {return checked_items; } + } + + [DefaultValue (false)] + public bool CheckOnClick { + get { return check_onclick; } + set { check_onclick = value; } + } + + protected override CreateParams CreateParams { + get { return base.CreateParams;} + } + + //[EditorBrowsable (EditorBrowsableState.Never)] + [Browsable (false)] + public new object DataSource { + get { return base.DataSource; } + // FIXME: docs say you can't use a DataSource with this subclass + set { base.DataSource = value; } + } + + //[EditorBrowsable (EditorBrowsableState.Never)] + [Browsable (false)] + public new string DisplayMember { + get { return base.DisplayMember; } + set { base.DisplayMember = value; } + } + + [Browsable (false)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public override DrawMode DrawMode { + get { return DrawMode.Normal; } + set { /* Not an exception, but has no effect. */ } + } + + [Browsable (false)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public override int ItemHeight { + get { return base.ItemHeight; } + set { /* Not an exception, but has no effect. */ } + } + + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Content)] + [Localizable (true)] + //[Editor ("ShiftUI.Design.ListWidgetstringCollectionEditor, " + Consts.AssemblySystem_Design, typeof (System.Drawing.Design.UITypeEditor))] + public new CheckedListBox.ObjectCollection Items { + get { return (CheckedListBox.ObjectCollection) base.Items; } + } + + public override SelectionMode SelectionMode { + get { return base.SelectionMode; } + set { + if (!Enum.IsDefined (typeof (SelectionMode), value)) + throw new InvalidEnumArgumentException ("value", (int) value, typeof (SelectionMode)); + + if (value == SelectionMode.MultiSimple || value == SelectionMode.MultiExtended) + throw new ArgumentException ("Multi selection not supported on CheckedListBox"); + + base.SelectionMode = value; + } + } + + [DefaultValue (false)] + public bool ThreeDCheckBoxes { + get { return three_dcheckboxes; } + set { + if (three_dcheckboxes == value) + return; + + three_dcheckboxes = value; + Refresh (); + } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new string ValueMember { + get { return base.ValueMember; } + set { base.ValueMember = value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + //[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public new Padding Padding { + get { return base.Padding; } + set { base.Padding = value; } + } + #endregion Public Properties + + #region Public Methods + + protected override AccessibleObject CreateAccessibilityInstance () + { + return base.CreateAccessibilityInstance (); + } + + protected override ListBox.ObjectCollection CreateItemCollection () + { + return new ObjectCollection (this); + } + + public bool GetItemChecked (int index) + { + return check_states.Contains (Items [index]); + } + + public CheckState GetItemCheckState (int index) + { + if (index < 0 || index >= Items.Count) + throw new ArgumentOutOfRangeException ("Index of out range"); + + object o = Items [index]; + if (check_states.Contains (o)) + return (CheckState) check_states [o]; + else + return CheckState.Unchecked; + } + + protected override void OnBackColorChanged (EventArgs e) + { + base.OnBackColorChanged (e); + } + + protected override void OnClick (EventArgs e) + { + base.OnClick (e); + } + + protected override void OnDrawItem (DrawItemEventArgs e) + { + if (check_states.Contains (Items [e.Index])) { + DrawItemState state = e.State | DrawItemState.Checked; + if (((CheckState) check_states [Items [e.Index]]) == CheckState.Indeterminate) + state |= DrawItemState.Inactive; + e = new DrawItemEventArgs (e.Graphics, e.Font, e.Bounds, e.Index, state, e.ForeColor, e.BackColor); + } + ThemeEngine.Current.DrawCheckedListBoxItem (this, e); + } + + protected override void OnFontChanged (EventArgs e) + { + base.OnFontChanged (e); + } + + protected override void OnHandleCreated (EventArgs e) + { + base.OnHandleCreated (e); + } + + protected virtual void OnItemCheck (ItemCheckEventArgs ice) + { + ItemCheckEventHandler eh = (ItemCheckEventHandler)(Events [ItemCheckEvent]); + if (eh != null) + eh (this, ice); + } + + protected override void OnKeyPress (KeyPressEventArgs e) + { + base.OnKeyPress (e); + + if (e.KeyChar == ' ' && FocusedItem != -1) + SetItemChecked (FocusedItem, !GetItemChecked (FocusedItem)); + } + + protected override void OnMeasureItem (MeasureItemEventArgs e) + { + base.OnMeasureItem (e); + } + + protected override void OnSelectedIndexChanged (EventArgs e) + { + base.OnSelectedIndexChanged (e); + } + + protected override void RefreshItems () + { + base.RefreshItems (); + } + + public void SetItemChecked (int index, bool value) + { + SetItemCheckState (index, value ? CheckState.Checked : CheckState.Unchecked); + } + + public void SetItemCheckState (int index, CheckState value) + { + if (index < 0 || index >= Items.Count) + throw new ArgumentOutOfRangeException ("Index of out range"); + + if (!Enum.IsDefined (typeof (CheckState), value)) + throw new InvalidEnumArgumentException (string.Format("Enum argument value '{0}' is not valid for CheckState", value)); + + CheckState old_value = GetItemCheckState (index); + + if (old_value == value) + return; + + ItemCheckEventArgs icea = new ItemCheckEventArgs (index, value, old_value); + OnItemCheck (icea); + + switch (icea.NewValue) { + case CheckState.Checked: + case CheckState.Indeterminate: + check_states[Items[index]] = icea.NewValue; + break; + case CheckState.Unchecked: + check_states.Remove (Items[index]); + break; + default: + break; + } + + UpdateCollections (); + + InvalidateCheckbox (index); + } + + protected override void WmReflectCommand (ref Message m) + { + base.WmReflectCommand (ref m); + } + + protected override void WndProc (ref Message m) + { + base.WndProc (ref m); + } + + #endregion Public Methods + + #region Private Methods + + int last_clicked_index = -1; + + internal override void OnItemClick (int index) + { + if ((CheckOnClick || last_clicked_index == index) && index > -1) { + if (GetItemChecked (index)) + SetItemCheckState (index, CheckState.Unchecked); + else + SetItemCheckState (index, CheckState.Checked); + } + + last_clicked_index = index; + base.OnItemClick (index); + } + + internal override void CollectionChanged () + { + base.CollectionChanged (); + UpdateCollections (); + } + + private void InvalidateCheckbox (int index) + { + Rectangle area = GetItemDisplayRectangle (index, TopIndex); + area.X += 2; + area.Y += (area.Height - 11) / 2; + area.Width = 11; + area.Height = 11; + Invalidate (area); + } + + private void UpdateCollections () + { + CheckedItems.Refresh (); + CheckedIndices.Refresh (); + } + + #endregion Private Methods + + public new class ObjectCollection : ListBox.ObjectCollection + { + private CheckedListBox owner; + + public ObjectCollection (CheckedListBox owner) : base (owner) + { + this.owner = owner; + } + + public int Add (object item, bool isChecked) + { + return Add (item, isChecked ? CheckState.Checked : CheckState.Unchecked); + } + + public int Add (object item, CheckState check) + { + int idx = Add (item); + + ItemCheckEventArgs icea = new ItemCheckEventArgs (idx, check, CheckState.Unchecked); + + if (check == CheckState.Checked) + owner.OnItemCheck (icea); + + if (icea.NewValue != CheckState.Unchecked) + owner.check_states[item] = icea.NewValue; + + owner.UpdateCollections (); + return idx; + } + } + + public class CheckedIndexCollection : IList, ICollection, IEnumerable + { + private CheckedListBox owner; + private ArrayList indices = new ArrayList (); + + internal CheckedIndexCollection (CheckedListBox owner) + { + this.owner = owner; + } + + #region Public Properties + public int Count { + get { return indices.Count; } + } + + public bool IsReadOnly { + get { return true;} + } + + bool ICollection.IsSynchronized { + get { return false; } + } + + bool IList.IsFixedSize{ + get { return true; } + } + + object ICollection.SyncRoot { + get { return this; } + } + + [Browsable (false)] + //[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public int this[int index] { + get { + if (index < 0 || index >= Count) + throw new ArgumentOutOfRangeException ("Index of out range"); + + return (int) indices[index]; + } + } + #endregion Public Properties + + public bool Contains (int index) + { + return indices.Contains (index); + } + + + public void CopyTo (Array dest, int index) + { + indices.CopyTo (dest, index); + } + + public IEnumerator GetEnumerator () + { + return indices.GetEnumerator (); + } + + int IList.Add (object value) + { + throw new NotSupportedException (); + } + + void IList.Clear () + { + throw new NotSupportedException (); + } + + bool IList.Contains (object index) + { + return Contains ((int)index); + } + + int IList.IndexOf (object index) + { + return IndexOf ((int) index); + } + + void IList.Insert (int index, object value) + { + throw new NotSupportedException (); + } + + void IList.Remove (object value) + { + throw new NotSupportedException (); + } + + void IList.RemoveAt (int index) + { + throw new NotSupportedException (); + } + + object IList.this[int index]{ + get {return indices[index]; } + set {throw new NotImplementedException (); } + } + + public int IndexOf (int index) + { + return indices.IndexOf (index); + } + + #region Private Methods + internal void Refresh () + { + indices.Clear (); + for (int i = 0; i < owner.Items.Count; i++) + if (owner.check_states.Contains (owner.Items [i])) + indices.Add (i); + } + #endregion Private Methods + + } + + public class CheckedItemCollection : IList, ICollection, IEnumerable + { + private CheckedListBox owner; + private ArrayList list = new ArrayList (); + + internal CheckedItemCollection (CheckedListBox owner) + { + this.owner = owner; + } + + #region Public Properties + public int Count { + get { return list.Count; } + } + + public bool IsReadOnly { + get { return true; } + } + + [Browsable (false)] + //[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public object this [int index] { + get { + if (index < 0 || index >= Count) + throw new ArgumentOutOfRangeException ("Index of out range"); + + return list[index]; + } + set {throw new NotSupportedException ();} + } + + bool ICollection.IsSynchronized { + get { return true; } + } + + object ICollection.SyncRoot { + get { return this; } + } + + bool IList.IsFixedSize { + get { return true; } + } + + #endregion Public Properties + + #region Public Methods + public bool Contains (object item) + { + return list.Contains (item); + } + + public void CopyTo (Array dest, int index) + { + list.CopyTo (dest, index); + } + + int IList.Add (object value) + { + throw new NotSupportedException (); + } + + void IList.Clear () + { + throw new NotSupportedException (); + } + + void IList.Insert (int index, object value) + { + throw new NotSupportedException (); + } + + void IList.Remove (object value) + { + throw new NotSupportedException (); + } + + void IList.RemoveAt (int index) + { + throw new NotSupportedException (); + } + + public int IndexOf (object item) + { + return list.IndexOf (item); + } + + public IEnumerator GetEnumerator () + { + return list.GetEnumerator (); + } + + #endregion Public Methods + + #region Private Methods + internal void Refresh () + { + list.Clear (); + for (int i = 0; i < owner.Items.Count; i++) + if (owner.check_states.Contains (owner.Items [i])) + list.Add (owner.Items[i]); + } + #endregion Private Methods + } + [DefaultValue (false)] + public bool UseCompatibleTextRendering { + get { return use_compatible_text_rendering; } + set { use_compatible_text_rendering = value; } + } + } +} + diff --git a/source/ShiftUI/Widgets/CollectionEditor.cs b/source/ShiftUI/Widgets/CollectionEditor.cs new file mode 100644 index 0000000..07bbc51 --- /dev/null +++ b/source/ShiftUI/Widgets/CollectionEditor.cs @@ -0,0 +1,738 @@ +// +// System.ComponentModel.Design.CollectionEditor +// +// Authors: +// Martin Willemoes Hansen ([email protected]) +// Andreas Nahr ([email protected]) +// Ivan N. Zlatev ([email protected]) +// +// (C) 2003 Martin Willemoes Hansen +// (C) 2007 Andreas Nahr +// (C) 2007 Ivan N. Zlatev +// (C) 2008 Novell, Inc +// + +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + + +using System; +using System.Reflection; +using System.Collections; +using System.ComponentModel; +using System.Drawing.Design; +using ShiftUI; +using ShiftUI.Design; + +namespace System.ComponentModel.Design +{ + public class CollectionEditor : UITypeEditor + { + protected abstract class CollectionForm : Form + { + private CollectionEditor editor; + private object editValue; + + public CollectionForm (CollectionEditor editor) + { + this.editor = editor; + } + + protected Type CollectionItemType + { + get { return editor.CollectionItemType; } + } + + protected Type CollectionType + { + get { return editor.CollectionType; } + } + + protected ITypeDescriptorContext Context + { + get { return editor.Context; } + } + + public object EditValue + { + get { return editValue; } + set + { + editValue = value; + OnEditValueChanged (); + } + } + + protected object[] Items + { + get { return editor.GetItems (editValue); } + set { + if (editValue == null) { + object newEmptyCollection = null; + try { + if (typeof (Array).IsAssignableFrom (CollectionType)) + newEmptyCollection = Array.CreateInstance (CollectionItemType, 0); + else + newEmptyCollection = Activator.CreateInstance (CollectionType); + } catch {} + + object val = editor.SetItems (newEmptyCollection, value); + if (val != newEmptyCollection) + EditValue = val; + } else { + object val = editor.SetItems (editValue, value); + if (val != editValue) + EditValue = val; + } + } + } + + protected Type[] NewItemTypes + { + get { return editor.NewItemTypes; } + } + + protected bool CanRemoveInstance (object value) + { + return editor.CanRemoveInstance (value); + } + + protected virtual bool CanSelectMultipleInstances () + { + return editor.CanSelectMultipleInstances (); + } + + protected object CreateInstance (Type itemType) + { + return editor.CreateInstance (itemType); + } + + protected void DestroyInstance (object instance) + { + editor.DestroyInstance (instance); + } + + protected virtual void DisplayError (Exception e) + { + MessageBox.Show (e.Message, "Error"); + } + + protected override object GetService (Type serviceType) + { + return editor.GetService (serviceType); + } + + protected abstract void OnEditValueChanged (); + + protected internal virtual DialogResult ShowEditorDialog (IWindowsFormsEditorService edSvc) + { + return edSvc.ShowDialog (this); + } + } + + private class ConcreteCollectionForm : CollectionForm + { + internal class ObjectContainerConverter : TypeConverter + { + private class ObjectContainerPropertyDescriptor : TypeConverter.SimplePropertyDescriptor + { + private AttributeCollection attributes; + + public ObjectContainerPropertyDescriptor (Type componentType, Type propertyType) + : base (componentType, "Value", propertyType) + { + CategoryAttribute cat = new CategoryAttribute (propertyType.Name); + attributes = new AttributeCollection (new Attribute[] { cat }); + } + + public override object GetValue (object component) + { + ObjectContainer container = (ObjectContainer)component; + return container.Object; + } + + public override void SetValue (object component, object value) + { + ObjectContainer container = (ObjectContainer)component; + container.Object = value; + } + + public override AttributeCollection Attributes + { + get { return attributes; } + } + } + + public override PropertyDescriptorCollection GetProperties (ITypeDescriptorContext context, object value, Attribute[] attributes) + { + ObjectContainer container = (ObjectContainer)value; + ObjectContainerPropertyDescriptor desc = new ObjectContainerPropertyDescriptor (value.GetType (), container.editor.CollectionItemType); + PropertyDescriptor[] properties = new PropertyDescriptor[] { desc }; + PropertyDescriptorCollection pc = new PropertyDescriptorCollection (properties); + return pc; + } + + public override bool GetPropertiesSupported (ITypeDescriptorContext context) + { + return true; + } + } + + [TypeConverter (typeof (ObjectContainerConverter))] + private class ObjectContainer + { + internal object Object; + internal CollectionEditor editor; + + public ObjectContainer (object obj, CollectionEditor editor) + { + this.Object = obj; + this.editor = editor; + } + + internal string Name { + get { return editor.GetDisplayText (Object); } + } + + public override string ToString () + { + return Name; + } + } + + private class UpdateableListbox : ListBox + { + public void DoRefreshItem (int index) + { + base.RefreshItem (index); + } + } + + private CollectionEditor editor; + + private ShiftUI.Label labelMember; + private ShiftUI.Label labelProperty; + private UpdateableListbox itemsList; + private ShiftUI.PropertyGrid itemDisplay; + private ShiftUI.Button doClose; + private ShiftUI.Button moveUp; + private ShiftUI.Button moveDown; + private ShiftUI.Button doAdd; + private ShiftUI.Button doRemove; + private ShiftUI.Button doCancel; + private ShiftUI.ComboBox addType; + + public ConcreteCollectionForm (CollectionEditor editor) + : base (editor) + { + this.editor = editor; + + this.labelMember = new ShiftUI.Label (); + this.labelProperty = new ShiftUI.Label (); + this.itemsList = new UpdateableListbox (); + this.itemDisplay = new ShiftUI.PropertyGrid (); + this.doClose = new ShiftUI.Button (); + this.moveUp = new ShiftUI.Button (); + this.moveDown = new ShiftUI.Button (); + this.doAdd = new ShiftUI.Button (); + this.doRemove = new ShiftUI.Button (); + this.doCancel = new ShiftUI.Button (); + this.addType = new ShiftUI.ComboBox (); + this.SuspendLayout (); + // + // labelMember + // + this.labelMember.Location = new System.Drawing.Point (12, 9); + this.labelMember.Size = new System.Drawing.Size (55, 13); + this.labelMember.Text = "Members:"; + // + // labelProperty + // + this.labelProperty.Anchor = ((ShiftUI.AnchorStyles)(((ShiftUI.AnchorStyles.Top | ShiftUI.AnchorStyles.Left) + | ShiftUI.AnchorStyles.Right))); + this.labelProperty.Location = new System.Drawing.Point (172, 9); + this.labelProperty.Size = new System.Drawing.Size (347, 13); + this.labelProperty.Text = "Properties:"; + // + // itemsList + // + this.itemsList.Anchor = ((ShiftUI.AnchorStyles)(((ShiftUI.AnchorStyles.Top | ShiftUI.AnchorStyles.Bottom) + | ShiftUI.AnchorStyles.Left))); + this.itemsList.HorizontalScrollbar = true; + this.itemsList.Location = new System.Drawing.Point (12, 25); + this.itemsList.SelectionMode = ShiftUI.SelectionMode.MultiExtended; + this.itemsList.Size = new System.Drawing.Size (120, 290); + this.itemsList.TabIndex = 0; + this.itemsList.SelectedIndexChanged += new System.EventHandler (this.itemsList_SelectedIndexChanged); + // + // itemDisplay + // + this.itemDisplay.Anchor = ((ShiftUI.AnchorStyles)((((ShiftUI.AnchorStyles.Top | ShiftUI.AnchorStyles.Bottom) + | ShiftUI.AnchorStyles.Left) + | ShiftUI.AnchorStyles.Right))); + this.itemDisplay.HelpVisible = false; + this.itemDisplay.Location = new System.Drawing.Point (175, 25); + this.itemDisplay.Size = new System.Drawing.Size (344, 314); + this.itemDisplay.TabIndex = 6; + this.itemDisplay.PropertyValueChanged += new ShiftUI.PropertyValueChangedEventHandler (this.itemDisplay_PropertyValueChanged); + // + // doClose + // + this.doClose.Anchor = ((ShiftUI.AnchorStyles)((ShiftUI.AnchorStyles.Bottom | ShiftUI.AnchorStyles.Right))); + this.doClose.Location = new System.Drawing.Point (341, 345); + this.doClose.Size = new System.Drawing.Size (86, 26); + this.doClose.TabIndex = 7; + this.doClose.Text = "OK"; + this.doClose.Click += new System.EventHandler (this.doClose_Click); + // + // moveUp + // + this.moveUp.Location = new System.Drawing.Point (138, 25); + this.moveUp.Size = new System.Drawing.Size (31, 28); + this.moveUp.TabIndex = 4; + this.moveUp.Enabled = false; + this.moveUp.Text = "Up"; + this.moveUp.Click += new System.EventHandler (this.moveUp_Click); + // + // moveDown + // + this.moveDown.Location = new System.Drawing.Point (138, 59); + this.moveDown.Size = new System.Drawing.Size (31, 28); + this.moveDown.TabIndex = 5; + this.moveDown.Enabled = false; + this.moveDown.Text = "Dn"; + this.moveDown.Click += new System.EventHandler (this.moveDown_Click); + // + // doAdd + // + this.doAdd.Anchor = ((ShiftUI.AnchorStyles)((ShiftUI.AnchorStyles.Bottom | ShiftUI.AnchorStyles.Left))); + this.doAdd.Location = new System.Drawing.Point (12, 346); + this.doAdd.Size = new System.Drawing.Size (59, 25); + this.doAdd.TabIndex = 1; + this.doAdd.Text = "Add"; + this.doAdd.Click += new System.EventHandler (this.doAdd_Click); + // + // doRemove + // + this.doRemove.Anchor = ((ShiftUI.AnchorStyles)((ShiftUI.AnchorStyles.Bottom | ShiftUI.AnchorStyles.Left))); + this.doRemove.Location = new System.Drawing.Point (77, 346); + this.doRemove.Size = new System.Drawing.Size (55, 25); + this.doRemove.TabIndex = 2; + this.doRemove.Text = "Remove"; + this.doRemove.Click += new System.EventHandler (this.doRemove_Click); + // + // doCancel + // + this.doCancel.Anchor = ((ShiftUI.AnchorStyles)((ShiftUI.AnchorStyles.Bottom | ShiftUI.AnchorStyles.Right))); + this.doCancel.DialogResult = ShiftUI.DialogResult.Cancel; + this.doCancel.Location = new System.Drawing.Point (433, 345); + this.doCancel.Size = new System.Drawing.Size (86, 26); + this.doCancel.TabIndex = 8; + this.doCancel.Text = "Cancel"; + this.doCancel.Click += new System.EventHandler (this.doCancel_Click); + // + // addType + // + this.addType.Anchor = ((ShiftUI.AnchorStyles)((ShiftUI.AnchorStyles.Bottom | ShiftUI.AnchorStyles.Left))); + this.addType.DropDownStyle = ShiftUI.ComboBoxStyle.DropDownList; + this.addType.Location = new System.Drawing.Point (12, 319); + this.addType.Size = new System.Drawing.Size (120, 21); + this.addType.TabIndex = 3; + // + // DesignerForm + // + this.AcceptButton = this.doClose; + this.CancelButton = this.doCancel; + this.ClientSize = new System.Drawing.Size (531, 381); + this.WidgetBox = false; + this.Widgets.Add (this.addType); + this.Widgets.Add (this.doCancel); + this.Widgets.Add (this.doRemove); + this.Widgets.Add (this.doAdd); + this.Widgets.Add (this.moveDown); + this.Widgets.Add (this.moveUp); + this.Widgets.Add (this.doClose); + this.Widgets.Add (this.itemDisplay); + this.Widgets.Add (this.itemsList); + this.Widgets.Add (this.labelProperty); + this.Widgets.Add (this.labelMember); + this.HelpButton = true; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.MinimumSize = new System.Drawing.Size (400, 300); + this.ShowInTaskbar = false; + this.StartPosition = ShiftUI.FormStartPosition.CenterScreen; + this.ResumeLayout (false); + + if (editor.CollectionType.IsGenericType) + this.Text = editor.CollectionItemType.Name + " Collection Editor"; + else + this.Text = editor.CollectionType.Name + " Collection Editor"; + foreach (Type type in editor.NewItemTypes) + addType.Items.Add (type.Name); + if (addType.Items.Count > 0) + addType.SelectedIndex = 0; + } + + private void UpdateItems () + { + object[] items = editor.GetItems (EditValue); + if (items != null) { + itemsList.BeginUpdate (); + itemsList.Items.Clear (); + foreach (object o in items) + this.itemsList.Items.Add (new ObjectContainer (o, editor)); + if (itemsList.Items.Count > 0) + itemsList.SelectedIndex = 0; + itemsList.EndUpdate (); + } + } + + private void doClose_Click (object sender, EventArgs e) + { + SetEditValue (); + this.Close (); + } + + private void SetEditValue () + { + object[] items = new object[itemsList.Items.Count]; + for (int i = 0; i < itemsList.Items.Count; i++) + items[i] = ((ObjectContainer)itemsList.Items[i]).Object; + this.Items = items; + } + + private void doCancel_Click (object sender, EventArgs e) + { + editor.CancelChanges (); + this.Close (); + } + + private void itemsList_SelectedIndexChanged (object sender, EventArgs e) + { + if (itemsList.SelectedIndex == -1) { + itemDisplay.SelectedObject = null; + return; + } + + if (itemsList.SelectedIndex <= 0 || itemsList.SelectedItems.Count > 1) + moveUp.Enabled = false; + else + moveUp.Enabled = true; + if (itemsList.SelectedIndex > itemsList.Items.Count - 2 || itemsList.SelectedItems.Count > 1) + moveDown.Enabled = false; + else + moveDown.Enabled = true; + + if (itemsList.SelectedItems.Count == 1) + { + ObjectContainer o = (ObjectContainer)itemsList.SelectedItem; + if (Type.GetTypeCode (o.Object.GetType ()) != TypeCode.Object) + itemDisplay.SelectedObject = o; + else + itemDisplay.SelectedObject = o.Object; + } + else + { + object[] items = new object[itemsList.SelectedItems.Count]; + for (int i = 0; i < itemsList.SelectedItems.Count; i++) + { + ObjectContainer o = (ObjectContainer)itemsList.SelectedItem; + if (Type.GetTypeCode (o.Object.GetType ()) != TypeCode.Object) + items[i] = ((ObjectContainer)itemsList.SelectedItems[i]); + else + items[i] = ((ObjectContainer)itemsList.SelectedItems[i]).Object; + } + itemDisplay.SelectedObjects = items; + } + labelProperty.Text = ((ObjectContainer)itemsList.SelectedItem).Name + " properties:"; + } + + private void itemDisplay_PropertyValueChanged (object sender, EventArgs e) + { + int[] selected = new int[itemsList.SelectedItems.Count]; + for (int i = 0; i < itemsList.SelectedItems.Count; i++) + selected[i] = itemsList.Items.IndexOf (itemsList.SelectedItems[i]); + + // The list might be repopulated if a new instance of the collection edited + // is created during the update. This happen for example for Arrays. + SetEditValue (); + + // Restore current selection in case the list gets repopulated. + // Refresh the item after that to reflect possible value change. + // + itemsList.BeginUpdate (); + itemsList.ClearSelected (); + foreach (int index in selected) { + itemsList.DoRefreshItem (index); + itemsList.SetSelected (index, true); + } + itemsList.SelectedIndex = selected[0]; + itemsList.EndUpdate (); + } + + private void moveUp_Click (object sender, EventArgs e) + { + if (itemsList.SelectedIndex <= 0) + return; + + object selected = itemsList.SelectedItem; + int index = itemsList.SelectedIndex; + itemsList.Items.RemoveAt (index); + itemsList.Items.Insert (index - 1, selected); + itemsList.SelectedIndex = index - 1; + } + + private void moveDown_Click (object sender, EventArgs e) + { + if (itemsList.SelectedIndex > itemsList.Items.Count - 2) + return; + + object selected = itemsList.SelectedItem; + int index = itemsList.SelectedIndex; + itemsList.Items.RemoveAt (index); + itemsList.Items.Insert (index + 1, selected); + itemsList.SelectedIndex = index + 1; + } + + private void doAdd_Click (object sender, EventArgs e) + { + object o; + try { + o = editor.CreateInstance (editor.NewItemTypes[addType.SelectedIndex]); + } catch (Exception ex) { + DisplayError (ex); + return; + } + itemsList.Items.Add (new ObjectContainer (o, editor)); + itemsList.SelectedIndex = -1; + itemsList.SelectedIndex = itemsList.Items.Count - 1; + } + + private void doRemove_Click (object sender, EventArgs e) + { + if (itemsList.SelectedIndex != -1) { + int[] selected = new int[itemsList.SelectedItems.Count]; + for (int i=0; i < itemsList.SelectedItems.Count; i++) + selected[i] = itemsList.Items.IndexOf (itemsList.SelectedItems[i]); + + for (int i = selected.Length - 1; i >= 0; i--) + itemsList.Items.RemoveAt (selected[i]); + + itemsList.SelectedIndex = Math.Min (selected[0], itemsList.Items.Count-1); + } + } + + // OnEditValueChanged is called only if the EditValue has changed, + // which is only in the case when a new instance of the collection is + // required, e.g for arrays. + // + protected override void OnEditValueChanged () + { + UpdateItems (); + } + } + + private Type type; + private Type collectionItemType; + private Type[] newItemTypes; + private ITypeDescriptorContext context; + private IServiceProvider provider; + private IWindowsFormsEditorService editorService; + + public CollectionEditor (Type type) + { + this.type = type; + this.collectionItemType = CreateCollectionItemType (); + this.newItemTypes = CreateNewItemTypes (); + } + + protected Type CollectionItemType + { + get { return collectionItemType; } + } + + protected Type CollectionType + { + get { return type; } + } + + protected ITypeDescriptorContext Context + { + get { return context; } + } + + protected virtual string HelpTopic + { + get { return "CollectionEditor"; } + } + + protected Type[] NewItemTypes + { + get { return newItemTypes; } + } + + protected virtual void CancelChanges () + { + } + + protected virtual bool CanRemoveInstance (object value) + { + return true; + } + + protected virtual bool CanSelectMultipleInstances () + { + return true; + } + + protected virtual CollectionEditor.CollectionForm CreateCollectionForm () + { + return new ConcreteCollectionForm (this); + } + + protected virtual Type CreateCollectionItemType () + { + PropertyInfo[] properties = type.GetProperties (); + foreach (PropertyInfo property in properties) + if (property.Name == "Item") + return property.PropertyType; + return typeof (object); + } + + protected virtual object CreateInstance (Type itemType) + { + object instance = null; + if (typeof (IComponent).IsAssignableFrom (itemType)) { + IDesignerHost host = GetService (typeof (IDesignerHost)) as IDesignerHost; + if (host != null) + instance = host.CreateComponent (itemType); + } + + if (instance == null) { + instance = TypeDescriptor.CreateInstance (provider, itemType, null, null); + } + return instance; + } + + protected virtual Type[] CreateNewItemTypes () + { + return new Type[] { collectionItemType }; + } + + protected virtual void DestroyInstance (object instance) + { + IComponent component = instance as IComponent; + if (component != null) { + IDesignerHost host = GetService (typeof (IDesignerHost)) as IDesignerHost; + if (host != null) + host.DestroyComponent (component); + } + } + + public override object EditValue (ITypeDescriptorContext context, IServiceProvider provider, object value) + { + this.context = context; + this.provider = provider; + + if (context != null && provider != null) + { + editorService = (IWindowsFormsEditorService)provider.GetService (typeof (IWindowsFormsEditorService)); + if (editorService != null) + { + CollectionForm editorForm = CreateCollectionForm (); + editorForm.EditValue = value; + editorForm.ShowEditorDialog (editorService); + return editorForm.EditValue; + } + } + return base.EditValue (context, provider, value); + } + + protected virtual string GetDisplayText (object value) + { + if (value == null) + return string.Empty; + + PropertyInfo nameProperty = value.GetType ().GetProperty ("Name"); + if (nameProperty != null) + { + string data = (nameProperty.GetValue (value, null)) as string; + if (data != null) + if (data.Length != 0) + return data; + } + + if (Type.GetTypeCode (value.GetType ()) == TypeCode.Object) + return value.GetType ().Name; + else + return value.ToString (); + } + + public override UITypeEditorEditStyle GetEditStyle (ITypeDescriptorContext context) + { + return UITypeEditorEditStyle.Modal; + } + + protected virtual object[] GetItems (object editValue) + { + if (editValue == null) + return new object[0]; + ICollection collection = editValue as ICollection; + if (collection == null) + return new object[0]; + + object[] result = new object[collection.Count]; + collection.CopyTo (result, 0); + return result; + } + + protected virtual IList GetObjectsFromInstance (object instance) + { + ArrayList list = new ArrayList (); + list.Add (instance); + return list; + } + + protected object GetService (Type serviceType) + { + return context.GetService (serviceType); + } + + protected virtual object SetItems (object editValue, object[] value) + { + IList list = (IList) editValue; + if (list == null) + return null; + + list.Clear (); + foreach (object o in value) + list.Add (o); + + return list; + } + + protected virtual void ShowHelp () + { + //TODO: Fixme No help provider. + } + } +} diff --git a/source/ShiftUI/Widgets/ComboBox.cs b/source/ShiftUI/Widgets/ComboBox.cs new file mode 100644 index 0000000..88bcbec --- /dev/null +++ b/source/ShiftUI/Widgets/ComboBox.cs @@ -0,0 +1,2825 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2004-2006 Novell, Inc. +// +// Authors: +// Jordi Mas i Hernandez, [email protected] +// Mike Kestner <[email protected]> +// Daniel Nauck (dna(at)mono-project(dot)de) + +using System; +using System.Collections; +using System.ComponentModel; +using System.ComponentModel.Design; +using System.ComponentModel.Design.Serialization; +using System.Drawing; +using System.Globalization; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Diagnostics; + +namespace ShiftUI +{ + [DefaultProperty("Items")] + [DefaultEvent("SelectedIndexChanged")] + //[Designer ("ShiftUI.Design.ComboBoxDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")] + [DefaultBindingProperty ("Text")] + [ClassInterface (ClassInterfaceType.AutoDispatch)] + [ComVisible(true)] + [ToolboxWidget] + public class ComboBox : ListWidget + { + private DrawMode draw_mode = DrawMode.Normal; + private ComboBoxStyle dropdown_style; + private int dropdown_width = -1; + private int selected_index = -1; + private ObjectCollection items; + private bool suspend_ctrlupdate; + private int maxdrop_items = 8; + private bool integral_height = true; + private bool sorted; + private int max_length; + private ComboListBox listbox_ctrl; + private ComboTextBox textbox_ctrl; + private bool process_textchanged_event = true; + private bool process_texchanged_autoscroll = true; + private bool item_height_specified; + private int item_height; + private int requested_height = -1; + private Hashtable item_heights; + private bool show_dropdown_button; + private ButtonState button_state = ButtonState.Normal; + private bool dropped_down; + private Rectangle text_area; + private Rectangle button_area; + private Rectangle listbox_area; + private const int button_width = 16; + bool drop_down_button_entered; + private AutoCompleteStringCollection auto_complete_custom_source = null; + private AutoCompleteMode auto_complete_mode = AutoCompleteMode.None; + private AutoCompleteSource auto_complete_source = AutoCompleteSource.None; + private FlatStyle flat_style; + private int drop_down_height; + const int default_drop_down_height = 106; + + [ComVisible(true)] + public class ChildAccessibleObject : AccessibleObject { + + public ChildAccessibleObject (ComboBox owner, IntPtr handle) + : base (owner) + { + } + + public override string Name { + get { + return base.Name; + } + } + } + + public ComboBox () + { + items = new ObjectCollection (this); + DropDownStyle = ComboBoxStyle.DropDown; + item_height = FontHeight + 2; + background_color = ThemeEngine.Current.ColorControl; + border_style = BorderStyle.None; + + drop_down_height = default_drop_down_height; + flat_style = FlatStyle.Standard; + + /* Events */ + MouseDown += new MouseEventHandler (OnMouseDownCB); + MouseUp += new MouseEventHandler (OnMouseUpCB); + MouseMove += new MouseEventHandler (OnMouseMoveCB); + MouseWheel += new MouseEventHandler (OnMouseWheelCB); + MouseEnter += new EventHandler (OnMouseEnter); + MouseLeave += new EventHandler (OnMouseLeave); + KeyDown +=new KeyEventHandler(OnKeyDownCB); + } + + #region events + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler BackgroundImageChanged { + add { base.BackgroundImageChanged += value; } + remove { base.BackgroundImageChanged -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler BackgroundImageLayoutChanged + { + add { base.BackgroundImageLayoutChanged += value; } + remove { base.BackgroundImageLayoutChanged -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler DoubleClick + { + add { base.DoubleClick += value; } + remove { base.DoubleClick -= value; } + } + + static object DrawItemEvent = new object (); + static object DropDownEvent = new object (); + static object DropDownStyleChangedEvent = new object (); + static object MeasureItemEvent = new object (); + static object SelectedIndexChangedEvent = new object (); + static object SelectionChangeCommittedEvent = new object (); + static object DropDownClosedEvent = new object (); + static object TextUpdateEvent = new object (); + + public event DrawItemEventHandler DrawItem { + add { Events.AddHandler (DrawItemEvent, value); } + remove { Events.RemoveHandler (DrawItemEvent, value); } + } + + public event EventHandler DropDown { + add { Events.AddHandler (DropDownEvent, value); } + remove { Events.RemoveHandler (DropDownEvent, value); } + } + public event EventHandler DropDownClosed + { + add { Events.AddHandler (DropDownClosedEvent, value); } + remove { Events.RemoveHandler (DropDownClosedEvent, value); } + } + + public event EventHandler DropDownStyleChanged { + add { Events.AddHandler (DropDownStyleChangedEvent, value); } + remove { Events.RemoveHandler (DropDownStyleChangedEvent, value); } + } + + public event MeasureItemEventHandler MeasureItem { + add { Events.AddHandler (MeasureItemEvent, value); } + remove { Events.RemoveHandler (MeasureItemEvent, value); } + } + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler PaddingChanged + { + add { base.PaddingChanged += value; } + remove { base.PaddingChanged -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event PaintEventHandler Paint { + add { base.Paint += value; } + remove { base.Paint -= value; } + } + + public event EventHandler SelectedIndexChanged { + add { Events.AddHandler (SelectedIndexChangedEvent, value); } + remove { Events.RemoveHandler (SelectedIndexChangedEvent, value); } + } + + public event EventHandler SelectionChangeCommitted { + add { Events.AddHandler (SelectionChangeCommittedEvent, value); } + remove { Events.RemoveHandler (SelectionChangeCommittedEvent, value); } + } + public event EventHandler TextUpdate + { + add { Events.AddHandler (TextUpdateEvent, value); } + remove { Events.RemoveHandler (TextUpdateEvent, value); } + } + + #endregion Events + + #region Public Properties + [MonoTODO("AutoCompletion algorithm is currently not implemented.")] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Content)] + [Browsable (true)] + //[EditorBrowsable (EditorBrowsableState.Always)] + [Localizable (true)] + //[Editor ("ShiftUI.Design.ListWidgetStringCollectionEditor, " + Consts.AssemblySystem_Design, + //"System.Drawing.Design.UITypeEditor, " + Consts.AssemblySystem_Drawing)] + public AutoCompleteStringCollection AutoCompleteCustomSource { + get { + if(auto_complete_custom_source == null) { + auto_complete_custom_source = new AutoCompleteStringCollection (); + auto_complete_custom_source.CollectionChanged += new CollectionChangeEventHandler (OnAutoCompleteCustomSourceChanged); + } + return auto_complete_custom_source; + } + set { + if(auto_complete_custom_source == value) + return; + + if(auto_complete_custom_source != null) //remove eventhandler from old collection + auto_complete_custom_source.CollectionChanged -= new CollectionChangeEventHandler (OnAutoCompleteCustomSourceChanged); + + auto_complete_custom_source = value; + + if(auto_complete_custom_source != null) + auto_complete_custom_source.CollectionChanged += new CollectionChangeEventHandler (OnAutoCompleteCustomSourceChanged); + + SetTextBoxAutoCompleteData (); + } + } + + [MonoTODO("AutoCompletion algorithm is currently not implemented.")] + [Browsable (true)] + //[EditorBrowsable (EditorBrowsableState.Always)] + [DefaultValue (AutoCompleteMode.None)] + public AutoCompleteMode AutoCompleteMode { + get { return auto_complete_mode; } + set { + if(auto_complete_mode == value) + return; + + if((value < AutoCompleteMode.None) || (value > AutoCompleteMode.SuggestAppend)) + throw new InvalidEnumArgumentException (String.Format("Enum argument value '{0}' is not valid for AutoCompleteMode", value)); + + auto_complete_mode = value; + SetTextBoxAutoCompleteData (); + } + } + + [MonoTODO("AutoCompletion algorithm is currently not implemented.")] + [Browsable (true)] + //[EditorBrowsable (EditorBrowsableState.Always)] + [DefaultValue (AutoCompleteSource.None)] + public AutoCompleteSource AutoCompleteSource { + get { return auto_complete_source; } + set { + if(auto_complete_source == value) + return; + + if(!Enum.IsDefined (typeof (AutoCompleteSource), value)) + throw new InvalidEnumArgumentException (String.Format ("Enum argument value '{0}' is not valid for AutoCompleteSource", value)); + + auto_complete_source = value; + SetTextBoxAutoCompleteData (); + } + } + + void SetTextBoxAutoCompleteData () + { + if (textbox_ctrl == null) + return; + + textbox_ctrl.AutoCompleteMode = auto_complete_mode; + + if (auto_complete_source == AutoCompleteSource.ListItems) { + textbox_ctrl.AutoCompleteSource = AutoCompleteSource.CustomSource; + textbox_ctrl.AutoCompleteCustomSource = null; + textbox_ctrl.AutoCompleteInternalSource = this; + } else { + textbox_ctrl.AutoCompleteSource = auto_complete_source; + textbox_ctrl.AutoCompleteCustomSource = auto_complete_custom_source; + textbox_ctrl.AutoCompleteInternalSource = null; + } + } + public override Color BackColor { + get { return base.BackColor; } + set { + if (base.BackColor == value) + return; + base.BackColor = value; + Refresh (); + } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public override Image BackgroundImage { + get { return base.BackgroundImage; } + set { + if (base.BackgroundImage == value) + return; + base.BackgroundImage = value; + Refresh (); + } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public override ImageLayout BackgroundImageLayout { + get { return base.BackgroundImageLayout; } + set { base.BackgroundImageLayout = value; } + } + + protected override CreateParams CreateParams { + get { return base.CreateParams;} + } + + [DefaultValue ((string)null)] + [AttributeProvider (typeof (IListSource))] + [RefreshProperties (RefreshProperties.Repaint)] + [MWFCategory("Data")] + public new object DataSource { + get { return base.DataSource; } + set { base.DataSource = value; } + } + + protected override Size DefaultSize { + get { return new Size (121, 21); } + } + + [RefreshProperties(RefreshProperties.Repaint)] + [DefaultValue (DrawMode.Normal)] + [MWFCategory("Behavior")] + public DrawMode DrawMode { + get { return draw_mode; } + set { + if (!Enum.IsDefined (typeof (DrawMode), value)) + throw new InvalidEnumArgumentException (string.Format("Enum argument value '{0}' is not valid for DrawMode", value)); + + if (draw_mode == value) + return; + + if (draw_mode == DrawMode.OwnerDrawVariable) + item_heights = null; + draw_mode = value; + if (draw_mode == DrawMode.OwnerDrawVariable) + item_heights = new Hashtable (); + Refresh (); + } + } + + [Browsable (true)] + [DefaultValue (106)] + //[EditorBrowsable (EditorBrowsableState.Always)] + [MWFCategory("Behavior")] + public int DropDownHeight { + get { + return drop_down_height; + } + set { + if (value < 1) + throw new ArgumentOutOfRangeException ("DropDownHeight", "DropDownHeight must be greater than 0."); + + if (value == drop_down_height) + return; + + drop_down_height = value; + IntegralHeight = false; + } + } + + [DefaultValue (ComboBoxStyle.DropDown)] + [RefreshProperties(RefreshProperties.Repaint)] + [MWFCategory("Appearance")] + public ComboBoxStyle DropDownStyle { + get { return dropdown_style; } + set { + if (!Enum.IsDefined (typeof (ComboBoxStyle), value)) + throw new InvalidEnumArgumentException (string.Format("Enum argument value '{0}' is not valid for ComboBoxStyle", value)); + + if (dropdown_style == value) + return; + + SuspendLayout (); + + if (dropdown_style == ComboBoxStyle.Simple) { + if (listbox_ctrl != null) { + Widgets.RemoveImplicit (listbox_ctrl); + listbox_ctrl.Dispose (); + listbox_ctrl = null; + } + } + + dropdown_style = value; + + if (dropdown_style == ComboBoxStyle.DropDownList && textbox_ctrl != null) { + Widgets.RemoveImplicit (textbox_ctrl); + textbox_ctrl.Dispose (); + textbox_ctrl = null; + } + + if (dropdown_style == ComboBoxStyle.Simple) { + show_dropdown_button = false; + + CreateComboListBox (); + Widgets.AddImplicit (listbox_ctrl); + listbox_ctrl.Visible = true; + + // This should give us a 150 default height + // for Simple mode if size hasn't been set + // (DefaultSize doesn't work for us in this case) + if (requested_height == -1) + requested_height = 150; + } else { + show_dropdown_button = true; + button_state = ButtonState.Normal; + } + + if (dropdown_style != ComboBoxStyle.DropDownList && textbox_ctrl == null) { + textbox_ctrl = new ComboTextBox (this); + object selected_item = SelectedItem; + if (selected_item != null) + textbox_ctrl.Text = GetItemText (selected_item); + textbox_ctrl.BorderStyle = BorderStyle.None; + textbox_ctrl.TextChanged += new EventHandler (OnTextChangedEdit); + textbox_ctrl.KeyPress += new KeyPressEventHandler (OnTextKeyPress); + textbox_ctrl.Click += new EventHandler (OnTextBoxClick); + textbox_ctrl.TopMargin = 1; // since we don't have borders, adjust manually the top + + if (IsHandleCreated == true) + Widgets.AddImplicit (textbox_ctrl); + SetTextBoxAutoCompleteData (); + } + + ResumeLayout (); + OnDropDownStyleChanged (EventArgs.Empty); + + LayoutComboBox (); + UpdateComboBoxBounds (); + Refresh (); + } + } + + [MWFCategory("Behavior")] + public int DropDownWidth { + get { + if (dropdown_width == -1) + return Width; + + return dropdown_width; + } + set { + if (dropdown_width == value) + return; + + if (value < 1) + throw new ArgumentOutOfRangeException ("DropDownWidth", + "The DropDownWidth value is less than one."); + + dropdown_width = value; + } + } + + [Browsable (false)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public bool DroppedDown { + get { + if (dropdown_style == ComboBoxStyle.Simple) + return true; + + return dropped_down; + } + set { + if (dropdown_style == ComboBoxStyle.Simple || dropped_down == value) + return; + + if (value) + DropDownListBox (); + else + listbox_ctrl.HideWindow (); + } + } + + [DefaultValue (FlatStyle.Standard)] + [Localizable (true)] + [MWFCategory("Appearance")] + public FlatStyle FlatStyle { + get { return flat_style; } + set { + if (!Enum.IsDefined (typeof (FlatStyle), value)) + throw new InvalidEnumArgumentException ("FlatStyle", (int) value, typeof (FlatStyle)); + + flat_style = value; + LayoutComboBox (); + Invalidate (); + } + } + + public override bool Focused { + get { return base.Focused; } + } + + public override Color ForeColor { + get { return base.ForeColor; } + set { + if (base.ForeColor == value) + return; + base.ForeColor = value; + Refresh (); + } + } + + [DefaultValue (true)] + [Localizable (true)] + [MWFCategory("Behavior")] + public bool IntegralHeight { + get { return integral_height; } + set { + if (integral_height == value) + return; + integral_height = value; + UpdateComboBoxBounds (); + Refresh (); + } + } + + [Localizable (true)] + [MWFCategory("Behavior")] + public int ItemHeight { + get { + if (item_height == -1) { + SizeF sz = TextRenderer.MeasureString ("The quick brown Fox", Font); + item_height = (int) sz.Height; + } + return item_height; + } + set { + if (value < 1) + throw new ArgumentOutOfRangeException ("ItemHeight", + "The item height value is less than one."); + + item_height_specified = true; + item_height = value; + if (IntegralHeight) + UpdateComboBoxBounds (); + LayoutComboBox (); + Refresh (); + } + } + + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Content)] + [Localizable (true)] + //[Editor ("ShiftUI.Design.ListWidgetStringCollectionEditor, " + Consts.AssemblySystem_Design, typeof (System.Drawing.Design.UITypeEditor))] + [MergableProperty (false)] + [MWFCategory("Data")] + public ComboBox.ObjectCollection Items { + get { return items; } + } + + [DefaultValue (8)] + [Localizable (true)] + [MWFCategory("Behavior")] + public int MaxDropDownItems { + get { return maxdrop_items; } + set { + if (maxdrop_items == value) + return; + maxdrop_items = value; + } + } + + public override Size MaximumSize { + get { return base.MaximumSize; } + set { + base.MaximumSize = new Size (value.Width, 0); + } + } + + [DefaultValue (0)] + [Localizable (true)] + [MWFCategory("Behavior")] + public int MaxLength { + get { return max_length; } + set { + if (max_length == value) + return; + + max_length = value; + + if (dropdown_style != ComboBoxStyle.DropDownList) { + if (value < 0) { + value = 0; + } + textbox_ctrl.MaxLength = value; + } + } + } + + public override Size MinimumSize { + get { return base.MinimumSize; } + set { + base.MinimumSize = new Size (value.Width, 0); + } + } + + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + //[EditorBrowsable (EditorBrowsableState.Never)] + [Browsable (false)] + public new Padding Padding { + get { return base.Padding; } + set { base.Padding = value; } + } + + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + [Browsable (false)] + public int PreferredHeight { + get { return Font.Height + 8; } + } + + [Browsable (false)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public override int SelectedIndex { + get { return selected_index; } + set { + SetSelectedIndex (value, false); + } + } + + [Browsable (false)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + [Bindable(true)] + public object SelectedItem { + get { return selected_index == -1 ? null : Items [selected_index]; } + set { + object item = selected_index == -1 ? null : Items [selected_index]; + if (item == value) + return; + + if (value == null) + SelectedIndex = -1; + else + SelectedIndex = Items.IndexOf (value); + } + } + + [Browsable (false)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public string SelectedText { + get { + if (dropdown_style == ComboBoxStyle.DropDownList) + return string.Empty; + + string retval = textbox_ctrl.SelectedText; + + return retval; + } + set { + if (dropdown_style == ComboBoxStyle.DropDownList) + return; + textbox_ctrl.SelectedText = value; + } + } + + [Browsable (false)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public int SelectionLength { + get { + if (dropdown_style == ComboBoxStyle.DropDownList) + return 0; + + int result = textbox_ctrl.SelectionLength; + return result == -1 ? 0 : result; + } + set { + if (dropdown_style == ComboBoxStyle.DropDownList) + return; + if (textbox_ctrl.SelectionLength == value) + return; + textbox_ctrl.SelectionLength = value; + } + } + + [Browsable (false)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public int SelectionStart { + get { + if (dropdown_style == ComboBoxStyle.DropDownList) + return 0; + return textbox_ctrl.SelectionStart; + } + set { + if (dropdown_style == ComboBoxStyle.DropDownList) + return; + if (textbox_ctrl.SelectionStart == value) + return; + textbox_ctrl.SelectionStart = value; + } + } + + [DefaultValue (false)] + [MWFCategory("Behavior")] + public bool Sorted { + get { return sorted; } + set { + if (sorted == value) + return; + sorted = value; + SelectedIndex = -1; + if (sorted) { + Items.Sort (); + LayoutComboBox (); + } + } + } + + [Bindable (true)] + [Localizable (true)] + public override string Text { + get { + if (dropdown_style != ComboBoxStyle.DropDownList) { + if (textbox_ctrl != null) { + return textbox_ctrl.Text; + } + } + + if (SelectedItem != null) + return GetItemText (SelectedItem); + + return base.Text; + } + set { + if (value == null) { + if (SelectedIndex == -1) { + if (dropdown_style != ComboBoxStyle.DropDownList) + SetWidgetText (string.Empty, false); + } else { + SelectedIndex = -1; + } + return; + } + + // don't set the index if value exactly matches text of selected item + if (SelectedItem == null || string.Compare (value, GetItemText (SelectedItem), false, CultureInfo.CurrentCulture) != 0) + { + // find exact match using case-sensitive comparison, and if does + // not result in any match then use case-insensitive comparison + int index = FindStringExact (value, -1, false); + if (index == -1) { + index = FindStringExact (value, -1, true); + } + if (index != -1) { + SelectedIndex = index; + return; + } + } + + // set directly the passed value + if (dropdown_style != ComboBoxStyle.DropDownList) + textbox_ctrl.Text = value; + } + } + + #endregion Public Properties + + #region Internal Properties + internal Rectangle ButtonArea { + get { return button_area; } + } + + internal Rectangle TextArea { + get { return text_area; } + } + #endregion + + #region UIA Framework Properties + + internal TextBox UIATextBox { + get { return textbox_ctrl; } + } + + internal ComboListBox UIAComboListBox { + get { return listbox_ctrl; } + } + + #endregion UIA Framework Properties + + #region Public Methods + [Obsolete ("This method has been deprecated")] + protected virtual void AddItemsCore (object[] value) + { + + } + + public void BeginUpdate () + { + suspend_ctrlupdate = true; + } + + protected override AccessibleObject CreateAccessibilityInstance () + { + return base.CreateAccessibilityInstance (); + } + + protected override void CreateHandle () + { + base.CreateHandle (); + } + + protected override void Dispose (bool disposing) + { + if (disposing) { + if (listbox_ctrl != null) { + listbox_ctrl.Dispose (); + Widgets.RemoveImplicit (listbox_ctrl); + listbox_ctrl = null; + } + + if (textbox_ctrl != null) { + Widgets.RemoveImplicit (textbox_ctrl); + textbox_ctrl.Dispose (); + textbox_ctrl = null; + } + } + + base.Dispose (disposing); + } + + public void EndUpdate () + { + suspend_ctrlupdate = false; + UpdatedItems (); + Refresh (); + } + + public int FindString (string s) + { + return FindString (s, -1); + } + + public int FindString (string s, int startIndex) + { + if (s == null || Items.Count == 0) + return -1; + + if (startIndex < -1 || startIndex >= Items.Count) + throw new ArgumentOutOfRangeException ("startIndex"); + + int i = startIndex; + if (i == (Items.Count - 1)) + i = -1; + do { + i++; + if (string.Compare (s, 0, GetItemText (Items [i]), 0, s.Length, true) == 0) + return i; + if (i == (Items.Count - 1)) + i = -1; + } while (i != startIndex); + + return -1; + } + + public int FindStringExact (string s) + { + return FindStringExact (s, -1); + } + + public int FindStringExact (string s, int startIndex) + { + return FindStringExact (s, startIndex, true); + } + + private int FindStringExact (string s, int startIndex, bool ignoreCase) + { + if (s == null || Items.Count == 0) + return -1; + + if (startIndex < -1 || startIndex >= Items.Count) + throw new ArgumentOutOfRangeException ("startIndex"); + + int i = startIndex; + if (i == (Items.Count - 1)) + i = -1; + do { + i++; + if (string.Compare (s, GetItemText (Items [i]), ignoreCase, CultureInfo.CurrentCulture) == 0) + return i; + if (i == (Items.Count - 1)) + i = -1; + } while (i != startIndex); + + return -1; + } + + public int GetItemHeight (int index) + { + if (DrawMode == DrawMode.OwnerDrawVariable && IsHandleCreated) { + + if (index < 0 || index >= Items.Count ) + throw new ArgumentOutOfRangeException ("The item height value is less than zero"); + + object item = Items [index]; + if (item_heights.Contains (item)) + return (int) item_heights [item]; + + MeasureItemEventArgs args = new MeasureItemEventArgs (DeviceContext, index, ItemHeight); + OnMeasureItem (args); + item_heights [item] = args.ItemHeight; + return args.ItemHeight; + } + + return ItemHeight; + } + + protected override bool IsInputKey (Keys keyData) + { + switch (keyData & ~Keys.Modifiers) { + case Keys.Up: + case Keys.Down: + case Keys.Left: + case Keys.Right: + case Keys.PageUp: + case Keys.PageDown: + case Keys.Home: + case Keys.End: + return true; + + default: + return false; + } + } + + protected override void OnBackColorChanged (EventArgs e) + { + base.OnBackColorChanged (e); + + if (textbox_ctrl != null) + textbox_ctrl.BackColor = BackColor; + } + + protected override void OnDataSourceChanged (EventArgs e) + { + base.OnDataSourceChanged (e); + BindDataItems (); + + /** + ** This 'Debugger.IsAttached' hack is here because of + ** Xamarin Bug #2234, which noted that when changing + ** the DataSource, in Windows exceptions are eaten + ** when SelectedIndexChanged is fired. However, when + ** the debugger is running (i.e. in MonoDevelop), we + ** want to be alerted of exceptions. + **/ + + if (Debugger.IsAttached) { + SetSelectedIndex (); + } else { + try { + SetSelectedIndex (); + } catch { + //ignore exceptions here per + //bug 2234 + } + } + } + + private void SetSelectedIndex () + { + if (DataSource == null || DataManager == null) { + SelectedIndex = -1; + } + else { + SelectedIndex = DataManager.Position; + } + } + + protected override void OnDisplayMemberChanged (EventArgs e) + { + base.OnDisplayMemberChanged (e); + + if (DataManager == null) + return; + + SelectedIndex = DataManager.Position; + + if (selected_index != -1 && DropDownStyle != ComboBoxStyle.DropDownList) + SetWidgetText (GetItemText (Items [selected_index]), true); + + if (!IsHandleCreated) + return; + + Invalidate (); + } + + protected virtual void OnDrawItem (DrawItemEventArgs e) + { + DrawItemEventHandler eh = (DrawItemEventHandler)(Events [DrawItemEvent]); + if (eh != null) + eh (this, e); + } + + internal void HandleDrawItem (DrawItemEventArgs e) + { + // Only raise OnDrawItem if we are in an OwnerDraw mode + switch (DrawMode) { + case DrawMode.OwnerDrawFixed: + case DrawMode.OwnerDrawVariable: + OnDrawItem (e); + break; + default: + ThemeEngine.Current.DrawComboBoxItem (this, e); + break; + } + } + + protected virtual void OnDropDown (EventArgs e) + { + EventHandler eh = (EventHandler)(Events [DropDownEvent]); + if (eh != null) + eh (this, e); + } + + protected virtual void OnDropDownClosed (EventArgs e) + { + EventHandler eh = (EventHandler) Events [DropDownClosedEvent]; + if (eh != null) + eh (this, e); + } + + protected virtual void OnDropDownStyleChanged (EventArgs e) + { + EventHandler eh = (EventHandler)(Events [DropDownStyleChangedEvent]); + if (eh != null) + eh (this, e); + } + + protected override void OnFontChanged (EventArgs e) + { + base.OnFontChanged (e); + + if (textbox_ctrl != null) + textbox_ctrl.Font = Font; + + if (!item_height_specified) + item_height = Font.Height + 2; + + if (IntegralHeight) + UpdateComboBoxBounds (); + + LayoutComboBox (); + } + + protected override void OnForeColorChanged (EventArgs e) + { + base.OnForeColorChanged (e); + if (textbox_ctrl != null) + textbox_ctrl.ForeColor = ForeColor; + } + + //[EditorBrowsable(EditorBrowsableState.Advanced)] + protected override void OnGotFocus (EventArgs e) + { + if (dropdown_style == ComboBoxStyle.DropDownList) { + // We draw DDL styles manually, so they require a + // refresh to have their selection drawn + Invalidate (); + } + + if (textbox_ctrl != null) { + textbox_ctrl.SetSelectable (false); + textbox_ctrl.ShowSelection = Enabled; + textbox_ctrl.ActivateCaret (true); + textbox_ctrl.SelectAll (); + } + + base.OnGotFocus (e); + } + + //[EditorBrowsable(EditorBrowsableState.Advanced)] + protected override void OnLostFocus (EventArgs e) + { + if (dropdown_style == ComboBoxStyle.DropDownList) { + // We draw DDL styles manually, so they require a + // refresh to have their selection drawn + Invalidate (); + } + + if (listbox_ctrl != null && dropped_down) { + listbox_ctrl.HideWindow (); + } + + if (textbox_ctrl != null) { + textbox_ctrl.SetSelectable (true); + textbox_ctrl.ActivateCaret (false); + textbox_ctrl.ShowSelection = false; + textbox_ctrl.SelectionLength = 0; + textbox_ctrl.HideAutoCompleteList (); + } + + base.OnLostFocus (e); + } + + protected override void OnHandleCreated (EventArgs e) + { + base.OnHandleCreated (e); + + SetBoundsInternal (Left, Top, Width, PreferredHeight, BoundsSpecified.None); + + if (textbox_ctrl != null) + Widgets.AddImplicit (textbox_ctrl); + + LayoutComboBox (); + UpdateComboBoxBounds (); + } + + protected override void OnHandleDestroyed (EventArgs e) + { + base.OnHandleDestroyed (e); + } + + protected override void OnKeyPress (KeyPressEventArgs e) + { + if (dropdown_style == ComboBoxStyle.DropDownList) { + int index = FindStringCaseInsensitive (e.KeyChar.ToString (), SelectedIndex + 1); + if (index != -1) { + SelectedIndex = index; + if (DroppedDown) { //Scroll into view + if (SelectedIndex >= listbox_ctrl.LastVisibleItem ()) + listbox_ctrl.Scroll (SelectedIndex - listbox_ctrl.LastVisibleItem () + 1); + // Or, selecting an item earlier in the list. + if (SelectedIndex < listbox_ctrl.FirstVisibleItem ()) + listbox_ctrl.Scroll (SelectedIndex - listbox_ctrl.FirstVisibleItem ()); + } + } + } + + base.OnKeyPress (e); + } + + protected virtual void OnMeasureItem (MeasureItemEventArgs e) + { + MeasureItemEventHandler eh = (MeasureItemEventHandler)(Events [MeasureItemEvent]); + if (eh != null) + eh (this, e); + } + + protected override void OnParentBackColorChanged (EventArgs e) + { + base.OnParentBackColorChanged (e); + } + + protected override void OnResize (EventArgs e) + { + LayoutComboBox (); + if (listbox_ctrl != null) + listbox_ctrl.CalcListBoxArea (); + } + + protected override void OnSelectedIndexChanged (EventArgs e) + { + base.OnSelectedIndexChanged (e); + + EventHandler eh = (EventHandler)(Events [SelectedIndexChangedEvent]); + if (eh != null) + eh (this, e); + } + + protected virtual void OnSelectedItemChanged (EventArgs e) + { + } + + protected override void OnSelectedValueChanged (EventArgs e) + { + base.OnSelectedValueChanged (e); + } + + protected virtual void OnSelectionChangeCommitted (EventArgs e) + { + EventHandler eh = (EventHandler)(Events [SelectionChangeCommittedEvent]); + if (eh != null) + eh (this, e); + } + + protected override void RefreshItem (int index) + { + if (index < 0 || index >= Items.Count) + throw new ArgumentOutOfRangeException ("index"); + + if (draw_mode == DrawMode.OwnerDrawVariable) + item_heights.Remove (Items [index]); + } + + protected override void RefreshItems () + { + for (int i = 0; i < Items.Count; i++) { + RefreshItem (i); + } + + LayoutComboBox (); + Refresh (); + + if (selected_index != -1 && DropDownStyle != ComboBoxStyle.DropDownList) + SetWidgetText (GetItemText (Items [selected_index]), false); + } + + public override void ResetText () + { + Text = String.Empty; + } + + protected override bool ProcessKeyEventArgs (ref Message m) + { + return base.ProcessKeyEventArgs (ref m); + } + + //[EditorBrowsable (EditorBrowsableState.Advanced)] + protected override void OnKeyDown (KeyEventArgs e) + { + base.OnKeyDown (e); + } + + //[EditorBrowsable (EditorBrowsableState.Advanced)] + protected override void OnValidating (CancelEventArgs e) + { + base.OnValidating (e); + } + + //[EditorBrowsable (EditorBrowsableState.Advanced)] + protected override void OnTextChanged (EventArgs e) + { + base.OnTextChanged (e); + } + + protected virtual void OnTextUpdate (EventArgs e) + { + EventHandler eh = (EventHandler) Events [TextUpdateEvent]; + if (eh != null) + eh (this, e); + } + protected override void OnMouseLeave (EventArgs e) + { + if (flat_style == FlatStyle.Popup) + Invalidate (); + base.OnMouseLeave (e); + } + + protected override void OnMouseEnter (EventArgs e) + { + if (flat_style == FlatStyle.Popup) + Invalidate (); + base.OnMouseEnter (e); + } + + protected override void ScaleWidget (SizeF factor, BoundsSpecified specified) + { + base.ScaleWidget (factor, specified); + } + + public void Select (int start, int length) + { + if (start < 0) + throw new ArgumentException ("Start cannot be less than zero"); + + if (length < 0) + throw new ArgumentException ("length cannot be less than zero"); + + if (dropdown_style == ComboBoxStyle.DropDownList) + return; + + textbox_ctrl.Select (start, length); + } + + public void SelectAll () + { + if (dropdown_style == ComboBoxStyle.DropDownList) + return; + + if (textbox_ctrl != null) { + textbox_ctrl.ShowSelection = true; + textbox_ctrl.SelectAll (); + } + } + + protected override void SetBoundsCore (int x, int y, int width, int height, BoundsSpecified specified) + { + bool vertically_anchored = (Anchor & AnchorStyles.Top) != 0 && (Anchor & AnchorStyles.Bottom) != 0; + bool vertically_docked = Dock == DockStyle.Left || Dock == DockStyle.Right || Dock == DockStyle.Fill; + + if ((specified & BoundsSpecified.Height) != 0 || + (specified == BoundsSpecified.None && (vertically_anchored || vertically_docked))) { + + requested_height = height; + height = SnapHeight (height); + } + + base.SetBoundsCore (x, y, width, height, specified); + } + + protected override void SetItemCore (int index, object value) + { + if (index < 0 || index >= Items.Count) + return; + + Items[index] = value; + } + + protected override void SetItemsCore (IList value) + { + BeginUpdate (); + try { + Items.Clear (); + Items.AddRange (value); + } finally { + EndUpdate (); + } + } + + public override string ToString () + { + return base.ToString () + ", Items.Count:" + Items.Count; + } + + protected override void WndProc (ref Message m) + { + switch ((Msg) m.Msg) { + case Msg.WM_KEYUP: + case Msg.WM_KEYDOWN: + Keys keys = (Keys) m.WParam.ToInt32 (); + // Don't pass the message to base if auto complete is being used and available. + if (textbox_ctrl != null && textbox_ctrl.CanNavigateAutoCompleteList) { + XplatUI.SendMessage (textbox_ctrl.Handle, (Msg) m.Msg, m.WParam, m.LParam); + return; + } + if (keys == Keys.Up || keys == Keys.Down) + break; + goto case Msg.WM_CHAR; + case Msg.WM_CHAR: + // Call our own handler first and send the message to the TextBox if still needed + if (!ProcessKeyMessage (ref m) && textbox_ctrl != null) + XplatUI.SendMessage (textbox_ctrl.Handle, (Msg) m.Msg, m.WParam, m.LParam); + return; + case Msg.WM_MOUSELEAVE: + Point location = PointToClient (Widget.MousePosition); + if (ClientRectangle.Contains (location)) + return; + break; + default: + break; + } + base.WndProc (ref m); + } + + #endregion Public Methods + + #region Private Methods + void OnAutoCompleteCustomSourceChanged(object sender, CollectionChangeEventArgs e) { + if(auto_complete_source == AutoCompleteSource.CustomSource) { + //FIXME: handle add, remove and refresh events in AutoComplete algorithm. + } + } + + internal override bool InternalCapture { + get { return Capture; } + set {} + } + + void LayoutComboBox () + { + int border = ThemeEngine.Current.Border3DSize.Width; + + text_area = ClientRectangle; + text_area.Height = PreferredHeight; + + listbox_area = ClientRectangle; + listbox_area.Y = text_area.Bottom + 3; + listbox_area.Height -= (text_area.Height + 2); + + Rectangle prev_button_area = button_area; + + if (DropDownStyle == ComboBoxStyle.Simple) + button_area = Rectangle.Empty; + else { + button_area = text_area; + button_area.X = text_area.Right - button_width - border; + button_area.Y = text_area.Y + border; + button_area.Width = button_width; + button_area.Height = text_area.Height - 2 * border; + if (flat_style == FlatStyle.Popup || flat_style == FlatStyle.Flat) { + button_area.Inflate (1, 1); + button_area.X += 2; + button_area.Width -= 2; + } + } + + if (button_area != prev_button_area) { + prev_button_area.Y -= border; + prev_button_area.Width += border; + prev_button_area.Height += 2 * border; + Invalidate (prev_button_area); + Invalidate (button_area); + } + + if (textbox_ctrl != null) { + int text_border = border + 1; + textbox_ctrl.Location = new Point (text_area.X + text_border, text_area.Y + text_border); + textbox_ctrl.Width = text_area.Width - button_area.Width - text_border * 2; + textbox_ctrl.Height = text_area.Height - text_border * 2; + } + + if (listbox_ctrl != null && dropdown_style == ComboBoxStyle.Simple) { + listbox_ctrl.Location = listbox_area.Location; + listbox_ctrl.CalcListBoxArea (); + } + } + + private void CreateComboListBox () + { + listbox_ctrl = new ComboListBox (this); + listbox_ctrl.HighlightedIndex = SelectedIndex; + } + + internal void Draw (Rectangle clip, Graphics dc) + { + Theme theme = ThemeEngine.Current; + FlatStyle style = FlatStyle.Standard; + bool is_flat = false; + + style = this.FlatStyle; + is_flat = style == FlatStyle.Flat || style == FlatStyle.Popup; + + theme.ComboBoxDrawBackground (this, dc, clip, style); + + int border = theme.Border3DSize.Width; + + // No edit Widget, we paint the edit ourselves + if (dropdown_style == ComboBoxStyle.DropDownList) { + DrawItemState state = DrawItemState.None; + Color back_color = BackColor; + Color fore_color = ForeColor; + Rectangle item_rect = text_area; + item_rect.X += border; + item_rect.Y += border; + item_rect.Width -= (button_area.Width + 2 * border); + item_rect.Height -= 2 * border; + + if (Focused) { + state = DrawItemState.Selected; + state |= DrawItemState.Focus; + back_color = SystemColors.Highlight; + fore_color = SystemColors.HighlightText; + } + + state |= DrawItemState.ComboBoxEdit; + HandleDrawItem (new DrawItemEventArgs (dc, Font, item_rect, SelectedIndex, state, fore_color, back_color)); + } + + if (show_dropdown_button) { + ButtonState current_state; + if (is_enabled) + current_state = button_state; + else + current_state = ButtonState.Inactive; + + if (is_flat || theme.ComboBoxNormalDropDownButtonHasTransparentBackground (this, current_state)) + dc.FillRectangle (theme.ResPool.GetSolidBrush (theme.ColorControl), button_area); + + if (is_flat) { + theme.DrawFlatStyleComboButton (dc, button_area, current_state); + } else { + theme.ComboBoxDrawNormalDropDownButton (this, dc, clip, button_area, current_state); + } + } + } + + internal bool DropDownButtonEntered { + get { return drop_down_button_entered; } + private set { + if (drop_down_button_entered == value) + return; + drop_down_button_entered = value; + if (ThemeEngine.Current.ComboBoxDropDownButtonHasHotElementStyle (this)) + Invalidate (button_area); + } + } + + internal void DropDownListBox () + { + DropDownButtonEntered = false; + + if (DropDownStyle == ComboBoxStyle.Simple) + return; + + if (listbox_ctrl == null) + CreateComboListBox (); + + listbox_ctrl.Location = PointToScreen (new Point (text_area.X, text_area.Y + text_area.Height)); + + FindMatchOrSetIndex(SelectedIndex); + + if (textbox_ctrl != null) + textbox_ctrl.HideAutoCompleteList (); + + if (listbox_ctrl.ShowWindow ()) + dropped_down = true; + + button_state = ButtonState.Pushed; + if (dropdown_style == ComboBoxStyle.DropDownList) + Invalidate (text_area); + } + + internal void DropDownListBoxFinished () + { + if (DropDownStyle == ComboBoxStyle.Simple) + return; + + FindMatchOrSetIndex (SelectedIndex); + button_state = ButtonState.Normal; + Invalidate (button_area); + dropped_down = false; + OnDropDownClosed (EventArgs.Empty); + /* + * Apples X11 looses override-redirect when doing a Unmap/Map on a previously mapped window + * this causes the popup to appear under the main form. This is horrible but necessary + */ + + // If the user opens a new form in an event, it will close our dropdown, + // so we need a null check here + if (listbox_ctrl != null) { + listbox_ctrl.Dispose (); + listbox_ctrl = null; + } + // The auto complete list could have been shown after the listbox, + // so make sure it's hidden. + if (textbox_ctrl != null) + textbox_ctrl.HideAutoCompleteList (); + } + + private int FindStringCaseInsensitive (string search) + { + if (search.Length == 0) { + return -1; + } + + for (int i = 0; i < Items.Count; i++) + { + if (String.Compare (GetItemText (Items[i]), 0, search, 0, search.Length, true) == 0) + return i; + } + + return -1; + } + + // Search in the list for the substring, starting the search at the list + // position specified, the search wraps thus covering all the list. + internal int FindStringCaseInsensitive (string search, int start_index) + { + if (search.Length == 0) { + return -1; + } + // Accept from first item to after last item. i.e. all cases of (SelectedIndex+1). + if (start_index < 0 || start_index > Items.Count) + throw new ArgumentOutOfRangeException("start_index"); + + for (int i = 0; i < Items.Count; i++) { + int index = (i + start_index) % Items.Count; + if (String.Compare (GetItemText (Items [index]), 0, search, 0, search.Length, true) == 0) + return index; + } + + return -1; + } + + internal override bool IsInputCharInternal (char charCode) + { + return true; + } + + internal void RestoreContextMenu () + { + textbox_ctrl.RestoreContextMenu (); + } + + private void OnKeyDownCB(object sender, KeyEventArgs e) + { + if (Items.Count == 0) + return; + + // for keyboard navigation, we have to do our own scroll, since + // the default behaviour for the SelectedIndex property is a little different, + // setting the selected index in the top always + + int offset; + switch (e.KeyCode) + { + case Keys.Up: + FindMatchOrSetIndex(Math.Max(SelectedIndex - 1, 0)); + + if (DroppedDown) + if (SelectedIndex < listbox_ctrl.FirstVisibleItem ()) + listbox_ctrl.Scroll (SelectedIndex - listbox_ctrl.FirstVisibleItem ()); + break; + + case Keys.Down: + if ((e.Modifiers & Keys.Alt) == Keys.Alt) + DropDownListBox (); + else + FindMatchOrSetIndex(Math.Min(SelectedIndex + 1, Items.Count - 1)); + + if (DroppedDown) + if (SelectedIndex >= listbox_ctrl.LastVisibleItem ()) + listbox_ctrl.Scroll (SelectedIndex - listbox_ctrl.LastVisibleItem () + 1); + break; + + case Keys.PageUp: + offset = listbox_ctrl == null ? MaxDropDownItems - 1 : listbox_ctrl.page_size - 1; + if (offset < 1) + offset = 1; + + SetSelectedIndex (Math.Max (SelectedIndex - offset, 0), true); + + if (DroppedDown) + if (SelectedIndex < listbox_ctrl.FirstVisibleItem ()) + listbox_ctrl.Scroll (SelectedIndex - listbox_ctrl.FirstVisibleItem ()); + break; + + case Keys.PageDown: + if (SelectedIndex == -1) { + SelectedIndex = 0; + if (dropdown_style != ComboBoxStyle.Simple) + return; + } + + offset = listbox_ctrl == null ? MaxDropDownItems - 1 : listbox_ctrl.page_size - 1; + if (offset < 1) + offset = 1; + + SetSelectedIndex (Math.Min (SelectedIndex + offset, Items.Count - 1), true); + + if (DroppedDown) + if (SelectedIndex >= listbox_ctrl.LastVisibleItem ()) + listbox_ctrl.Scroll (SelectedIndex - listbox_ctrl.LastVisibleItem () + 1); + break; + + case Keys.Enter: + case Keys.Escape: + if (listbox_ctrl != null && listbox_ctrl.Visible) + DropDownListBoxFinished (); + break; + + case Keys.Home: + if (dropdown_style == ComboBoxStyle.DropDownList) { + SelectedIndex = 0; + + if (DroppedDown) + if (SelectedIndex < listbox_ctrl.FirstVisibleItem ()) + listbox_ctrl.Scroll (SelectedIndex - listbox_ctrl.FirstVisibleItem ()); + } + + break; + case Keys.End: + if (dropdown_style == ComboBoxStyle.DropDownList) { + SetSelectedIndex (Items.Count - 1, true); + + if (DroppedDown) + if (SelectedIndex >= listbox_ctrl.LastVisibleItem ()) + listbox_ctrl.Scroll (SelectedIndex - listbox_ctrl.LastVisibleItem () + 1); + } + + break; + default: + break; + } + } + + void SetSelectedIndex (int value, bool supressAutoScroll) + { + if (selected_index == value) + return; + + if (value <= -2 || value >= Items.Count) + throw new ArgumentOutOfRangeException ("SelectedIndex"); + + selected_index = value; + + if (dropdown_style != ComboBoxStyle.DropDownList) { + if (value == -1) + SetWidgetText (string.Empty, false, supressAutoScroll); + else + SetWidgetText (GetItemText (Items [value]), false, supressAutoScroll); + } + + if (DropDownStyle == ComboBoxStyle.DropDownList) + Invalidate (); + + if (listbox_ctrl != null) + listbox_ctrl.HighlightedIndex = value; + + OnSelectedValueChanged (EventArgs.Empty); + OnSelectedIndexChanged (EventArgs.Empty); + OnSelectedItemChanged (EventArgs.Empty); + } + + // If no item is currently selected, and an item is found matching the text + // in the textbox, then selected that item. Otherwise the item at the given + // index is selected. + private void FindMatchOrSetIndex(int index) + { + int match = -1; + if (SelectedIndex == -1 && Text.Length != 0) + match = FindStringCaseInsensitive(Text); + if (match != -1) + SetSelectedIndex (match, true); + else + SetSelectedIndex (index, true); + } + + void OnMouseDownCB (object sender, MouseEventArgs e) + { + Rectangle area; + if (DropDownStyle == ComboBoxStyle.DropDownList) + area = ClientRectangle; + else + area = button_area; + + if (area.Contains (e.X, e.Y)) { + if (Items.Count > 0) + DropDownListBox (); + else { + button_state = ButtonState.Pushed; + OnDropDown (EventArgs.Empty); + } + + Invalidate (button_area); + Update (); + } + Capture = true; + } + + void OnMouseEnter (object sender, EventArgs e) + { + if (ThemeEngine.Current.CombBoxBackgroundHasHotElementStyle (this)) + Invalidate (); + } + + void OnMouseLeave (object sender, EventArgs e) + { + if (ThemeEngine.Current.CombBoxBackgroundHasHotElementStyle (this)) { + drop_down_button_entered = false; + Invalidate (); + } else { + if (show_dropdown_button) + DropDownButtonEntered = false; + } + } + + void OnMouseMoveCB (object sender, MouseEventArgs e) + { + if (show_dropdown_button && !dropped_down) + DropDownButtonEntered = button_area.Contains (e.Location); + + if (DropDownStyle == ComboBoxStyle.Simple) + return; + + if (listbox_ctrl != null && listbox_ctrl.Visible) { + Point location = listbox_ctrl.PointToClient (Widget.MousePosition); + if (listbox_ctrl.ClientRectangle.Contains (location)) + listbox_ctrl.Capture = true; + } + } + + void OnMouseUpCB (object sender, MouseEventArgs e) + { + Capture = false; + + button_state = ButtonState.Normal; + Invalidate (button_area); + + OnClick (EventArgs.Empty); + + if (dropped_down) + listbox_ctrl.Capture = true; + } + + private void OnMouseWheelCB (object sender, MouseEventArgs me) + { + if (Items.Count == 0) + return; + + if (listbox_ctrl != null && listbox_ctrl.Visible) { + int lines = me.Delta / 120 * SystemInformation.MouseWheelScrollLines; + listbox_ctrl.Scroll (-lines); + } else { + int lines = me.Delta / 120; + int index = SelectedIndex - lines; + if (index < 0) + index = 0; + else if (index >= Items.Count) + index = Items.Count - 1; + SelectedIndex = index; + } + } + + MouseEventArgs TranslateMouseEventArgs (MouseEventArgs args) + { + Point loc = PointToClient (Widget.MousePosition); + return new MouseEventArgs (args.Button, args.Clicks, loc.X, loc.Y, args.Delta); + } + + internal override void OnPaintInternal (PaintEventArgs pevent) + { + if (suspend_ctrlupdate) + return; + + Draw (ClientRectangle, pevent.Graphics); + } + + private void OnTextBoxClick (object sender, EventArgs e) + { + OnClick (e); + } + + private void OnTextChangedEdit (object sender, EventArgs e) + { + if (process_textchanged_event == false) + return; + + int item = FindStringCaseInsensitive (textbox_ctrl.Text); + + if (item == -1) { + // Setting base.Text below will raise this event + // if we found something + OnTextChanged (EventArgs.Empty); + return; + } + + if (listbox_ctrl != null) { + // Set as top item + if (process_texchanged_autoscroll) + listbox_ctrl.EnsureTop (item); + } + + base.Text = textbox_ctrl.Text; + } + + private void OnTextKeyPress (object sender, KeyPressEventArgs e) + { + selected_index = -1; + if (listbox_ctrl != null) + listbox_ctrl.HighlightedIndex = -1; + } + + internal void SetWidgetText (string s, bool suppressTextChanged) + { + SetWidgetText (s, suppressTextChanged, false); + } + + internal void SetWidgetText (string s, bool suppressTextChanged, bool supressAutoScroll) + { + if (suppressTextChanged) + process_textchanged_event = false; + if (supressAutoScroll) + process_texchanged_autoscroll = false; + + textbox_ctrl.Text = s; + textbox_ctrl.SelectAll (); + process_textchanged_event = true; + process_texchanged_autoscroll = true; + } + + void UpdateComboBoxBounds () + { + if (requested_height == -1) + return; + + // Save the requested height since set bounds can destroy it + int save_height = requested_height; + SetBounds (bounds.X, bounds.Y, bounds.Width, SnapHeight (requested_height), + BoundsSpecified.Height); + requested_height = save_height; + } + + int SnapHeight (int height) + { + if (DropDownStyle == ComboBoxStyle.Simple && height > PreferredHeight) { + if (IntegralHeight) { + int border = ThemeEngine.Current.Border3DSize.Height; + int lb_height = (height - PreferredHeight - 2) - border * 2; + if (lb_height > ItemHeight) { + int partial = (lb_height) % ItemHeight; + height -= partial; + } else if (lb_height < ItemHeight) + height = PreferredHeight; + } + } else + height = PreferredHeight; + + return height; + } + + private void UpdatedItems () + { + if (listbox_ctrl != null) { + listbox_ctrl.UpdateLastVisibleItem (); + listbox_ctrl.CalcListBoxArea (); + listbox_ctrl.Refresh (); + } + } + + #endregion Private Methods + + [ListBindableAttribute (false)] + public class ObjectCollection : IList, ICollection, IEnumerable + { + + private ComboBox owner; + internal ArrayList object_items = new ArrayList (); + + public ComboBox Owner + { + get { return owner; } + } + + #region UIA Framework Events + + //NOTE: + // We are using Reflection to add/remove internal events. + // Class ListProvider uses the events. + // + //Event used to generate UIA StructureChangedEvent + static object UIACollectionChangedEvent = new object (); + + internal event CollectionChangeEventHandler UIACollectionChanged { + add { owner.Events.AddHandler (UIACollectionChangedEvent, value); } + remove { owner.Events.RemoveHandler (UIACollectionChangedEvent, value); } + } + + internal void OnUIACollectionChangedEvent (CollectionChangeEventArgs args) + { + CollectionChangeEventHandler eh + = (CollectionChangeEventHandler) owner.Events [UIACollectionChangedEvent]; + if (eh != null) + eh (owner, args); + } + + #endregion UIA Framework Events + + public ObjectCollection (ComboBox owner) + { + this.owner = owner; + } + + #region Public Properties + public int Count { + get { return object_items.Count; } + } + + public bool IsReadOnly { + get { return false; } + } + + [Browsable (false)] + //[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual object this [int index] { + get { + if (index < 0 || index >= Count) + throw new ArgumentOutOfRangeException ("index"); + + return object_items[index]; + } + set { + if (index < 0 || index >= Count) + throw new ArgumentOutOfRangeException ("index"); + if (value == null) + throw new ArgumentNullException ("value"); + + //UIA Framework event: Item Removed + OnUIACollectionChangedEvent (new CollectionChangeEventArgs (CollectionChangeAction.Remove, object_items [index])); + + object_items[index] = value; + + //UIA Framework event: Item Added + OnUIACollectionChangedEvent (new CollectionChangeEventArgs (CollectionChangeAction.Add, value)); + + if (owner.listbox_ctrl != null) + owner.listbox_ctrl.InvalidateItem (index); + if (index == owner.SelectedIndex) { + if (owner.textbox_ctrl == null) + owner.Refresh (); + else { + owner.textbox_ctrl.Text = value.ToString (); + owner.textbox_ctrl.SelectAll (); + } + } + } + } + + bool ICollection.IsSynchronized { + get { return false; } + } + + object ICollection.SyncRoot { + get { return this; } + } + + bool IList.IsFixedSize { + get { return false; } + } + + #endregion Public Properties + + #region Public Methods + public int Add (object item) + { + int idx; + + idx = AddItem (item, false); + owner.UpdatedItems (); + return idx; + } + + public void AddRange (object[] items) + { + if (items == null) + throw new ArgumentNullException ("items"); + + foreach (object mi in items) + AddItem (mi, true); + + if (owner.sorted) + Sort (); + + owner.UpdatedItems (); + } + + public void Clear () + { + owner.selected_index = -1; + object_items.Clear (); + owner.UpdatedItems (); + owner.Refresh (); + + //UIA Framework event: Items list cleared + OnUIACollectionChangedEvent (new CollectionChangeEventArgs (CollectionChangeAction.Refresh, null)); + } + + public bool Contains (object value) + { + if (value == null) + throw new ArgumentNullException ("value"); + + return object_items.Contains (value); + } + + public void CopyTo (object [] destination, int arrayIndex) + { + object_items.CopyTo (destination, arrayIndex); + } + + void ICollection.CopyTo (Array destination, int index) + { + object_items.CopyTo (destination, index); + } + + public IEnumerator GetEnumerator () + { + return object_items.GetEnumerator (); + } + + int IList.Add (object item) + { + return Add (item); + } + + public int IndexOf (object value) + { + if (value == null) + throw new ArgumentNullException ("value"); + + return object_items.IndexOf (value); + } + + public void Insert (int index, object item) + { + if (index < 0 || index > Count) + throw new ArgumentOutOfRangeException ("index"); + if (item == null) + throw new ArgumentNullException ("item"); + + owner.BeginUpdate (); + + if (owner.Sorted) + AddItem (item, false); + else { + object_items.Insert (index, item); + //UIA Framework event: Item added + OnUIACollectionChangedEvent (new CollectionChangeEventArgs (CollectionChangeAction.Add, item)); + } + + owner.EndUpdate (); // Calls UpdatedItems + } + + public void Remove (object value) + { + if (value == null) + return; + int index = IndexOf (value); + if (index >= 0) + RemoveAt (index); + } + + public void RemoveAt (int index) + { + if (index < 0 || index >= Count) + throw new ArgumentOutOfRangeException ("index"); + + if (index < owner.SelectedIndex) + --owner.SelectedIndex; + else if (index == owner.SelectedIndex) + owner.SelectedIndex = -1; + + object removed = object_items [index]; + + object_items.RemoveAt (index); + owner.UpdatedItems (); + + //UIA Framework event: Item removed + OnUIACollectionChangedEvent (new CollectionChangeEventArgs (CollectionChangeAction.Remove, removed)); + } + #endregion Public Methods + + #region Private Methods + private int AddItem (object item, bool suspend) + { + // suspend means do not sort as we put new items in, we will do a + // big sort at the end + if (item == null) + throw new ArgumentNullException ("item"); + + if (owner.Sorted && !suspend) { + int index = 0; + foreach (object o in object_items) { + if (String.Compare (item.ToString (), o.ToString ()) < 0) { + object_items.Insert (index, item); + + // If we added the new item before the selectedindex + // bump the selectedindex by one, behavior differs if + // Handle has not been created. + if (index <= owner.selected_index && owner.IsHandleCreated) + owner.selected_index++; + + //UIA Framework event: Item added + OnUIACollectionChangedEvent (new CollectionChangeEventArgs (CollectionChangeAction.Add, item)); + + return index; + } + index++; + } + } + object_items.Add (item); + + //UIA Framework event: Item added + OnUIACollectionChangedEvent (new CollectionChangeEventArgs (CollectionChangeAction.Add, item)); + + return object_items.Count - 1; + } + + internal void AddRange (IList items) + { + foreach (object mi in items) + AddItem (mi, false); + + if (owner.sorted) + Sort (); + + owner.UpdatedItems (); + } + + internal void Sort () + { + // If the objects the user put here don't have their own comparer, + // use one that compares based on the object's ToString + if (object_items.Count > 0 && object_items[0] is IComparer) + object_items.Sort (); + else + object_items.Sort (new ObjectComparer (owner)); + } + + private class ObjectComparer : IComparer + { + private ListWidget owner; + + public ObjectComparer (ListWidget owner) + { + this.owner = owner; + } + + #region IComparer Members + public int Compare (object x, object y) + { + return string.Compare (owner.GetItemText (x), owner.GetItemText (y)); + } + #endregion + } + #endregion Private Methods + } + + internal class ComboTextBox : TextBox { + + private ComboBox owner; + + public ComboTextBox (ComboBox owner) + { + this.owner = owner; + ShowSelection = false; + owner.EnabledChanged += OwnerEnabledChangedHandler; + owner.LostFocus += OwnerLostFocusHandler; + } + + void OwnerEnabledChangedHandler (object o, EventArgs args) + { + ShowSelection = owner.Focused && owner.Enabled; + } + + void OwnerLostFocusHandler (object o, EventArgs args) + { + if (IsAutoCompleteAvailable) + owner.Text = Text; + } + + protected override void OnKeyDown (KeyEventArgs args) + { + if (args.KeyCode == Keys.Enter && IsAutoCompleteAvailable) + owner.Text = Text; + + base.OnKeyDown (args); + } + + internal override void OnAutoCompleteValueSelected (EventArgs args) + { + base.OnAutoCompleteValueSelected (args); + owner.Text = Text; + } + + internal void SetSelectable (bool selectable) + { + SetStyle (Widgetstyles.Selectable, selectable); + } + + internal void ActivateCaret (bool active) + { + if (active) + document.CaretHasFocus (); + else + document.CaretLostFocus (); + } + + internal override void OnTextUpdate () + { + base.OnTextUpdate (); + owner.OnTextUpdate (EventArgs.Empty); + } + + protected override void OnGotFocus (EventArgs e) + { + owner.Select (false, true); + } + + protected override void OnLostFocus (EventArgs e) + { + owner.Select (false, true); + } + + // We have to pass these events to our owner - MouseMove is not, however. + + protected override void OnMouseDown (MouseEventArgs e) + { + base.OnMouseDown (e); + owner.OnMouseDown (owner.TranslateMouseEventArgs (e)); + } + + protected override void OnMouseUp (MouseEventArgs e) + { + base.OnMouseUp (e); + owner.OnMouseUp (owner.TranslateMouseEventArgs (e)); + } + + protected override void OnMouseClick (MouseEventArgs e) + { + base.OnMouseClick (e); + owner.OnMouseClick (owner.TranslateMouseEventArgs (e)); + } + + protected override void OnMouseDoubleClick (MouseEventArgs e) + { + base.OnMouseDoubleClick (e); + owner.OnMouseDoubleClick (owner.TranslateMouseEventArgs (e)); + } + + public override bool Focused { + get { + return owner.Focused; + } + } + protected override void Dispose(bool disposing) + { + if (disposing ) { + // Prevents corruption of combobox text by disposed object + owner.EnabledChanged -= OwnerEnabledChangedHandler; + owner.LostFocus -= OwnerLostFocusHandler; + } + base.Dispose(disposing); + } + + internal override bool ActivateOnShow { get { return false; } } + } + + internal class ComboListBox : Widget + { + private ComboBox owner; + private VScrollBarLB vscrollbar_ctrl; + private int top_item; /* First item that we show the in the current page */ + private int last_item; /* Last visible item */ + internal int page_size; /* Number of listbox items per page */ + private Rectangle textarea_drawable; /* Rectangle of the drawable text area */ + + internal enum ItemNavigation + { + First, + Last, + Next, + Previous, + NextPage, + PreviousPage, + } + + #region UIA Framework: Properties + + internal int UIATopItem { + get { return top_item; } + } + + internal int UIALastItem { + get { return last_item; } + } + + internal ScrollBar UIAVScrollBar { + get { return vscrollbar_ctrl; } + } + + #endregion + + class VScrollBarLB : VScrollBar + { + public VScrollBarLB () + { + } + + internal override bool InternalCapture { + get { return Capture; } + set { } + } + + public void FireMouseDown (MouseEventArgs e) + { + if (!Visible) + return; + + e = TranslateEvent (e); + OnMouseDown (e); + } + + public void FireMouseUp (MouseEventArgs e) + { + if (!Visible) + return; + + e = TranslateEvent (e); + OnMouseUp (e); + } + + public void FireMouseMove (MouseEventArgs e) + { + if (!Visible) + return; + + e = TranslateEvent (e); + OnMouseMove (e); + } + + MouseEventArgs TranslateEvent (MouseEventArgs e) + { + Point loc = PointToClient (Widget.MousePosition); + return new MouseEventArgs (e.Button, e.Clicks, loc.X, loc.Y, e.Delta); + } + } + + public ComboListBox (ComboBox owner) + { + this.owner = owner; + top_item = 0; + last_item = 0; + page_size = 0; + + MouseWheel += new MouseEventHandler (OnMouseWheelCLB); + + SetStyle (Widgetstyles.UserPaint | Widgetstyles.AllPaintingInWmPaint, true); + SetStyle (Widgetstyles.ResizeRedraw | Widgetstyles.Opaque, true); + + this.is_visible = false; + + if (owner.DropDownStyle == ComboBoxStyle.Simple) + InternalBorderStyle = BorderStyle.Fixed3D; + else + InternalBorderStyle = BorderStyle.FixedSingle; + } + + protected override CreateParams CreateParams + { + get { + CreateParams cp = base.CreateParams; + if (owner == null || owner.DropDownStyle == ComboBoxStyle.Simple) + return cp; + + cp.Style ^= (int)WindowStyles.WS_CHILD; + cp.Style ^= (int)WindowStyles.WS_VISIBLE; + cp.Style |= (int)WindowStyles.WS_POPUP; + cp.ExStyle |= (int) WindowExStyles.WS_EX_TOOLWINDOW | (int) WindowExStyles.WS_EX_TOPMOST; + return cp; + } + } + + internal override bool InternalCapture { + get { + return Capture; + } + + set { + } + } + + internal override bool ActivateOnShow { get { return false; } } + #region Private Methods + + // Calcs the listbox area + internal void CalcListBoxArea () + { + int width, height; + bool show_scrollbar; + + if (owner.DropDownStyle == ComboBoxStyle.Simple) { + Rectangle area = owner.listbox_area; + width = area.Width; + height = area.Height; + show_scrollbar = owner.Items.Count * owner.ItemHeight > height; + + // No calculation needed + if (height <= 0 || width <= 0) + return; + + } + else { // DropDown or DropDownList + + width = owner.DropDownWidth; + int visible_items_count = (owner.Items.Count <= owner.MaxDropDownItems) ? owner.Items.Count : owner.MaxDropDownItems; + + if (owner.DrawMode == DrawMode.OwnerDrawVariable) { + height = 0; + for (int i = 0; i < visible_items_count; i++) { + height += owner.GetItemHeight (i); + } + + show_scrollbar = owner.Items.Count > owner.MaxDropDownItems; + + } else { + if (owner.DropDownHeight == default_drop_down_height) { // ignore DropDownHeight + height = owner.ItemHeight * visible_items_count; + show_scrollbar = owner.Items.Count > owner.MaxDropDownItems; + } else { + // ignore visible items count, and use manual height instead + height = owner.DropDownHeight; + show_scrollbar = (owner.Items.Count * owner.ItemHeight) > height; + } + } + } + + page_size = Math.Max (height / owner.ItemHeight, 1); + + ComboBoxStyle dropdown_style = owner.DropDownStyle; + if (!show_scrollbar) { + + if (vscrollbar_ctrl != null) + vscrollbar_ctrl.Visible = false; + if (dropdown_style != ComboBoxStyle.Simple) + height = owner.ItemHeight * owner.items.Count; + } else { + /* Need vertical scrollbar */ + if (vscrollbar_ctrl == null) { + vscrollbar_ctrl = new VScrollBarLB (); + vscrollbar_ctrl.Minimum = 0; + vscrollbar_ctrl.SmallChange = 1; + vscrollbar_ctrl.LargeChange = 1; + vscrollbar_ctrl.Maximum = 0; + vscrollbar_ctrl.ValueChanged += new EventHandler (VerticalScrollEvent); + Widgets.AddImplicit (vscrollbar_ctrl); + } + + vscrollbar_ctrl.Dock = DockStyle.Right; + + vscrollbar_ctrl.Maximum = owner.Items.Count - 1; + int large = page_size; + if (large < 1) + large = 1; + vscrollbar_ctrl.LargeChange = large; + vscrollbar_ctrl.Visible = true; + + int hli = HighlightedIndex; + if (hli > 0) { + hli = Math.Min (hli, vscrollbar_ctrl.Maximum); + vscrollbar_ctrl.Value = hli; + } + } + + var borderWidth = Hwnd.GetBorderWidth (CreateParams); + var borderAdjustment = dropdown_style == ComboBoxStyle.Simple ? new Size (0, 0) : + new Size (borderWidth.top + borderWidth.bottom, borderWidth.left + borderWidth.right); + Size = new Size (width, height + borderAdjustment.Height); + textarea_drawable = new Rectangle (ClientRectangle.Location, + new Size (width - borderAdjustment.Width, height)); + + if (vscrollbar_ctrl != null && show_scrollbar) + textarea_drawable.Width -= vscrollbar_ctrl.Width; + + last_item = LastVisibleItem (); + } + + private void Draw (Rectangle clip, Graphics dc) + { + dc.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (owner.BackColor), clip); + + if (owner.Items.Count > 0) { + + for (int i = top_item; i <= last_item; i++) { + Rectangle item_rect = GetItemDisplayRectangle (i, top_item); + + if (!clip.IntersectsWith (item_rect)) + continue; + + DrawItemState state = DrawItemState.None; + Color back_color = owner.BackColor; + Color fore_color = owner.ForeColor; + + if (i == HighlightedIndex) { + state |= DrawItemState.Selected; + back_color = SystemColors.Highlight; + fore_color = SystemColors.HighlightText; + + if (owner.DropDownStyle == ComboBoxStyle.DropDownList) { + state |= DrawItemState.Focus; + } + } + + owner.HandleDrawItem (new DrawItemEventArgs (dc, owner.Font, item_rect, + i, state, fore_color, back_color)); + } + } + } + + int highlighted_index = -1; + + public int HighlightedIndex { + get { return highlighted_index; } + set { + if (highlighted_index == value) + return; + + if (highlighted_index != -1 && highlighted_index < this.owner.Items.Count) + Invalidate (GetItemDisplayRectangle (highlighted_index, top_item)); + highlighted_index = value; + if (highlighted_index != -1) + Invalidate (GetItemDisplayRectangle (highlighted_index, top_item)); + } + } + + private Rectangle GetItemDisplayRectangle (int index, int top_index) + { + if (index < 0 || index >= owner.Items.Count) + throw new ArgumentOutOfRangeException ("GetItemRectangle index out of range."); + + Rectangle item_rect = new Rectangle (); + int height = owner.GetItemHeight (index); + + item_rect.X = 0; + item_rect.Width = textarea_drawable.Width; + if (owner.DrawMode == DrawMode.OwnerDrawVariable) { + item_rect.Y = 0; + for (int i = top_index; i < index; i++) + item_rect.Y += owner.GetItemHeight (i); + } else + item_rect.Y = height * (index - top_index); + + item_rect.Height = height; + return item_rect; + } + + public void HideWindow () + { + if (owner.DropDownStyle == ComboBoxStyle.Simple) + return; + + Capture = false; + Hide (); + owner.DropDownListBoxFinished (); + } + + private int IndexFromPointDisplayRectangle (int x, int y) + { + for (int i = top_item; i <= last_item; i++) { + if (GetItemDisplayRectangle (i, top_item).Contains (x, y) == true) + return i; + } + + return -1; + } + + public void InvalidateItem (int index) + { + if (Visible) + Invalidate (GetItemDisplayRectangle (index, top_item)); + } + + public int LastVisibleItem () + { + Rectangle item_rect; + int top_y = textarea_drawable.Y + textarea_drawable.Height; + int i = 0; + + for (i = top_item; i < owner.Items.Count; i++) { + item_rect = GetItemDisplayRectangle (i, top_item); + if (item_rect.Y + item_rect.Height > top_y) { + return i; + } + } + return i - 1; + } + + public void SetTopItem (int item) + { + if (top_item == item) + return; + top_item = item; + UpdateLastVisibleItem (); + Invalidate (); + } + + public int FirstVisibleItem () + { + return top_item; + } + + public void EnsureTop (int item) + { + if (owner.Items.Count == 0) + return; + if (vscrollbar_ctrl == null || !vscrollbar_ctrl.Visible) + return; + + int max = vscrollbar_ctrl.Maximum - page_size + 1; + if (item > max) + item = max; + else if (item < vscrollbar_ctrl.Minimum) + item = vscrollbar_ctrl.Minimum; + + vscrollbar_ctrl.Value = item; + } + + bool scrollbar_grabbed = false; + + bool InScrollBar { + get { + if (vscrollbar_ctrl == null || !vscrollbar_ctrl.is_visible) + return false; + + return vscrollbar_ctrl.Bounds.Contains (PointToClient (Widget.MousePosition)); + } + } + + protected override void OnMouseDown (MouseEventArgs e) + { + if (InScrollBar) { + vscrollbar_ctrl.FireMouseDown (e); + scrollbar_grabbed = true; + } + } + + protected override void OnMouseMove (MouseEventArgs e) + { + if (owner.DropDownStyle == ComboBoxStyle.Simple) + return; + + if (scrollbar_grabbed || (!Capture && InScrollBar)) { + vscrollbar_ctrl.FireMouseMove (e); + return; + } + + Point pt = PointToClient (Widget.MousePosition); + int index = IndexFromPointDisplayRectangle (pt.X, pt.Y); + + if (index != -1) + HighlightedIndex = index; + } + + protected override void OnMouseUp (MouseEventArgs e) + { + int index = IndexFromPointDisplayRectangle (e.X, e.Y); + + if (scrollbar_grabbed) { + vscrollbar_ctrl.FireMouseUp (e); + scrollbar_grabbed = false; + if (index != -1) + HighlightedIndex = index; + return; + } + + if (index == -1) { + HideWindow (); + return; + } + + bool is_change = owner.SelectedIndex != index; + + owner.SetSelectedIndex (index, true); + owner.OnSelectionChangeCommitted (new EventArgs ()); + + // If the user selected the already selected item, SelectedIndex + // won't fire these events, but .Net does, so we do it here + if (!is_change) { + owner.OnSelectedValueChanged (EventArgs.Empty); + owner.OnSelectedIndexChanged (EventArgs.Empty); + } + + HideWindow (); + } + + internal override void OnPaintInternal (PaintEventArgs pevent) + { + Draw (pevent.ClipRectangle,pevent.Graphics); + } + + public bool ShowWindow () + { + if (owner.DropDownStyle == ComboBoxStyle.Simple && owner.Items.Count == 0) + return false; + + HighlightedIndex = owner.SelectedIndex; + + CalcListBoxArea (); + // If the listbox would extend below the screen, move it above the textbox. + Rectangle scrn_rect = Screen.FromControl (owner).Bounds; + if (this.Location.Y + this.Height >= scrn_rect.Bottom) + this.Location = new Point (this.Location.X, this.Location.Y - (this.Height + owner.TextArea.Height)); + Show (); + + Refresh (); + owner.OnDropDown (EventArgs.Empty); + return true; + } + + public void UpdateLastVisibleItem () + { + last_item = LastVisibleItem (); + } + + public void Scroll (int delta) + { + if (delta == 0 || vscrollbar_ctrl == null || !vscrollbar_ctrl.Visible) + return; + + int max = vscrollbar_ctrl.Maximum - page_size + 1; + + int val = vscrollbar_ctrl.Value + delta; + if (val > max) + val = max; + else if (val < vscrollbar_ctrl.Minimum) + val = vscrollbar_ctrl.Minimum; + vscrollbar_ctrl.Value = val; + } + + private void OnMouseWheelCLB (object sender, MouseEventArgs me) + { + if (owner.Items.Count == 0) + return; + + int lines = me.Delta / 120 * SystemInformation.MouseWheelScrollLines; + Scroll (-lines); + } + + // Value Changed + private void VerticalScrollEvent (object sender, EventArgs e) + { + if (top_item == vscrollbar_ctrl.Value) + return; + + top_item = vscrollbar_ctrl.Value; + UpdateLastVisibleItem (); + Invalidate (); + } + + protected override void WndProc(ref Message m) { + if (m.Msg == (int)Msg.WM_SETFOCUS) { + owner.Select (false, true); + } + base.WndProc (ref m); + } + + #endregion Private Methods + } + } +} diff --git a/source/ShiftUI/Widgets/ContainerControl.cs b/source/ShiftUI/Widgets/ContainerControl.cs new file mode 100644 index 0000000..5d02f46 --- /dev/null +++ b/source/ShiftUI/Widgets/ContainerControl.cs @@ -0,0 +1,850 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2004 Novell, Inc. +// +// Authors: +// Peter Bartok [email protected] +// +// + + +using System.Collections; +using System.ComponentModel; +using System.ComponentModel.Design; +using System.Drawing; +using System.Runtime.InteropServices; +using System; + +namespace ShiftUI { + [ClassInterface (ClassInterfaceType.AutoDispatch)] + [ComVisible (true)] + public class ContainerWidget : ScrollableWidget, IContainerWidget { + private Widget active_Widget; + private Widget unvalidated_Widget; + private ArrayList pending_validation_chain; + + // This is an internal hack that allows some container Widgets + // to not auto select their child when they are activated + internal bool auto_select_child = true; + private SizeF auto_scale_dimensions; + private AutoScaleMode auto_scale_mode; + private bool auto_scale_mode_set; + private bool auto_scale_pending; + private bool is_auto_scaling; + + internal bool validation_failed; //track whether validation was cancelled by a validating Widget + + #region Public Constructors + public ContainerWidget() { + active_Widget = null; + unvalidated_Widget = null; + WidgetRemoved += new WidgetEventHandler(OnWidgetRemoved); + auto_scale_dimensions = SizeF.Empty; + auto_scale_mode = AutoScaleMode.Inherit; + } + #endregion // Public Constructors + + #region Public Instance Properties + [Browsable (false)] + //[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public Widget ActiveWidget { + get { + return active_Widget; + } + + set { + if (value==null || (active_Widget == value && active_Widget.Focused)) { + return; + } + + if (!Contains(value)) { + throw new ArgumentException("Cannot activate invisible or disabled Widget."); + } + + // Fire the enter and leave events if possible + Form form = FindForm (); + Widget active = GetMostDeeplyNestedActiveWidget (form == null ? this : form); + Widget common_ancestor = GetCommonContainer (active, value); + ArrayList chain = new ArrayList (); + ArrayList validation_chain = new ArrayList (); + Widget walk = active; + + // we split this up into three steps: + // 1. walk up the tree (if we need to) to our root, firing leave events. + // 2. validate. + // 3. walk down the tree (if we need to), firing enter events. + + // "our root" is either the common ancestor of the current active + // Widget and the new active Widget, or the current active Widget, + // or the new active Widget. That is, it's either one of these three + // configurations: + + // (1) root (2) new (3) current + // / \ / \ / \ + // ... ... ... ... ... ... + // / \ / \ + // current new current new + + + // note (3) doesn't require any upward walking, and no leave events are generated. + // (2) doesn't require any downward walking, and no enter events are generated. + + // as we walk up the tree, we generate a list of all Widgets which cause + // validation. After firing the leave events, we invoke (in order starting from + // the most deeply nested) their Validating event. If one sets CancelEventArgs.Cancel + // to true, we ignore the Widget the user wanted to set ActiveWidget to, and use + // the Validating Widget instead. + + bool fire_enter = true; + Widget root = common_ancestor; + + active_Widget = value; + + // Generate the leave messages + while (walk != common_ancestor && walk != null) { + if (walk == value) { + root = value; + fire_enter = false; + break; + } + walk.FireLeave (); + /* clear our idea of the active Widget as we go back up */ + if (walk is ContainerWidget) + ((ContainerWidget)walk).active_Widget = null; + + if (walk.CausesValidation) + validation_chain.Add (walk); + + walk = walk.Parent; + } + + // Validation can be postponed due to all the Widgets + // in the enter chain not causing validation. If we don't have any + // enter chain, it means that the selected Widget is a child and then + // we need to validate the Widgets anyway + bool postpone_validation; + Widget topmost_under_root = null; // topmost under root, in the *enter* chain + if (value == root) + postpone_validation = false; + else { + postpone_validation = true; + walk = value; + while (walk != root && walk != null) { + if (walk.CausesValidation) + postpone_validation = false; + + topmost_under_root = walk; + walk = walk.Parent; + } + } + + Widget failed_validation_Widget = PerformValidation (form == null ? this : form, postpone_validation, + validation_chain, topmost_under_root); + if (failed_validation_Widget != null) { + active_Widget = value = failed_validation_Widget; + fire_enter = true; + } + + if (fire_enter) { + walk = value; + while (walk != root && walk != null) { + chain.Add (walk); + walk = walk.Parent; + } + + if (root != null && walk == root && !(root is ContainerWidget)) + chain.Add (walk); + + for (int i = chain.Count - 1; i >= 0; i--) { + walk = (Widget) chain [i]; + walk.FireEnter (); + } + } + + walk = this; + Widget ctl = this; + while (walk != null) { + if (walk.Parent is ContainerWidget) { + ((ContainerWidget) walk.Parent).active_Widget = ctl; + ctl = walk.Parent; + } + walk = walk.Parent; + } + + if (this is Form) + CheckAcceptButton(); + + // Scroll Widget into view + ScrollWidgetIntoView(active_Widget); + + + walk = this; + ctl = this; + while (walk != null) { + if (walk.Parent is ContainerWidget) { + ctl = walk.Parent; + } + walk = walk.Parent; + } + + // Let the Widget know it's selected + if (ctl.InternalContainsFocus) + SendWidgetFocus (active_Widget); + } + } + + // Return the Widget where validation failed, and null otherwise + // @topmost_under_root is the Widget under the root in the enter chain, if any + // + // The process of validation happens as described: + // + // 1. Iterate over the nodes in the enter chain (walk down), looking for any node + // causing validation. If we can't find any, don't validate the current validation chain, but postpone it, + // saving it in the top_container.pending_validation_chain field, since we need to keep track of it later. + // If we have a previous pending_validation_chain, add the new nodes, making sure they are not repeated + // (this is computed in ActiveWidget and we receive if as the postpone_validation parameter). + // + // 2. If we found at least one node causing validation in the enter chain, try to validate the elements + // in pending_validation_chain, if any. Then continue with the ones receives as parameters. + // + // 3. Return null if all the validation performed successfully, and return the Widget where the validation + // failed otherwise. + // + private Widget PerformValidation (ContainerWidget top_container, bool postpone_validation, ArrayList validation_chain, + Widget topmost_under_root) + { + validation_failed = false; + + if (postpone_validation) { + AddValidationChain (top_container, validation_chain); + return null; + } + + // if not null, pending chain has always one element or more + if (top_container.pending_validation_chain != null) { + // if the topmost node in the enter chain is exactly the topmost + // int the validation chain, remove it, as .net does + int last_idx = top_container.pending_validation_chain.Count - 1; + if (topmost_under_root == top_container.pending_validation_chain [last_idx]) + top_container.pending_validation_chain.RemoveAt (last_idx); + + AddValidationChain (top_container, validation_chain); + validation_chain = top_container.pending_validation_chain; + top_container.pending_validation_chain = null; + } + + for (int i = 0; i < validation_chain.Count; i ++) { + if (!ValidateWidget ((Widget)validation_chain[i])) { + validation_failed = true; + return (Widget)validation_chain[i]; + } + } + + return null; + } + + // Add the elements in validation_chain to the pending validation chain stored in top_container + private void AddValidationChain (ContainerWidget top_container, ArrayList validation_chain) + { + if (validation_chain.Count == 0) + return; + + if (top_container.pending_validation_chain == null || top_container.pending_validation_chain.Count == 0) { + top_container.pending_validation_chain = validation_chain; + return; + } + + foreach (Widget c in validation_chain) + if (!top_container.pending_validation_chain.Contains (c)) + top_container.pending_validation_chain.Add (c); + } + + private bool ValidateWidget (Widget c) + { + CancelEventArgs e = new CancelEventArgs (); + + c.FireValidating (e); + + if (e.Cancel) + return false; + + c.FireValidated (); + return true; + } + + private Widget GetMostDeeplyNestedActiveWidget (ContainerWidget container) + { + Widget active = container.ActiveWidget; + while (active is ContainerWidget) { + if (((ContainerWidget)active).ActiveWidget == null) + break; + active = ((ContainerWidget)active).ActiveWidget; + } + return active; + } + + // Just in a separate method to make debugging a little easier, + // should eventually be rolled into ActiveWidget setter + private Widget GetCommonContainer (Widget active_Widget, Widget value) + { + Widget new_container = null; + Widget prev_container = active_Widget; + + while (prev_container != null) { + new_container = value.Parent; + while (new_container != null) { + if (new_container == prev_container) + return new_container; + new_container = new_container.Parent; + } + + prev_container = prev_container.Parent; + } + + return null; + } + + internal void SendWidgetFocus (Widget c) + { + if (c != null && c.IsHandleCreated) { + XplatUI.SetFocus (c.window.Handle); + } + } + + [Browsable (false)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + //[EditorBrowsable (EditorBrowsableState.Advanced)] + [Localizable (true)] + public SizeF AutoScaleDimensions { + get { + return auto_scale_dimensions; + } + + set { + if (auto_scale_dimensions != value) { + auto_scale_dimensions = value; + + PerformAutoScale (); + } + } + } + + protected SizeF AutoScaleFactor { + get { + if (auto_scale_dimensions.IsEmpty) + return new SizeF (1f, 1f); + + return new SizeF(CurrentAutoScaleDimensions.Width / auto_scale_dimensions.Width, + CurrentAutoScaleDimensions.Height / auto_scale_dimensions.Height); + } + } + + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Advanced)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public AutoScaleMode AutoScaleMode { + get { + return auto_scale_mode; + } + set { + if (this is Form) + (this as Form).AutoScale = false; + + if (auto_scale_mode != value) { + auto_scale_mode = value; + + if (auto_scale_mode_set) + auto_scale_dimensions = SizeF.Empty; + + auto_scale_mode_set = true; + + PerformAutoScale (); + } + } + } + + [Browsable (false)] + public override BindingContext BindingContext { + get { + if (base.BindingContext == null) { + base.BindingContext = new BindingContext(); + } + return base.BindingContext; + } + + set { + base.BindingContext = value; + } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Advanced)] + public SizeF CurrentAutoScaleDimensions { + get { + switch(auto_scale_mode) { + case AutoScaleMode.Dpi: + return TextRenderer.GetDpi (); + + case AutoScaleMode.Font: + Size s = TextRenderer.MeasureText ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890", Font); + int width = (int)Math.Round ((float)s.Width / 62f); + + return new SizeF (width, s.Height); + } + + return auto_scale_dimensions; + } + } + + [Browsable (false)] + //[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public Form ParentForm { + get { + Widget parent; + + parent = this.Parent; + + while (parent != null) { + if (parent is Form) { + return (Form)parent; + } + parent = parent.Parent; + } + + return null; + } + } + #endregion // Public Instance Properties + + #region Protected Instance Methods + protected override bool CanEnableIme { + get { return false; } + } + protected override CreateParams CreateParams { + get { + return base.CreateParams; + } + } + #endregion // Public Instance Methods + + #region Public Instance Methods + internal void PerformAutoScale (bool called_by_scale) + { + if ((AutoScaleMode == AutoScaleMode.Inherit) && !called_by_scale) + return; + + if ((layout_suspended > 0) && !called_by_scale) { + auto_scale_pending = true; + return; + } + // Set this first so we don't get called again from + // PerformDelayedAutoScale after ResumeLayout + auto_scale_pending = false; + + SizeF factor = AutoScaleFactor; + if (AutoScaleMode == AutoScaleMode.Inherit) { + ContainerWidget cc = FindContainer (this.Parent); + if (cc != null) + factor = cc.AutoScaleFactor; + } + if (factor != new SizeF (1F, 1F)) { + is_auto_scaling = true; + SuspendLayout (); + Scale (factor); + ResumeLayout (false); + is_auto_scaling = false; + } + + auto_scale_dimensions = CurrentAutoScaleDimensions; + } + + public void PerformAutoScale () + { + PerformAutoScale (false); + } + + internal void PerformDelayedAutoScale () + { + if (auto_scale_pending) + PerformAutoScale (); + } + + internal bool IsAutoScaling { + get { return is_auto_scaling; } + } + + [MonoTODO ("Stub, not implemented")] + static bool ValidateWarned; + public bool Validate() { + //throw new NotImplementedException(); + if (!ValidateWarned) { + Console.WriteLine("ContainerWidget.Validate is not yet implemented"); + ValidateWarned = true; + } + return true; + } + + public bool Validate (bool checkAutoValidate) + { + if ((checkAutoValidate && (AutoValidate != AutoValidate.Disable)) || !checkAutoValidate) + return Validate (); + + return true; + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public virtual bool ValidateChildren () + { + return ValidateChildren (ValidationConstraints.Selectable); + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public virtual bool ValidateChildren (ValidationConstraints validationConstraints) + { + bool recurse = !((validationConstraints & ValidationConstraints.ImmediateChildren) == ValidationConstraints.ImmediateChildren); + + foreach (Widget Widget in Widgets) + if (!ValidateNestedWidgets (Widget, validationConstraints, recurse)) + return false; + + return true; + } + + bool IContainerWidget.ActivateWidget(Widget Widget) { + return Select(Widget); + } + #endregion // Public Instance Methods + + #region Protected Instance Methods + //[EditorBrowsable (EditorBrowsableState.Advanced)] + protected override void AdjustFormScrollbars(bool displayScrollbars) { + base.AdjustFormScrollbars(displayScrollbars); + } + + protected override void Dispose(bool disposing) { + base.Dispose(disposing); + } + + // LAMESPEC This used to be documented, but it's not in code + // and no longer listed in MSDN2 + // //[EditorBrowsable (EditorBrowsableState.Advanced)] + // protected override void OnWidgetRemoved(WidgetEventArgs e) { + private void OnWidgetRemoved(object sender, WidgetEventArgs e) { + if (e.Widget == this.unvalidated_Widget) { + this.unvalidated_Widget = null; + } + + if (e.Widget == this.active_Widget) { + this.unvalidated_Widget = null; + } + + // base.OnWidgetRemoved(e); + } + + protected override void OnCreateWidget() { + base.OnCreateWidget(); + // MS seems to call this here, it gets the whole databinding process started + OnBindingContextChanged (EventArgs.Empty); + } + + protected override bool ProcessCmdKey (ref Message msg, Keys keyData) + { + if (ToolStripManager.ProcessCmdKey (ref msg, keyData) == true) + return true; + + return base.ProcessCmdKey (ref msg, keyData); + } + + //[EditorBrowsable (EditorBrowsableState.Advanced)] + protected override bool ProcessDialogChar(char charCode) { + if (GetTopLevel()) { + if (ProcessMnemonic(charCode)) { + return true; + } + } + return base.ProcessDialogChar(charCode); + } + + protected override bool ProcessDialogKey(Keys keyData) { + Keys key; + bool forward; + + key = keyData & Keys.KeyCode; + forward = true; + + switch (key) { + case Keys.Tab: { + if ((keyData & (Keys.Alt | Keys.Widget)) == Keys.None) { + if (ProcessTabKey ((Widget.ModifierKeys & Keys.Shift) == 0)) { + return true; + } + } + break; + } + + case Keys.Left: { + forward = false; + goto case Keys.Down; + } + + case Keys.Up: { + forward = false; + goto case Keys.Down; + } + + case Keys.Right: { + goto case Keys.Down; + } + case Keys.Down: { + if (SelectNextWidget(active_Widget, forward, false, false, true)) { + return true; + } + break; + } + + + } + return base.ProcessDialogKey(keyData); + } + + protected override bool ProcessMnemonic(char charCode) { + bool wrapped; + Widget c; + + wrapped = false; + c = active_Widget; + + do { + c = GetNextWidget(c, true); + if (c != null) { + // This is stupid. I want to be able to call c.ProcessMnemonic directly + if (c.ProcessWidgetMnemonic(charCode)) { + return(true); + } + continue; + } else { + if (wrapped) { + break; + } + wrapped = true; + } + } while (c != active_Widget); + + return false; + } + + protected virtual bool ProcessTabKey(bool forward) { + return SelectNextWidget(active_Widget, forward, true, true, false); + } + + protected override void Select(bool directed, bool forward) + { + if (Parent != null) { + IContainerWidget parent = Parent.GetContainerWidget (); + if (parent != null) { + parent.ActiveWidget = this; + } + } + + if (directed && auto_select_child) { + SelectNextWidget (null, forward, true, true, false); + } + } + + protected virtual void UpdateDefaultButton() { + // MS Internal + } + + //[EditorBrowsable (EditorBrowsableState.Advanced)] + protected override void WndProc(ref Message m) { + switch ((Msg) m.Msg) { + + case Msg.WM_SETFOCUS: + if (active_Widget != null) + Select (active_Widget); + else + base.WndProc (ref m); + break; + + default: + base.WndProc(ref m); + break; + } + } + #endregion // Protected Instance Methods + + #region Internal Methods + internal void ChildWidgetRemoved (Widget Widget) + { + ContainerWidget top_container = FindForm (); + if (top_container == null) + top_container = this; + + // Remove Widgets -as well as any sub Widget- that was in the pending validation chain + ArrayList pending_validation_chain = top_container.pending_validation_chain; + if (pending_validation_chain != null) { + RemoveChildrenFromValidation (pending_validation_chain, Widget); + + if (pending_validation_chain.Count == 0) + top_container.pending_validation_chain = null; + } + + if (Widget == active_Widget || Widget.Contains (active_Widget)) { + SelectNextWidget (this, true, true, true, true); + if (Widget == active_Widget || Widget.Contains (active_Widget)) { + active_Widget = null; + } + } + } + + // Check that this Widget (or any child) is included in the pending validation chain + bool RemoveChildrenFromValidation (ArrayList validation_chain, Widget c) + { + if (RemoveFromValidationChain (validation_chain, c)) + return true; + + foreach (Widget child in c.Widgets) + if (RemoveChildrenFromValidation (validation_chain, child)) + return true; + + return false; + } + + // Remove the top most Widget in the pending validation chain, as well as any children there, + // taking advantage of the fact that the chain is in reverse order of the Widget's hierarchy + bool RemoveFromValidationChain (ArrayList validation_chain, Widget c) + { + int idx = validation_chain.IndexOf (c); + if (idx > -1) { + pending_validation_chain.RemoveAt (idx--); + return true; + } + + return false; + } + + internal virtual void CheckAcceptButton() + { + // do nothing here, only called if it is a Form + } + + private bool ValidateNestedWidgets (Widget c, ValidationConstraints constraints, bool recurse) + { + bool validate_result = true; + + if (!c.CausesValidation) + validate_result = true; + else if (!ValidateThisWidget (c, constraints)) + validate_result = true; + else if (!ValidateWidget (c)) + validate_result = false; + + if (recurse) + foreach (Widget Widget in c.Widgets) + if (!ValidateNestedWidgets (Widget, constraints, recurse)) + return false; + + return validate_result; + } + + private bool ValidateThisWidget (Widget c, ValidationConstraints constraints) + { + if (constraints == ValidationConstraints.None) + return true; + + if ((constraints & ValidationConstraints.Enabled) == ValidationConstraints.Enabled && !c.Enabled) + return false; + + if ((constraints & ValidationConstraints.Selectable) == ValidationConstraints.Selectable && !c.GetStyle (Widgetstyles.Selectable)) + return false; + + if ((constraints & ValidationConstraints.TabStop) == ValidationConstraints.TabStop && !c.TabStop) + return false; + + if ((constraints & ValidationConstraints.Visible) == ValidationConstraints.Visible && !c.Visible) + return false; + + return true; + } + #endregion // Internal Methods + + protected override void OnParentChanged (EventArgs e) + { + base.OnParentChanged (e); + } + + //[EditorBrowsable (EditorBrowsableState.Advanced)] + protected override void OnFontChanged (EventArgs e) + { + base.OnFontChanged (e); + + if (AutoScaleMode == AutoScaleMode.Font) + PerformAutoScale (); + } + + protected override void OnLayout (LayoutEventArgs e) + { + base.OnLayout (e); + } + + AutoValidate auto_validate = AutoValidate.Inherit; + + [Browsable (false)] + [AmbientValue (AutoValidate.Inherit)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public virtual AutoValidate AutoValidate { + get { + return auto_validate; + } + + [MonoTODO("Currently does nothing with the setting")] + set { + if (auto_validate != value){ + auto_validate = value; + OnAutoValidateChanged (new EventArgs ()); + } + } + } + + internal bool ShouldSerializeAutoValidate () + { + return this.AutoValidate != AutoValidate.Inherit; + } + + static object OnValidateChanged = new object (); + + protected virtual void OnAutoValidateChanged (EventArgs e) + { + EventHandler eh = (EventHandler) (Events [OnValidateChanged]); + if (eh != null) + eh (this, e); + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public event EventHandler AutoValidateChanged { + add { Events.AddHandler (OnValidateChanged, value); } + remove { Events.RemoveHandler (OnValidateChanged, value); } + } + } +} diff --git a/source/ShiftUI/Widgets/DateTimePicker.cs b/source/ShiftUI/Widgets/DateTimePicker.cs new file mode 100644 index 0000000..183fd77 --- /dev/null +++ b/source/ShiftUI/Widgets/DateTimePicker.cs @@ -0,0 +1,2018 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2004-2005 Novell, Inc. +// +// Authors: +// John BouAntoun [email protected] +// Rolf Bjarne Kvinge [email protected] +// +// TODO: +// - wire in all events from monthcalendar + + +using System; +using System.Drawing; +using System.Collections; +using System.ComponentModel; +using System.Globalization; +using System.Runtime.InteropServices; +using System.Threading; + +namespace ShiftUI { + [ClassInterface (ClassInterfaceType.AutoDispatch)] + [DefaultBindingProperty ("Value")] + [ComVisible (true)] + [DefaultEvent("ValueChanged")] + [DefaultProperty("Value")] + //[Designer("ShiftUI.Design.DateTimePickerDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")] + [ToolboxWidget] + public class DateTimePicker : Widget { + + #region Public variables + + // this class has to have the specified hour, minute and second, as it says in msdn + //[EditorBrowsable (EditorBrowsableState.Never)] + [Browsable (false)] + public static readonly DateTime MaxDateTime = new DateTime (9998, 12, 31, 0, 0, 0); + + //[EditorBrowsable (EditorBrowsableState.Never)] + [Browsable (false)] + public static readonly DateTime MinDateTime = new DateTime (1753, 1, 1); + + internal const int check_box_size = 13; + internal const int check_box_space = 4; + + #endregion // Public variables + + #region Local variables + + protected static readonly Color DefaultMonthBackColor = ThemeEngine.Current.ColorWindow; + protected static readonly Color DefaultTitleBackColor = ThemeEngine.Current.ColorActiveCaption; + protected static readonly Color DefaultTitleForeColor = ThemeEngine.Current.ColorActiveCaptionText; + protected static readonly Color DefaultTrailingForeColor = SystemColors.GrayText; + + internal MonthCalendar month_calendar; + bool is_checked; + string custom_format; + LeftRightAlignment drop_down_align; + DateTimePickerFormat format; + DateTime max_date; + DateTime min_date; + bool show_check_box; + bool show_up_down; + DateTime date_value; + bool right_to_left_layout; + + // variables used for drawing and such + internal const int up_down_width = check_box_size; + internal bool is_drop_down_visible; + internal bool is_up_pressed; + internal bool is_down_pressed; + internal Timer updown_timer; + internal const int initial_timer_delay = 500; + internal const int subsequent_timer_delay = 100; + internal bool is_checkbox_selected; + + // variables for determining how to format the string + internal PartData[] part_data; + internal int editing_part_index = -1; + internal int editing_number = -1; + internal string editing_text; + + bool drop_down_button_entered; + #endregion // Local variables + + #region DateTimePickerAccessibleObject Subclass + [ComVisible(true)] + public class DateTimePickerAccessibleObject : WidgetAccessibleObject { + #region DateTimePickerAccessibleObject Local Variables + private new DateTimePicker owner; + #endregion // DateTimePickerAccessibleObject Local Variables + + #region DateTimePickerAccessibleObject Constructors + public DateTimePickerAccessibleObject(DateTimePicker owner) : base(owner) { + this.owner = owner; + } + #endregion // DateTimePickerAccessibleObject Constructors + + #region DateTimePickerAccessibleObject Properties + public override string KeyboardShortcut { + get { + return base.KeyboardShortcut; + } + } + + public override AccessibleRole Role { + get { + return base.Role; + } + } + + public override AccessibleStates State { + get { + AccessibleStates retval; + + retval = AccessibleStates.Default; + + if (owner.Checked) { + retval |= AccessibleStates.Checked; + } + + return retval; + } + } + + public override string Value { + get { + return owner.Text; + } + } + #endregion // DateTimePickerAccessibleObject Properties + } + #endregion // DateTimePickerAccessibleObject Sub-class + + #region public constructors + + // only public constructor + public DateTimePicker () { + + // initialise the month calendar + month_calendar = new MonthCalendar (this); + month_calendar.CalendarDimensions = new Size (1, 1); + month_calendar.MaxSelectionCount = 1; + month_calendar.ForeColor = Widget.DefaultForeColor; + month_calendar.BackColor = DefaultMonthBackColor; + month_calendar.TitleBackColor = DefaultTitleBackColor; + month_calendar.TitleForeColor = DefaultTitleForeColor; + month_calendar.TrailingForeColor = DefaultTrailingForeColor; + month_calendar.Visible = false; + // initialize the timer + updown_timer = new Timer(); + updown_timer.Interval = initial_timer_delay; + + + // initialise other variables + is_checked = true; + custom_format = null; + drop_down_align = LeftRightAlignment.Left; + format = DateTimePickerFormat.Long; + max_date = MaxDateTime; + min_date = MinDateTime; + show_check_box = false; + show_up_down = false; + date_value = DateTime.Now; + + is_drop_down_visible = false; + BackColor = SystemColors.Window; + ForeColor = SystemColors.WindowText; + + month_calendar.DateChanged += new DateRangeEventHandler (MonthCalendarDateChangedHandler); + month_calendar.DateSelected += new DateRangeEventHandler (MonthCalendarDateSelectedHandler); + month_calendar.LostFocus += new EventHandler (MonthCalendarLostFocusHandler); + updown_timer.Tick += new EventHandler (UpDownTimerTick); + KeyPress += new KeyPressEventHandler (KeyPressHandler); + KeyDown += new KeyEventHandler (KeyDownHandler); + GotFocus += new EventHandler (GotFocusHandler); + LostFocus += new EventHandler (LostFocusHandler); + MouseDown += new MouseEventHandler (MouseDownHandler); + MouseUp += new MouseEventHandler (MouseUpHandler); + MouseEnter += new EventHandler (OnMouseEnter); + MouseLeave += new EventHandler (OnMouseLeave); + MouseMove += new MouseEventHandler (OnMouseMove); + Paint += new PaintEventHandler (PaintHandler); + Resize += new EventHandler (ResizeHandler); + SetStyle (Widgetstyles.UserPaint | Widgetstyles.StandardClick, false); + SetStyle (Widgetstyles.FixedHeight, true); + SetStyle (Widgetstyles.Selectable, true); + + CalculateFormats (); + } + + #endregion + + #region public properties + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public override Color BackColor { + set { + base.BackColor = value; + } + get { + return base.BackColor; + } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public override Image BackgroundImage { + set { + base.BackgroundImage = value; + } + get { + return base.BackgroundImage; + } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public override ImageLayout BackgroundImageLayout { + get{ + return base.BackgroundImageLayout; + } + set { + base.BackgroundImageLayout = value; + } + } + + [AmbientValue(null)] + [Localizable(true)] + public Font CalendarFont { + set { + month_calendar.Font = value; + } + get { + return month_calendar.Font; + } + } + + public Color CalendarForeColor { + set { + month_calendar.ForeColor = value; + } + get { + return month_calendar.ForeColor; + } + } + + public Color CalendarMonthBackground { + set { + month_calendar.BackColor = value; + } + get { + return month_calendar.BackColor; + } + } + + public Color CalendarTitleBackColor { + set { + month_calendar.TitleBackColor = value; + } + get { + return month_calendar.TitleBackColor; + } + } + + public Color CalendarTitleForeColor { + set { + month_calendar.TitleForeColor = value; + } + get { + return month_calendar.TitleForeColor; + } + } + + public Color CalendarTrailingForeColor { + set { + month_calendar.TrailingForeColor = value; + } + get { + return month_calendar.TrailingForeColor; + } + } + + // when checked the value is grayed out + [Bindable(true)] + [DefaultValue(true)] + public bool Checked { + set { + if (is_checked != value) { + is_checked = value; + // invalidate the value inside this control + if (ShowCheckBox) { + for (int i = 0; i < part_data.Length; i++) + part_data [i].Selected = false; + Invalidate (date_area_rect); + OnUIAChecked (); + OnUIASelectionChanged (); + } + } + } + get { + return is_checked; + } + } + + // the custom format string to format this control with + [Localizable (true)] + [DefaultValue(null)] + [RefreshProperties(RefreshProperties.Repaint)] + public string CustomFormat { + set { + if (custom_format != value) { + custom_format = value; + if (this.Format == DateTimePickerFormat.Custom) { + CalculateFormats (); + } + } + } + get { + return custom_format; + } + } + + //[EditorBrowsable (EditorBrowsableState.Never)] + protected override bool DoubleBuffered { + get { + return base.DoubleBuffered; + } + set { + base.DoubleBuffered = value; + } + } + + // which side the drop down is to be aligned on + [DefaultValue(LeftRightAlignment.Left)] + [Localizable(true)] + public LeftRightAlignment DropDownAlign { + set { + if (drop_down_align != value) { + drop_down_align = value; + } + } + get { + return drop_down_align; + } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public override Color ForeColor { + set { + base.ForeColor = value; + } + get { + return base.ForeColor; + } + } + + // the format of the date time picker text, default is long + [RefreshProperties(RefreshProperties.Repaint)] + public DateTimePickerFormat Format { + set { + if (format != value) { + format = value; + RecreateHandle (); // MS recreates the handle on every format change. + CalculateFormats (); + this.OnFormatChanged (EventArgs.Empty); + // invalidate the value inside this control + this.Invalidate (date_area_rect); + } + } + get { + return format; + } + } + + public DateTime MaxDate { + set { + if (value < min_date) { + string msg = string.Format (CultureInfo.CurrentCulture, + "'{0}' is not a valid value for 'MaxDate'. 'MaxDate' " + + "must be greater than or equal to MinDate.", + value.ToString ("G")); + throw new ArgumentOutOfRangeException ("MaxDate", msg); + } + if (value > MaxDateTime) { + string msg = string.Format (CultureInfo.CurrentCulture, + "DateTimePicker does not support dates after {0}.", + MaxDateTime.ToString ("G", CultureInfo.CurrentCulture)); + throw new ArgumentOutOfRangeException ("MaxDate", msg); + } + if (max_date != value) { + max_date = value; + if (Value > max_date) { + Value = max_date; + // invalidate the value inside this control + this.Invalidate (date_area_rect); + } + OnUIAMaximumChanged (); + } + } + get { + return max_date; + } + } + + public static DateTime MaximumDateTime { + get { + return MaxDateTime; + } + } + + public DateTime MinDate { + set { + // If the user tries to set DateTime.MinValue, fix it to + // DateTimePicker's minimum. + if (value == DateTime.MinValue) + value = MinDateTime; + + if (value > MaxDate) { + string msg = string.Format (CultureInfo.CurrentCulture, + "'{0}' is not a valid value for 'MinDate'. 'MinDate' " + + "must be less than MaxDate.", + value.ToString ("G")); + throw new ArgumentOutOfRangeException ("MinDate", msg); + } + if (value < MinDateTime) { + string msg = string.Format (CultureInfo.CurrentCulture, + "DateTimePicker does not support dates before {0}.", + MinDateTime.ToString ("G", CultureInfo.CurrentCulture)); + throw new ArgumentOutOfRangeException ("MinDate", msg); + } + if (min_date != value) { + min_date = value; + if (Value < min_date) { + Value = min_date; + // invalidate the value inside this control + this.Invalidate (date_area_rect); + } + OnUIAMinimumChanged (); + } + } + get { + return min_date; + } + } + + public static DateTime MinimumDateTime { + get { + return MinDateTime; + } + } + + //[EditorBrowsable (EditorBrowsableState.Never)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + [Browsable (false)] + public new Padding Padding { + get { return base.Padding; } + set { base.Padding = value; } + } + + // the prefered height to draw this control using current font + [Browsable(false)] + //[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public int PreferredHeight { + get { + // Make it proportional + return (int) Math.Ceiling (Font.Height * 1.5); + } + } + + [DefaultValue (false)] + [Localizable (true)] + public virtual bool RightToLeftLayout { + get { + return right_to_left_layout; + } + set { + if (right_to_left_layout != value) { + right_to_left_layout = value; + OnRightToLeftLayoutChanged (EventArgs.Empty); + } + } + } + + // whether or not the check box is shown + [DefaultValue(false)] + public bool ShowCheckBox { + set { + if (show_check_box != value) { + show_check_box = value; + // invalidate the value inside this control + this.Invalidate (date_area_rect); + OnUIAShowCheckBoxChanged (); + } + } + get { + return show_check_box; + } + } + + // if true show the updown control, else popup the monthcalendar + [DefaultValue(false)] + public bool ShowUpDown { + set { + if (show_up_down != value) { + show_up_down = value; + // need to invalidate the whole control + this.Invalidate (); + OnUIAShowUpDownChanged (); + } + } + get { + return show_up_down; + } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Advanced)] + //[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public override string Text { + set { + DateTime parsed_value; + + if (value == null || value == string.Empty) { + date_value = DateTime.Now; + OnValueChanged (EventArgs.Empty); + OnTextChanged (EventArgs.Empty); + return; + } + + if (format == DateTimePickerFormat.Custom) { + // TODO: if the format is a custom format we need to do a custom parse here + // This implementation will fail if the custom format is set to something that can + // be a standard datetime format string + // http://msdn2.microsoft.com/en-us/library/az4se3k1.aspx + parsed_value = DateTime.ParseExact (value, GetExactFormat (), null); + } else { + parsed_value = DateTime.ParseExact (value, GetExactFormat (), null); + } + + if (date_value != parsed_value) { + Value = parsed_value; + } + } + get { + if (!IsHandleCreated) + return ""; + + if (format == DateTimePickerFormat.Custom) { + System.Text.StringBuilder result = new System.Text.StringBuilder (); + for (int i = 0; i < part_data.Length; i++) { + result.Append(part_data[i].GetText(date_value)); + } + return result.ToString (); + } else { + return Value.ToString (GetExactFormat ()); + } + } + } + + [Bindable(true)] + [RefreshProperties(RefreshProperties.All)] + public DateTime Value { + set { + if (date_value != value) { + if (value < MinDate || value > MaxDate) + throw new ArgumentOutOfRangeException ("value", "value must be between MinDate and MaxDate"); + + date_value = value; + this.OnValueChanged (EventArgs.Empty); + this.Invalidate (date_area_rect); + } + } + get { + return date_value; + } + } + + #endregion // public properties + + #region public methods + + // just return the text value + public override string ToString () { + return this.Text; + } + + #endregion // public methods + + #region public events + static object CloseUpEvent = new object (); + static object DropDownEvent = new object (); + static object FormatChangedEvent = new object (); + static object ValueChangedEvent = new object (); + static object RightToLeftLayoutChangedEvent = new object (); + + // raised when the monthcalendar is closed + public event EventHandler CloseUp { + add { Events.AddHandler (CloseUpEvent, value); } + remove { Events.RemoveHandler (CloseUpEvent, value); } + } + + // raised when the monthcalendar is opened + public event EventHandler DropDown { + add { Events.AddHandler (DropDownEvent, value); } + remove { Events.RemoveHandler (DropDownEvent, value); } + } + + // raised when the format of the value is changed + public event EventHandler FormatChanged { + add { Events.AddHandler (FormatChangedEvent, value); } + remove { Events.RemoveHandler (FormatChangedEvent, value); } + } + + // raised when the date Value is changed + public event EventHandler ValueChanged { + add { Events.AddHandler (ValueChangedEvent, value); } + remove { Events.RemoveHandler (ValueChangedEvent, value); } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler BackColorChanged { + add { + base.BackColorChanged += value; + } + + remove { + base.BackColorChanged -= value; + } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler BackgroundImageChanged { + add { + base.BackgroundImageChanged += value; + } + + remove { + base.BackgroundImageChanged -= value; + } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler BackgroundImageLayoutChanged { + add + { + base.BackgroundImageLayoutChanged += value; + } + + remove + { + base.BackgroundImageLayoutChanged -= value; + } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler Click { + add { + base.Click += value; + } + remove { + base.Click -= value; + } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler DoubleClick { + add { + base.DoubleClick += value; + } + remove { + base.DoubleClick -= value; + } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler ForeColorChanged { + add { + base.ForeColorChanged += value; + } + + remove { + base.ForeColorChanged -= value; + } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event MouseEventHandler MouseClick { + add { + base.MouseClick += value; + } + remove { + base.MouseClick -= value; + } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event MouseEventHandler MouseDoubleClick { + add { + base.MouseDoubleClick += value; + } + remove { + base.MouseDoubleClick -= value; + } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler PaddingChanged { + add + { + base.PaddingChanged += value; + } + remove + { + base.PaddingChanged -= value; + } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event PaintEventHandler Paint { + add { + base.Paint += value; + } + + remove { + base.Paint -= value; + } + } + + public event EventHandler RightToLeftLayoutChanged { + add { + Events.AddHandler (RightToLeftLayoutChangedEvent, value); + } + remove { + Events.RemoveHandler (RightToLeftLayoutChangedEvent, value); + } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Advanced)] + public new event EventHandler TextChanged { + add { + base.TextChanged += value; + } + + remove { + base.TextChanged -= value; + } + } + #endregion // public events + + #region protected properties + + // not sure why we're overriding this one + protected override CreateParams CreateParams { + get { + return base.CreateParams; + } + } + + // specify the default size for this control + protected override Size DefaultSize { + get { + // todo actually measure this properly + return new Size (200, PreferredHeight); + } + } + + #endregion // protected properties + + #region protected methods + + // not sure why we're overriding this one + protected override AccessibleObject CreateAccessibilityInstance () { + return base.CreateAccessibilityInstance (); + } + + // not sure why we're overriding this one + protected override void CreateHandle () { + base.CreateHandle (); + } + + // not sure why we're overriding this one + protected override void DestroyHandle () { + base.DestroyHandle (); + } + + // find out if this key is an input key for us, depends on which date part is focused + protected override bool IsInputKey (Keys keyData) { + switch (keyData) + { + case Keys.Up: + case Keys.Down: + case Keys.Left: + case Keys.Right: + return true; + } + return false; + } + + // raises the CloseUp event + protected virtual void OnCloseUp (EventArgs eventargs) { + EventHandler eh = (EventHandler)(Events [CloseUpEvent]); + if (eh != null) + eh (this, eventargs); + } + + // raise the drop down event + protected virtual void OnDropDown (EventArgs eventargs) { + EventHandler eh = (EventHandler)(Events [DropDownEvent]); + if (eh != null) + eh (this, eventargs); + } + + protected override void OnFontChanged(EventArgs e) { + // FIXME - do we need to update/invalidate/recalc our stuff? + month_calendar.Font = Font; + Size = new Size (Size.Width, PreferredHeight); + + base.OnFontChanged (e); + } + + // raises the format changed event + protected virtual void OnFormatChanged (EventArgs e) { + EventHandler eh = (EventHandler)(Events [FormatChangedEvent]); + if (eh != null) + eh (this, e); + } + + protected override void OnHandleCreated (EventArgs e) { + base.OnHandleCreated(e); + } + protected override void OnHandleDestroyed (EventArgs e) { + base.OnHandleDestroyed(e); + } + + //[EditorBrowsable (EditorBrowsableState.Advanced)] + protected virtual void OnRightToLeftLayoutChanged (EventArgs e) { + EventHandler eh = (EventHandler) Events [RightToLeftLayoutChangedEvent]; + if (eh != null) + eh (this, e); + } + + // not sure why we're overriding this one + protected override void OnSystemColorsChanged (EventArgs e) { + base.OnSystemColorsChanged (e); + } + + // raise the ValueChanged event + protected virtual void OnValueChanged (EventArgs eventargs) { + EventHandler eh = (EventHandler)(Events [ValueChangedEvent]); + if (eh != null) + eh (this, eventargs); + } + + // SetBoundsCore was removed from the 2.0 public API, so + // I had to do this hack instead. :/ + internal override int OverrideHeight (int height) + { + return DefaultSize.Height; + } + + // not sure why we're overriding this + protected override void WndProc (ref Message m) { + base.WndProc (ref m); + } + + #endregion // protected methods + + #region internal / private properties + + // this is the region that the date and the check box is drawn on + internal Rectangle date_area_rect { + get { + return ThemeEngine.Current.DateTimePickerGetDateArea (this); + } + } + + internal Rectangle CheckBoxRect { + get { + Rectangle retval = new Rectangle (check_box_space, ClientSize.Height / 2 - check_box_size / 2, + check_box_size, check_box_size); + return retval; + } + } + + // the rectangle for the drop down arrow + internal Rectangle drop_down_arrow_rect { + get { + return ThemeEngine.Current.DateTimePickerGetDropDownButtonArea (this); + } + } + + // the part of the date that is currently hilighted + internal Rectangle hilight_date_area { + get { + // TODO: put hilighted part calculation in here + return Rectangle.Empty; + } + } + + internal bool DropDownButtonEntered { + get { return drop_down_button_entered; } + } + + #endregion + + #region internal / private methods + + private void ResizeHandler (object sender, EventArgs e) + { + Invalidate (); + } + + private void UpDownTimerTick (object sender, EventArgs e) + { + if (updown_timer.Interval == initial_timer_delay) + updown_timer.Interval = subsequent_timer_delay; + + if (is_down_pressed) + IncrementSelectedPart (-1); + else if (is_up_pressed) + IncrementSelectedPart (1); + else + updown_timer.Enabled = false; + } + + // calculates the maximum width + internal Single CalculateMaxWidth(string format, Graphics gr, StringFormat string_format) + { + SizeF size; + float result = 0; + string text; + Font font = this.Font; + + switch (format) + { + case "M": + case "MM": + case "MMM": + case "MMMM": + for (int i = 1; i <= 12; i++) { + text = PartData.GetText (Value.AddMonths (i), format); + size = gr.MeasureString (text, font, int.MaxValue, string_format); + result = Math.Max (result, size.Width); + } + return result; + case "d": + case "dd": + case "ddd": + case "dddd": + for (int i = 1; i <= 12; i++) { + text = PartData.GetText (Value.AddDays (i), format); + size = gr.MeasureString (text, font, int.MaxValue, string_format); + result = Math.Max (result, size.Width); + } + return result; + case "h": + case "hh": + for (int i = 1; i <= 12; i++) { + text = PartData.GetText (Value.AddHours (i), format); + size = gr.MeasureString (text, font, int.MaxValue, string_format); + result = Math.Max (result, size.Width); + } + return result; + case "H": + case "HH": + for (int i = 1; i <= 24; i++) { + text = PartData.GetText (Value.AddDays (i), format); + size = gr.MeasureString (text, font, int.MaxValue, string_format); + result = Math.Max (result, size.Width); + } + return result; + case "m": + case "mm": + for (int i = 1; i <= 60; i++) { + text = PartData.GetText (Value.AddMinutes (i), format); + size = gr.MeasureString (text, font, int.MaxValue, string_format); + result = Math.Max (result, size.Width); + } + return result; + case "s": + case "ss": + for (int i = 1; i <= 60; i++) { + text = PartData.GetText (Value.AddSeconds (i), format); + size = gr.MeasureString (text, font, int.MaxValue, string_format); + result = Math.Max (result, size.Width); + } + return result; + case "t": + case "tt": + for (int i = 1; i <= 2; i++) { + text = PartData.GetText (Value.AddHours (i * 12), format); + size = gr.MeasureString (text, font, int.MaxValue, string_format); + result = Math.Max (result, size.Width); + } + return result; + case "y": + case "yy": + case "yyyy": + // Actually all the allowed year values are between MinDateTime and MaxDateTime, + // which are 4 digits always + text = PartData.GetText (Value, format); + size = gr.MeasureString (text, font, int.MaxValue, string_format); + result = Math.Max (result, size.Width); + return result; + default: + return gr.MeasureString (format, font, int.MaxValue, string_format).Width; + } + } + + // returns the format of the date as a string + // (i.e. resolves the Format enum values to it's corresponding string format) + // Why CurrentCulture and not CurrentUICulture is explained here: + // http://blogs.msdn.com/michkap/archive/2007/01/11/1449754.aspx + private string GetExactFormat() + { + switch (this.format) { + case DateTimePickerFormat.Long: + return Thread.CurrentThread.CurrentCulture.DateTimeFormat.LongDatePattern; + case DateTimePickerFormat.Short: + return Thread.CurrentThread.CurrentCulture.DateTimeFormat.ShortDatePattern; + case DateTimePickerFormat.Time: + return Thread.CurrentThread.CurrentCulture.DateTimeFormat.LongTimePattern; + case DateTimePickerFormat.Custom: + return this.custom_format == null ? String.Empty : this.custom_format; + default: + return Thread.CurrentThread.CurrentCulture.DateTimeFormat.LongDatePattern; + } + } + + private void CalculateFormats() + { + string real_format; + System.Text.StringBuilder literal = new System.Text.StringBuilder (); + System.Collections.ArrayList formats = new ArrayList (); + bool is_literal = false; + char lastch = (char) 0; + char ch; + + real_format = GetExactFormat (); + + // parse the format string + for (int i = 0; i < real_format.Length; i++) + { + ch = real_format [i]; + + if (is_literal && ch != '\'') + { + literal.Append (ch); + continue; + } + + switch (ch) + { + case 't': + case 'd': + case 'h': + case 'H': + case 'm': + case 'M': + case 's': + case 'y': + case 'g': // Spec says nothing about g, but it seems to be treated like spaces. + if (!(lastch == ch || lastch == 0) && literal.Length != 0) + { + formats.Add (new PartData(literal.ToString (), false, this)); + literal.Length = 0; + } + literal.Append (ch); + break; + case '\'': + if (is_literal && i < real_format.Length - 1 && real_format [i + 1] == '\'') { + literal.Append (ch); + i++; + break; + } + if (literal.Length == 0) { + is_literal = !is_literal; + break; + } + formats.Add (new PartData (literal.ToString (), is_literal, this)); + literal.Length = 0; + is_literal = !is_literal; + break; + default: + if (literal.Length != 0) + { + formats.Add (new PartData(literal.ToString (), false, this)); + literal.Length = 0; + } + formats.Add (new PartData (ch.ToString(), true, this)); + break; + + } + lastch = ch; + } + if (literal.Length >= 0) + formats.Add (new PartData (literal.ToString (), is_literal, this)); + + part_data = new PartData [formats.Count]; + formats.CopyTo (part_data); + } + + private Point CalculateDropDownLocation (Rectangle parent_control_rect, Size child_size, bool align_left) + { + // default bottom left + Point location = new Point(parent_control_rect.Left + 5, parent_control_rect.Bottom); + // now adjust the alignment + if (!align_left) { + location.X = parent_control_rect.Right - child_size.Width; + } + + Point screen_location = PointToScreen (location); + Rectangle working_area = Screen.FromControl(this).WorkingArea; + // now adjust if off the right side of the screen + if (screen_location.X < working_area.X) { + screen_location.X = working_area.X; + } + // now adjust if it should be displayed above control + if (screen_location.Y + child_size.Height > working_area.Bottom) { + screen_location.Y -= (parent_control_rect.Height + child_size.Height); + } + + // since the parent of the month calendar is the form, adjust accordingly. + if (month_calendar.Parent != null) { + screen_location = month_calendar.Parent.PointToClient(screen_location); + } + + return screen_location; + } + + // actually draw this control + internal void Draw (Rectangle clip_rect, Graphics dc) + { + ThemeEngine.Current.DrawDateTimePicker (dc, clip_rect, this); + } + + // drop the calendar down + internal void DropDownMonthCalendar () + { + EndDateEdit (true); + // ensure the right date is set for the month_calendar + month_calendar.SetDate (this.date_value); + // get a rectangle that has the dimensions of the text area, + // but the height of the dtp control. + Rectangle align_area = this.date_area_rect; + align_area.Y = this.ClientRectangle.Y; + align_area.Height = this.ClientRectangle.Height; + + // establish the month calendar's location + month_calendar.Location = CalculateDropDownLocation ( + align_area, + month_calendar.Size, + (this.DropDownAlign == LeftRightAlignment.Left)); + month_calendar.Show (); + month_calendar.Focus (); + month_calendar.Capture = true; + + // fire any registered events + // XXX should this just call OnDropDown? + EventHandler eh = (EventHandler)(Events [DropDownEvent]); + if (eh != null) + eh (this, EventArgs.Empty); + } + + // hide the month calendar + internal void HideMonthCalendar () + { + this.is_drop_down_visible = false; + Invalidate (drop_down_arrow_rect); + month_calendar.Capture = false; + if (month_calendar.Visible) { + month_calendar.Hide (); + } + Focus (); + } + + private int GetSelectedPartIndex() + { + for (int i = 0; i < part_data.Length; i++) + { + if (part_data[i].Selected && !part_data[i].is_literal) + return i; + } + return -1; + } + + internal void IncrementSelectedPart(int delta) + { + int selected_index = GetSelectedPartIndex(); + + if (selected_index == -1) { + return; + } + + EndDateEdit (false); + + DateTimePart dt_part = part_data [selected_index].date_time_part; + switch (dt_part) + { + case DateTimePart.Day: + if (delta < 0) { + if (Value.Day == 1) + SetPart(DateTime.DaysInMonth(Value.Year, Value.Month), dt_part); + else + SetPart(Value.Day + delta, dt_part); + } else { + if (Value.Day == DateTime.DaysInMonth(Value.Year, Value.Month)) + SetPart(1, dt_part); + else + SetPart(Value.Day + delta, dt_part) ; + } + break; + case DateTimePart.DayName: + Value = Value.AddDays(delta); + break; + case DateTimePart.AMPMHour: + case DateTimePart.Hour: + SetPart(Value.Hour + delta, dt_part); + break; + case DateTimePart.Minutes: + SetPart(Value.Minute + delta, dt_part); + break; + case DateTimePart.Month: + SetPart (Value.Month + delta, dt_part, true); + break; + case DateTimePart.Seconds: + SetPart(Value.Second + delta, dt_part); + break; + case DateTimePart.AMPMSpecifier: + int hour = Value.Hour; + hour = hour >= 0 && hour <= 11 ? hour + 12 : hour - 12; + SetPart (hour, DateTimePart.Hour); + break; + case DateTimePart.Year: + SetPart(Value.Year + delta, dt_part); + break; + } + } + + internal void SelectPart (int index) + { + is_checkbox_selected = false; + for (int i = 0; i < part_data.Length; i++) + { + part_data[i].Selected = (i == index); + } + + Invalidate (); + OnUIASelectionChanged (); + } + + internal void SelectNextPart() + { + int selected_index; + if (is_checkbox_selected) { + for (int i = 0; i < part_data.Length; i++) + { + if (!part_data[i].is_literal) + { + is_checkbox_selected = false; + part_data[i].Selected = true; + Invalidate(); + break; + } + } + } else { + selected_index = GetSelectedPartIndex(); + if (selected_index >= 0) + part_data [selected_index].Selected = false; + + for (int i = selected_index + 1; i < part_data.Length; i++) + { + if (!part_data[i].is_literal) + { + part_data [i].Selected = true; + Invalidate(); + break; + } + } + if (GetSelectedPartIndex() == -1) + { // if no part was found before the end, look from the beginning + if (ShowCheckBox) + { + is_checkbox_selected = true; + Invalidate(); + } + else + { + for (int i = 0; i <= selected_index; i++) + { + if (!part_data[i].is_literal) + { + part_data[i].Selected = true; + Invalidate(); + break; + } + } + } + } + } + + OnUIASelectionChanged (); + } + + internal void SelectPreviousPart() + { + if (is_checkbox_selected) + { + for (int i = part_data.Length - 1; i >= 0; i--) + { + if (!part_data[i].is_literal) + { + is_checkbox_selected = false; + part_data[i].Selected = true; + Invalidate(); + break; + } + } + } + else + { + int selected_index = GetSelectedPartIndex(); + + if (selected_index >= 0) + part_data[selected_index].Selected = false; + + for (int i = selected_index - 1; i >= 0; i--) + { + if (!part_data[i].is_literal) + { + part_data[i].Selected = true; + Invalidate(); + break; + } + } + if (GetSelectedPartIndex() == -1) + { // if no part was found before the beginning, look from the end + if (ShowCheckBox) + { + is_checkbox_selected = true; + Invalidate(); + } + else + { + for (int i = part_data.Length - 1; i >= selected_index; i--) + { + if (!part_data[i].is_literal) + { + part_data[i].Selected = true; + Invalidate(); + break; + } + } + } + } + } + + OnUIASelectionChanged (); + } + + // raised by key down events. + private void KeyDownHandler(object sender, KeyEventArgs e) + { + switch (e.KeyCode) + { + case Keys.Add: + case Keys.Up: + { + if (ShowCheckBox && Checked == false) + break; + IncrementSelectedPart(1); + e.Handled = true; + break; + } + case Keys.Subtract: + case Keys.Down: + { + if (ShowCheckBox && Checked == false) + break; + IncrementSelectedPart(-1); + e.Handled = true; + break; + } + case Keys.Left: + {// select the next part to the left + if (ShowCheckBox && Checked == false) + break; + SelectPreviousPart(); + e.Handled = true; + break; + } + case Keys.Right: + {// select the next part to the right + if (ShowCheckBox && Checked == false) + break; + SelectNextPart(); + e.Handled = true; + break; + } + case Keys.F4: + if (!e.Alt && !is_drop_down_visible) { + DropDownMonthCalendar (); + e.Handled = true; + } + + break; + } + } + + // raised by any key down events + private void KeyPressHandler (object sender, KeyPressEventArgs e) + { + switch (e.KeyChar) { + case ' ': + if (show_check_box && is_checkbox_selected) + Checked = !Checked; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + int number = e.KeyChar - (int) '0'; + int selected_index = GetSelectedPartIndex(); + if (selected_index == -1) + break; + if (!part_data[selected_index].is_numeric_format) + break; + + DateTimePart dt_part = part_data [selected_index].date_time_part; + if (editing_part_index < 0) { + editing_part_index = selected_index; + editing_number = 0; + editing_text = String.Empty; + } + + editing_text += number.ToString (); + int date_part_max_length = 0; + + switch (dt_part) { + case DateTimePart.Day: + case DateTimePart.Month: + case DateTimePart.Seconds: + case DateTimePart.Minutes: + case DateTimePart.AMPMHour: + case DateTimePart.Hour: + date_part_max_length = 2; + break; + case DateTimePart.Year: + date_part_max_length = 4; + break; + } + + editing_number = editing_number * 10 + number; + if (editing_text.Length >= date_part_max_length) + EndDateEdit (false); + + Invalidate (date_area_rect); + break; + default: + break; + } + e.Handled = true; + } + + private void EndDateEdit (bool invalidate) + { + if (editing_part_index == -1) + return; + + PartData part = part_data [editing_part_index]; + if (part.date_time_part == DateTimePart.Year) { // Special case + // Infer, like .Net does + if (editing_number > 0 && editing_number < 30) + editing_number += 2000; + else if (editing_number >= 30 && editing_number < 100) + editing_number += 1900; + } + + SetPart (editing_number, part.date_time_part); + editing_part_index = editing_number = -1; + editing_text = null; + + if (invalidate) + Invalidate (date_area_rect); + } + + internal void SetPart (int value, DateTimePart dt_part) + { + SetPart (value, dt_part, false); + } + + // set the specified part of the date to the specified value + internal void SetPart (int value, DateTimePart dt_part, bool adjust) + { + switch (dt_part) + { + case DateTimePart.Seconds: + if (value == -1) + value = 59; + if (value >= 0 && value <= 59) + Value = new DateTime(Value.Year, Value.Month, Value.Day, Value.Hour, Value.Minute, value, Value.Millisecond); + break; + case DateTimePart.Minutes: + if (value == -1) + value = 59; + if (value >= 0 && value <= 59) + Value = new DateTime(Value.Year, Value.Month, Value.Day, Value.Hour, value, Value.Second, Value.Millisecond); + break; + case DateTimePart.AMPMHour: + if (value == -1) + value = 23; + if (value >= 0 && value <= 23) { + int prev_hour = Value.Hour; + if ((prev_hour >= 12 && prev_hour <= 23) && value < 12) // Adjust to p.m. + value += 12; + Value = new DateTime (Value.Year, Value.Month, Value.Day, value, Value.Minute, Value.Second, Value.Millisecond); + } + break; + case DateTimePart.Hour: + if (value == -1) + value = 23; + if (value >= 0 && value <= 23) + Value = new DateTime(Value.Year, Value.Month, Value.Day, value, Value.Minute, Value.Second, Value.Millisecond); + break; + case DateTimePart.Day: + int max_days = DateTime.DaysInMonth(Value.Year, Value.Month); + if (value >= 1 && value <= 31 && value <= max_days) + Value = new DateTime(Value.Year, Value.Month, value, Value.Hour, Value.Minute, Value.Second, Value.Millisecond); + break; + case DateTimePart.Month: + DateTime date = Value; + + if (adjust) { + if (value == 0) { + date = date.AddYears (-1); + value = 12; + } else if (value == 13) { + date = date.AddYears (1); + value = 1; + } + } + + if (value >= 1 && value <= 12) { + // if we move from say december to november with days on 31, we must + // remap to the maximum number of days + int days_in_new_month = DateTime.DaysInMonth (date.Year, value); + + if (date.Day > days_in_new_month) + Value = new DateTime (date.Year, value, days_in_new_month, date.Hour, date.Minute, date.Second, date.Millisecond); + else + Value = new DateTime (date.Year, value, date.Day, date.Hour, date.Minute, date.Second, date.Millisecond); + } + break; + case DateTimePart.Year: + if (value >= min_date.Year && value <= max_date.Year) { + // if we move to a leap year, the days in month could throw an exception + int days_in_new_month = DateTime.DaysInMonth (value, Value.Month); + + if (Value.Day > days_in_new_month) + Value = new DateTime (value, Value.Month, days_in_new_month, Value.Hour, Value.Minute, Value.Second, Value.Millisecond); + else + Value = new DateTime (value, Value.Month, Value.Day, Value.Hour, Value.Minute, Value.Second, Value.Millisecond); + } + break; + } + } + + private void GotFocusHandler (object sender, EventArgs e) + { + if (ShowCheckBox) { + is_checkbox_selected = true; + Invalidate (CheckBoxRect); + OnUIASelectionChanged (); + } + } + + // if we loose focus deselect any selected parts. + private void LostFocusHandler (object sender, EventArgs e) + { + int selected_index = GetSelectedPartIndex (); + if (selected_index != -1) + { + part_data [selected_index].Selected = false; + Rectangle invalidate_rect = Rectangle.Ceiling (part_data [selected_index].drawing_rectangle); + invalidate_rect.Inflate (2, 2); + Invalidate (invalidate_rect); + OnUIASelectionChanged (); + } + else if (is_checkbox_selected) + { + is_checkbox_selected = false; + Invalidate (CheckBoxRect); + OnUIASelectionChanged (); + } + } + + // if month calendar looses focus and the drop down is up, then close it + private void MonthCalendarLostFocusHandler(object sender, EventArgs e) + { + if (is_drop_down_visible && !month_calendar.Focused) + { + //this.HideMonthCalendar(); + //This is handled from the monthcalender itself, + //it may loose focus, but still has to be visible, + //for instance when the context menu is displayed. + } + + } + + private void MonthCalendarDateChangedHandler (object sender, DateRangeEventArgs e) + { + if (month_calendar.Visible) + this.Value = e.Start.Date.Add (this.Value.TimeOfDay); + } + + // fired when a user clicks on the month calendar to select a date + private void MonthCalendarDateSelectedHandler (object sender, DateRangeEventArgs e) + { + this.HideMonthCalendar (); + } + + private void MouseUpHandler(object sender, MouseEventArgs e) + { + if (ShowUpDown) + { + if (is_up_pressed || is_down_pressed) + { + updown_timer.Enabled = false; + is_up_pressed = false; + is_down_pressed = false; + Invalidate (drop_down_arrow_rect); + } + } + } + + // to check if the mouse has come down on this control + private void MouseDownHandler (object sender, MouseEventArgs e) + { + // Only left clicks are handled. + if (e.Button != MouseButtons.Left) + return; + + if (ShowCheckBox && CheckBoxRect.Contains(e.X, e.Y)) + { + is_checkbox_selected = true; + Checked = !Checked; + OnUIASelectionChanged (); + return; + } + + // Deselect the checkbox only if the pointer is not on it + // *and* the other parts are enabled (Checked as true) + if (Checked) { + is_checkbox_selected = false; + OnUIASelectionChanged (); + } + + if (ShowUpDown && drop_down_arrow_rect.Contains (e.X, e.Y)) + { + if (!(ShowCheckBox && Checked == false)) + { + if (e.Y < this.Height / 2) { + is_up_pressed = true; + is_down_pressed = false; + IncrementSelectedPart (1); + } else { + is_up_pressed = false; + is_down_pressed = true; + IncrementSelectedPart (-1); + } + Invalidate (drop_down_arrow_rect); + updown_timer.Interval = initial_timer_delay; + updown_timer.Enabled = true; + } + } else if (is_drop_down_visible == false && drop_down_arrow_rect.Contains (e.X, e.Y)) { + DropDownButtonClicked (); + } else { + // mouse down on this control anywhere else collapses it + if (is_drop_down_visible) { + HideMonthCalendar (); + } + if (!(ShowCheckBox && Checked == false)) + { + // go through the parts to see if the click is in any of them + bool invalidate_afterwards = false; + for (int i = 0; i < part_data.Length; i++) { + bool old = part_data [i].Selected; + + if (part_data [i].is_literal) + continue; + + if (part_data [i].drawing_rectangle.Contains (e.X, e.Y)) { + part_data [i].Selected = true; + } else + part_data [i].Selected = false; + + if (old != part_data [i].Selected) + invalidate_afterwards = true; + } + if (invalidate_afterwards) { + Invalidate (); + OnUIASelectionChanged (); + } + } + + } + } + + internal void DropDownButtonClicked () + { + if (!is_drop_down_visible) { + is_drop_down_visible = true; + if (!Checked) + Checked = true; + Invalidate (drop_down_arrow_rect); + DropDownMonthCalendar (); + } else { + HideMonthCalendar (); + } + } + + // paint this control now + private void PaintHandler (object sender, PaintEventArgs pe) { + if (Width <= 0 || Height <= 0 || Visible == false) + return; + + Draw (pe.ClipRectangle, pe.Graphics); + } + + void OnMouseEnter (object sender, EventArgs e) + { + if (ThemeEngine.Current.DateTimePickerBorderHasHotElementStyle) + Invalidate (); + } + + void OnMouseLeave (object sender, EventArgs e) + { + drop_down_button_entered = false; + if (ThemeEngine.Current.DateTimePickerBorderHasHotElementStyle) + Invalidate (); + } + + void OnMouseMove (object sender, MouseEventArgs e) + { + if (!is_drop_down_visible && + ThemeEngine.Current.DateTimePickerDropDownButtonHasHotElementStyle && + drop_down_button_entered != drop_down_arrow_rect.Contains (e.Location)) { + drop_down_button_entered = !drop_down_button_entered; + Invalidate (drop_down_arrow_rect); + } + } + #endregion + + #region internal classes + internal enum DateTimePart { + Seconds, + Minutes, + AMPMHour, + Hour, + Day, + DayName, + Month, + Year, + AMPMSpecifier, + Literal + } + + internal class PartData + { + internal string value; + internal bool is_literal; + bool is_selected; + internal RectangleF drawing_rectangle; + internal DateTimePart date_time_part; + DateTimePicker owner; + + internal bool is_numeric_format + { + get + { + if (is_literal) + return false; + switch (value) { + case "m": + case "mm": + case "d": + case "dd": + case "h": + case "hh": + case "H": + case "HH": + case "M": + case "MM": + case "s": + case "ss": + case "y": + case "yy": + case "yyyy": + return true; + case "ddd": + case "dddd": + return false; + default: + return false; + } + } + } + + internal PartData(string value, bool is_literal, DateTimePicker owner) + { + this.value = value; + this.is_literal = is_literal; + this.owner = owner; + date_time_part = GetDateTimePart (value); + } + + internal bool Selected { + get { + return is_selected; + } + set { + if (value == is_selected) + return; + + owner.EndDateEdit (false); + is_selected = value; + } + } + + // calculate the string to show for this data + internal string GetText(DateTime date) + { + if (is_literal) { + return value; + } else { + return GetText (date, value); + } + } + + static DateTimePart GetDateTimePart (string value) + { + switch (value) { + case "s": + case "ss": + return DateTimePart.Seconds; + case "m": + case "mm": + return DateTimePart.Minutes; + case "h": + case "hh": + return DateTimePart.AMPMHour; + case "H": + case "HH": + return DateTimePart.Hour; + case "d": + case "dd": + return DateTimePart.Day; + case "ddd": + case "dddd": + return DateTimePart.DayName; + case "M": + case "MM": + case "MMMM": + return DateTimePart.Month; + case "y": + case "yy": + case "yyy": + case "yyyy": + return DateTimePart.Year; + case "t": + case "tt": + return DateTimePart.AMPMSpecifier; + } + + return DateTimePart.Literal; + } + + static internal string GetText(DateTime date, string format) + { + if (format.StartsWith ("g")) + return " "; + else if (format.Length == 1) + return date.ToString ("%" + format); + else if (format == "yyyyy" || format == "yyyyyy" || format == "yyyyyyy" || format == "yyyyyyyy") + return date.ToString ("yyyy"); + else if (format.Length > 1) + return date.ToString (format); + else + return string.Empty; + } + } + + #endregion + + #region UIA Framework: Methods, Properties and Events + + internal bool UIAIsCheckBoxSelected { + get { return is_checkbox_selected; } + } + + static object UIAMinimumChangedEvent = new object (); + static object UIAMaximumChangedEvent = new object (); + static object UIASelectionChangedEvent = new object (); + static object UIACheckedEvent = new object (); + static object UIAShowCheckBoxChangedEvent = new object (); + static object UIAShowUpDownChangedEvent = new object (); + + internal event EventHandler UIAMinimumChanged { + add { Events.AddHandler (UIAMinimumChangedEvent, value); } + remove { Events.RemoveHandler (UIAMinimumChangedEvent, value); } + } + + internal event EventHandler UIAMaximumChanged { + add { Events.AddHandler (UIAMinimumChangedEvent, value); } + remove { Events.RemoveHandler (UIAMinimumChangedEvent, value); } + } + + internal event EventHandler UIASelectionChanged { + add { Events.AddHandler (UIASelectionChangedEvent, value); } + remove { Events.RemoveHandler (UIASelectionChangedEvent, value); } + } + + internal event EventHandler UIAChecked { + add { Events.AddHandler (UIACheckedEvent, value); } + remove { Events.RemoveHandler (UIACheckedEvent, value); } + } + + internal event EventHandler UIAShowCheckBoxChanged { + add { Events.AddHandler (UIAShowCheckBoxChangedEvent, value); } + remove { Events.RemoveHandler (UIAShowCheckBoxChangedEvent, value); } + } + + internal event EventHandler UIAShowUpDownChanged { + add { Events.AddHandler (UIAShowUpDownChangedEvent, value); } + remove { Events.RemoveHandler (UIAShowUpDownChangedEvent, value); } + } + + internal void OnUIAMinimumChanged () + { + EventHandler eh = (EventHandler)(Events [UIAMinimumChangedEvent]); + if (eh != null) + eh (this, EventArgs.Empty); + } + + internal void OnUIAMaximumChanged () + { + EventHandler eh = (EventHandler)(Events [UIAMaximumChangedEvent]); + if (eh != null) + eh (this, EventArgs.Empty); + } + + internal void OnUIASelectionChanged () + { + EventHandler eh = (EventHandler)(Events [UIASelectionChangedEvent]); + if (eh != null) + eh (this, EventArgs.Empty); + } + + internal void OnUIAChecked () + { + EventHandler eh = (EventHandler)(Events [UIACheckedEvent]); + if (eh != null) + eh (this, EventArgs.Empty); + } + + internal void OnUIAShowCheckBoxChanged () + { + EventHandler eh = (EventHandler)(Events [UIAShowCheckBoxChangedEvent]); + if (eh != null) + eh (this, EventArgs.Empty); + } + + internal void OnUIAShowUpDownChanged () + { + EventHandler eh = (EventHandler)(Events [UIAShowUpDownChangedEvent]); + if (eh != null) + eh (this, EventArgs.Empty); + } + + #endregion + } +} diff --git a/source/ShiftUI/Widgets/FlowLayoutPanel.cs b/source/ShiftUI/Widgets/FlowLayoutPanel.cs new file mode 100644 index 0000000..680cc57 --- /dev/null +++ b/source/ShiftUI/Widgets/FlowLayoutPanel.cs @@ -0,0 +1,197 @@ +// +// FlowLayoutPanel.cs +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2006 Jonathan Pobst +// +// Authors: +// Jonathan Pobst ([email protected]) +// + +using ShiftUI.Layout; +using System.ComponentModel; +using System.Runtime.InteropServices; +using System.Drawing; +using System; + +namespace ShiftUI +{ + [ComVisibleAttribute (true)] + [ClassInterfaceAttribute (ClassInterfaceType.AutoDispatch)] + [ProvideProperty ("FlowBreak", typeof (Widget))] + [DefaultProperty ("FlowDirection")] + [Docking (DockingBehavior.Ask)] + //[Designer ("ShiftUI.Design.FlowLayoutPanelDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")] + [ToolboxWidget] + public class FlowLayoutPanel : Panel, IExtenderProvider + { + private FlowLayoutSettings settings; + + public FlowLayoutPanel () : base () + { + CreateDockPadding (); + } + + #region Properties + [Localizable (true)] + [DefaultValue (FlowDirection.LeftToRight)] + public FlowDirection FlowDirection { + get { return LayoutSettings.FlowDirection; } + set { LayoutSettings.FlowDirection = value; } + } + + [LocalizableAttribute (true)] + [DefaultValue (true)] + public bool WrapContents { + get { return LayoutSettings.WrapContents; } + set { LayoutSettings.WrapContents = value; } + } + + public override LayoutEngine LayoutEngine { + get { return this.LayoutSettings.LayoutEngine; } + } + + internal FlowLayoutSettings LayoutSettings { + get { + if (this.settings == null) + this.settings = new FlowLayoutSettings (this); + + return this.settings; + } + } + #endregion + + #region Public Methods + [DefaultValue (false)] + [DisplayName ("FlowBreak")] + public bool GetFlowBreak (Widget control) + { + return LayoutSettings.GetFlowBreak (control); + } + + [DisplayName ("FlowBreak")] + public void SetFlowBreak (Widget control, bool value) + { + LayoutSettings.SetFlowBreak (control, value); + } + #endregion + + #region IExtenderProvider Members + bool IExtenderProvider.CanExtend (object obj) + { + if (obj is Widget) + if ((obj as Widget).Parent == this) + return true; + + return false; + } + #endregion + + #region Internal Methods + internal override void CalculateCanvasSize (bool canOverride) + { + if (canOverride) + canvas_size = ClientSize; + else + base.CalculateCanvasSize (canOverride); + } + + protected override void OnLayout (LayoutEventArgs levent) + { + base.OnLayout (levent); + + // base.OnLayout() calls CalculateCanvasSize(true) in which we just set the canvas to + // clientsize so we could re-layout everything according to the flow. + // This time we want to actually calculate the canvas. + CalculateCanvasSize (false); + if (AutoSize && (canvas_size.Width > ClientSize.Width || canvas_size.Height > ClientSize.Height)) { + ClientSize = canvas_size; + } + AdjustFormScrollbars (AutoScroll); + } + + internal override Size GetPreferredSizeCore (Size proposedSize) + { + int width = 0; + int height = 0; + bool horizontal = FlowDirection == FlowDirection.LeftToRight || FlowDirection == FlowDirection.RightToLeft; + if (!WrapContents || (horizontal && proposedSize.Width == 0) || (!horizontal && proposedSize.Height == 0)) { + foreach (Widget control in Widgets) { + Size control_preferred_size; + if (control.AutoSize) + control_preferred_size = control.PreferredSize; + else + control_preferred_size = control.Size; + Padding control_margin = control.Margin; + if (horizontal) { + width += control_preferred_size.Width + control_margin.Horizontal; + height = Math.Max (height, control_preferred_size.Height + control_margin.Vertical); + } else { + height += control_preferred_size.Height + control_margin.Vertical; + width = Math.Max (width, control_preferred_size.Width + control_margin.Horizontal); + } + } + } else { + int size_in_flow_direction = 0; + int size_in_other_direction = 0; + int increase; + foreach (Widget control in Widgets) { + Size control_preferred_size; + if (control.AutoSize) + control_preferred_size = control.PreferredSize; + else + control_preferred_size = control.ExplicitBounds.Size; + Padding control_margin = control.Margin; + if (horizontal) { + increase = control_preferred_size.Width + control_margin.Horizontal; + if (size_in_flow_direction != 0 && size_in_flow_direction + increase >= proposedSize.Width) { + width = Math.Max (width, size_in_flow_direction); + size_in_flow_direction = 0; + height += size_in_other_direction; + size_in_other_direction = 0; + } + size_in_flow_direction += increase; + size_in_other_direction = Math.Max (size_in_other_direction, control_preferred_size.Height + control_margin.Vertical); + } else { + increase = control_preferred_size.Height + control_margin.Vertical; + if (size_in_flow_direction != 0 && size_in_flow_direction + increase >= proposedSize.Height) { + height = Math.Max (height, size_in_flow_direction); + size_in_flow_direction = 0; + width += size_in_other_direction; + size_in_other_direction = 0; + } + size_in_flow_direction += increase; + size_in_other_direction = Math.Max (size_in_other_direction, control_preferred_size.Width + control_margin.Horizontal); + } + } + if (horizontal) { + width = Math.Max (width, size_in_flow_direction); + height += size_in_other_direction; + } else { + height = Math.Max (height, size_in_flow_direction); + width += size_in_other_direction; + } + } + return new Size (width, height); + } + #endregion + } +} diff --git a/source/ShiftUI/Widgets/GridEntry.cs b/source/ShiftUI/Widgets/GridEntry.cs new file mode 100644 index 0000000..619bb4c --- /dev/null +++ b/source/ShiftUI/Widgets/GridEntry.cs @@ -0,0 +1,860 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2004-2008 Novell, Inc. +// +// Authors: +// Jonathan Chambers ([email protected]) +// Ivan N. Zlatev ([email protected]) +// + +// NOT COMPLETE + +using System; +using System.Collections; +using System.Drawing; +using System.Drawing.Design; +using ShiftUI; +using ShiftUI.Design; +using System.ComponentModel; +using System.ComponentModel.Design; +using System.Globalization; + +namespace ShiftUI.PropertyGridInternal +{ + internal class GridEntry : GridItem, ITypeDescriptorContext + { + #region Local Variables + private PropertyGrid property_grid; + private bool expanded; + private GridItemCollection grid_items; + private GridItem parent; + private PropertyDescriptor[] property_descriptors; + private int top; + private Rectangle plus_minus_bounds; + private GridItemCollection child_griditems_cache; + #endregion // Local Variables + + #region Contructors + protected GridEntry (PropertyGrid propertyGrid, GridEntry parent) + { + if (propertyGrid == null) + throw new ArgumentNullException ("propertyGrid"); + property_grid = propertyGrid; + plus_minus_bounds = new Rectangle (0,0,0,0); + top = -1; + grid_items = new GridItemCollection (); + expanded = false; + this.parent = parent; + child_griditems_cache = null; + } + + // Cannot use one PropertyDescriptor for all owners, because the + // propertydescriptors might have different Invokees. Check + // ReflectionPropertyDescriptor.GetInvokee and how it's used. + // + public GridEntry (PropertyGrid propertyGrid, PropertyDescriptor[] properties, + GridEntry parent) : this (propertyGrid, parent) + { + if (properties == null || properties.Length == 0) + throw new ArgumentNullException ("prop_desc"); + property_descriptors = properties; + } + #endregion // Constructors + + + public override bool Expandable { + get { + TypeConverter converter = GetConverter (); + if (converter == null || !converter.GetPropertiesSupported ((ITypeDescriptorContext)this)) + return false; + + if (GetChildGridItemsCached ().Count > 0) + return true; + + return false; + } + } + + public override bool Expanded { + get { return expanded; } + set { + if (expanded != value) { + expanded = value; + PopulateChildGridItems (); + if (value) + property_grid.OnExpandItem (this); + else + property_grid.OnCollapseItem (this); + } + } + } + + public override GridItemCollection GridItems { + get { + PopulateChildGridItems (); + return grid_items; + } + } + + public override GridItemType GridItemType { + get { return GridItemType.Property; } + } + + public override string Label { + get { + PropertyDescriptor property = this.PropertyDescriptor; + if (property != null) { + string label = property.DisplayName; + ParenthesizePropertyNameAttribute parensAttr = + property.Attributes[typeof (ParenthesizePropertyNameAttribute)] as ParenthesizePropertyNameAttribute; + if (parensAttr != null && parensAttr.NeedParenthesis) + label = "(" + label + ")"; + return label; + } + return String.Empty; + } + } + + public override GridItem Parent { + get { return parent; } + } + + public GridEntry ParentEntry { + get { + if (parent != null && parent.GridItemType == GridItemType.Category) + return parent.Parent as GridEntry; + return parent as GridEntry; + } + } + + public override PropertyDescriptor PropertyDescriptor { + get { return property_descriptors != null ? property_descriptors[0] : null; } + } + + public PropertyDescriptor[] PropertyDescriptors { + get { return property_descriptors; } + } + + public object PropertyOwner { + get { + object[] owners = PropertyOwners; + if (owners != null) + return owners[0]; + return null; + } + } + + public object[] PropertyOwners { + get { + if (ParentEntry == null) + return null; + + object[] owners = ParentEntry.Values; + PropertyDescriptor[] properties = PropertyDescriptors; + object newOwner = null; + for (int i=0; i < owners.Length; i++) { + if (owners[i] is ICustomTypeDescriptor) { + newOwner = ((ICustomTypeDescriptor)owners[i]).GetPropertyOwner (properties[i]); + if (newOwner != null) + owners[i] = newOwner; + } + } + return owners; + } + } + + // true if the value is the same among all properties + public bool HasMergedValue { + get { + if (!IsMerged) + return false; + + object[] values = this.Values; + for (int i=0; i+1 < values.Length; i++) { + if (!Object.Equals (values[i], values[i+1])) + return false; + } + return true; + } + } + + public virtual bool IsMerged { + get { return (PropertyDescriptors != null && PropertyDescriptors.Length > 1); } + } + + // If IsMerged this will return all values for all properties in all owners + public virtual object[] Values { + get { + if (PropertyDescriptor == null || this.PropertyOwners == null) + return null; + if (IsMerged) { + object[] owners = this.PropertyOwners; + PropertyDescriptor[] properties = PropertyDescriptors; + object[] values = new object[owners.Length]; + for (int i=0; i < owners.Length; i++) + values[i] = properties[i].GetValue (owners[i]); + return values; + } else { + return new object[] { this.Value }; + } + } + } + + // Returns the first value for the first propertyowner and propertydescriptor + // + public override object Value { + get { + if (PropertyDescriptor == null || PropertyOwner == null) + return null; + + return PropertyDescriptor.GetValue (PropertyOwner); + } + set + { + PropertyDescriptor.SetValue(PropertyOwner, value); + } + } + + public string ValueText { + get { + string text = null; + try { + text = ConvertToString (this.Value); + if (text == null) + text = String.Empty; + } catch { + text = String.Empty; + } + return text; + } + } + + public override bool Select () + { + property_grid.SelectedGridItem = this; + return true; + } + + #region ITypeDescriptorContext + void ITypeDescriptorContext.OnComponentChanged () + { + } + + bool ITypeDescriptorContext.OnComponentChanging () + { + return false; + } + + IContainer ITypeDescriptorContext.Container { + get { + if (PropertyOwner == null) + return null; + + IComponent component = property_grid.SelectedObject as IComponent; + if (component != null && component.Site != null) + return component.Site.Container; + return null; + } + } + + object ITypeDescriptorContext.Instance { + get { + if (ParentEntry != null && ParentEntry.PropertyOwner != null) + return ParentEntry.PropertyOwner; + return PropertyOwner; + } + } + + PropertyDescriptor ITypeDescriptorContext.PropertyDescriptor { + get { + if (ParentEntry != null && ParentEntry.PropertyDescriptor != null) + return ParentEntry.PropertyDescriptor; + return PropertyDescriptor; + } + } + #endregion + + #region IServiceProvider Members + + object IServiceProvider.GetService (Type serviceType) { + IComponent selectedComponent = property_grid.SelectedObject as IComponent; + if (selectedComponent != null && selectedComponent.Site != null) + return selectedComponent.Site.GetService (serviceType); + return null; + } + + #endregion + + internal int Top { + get { return top; } + set { + if (top != value) + top = value; + } + } + + internal Rectangle PlusMinusBounds { + get { return plus_minus_bounds; } + set { plus_minus_bounds = value; } + } + + public void SetParent (GridItem parent) + { + this.parent = parent; + } + + public ICollection AcceptedValues { + get { + TypeConverter converter = GetConverter (); + if (PropertyDescriptor != null && converter != null && + converter.GetStandardValuesSupported ((ITypeDescriptorContext)this)) { + ArrayList values = new ArrayList (); + string stringVal = null; + ICollection standardValues = converter.GetStandardValues ((ITypeDescriptorContext)this); + if (standardValues != null) { + foreach (object value in standardValues) { + stringVal = ConvertToString (value); + if (stringVal != null) + values.Add (stringVal); + } + } + return values.Count > 0 ? values : null; + } + return null; + } + } + + private string ConvertToString (object value) + { + if (value is string) + return (string)value; + + if (PropertyDescriptor != null && PropertyDescriptor.Converter != null && + PropertyDescriptor.Converter.CanConvertTo ((ITypeDescriptorContext)this, typeof (string))) { + try { + return PropertyDescriptor.Converter.ConvertToString ((ITypeDescriptorContext)this, value); + } catch { + // XXX: Happens too often... + // property_grid.ShowError ("Property value of '" + property_descriptor.Name + "' is not convertible to string."); + return null; + } + } + + return null; + } + + public bool HasCustomEditor { + get { return EditorStyle != UITypeEditorEditStyle.None; } + } + + public UITypeEditorEditStyle EditorStyle { + get { + UITypeEditor editor = GetEditor (); + if (editor != null) { + try { + return editor.GetEditStyle ((ITypeDescriptorContext)this); + } catch { + // Some of our Editors throw NotImplementedException + } + } + return UITypeEditorEditStyle.None; + } + } + + public bool EditorResizeable { + get { + if (this.EditorStyle == UITypeEditorEditStyle.DropDown) { + UITypeEditor editor = GetEditor (); + if (editor != null && editor.IsDropDownResizable) + return true; + } + return false; + } + } + + public bool EditValue (IWindowsFormsEditorService service) + { + if (service == null) + throw new ArgumentNullException ("service"); + + IServiceContainer parent = ((ITypeDescriptorContext)this).GetService (typeof (IServiceContainer)) as IServiceContainer; + ServiceContainer container = null; + + if (parent != null) + container = new ServiceContainer (parent); + else + container = new ServiceContainer (); + + container.AddService (typeof (IWindowsFormsEditorService), service); + + UITypeEditor editor = GetEditor (); + if (editor != null) { + try { + object value = editor.EditValue ((ITypeDescriptorContext)this, + container, + this.Value); + string error = null; + return SetValue (value, out error); + } catch { //(Exception e) { + // property_grid.ShowError (e.Message + Environment.NewLine + e.StackTrace); + } + } + return false; + } + + private UITypeEditor GetEditor () + { + if (PropertyDescriptor != null) { + try { // can happen, because we are missing some editors + if (PropertyDescriptor != null) + return (UITypeEditor) PropertyDescriptor.GetEditor (typeof (UITypeEditor)); + } catch { + // property_grid.ShowError ("Unable to load UITypeEditor for property '" + PropertyDescriptor.Name + "'."); + } + } + return null; + } + + private TypeConverter GetConverter () + { + if (PropertyDescriptor != null) + return PropertyDescriptor.Converter; + return null; + } + + public bool ToggleValue () + { + if (IsReadOnly || (IsMerged && !HasMergedValue)) + return false; + + bool success = false; + string error = null; + object value = this.Value; + if (PropertyDescriptor.PropertyType == typeof(bool)) + success = SetValue (!(bool)value, out error); + else { + TypeConverter converter = GetConverter (); + if (converter != null && + converter.GetStandardValuesSupported ((ITypeDescriptorContext)this)) { + TypeConverter.StandardValuesCollection values = + (TypeConverter.StandardValuesCollection) converter.GetStandardValues ((ITypeDescriptorContext)this); + if (values != null) { + for (int i = 0; i < values.Count; i++) { + if (value != null && value.Equals (values[i])){ + if (i < values.Count-1) + success = SetValue (values[i+1], out error); + else + success = SetValue (values[0], out error); + break; + } + } + } + } + } + if (!success && error != null) + property_grid.ShowError (error); + return success; + } + + public bool SetValue (object value, out string error) + { + error = null; + if (this.IsReadOnly) + return false; + + if (SetValueCore (value, out error)) { + InvalidateChildGridItemsCache (); + property_grid.OnPropertyValueChangedInternal (this, this.Value); + return true; + } + return false; + } + + protected virtual bool SetValueCore (object value, out string error) + { + error = null; + + TypeConverter converter = GetConverter (); + Type valueType = value != null ? value.GetType () : null; + // if the new value is not of the same type try to convert it + if (valueType != null && this.PropertyDescriptor.PropertyType != null && + !this.PropertyDescriptor.PropertyType.IsAssignableFrom (valueType)) { + bool conversionError = false; + try { + if (converter != null && + converter.CanConvertFrom ((ITypeDescriptorContext)this, valueType)) + value = converter.ConvertFrom ((ITypeDescriptorContext)this, + CultureInfo.CurrentCulture, value); + else + conversionError = true; + } catch (Exception e) { + error = e.Message; + conversionError = true; + } + if (conversionError) { + string valueText = ConvertToString (value); + string errorShortDescription = null; + if (valueText != null) { + errorShortDescription = "Property value '" + valueText + "' of '" + + PropertyDescriptor.Name + "' is not convertible to type '" + + this.PropertyDescriptor.PropertyType.Name + "'"; + + } else { + errorShortDescription = "Property value of '" + + PropertyDescriptor.Name + "' is not convertible to type '" + + this.PropertyDescriptor.PropertyType.Name + "'"; + } + error = errorShortDescription + Environment.NewLine + Environment.NewLine + error; + return false; + } + } + + bool changed = false; + bool current_changed = false; + object[] propertyOwners = this.PropertyOwners; + PropertyDescriptor[] properties = PropertyDescriptors; + for (int i=0; i < propertyOwners.Length; i++) { + object currentVal = properties[i].GetValue (propertyOwners[i]); + current_changed = false; + if (!Object.Equals (currentVal, value)) { + if (this.ShouldCreateParentInstance) { + Hashtable updatedParentProperties = new Hashtable (); + PropertyDescriptorCollection parentProperties = TypeDescriptor.GetProperties (propertyOwners[i]); + foreach (PropertyDescriptor property in parentProperties) { + if (property.Name == properties[i].Name) + updatedParentProperties[property.Name] = value; + else + updatedParentProperties[property.Name] = property.GetValue (propertyOwners[i]); + } + object updatedParentValue = this.ParentEntry.PropertyDescriptor.Converter.CreateInstance ( + (ITypeDescriptorContext)this, updatedParentProperties); + if (updatedParentValue != null) + current_changed = this.ParentEntry.SetValueCore (updatedParentValue, out error); + } else { + try { + properties[i].SetValue (propertyOwners[i], value); + } catch { + // MS seems to swallow this + // + // string valueText = ConvertToString (value); + // if (valueText != null) + // error = "Property value '" + valueText + "' of '" + properties[i].Name + "' is invalid."; + // else + // error = "Property value of '" + properties[i].Name + "' is invalid."; + return false; + } + + if (IsValueType (this.ParentEntry)) + current_changed = ParentEntry.SetValueCore (propertyOwners[i], out error); + else + current_changed = Object.Equals (properties[i].GetValue (propertyOwners[i]), value); + } + } + if (current_changed) + changed = true; + } + return changed; + } + + private bool IsValueType (GridEntry item) + { + if (item != null && item.PropertyDescriptor != null && + (item.PropertyDescriptor.PropertyType.IsValueType || + item.PropertyDescriptor.PropertyType.IsPrimitive)) + return true; + return false; + } + + public bool ResetValue () + { + if (IsResetable) { + object[] owners = this.PropertyOwners; + PropertyDescriptor[] properties = PropertyDescriptors; + for (int i=0; i < owners.Length; i++) { + properties[i].ResetValue (owners[i]); + if (IsValueType (this.ParentEntry)) { + string error = null; + if (!ParentEntry.SetValueCore (owners[i], out error) && error != null) + property_grid.ShowError (error); + } + } + property_grid.OnPropertyValueChangedInternal (this, this.Value); + return true; + } + return false; + } + + public bool HasDefaultValue { + get { + if (PropertyDescriptor != null) + return !PropertyDescriptor.ShouldSerializeValue (PropertyOwner); + return false; + } + } + + // Determines if the current value can be reset + // + public virtual bool IsResetable { + get { return (!IsReadOnly && PropertyDescriptor.CanResetValue (PropertyOwner)); } + + } + + // If false the entry can be modified only by the means of a predefined values + // and not such inputed by the user. + // + public virtual bool IsEditable { + get { + TypeConverter converter = GetConverter (); + if (PropertyDescriptor == null) + return false; + else if (PropertyDescriptor.PropertyType.IsArray) + return false; + else if (PropertyDescriptor.IsReadOnly && !this.ShouldCreateParentInstance) + return false; + else if (converter == null || + !converter.CanConvertFrom ((ITypeDescriptorContext)this, typeof (string))) + return false; + else if (converter.GetStandardValuesSupported ((ITypeDescriptorContext)this) && + converter.GetStandardValuesExclusive ((ITypeDescriptorContext)this)) + return false; + else + return true; + } + } + + // If true the the entry cannot be modified at all + // + public virtual bool IsReadOnly { + get { + TypeConverter converter = GetConverter (); + if (PropertyDescriptor == null || PropertyOwner == null) + return true; + else if (PropertyDescriptor.IsReadOnly && + (EditorStyle != UITypeEditorEditStyle.Modal || PropertyDescriptor.PropertyType.IsValueType) && + !this.ShouldCreateParentInstance) + return true; + else if (PropertyDescriptor.IsReadOnly && + TypeDescriptor.GetAttributes (PropertyDescriptor.PropertyType) + [typeof(ImmutableObjectAttribute)].Equals (ImmutableObjectAttribute.Yes)) + return true; + else if (ShouldCreateParentInstance && ParentEntry.IsReadOnly) + return true; + else if (!HasCustomEditor && converter == null) + return true; + else if (converter != null && + !converter.GetStandardValuesSupported ((ITypeDescriptorContext)this) && + !converter.CanConvertFrom ((ITypeDescriptorContext)this, + typeof (string)) && + !HasCustomEditor) { + return true; + } else if (PropertyDescriptor.PropertyType.IsArray && !HasCustomEditor) + return true; + else + return false; + } + } + + public bool IsPassword { + get { + if (PropertyDescriptor != null) + return PropertyDescriptor.Attributes.Contains (PasswordPropertyTextAttribute.Yes); + return false; + } + } + // This is a way to set readonly properties (e.g properties without a setter). + // The way it works is that if CreateInstance is supported by the parent's converter + // it gets passed a list of properties and their values which it uses to create an + // instance (e.g by passing them to the ctor of that object type). + // + // This is used for e.g Font + // + public virtual bool ShouldCreateParentInstance { + get { + if (this.ParentEntry != null && ParentEntry.PropertyDescriptor != null) { + TypeConverter parentConverter = ParentEntry.GetConverter (); + if (parentConverter != null && parentConverter.GetCreateInstanceSupported ((ITypeDescriptorContext)this)) + return true; + } + return false; + } + } + + public virtual bool PaintValueSupported { + get { + UITypeEditor editor = GetEditor (); + if (editor != null) { + try { + return editor.GetPaintValueSupported (); + } catch { + // Some of our Editors throw NotImplementedException + } + } + return false; + } + } + + public virtual void PaintValue (Graphics gfx, Rectangle rect) + { + UITypeEditor editor = GetEditor (); + if (editor != null) { + try { + editor.PaintValue (this.Value, gfx, rect); + } catch { + // Some of our Editors throw NotImplementedException + } + } + } + +#region Population + protected void PopulateChildGridItems () + { + grid_items = GetChildGridItemsCached (); + } + + private void InvalidateChildGridItemsCache () + { + if (child_griditems_cache != null) { + child_griditems_cache = null; + PopulateChildGridItems (); + } + } + + private GridItemCollection GetChildGridItemsCached () + { + if (child_griditems_cache == null) { + child_griditems_cache = GetChildGridItems (); + // foreach (GridEntry item in child_griditems_cache) + // PrintDebugInfo (item); + } + + return child_griditems_cache; + } + + // private static void PrintDebugInfo (GridEntry item) + // { + // if (item.PropertyDescriptor != null) { + // Console.WriteLine ("=== [" + item.PropertyDescriptor.Name + "] ==="); + // try { + // TypeConverter converter = item.GetConverter (); + // Console.WriteLine ("IsReadOnly: " + item.IsReadOnly); + // Console.WriteLine ("IsEditable: " + item.IsEditable); + // Console.WriteLine ("PropertyDescriptor.IsReadOnly: " + item.PropertyDescriptor.IsReadOnly); + // if (item.ParentEntry != null) + // Console.WriteLine ("ParentEntry.IsReadOnly: " + item.ParentEntry.IsReadOnly); + // Console.WriteLine ("ImmutableObjectAttribute.Yes: " + TypeDescriptor.GetAttributes (item.PropertyDescriptor.PropertyType) + // [typeof(ImmutableObjectAttribute)].Equals (ImmutableObjectAttribute.Yes)); + // UITypeEditor editor = item.GetEditor (); + // Console.WriteLine ("Editor: " + (editor == null ? "none" : editor.GetType ().Name)); + // if (editor != null) + // Console.WriteLine ("Editor.EditorStyle: " + editor.GetEditStyle ((ITypeDescriptorContext)item)); + // Console.WriteLine ("Converter: " + (converter == null ? "none" : converter.GetType ().Name)); + // if (converter != null) { + // Console.WriteLine ("Converter.GetStandardValuesSupported: " + converter.GetStandardValuesSupported ((ITypeDescriptorContext)item).ToString ()); + // Console.WriteLine ("Converter.GetStandardValuesExclusive: " + converter.GetStandardValuesExclusive ((ITypeDescriptorContext)item).ToString ()); + // Console.WriteLine ("ShouldCreateParentInstance: " + item.ShouldCreateParentInstance); + // Console.WriteLine ("CanConvertFrom (string): " + converter.CanConvertFrom ((ITypeDescriptorContext)item, typeof (string))); + // } + // Console.WriteLine ("IsArray: " + item.PropertyDescriptor.PropertyType.IsArray.ToString ()); + // } catch { /* Some converters and editor throw NotImplementedExceptions */ } + // } + // } + + private GridItemCollection GetChildGridItems () + { + object[] propertyOwners = this.Values; + string[] propertyNames = GetMergedPropertyNames (propertyOwners); + GridItemCollection items = new GridItemCollection (); + + foreach (string propertyName in propertyNames) { + PropertyDescriptor[] properties = new PropertyDescriptor[propertyOwners.Length]; + for (int i=0; i < propertyOwners.Length; i++) + properties[i] = GetPropertyDescriptor (propertyOwners[i], propertyName); + items.Add (new GridEntry (property_grid, properties, this)); + } + + return items; + } + + private bool IsPropertyMergeable (PropertyDescriptor property) + { + if (property == null) + return false; + + MergablePropertyAttribute attrib = property.Attributes [typeof (MergablePropertyAttribute)] as MergablePropertyAttribute; + if (attrib != null && !attrib.AllowMerge) + return false; + + return true; + } + + private string[] GetMergedPropertyNames (object [] objects) + { + if (objects == null || objects.Length == 0) + return new string[0]; + + ArrayList intersection = new ArrayList (); + for (int i = 0; i < objects.Length; i ++) { + if (objects [i] == null) + continue; + + PropertyDescriptorCollection properties = GetProperties (objects[i], property_grid.BrowsableAttributes); + ArrayList new_intersection = new ArrayList (); + + foreach (PropertyDescriptor currentProperty in (i == 0 ? (ICollection)properties : (ICollection)intersection)) { + PropertyDescriptor matchingProperty = (i == 0 ? currentProperty : properties [currentProperty.Name]); + if (objects.Length > 1 && !IsPropertyMergeable (matchingProperty)) + continue; + if (matchingProperty.PropertyType == currentProperty.PropertyType) + new_intersection.Add (matchingProperty); + } + + intersection = new_intersection; + } + + string[] propertyNames = new string [intersection.Count]; + for (int i=0; i < intersection.Count; i++) + propertyNames[i] = ((PropertyDescriptor)intersection[i]).Name; + + return propertyNames; + } + + private PropertyDescriptor GetPropertyDescriptor (object propertyOwner, string propertyName) + { + if (propertyOwner == null || propertyName == null) + return null; + + PropertyDescriptorCollection properties = GetProperties (propertyOwner, property_grid.BrowsableAttributes); + if (properties != null) + return properties[propertyName]; + return null; + } + + private PropertyDescriptorCollection GetProperties (object propertyOwner, AttributeCollection attributes) + { + if (propertyOwner == null || property_grid.SelectedTab == null) + return new PropertyDescriptorCollection (null); + + Attribute[] atts = new Attribute[attributes.Count]; + attributes.CopyTo (atts, 0); + return property_grid.SelectedTab.GetProperties ((ITypeDescriptorContext)this, propertyOwner, atts); + } +#endregion + } +} diff --git a/source/ShiftUI/Widgets/GridItemCollection.cs b/source/ShiftUI/Widgets/GridItemCollection.cs new file mode 100644 index 0000000..22eedc8 --- /dev/null +++ b/source/ShiftUI/Widgets/GridItemCollection.cs @@ -0,0 +1,158 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2004-2005 Novell, Inc. +// +// Authors: +// Jonathan Chambers ([email protected]) +// + +// COMPLETE + +using System; +using System.Collections; +using ShiftUI.PropertyGridInternal; + +namespace ShiftUI +{ + public class GridItemCollection : IEnumerable, ICollection + { + #region Local Variables + private System.Collections.SortedList list; + #endregion // Local Variables + + #region Public Static Fields + public static GridItemCollection Empty = new GridItemCollection(); + #endregion // Public Static Fields + + #region Constructors + internal GridItemCollection() + { + list = new SortedList(); + } + #endregion // Constructors + + #region Internal Properties and Methods + internal void Add (GridItem grid_item) + { + string key = grid_item.Label; + while (list.ContainsKey (key)) + key += "_"; + list.Add (key, grid_item); + } + + internal void AddRange (GridItemCollection items) + { + foreach (GridItem item in items) + Add (item); + } + + internal int IndexOf (GridItem grid_item) + { + return list.IndexOfValue (grid_item); + } + #endregion // Internal Properties and Methods + + #region Public Instance Properties + public int Count { + get { + return list.Count; + } + } + + public GridItem this [int index] { + get { + if (index>=list.Count) { + throw new ArgumentOutOfRangeException("index"); + } + return (GridItem)list.GetByIndex(index); + } + } + + public GridItem this [string label] { + get { + return (GridItem)list[label]; + } + } + #endregion // Public Instance Properties + + #region IEnumerable Members + public IEnumerator GetEnumerator() + { + return new GridItemEnumerator (this); + } + #endregion + + #region Enumerator Class + internal class GridItemEnumerator : IEnumerator{ + int nIndex; + GridItemCollection collection; + + public GridItemEnumerator(GridItemCollection coll) + { + collection = coll; + nIndex = -1; + } + + public bool MoveNext () + { + nIndex++; + return (nIndex < collection.Count); + } + + public void Reset () + { + nIndex = -1; + } + + object System.Collections.IEnumerator.Current { + get { + return collection [nIndex]; + } + } + } + #endregion + + #region ICollection Members + + bool ICollection.IsSynchronized { + get { + return list.IsSynchronized; + } + } + + void ICollection.CopyTo(Array dest, int index) + { + list.CopyTo (dest, index); + } + + object ICollection.SyncRoot { + get { + return list.SyncRoot; + } + } + + #endregion + + internal void Clear () + { + list.Clear (); + } + } +} diff --git a/source/ShiftUI/Widgets/GroupBox.cs b/source/ShiftUI/Widgets/GroupBox.cs new file mode 100644 index 0000000..27b8384 --- /dev/null +++ b/source/ShiftUI/Widgets/GroupBox.cs @@ -0,0 +1,344 @@ +// +// ShiftUI.GroupBox.cs +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Authors: +// Jordi Mas i Hernandez, [email protected] +// +// TODO: +// +// Copyright (C) Novell Inc., 2004-2005 +// +// + +using System.Drawing; +using System.ComponentModel; +using System.ComponentModel.Design; +using System.Runtime.InteropServices; +using System; + +namespace ShiftUI +{ + [DefaultProperty("Text")] + [DefaultEvent("Enter")] + //[Designer ("ShiftUI.Design.GroupBoxDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")] + [ClassInterface (ClassInterfaceType.AutoDispatch)] + [ComVisible (true)] + [ToolboxWidget] + public class GroupBox : Widget + { + private FlatStyle flat_style; + private Rectangle display_rectangle = new Rectangle (); + + #region Events + [Browsable (true)] + //[EditorBrowsable (EditorBrowsableState.Always)] + public new event EventHandler AutoSizeChanged { + add { base.AutoSizeChanged += value; } + remove { base.AutoSizeChanged -= value; } + } + + [Browsable (false)] + //[EditorBrowsable(EditorBrowsableState.Advanced)] + public new event EventHandler Click { + add { base.Click += value; } + remove { base.Click -= value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Advanced)] + public new event EventHandler DoubleClick { + add { base.DoubleClick += value; } + remove { base.DoubleClick -= value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Advanced)] + public new event KeyEventHandler KeyDown { + add { base.KeyDown += value; } + remove { base.KeyDown -= value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Advanced)] + public new event KeyPressEventHandler KeyPress { + add { base.KeyPress += value; } + remove { base.KeyPress -= value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Advanced)] + public new event KeyEventHandler KeyUp { + add { base.KeyUp += value; } + remove { base.KeyUp -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Advanced)] + public new event MouseEventHandler MouseClick { + add { base.MouseClick += value; } + remove { base.MouseClick -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Advanced)] + public new event MouseEventHandler MouseDoubleClick { + add { base.MouseDoubleClick += value; } + remove { base.MouseDoubleClick -= value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Advanced)] + public new event MouseEventHandler MouseDown { + add { base.MouseDown += value; } + remove { base.MouseDown -= value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Advanced)] + public new event EventHandler MouseEnter { + add { base.MouseEnter += value; } + remove { base.MouseEnter -= value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Advanced)] + public new event EventHandler MouseLeave { + add { base.MouseLeave += value; } + remove { base.MouseLeave -= value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Advanced)] + public new event MouseEventHandler MouseMove { + add { base.MouseMove += value; } + remove { base.MouseMove -= value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Advanced)] + public new event MouseEventHandler MouseUp { + add { base.MouseUp += value; } + remove { base.MouseUp -= value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Advanced)] + public new event EventHandler TabStopChanged { + add { base.TabStopChanged += value; } + remove { base.TabStopChanged -= value; } + } + #endregion Events + + public GroupBox () + { + TabStop = false; + flat_style = FlatStyle.Standard; + + SetStyle(Widgetstyles.ContainerWidget | Widgetstyles.ResizeRedraw | Widgetstyles.SupportsTransparentBackColor, true); + SetStyle(Widgetstyles.Selectable, false); + } + + #region Public Properties + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Advanced)] + public override bool AllowDrop { + get { return base.AllowDrop; } + set { base.AllowDrop = value; } + } + + [Browsable (true)] + //[EditorBrowsable (EditorBrowsableState.Always)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Visible)] + public override bool AutoSize { + get { return base.AutoSize; } + set { base.AutoSize = value; } + } + + [Browsable (true)] + [DefaultValue (AutoSizeMode.GrowOnly)] + [Localizable (true)] + public AutoSizeMode AutoSizeMode { + get { return base.GetAutoSizeMode (); } + set { base.SetAutoSizeMode (value); } + } + + protected override CreateParams CreateParams { + get { return base.CreateParams; } + } + + protected override Size DefaultSize { + get { return ThemeEngine.Current.GroupBoxDefaultSize;} + } + + public override Rectangle DisplayRectangle { + get { + display_rectangle.X = Padding.Left; + display_rectangle.Y = Font.Height + Padding.Top; + display_rectangle.Width = Width - Padding.Horizontal; + display_rectangle.Height = Height - Font.Height - Padding.Vertical; + return display_rectangle; + } + } + + [DefaultValue(FlatStyle.Standard)] + public FlatStyle FlatStyle { + get { return flat_style; } + set { + if (!Enum.IsDefined (typeof (FlatStyle), value)) + throw new InvalidEnumArgumentException (string.Format("Enum argument value '{0}' is not valid for FlatStyle", value)); + + if (flat_style == value) + return; + + flat_style = value; + Refresh (); + } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Advanced)] + public new bool TabStop { + get { return base.TabStop; } + set { base.TabStop = value; } + } + + [Localizable(true)] + public override string Text { + get { return base.Text; } + set { + if (base.Text == value) + return; + + base.Text = value; + Refresh (); + } + } + + #endregion //Public Properties + + #region Public Methods + protected override AccessibleObject CreateAccessibilityInstance () + { + return new GroupBoxAccessibleObject (this); + } + + protected override void OnFontChanged (EventArgs e) + { + base.OnFontChanged (e); + Refresh (); + } + + protected override void OnPaint (PaintEventArgs e) + { + ThemeEngine.Current.DrawGroupBox (e.Graphics, ClientRectangle, this); + base.OnPaint(e); + } + + protected override bool ProcessMnemonic (char charCode) + { + if (IsMnemonic(charCode, Text) == true) { + // Select item next in line in tab order + if (this.Parent != null) { + Parent.SelectNextWidget(this, true, false, true, false); + } + return true; + } + + return base.ProcessMnemonic (charCode); + } + + protected override void ScaleWidget (SizeF factor, BoundsSpecified specified) + { + base.ScaleWidget (factor, specified); + } + + public override string ToString() + { + return GetType ().FullName + ", Text: " + Text; + } + + protected override void WndProc(ref Message m) { + base.WndProc (ref m); + } + + #endregion Public Methods + + [DefaultValue (false)] + public bool UseCompatibleTextRendering { + get { + return use_compatible_text_rendering; + } + + set { + if (use_compatible_text_rendering != value) { + use_compatible_text_rendering = value; + if (Parent != null) + Parent.PerformLayout (this, "UseCompatibleTextRendering"); + Invalidate (); + } + } + } + + #region Protected Properties + protected override Padding DefaultPadding { + get { return new Padding (3); } + } + #endregion + + #region Internal Methods + internal override Size GetPreferredSizeCore (Size proposedSize) + { + Size retsize = new Size (Padding.Left, Padding.Top); + + foreach (Widget child in Widgets) { + if (child.Dock == DockStyle.Fill) { + if (child.Bounds.Right > retsize.Width) + retsize.Width = child.Bounds.Right; + } else if (child.Dock != DockStyle.Top && child.Dock != DockStyle.Bottom && (child.Bounds.Right + child.Margin.Right) > retsize.Width) + retsize.Width = child.Bounds.Right + child.Margin.Right; + + if (child.Dock == DockStyle.Fill) { + if (child.Bounds.Bottom > retsize.Height) + retsize.Height = child.Bounds.Bottom; + } else if (child.Dock != DockStyle.Left && child.Dock != DockStyle.Right && (child.Bounds.Bottom + child.Margin.Bottom) > retsize.Height) + retsize.Height = child.Bounds.Bottom + child.Margin.Bottom; + } + + retsize.Width += Padding.Right; + retsize.Height += Padding.Bottom; + + retsize.Height += this.Font.Height; + + return retsize; + } + #endregion + + #region Private Classes + private class GroupBoxAccessibleObject : Widget.WidgetAccessibleObject + { + public GroupBoxAccessibleObject (Widget owner) : base (owner) + { + } + } + #endregion + } +} diff --git a/source/ShiftUI/Widgets/HScrollBar.cs b/source/ShiftUI/Widgets/HScrollBar.cs new file mode 100644 index 0000000..a6d1fe2 --- /dev/null +++ b/source/ShiftUI/Widgets/HScrollBar.cs @@ -0,0 +1,52 @@ +// +// ShiftUI.HScrollBar.cs +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (C) 2004, Novell, Inc. +// +// Authors: +// Jordi Mas i Hernandez [email protected] +// + + +using System.Drawing; +using System.Runtime.InteropServices; + +namespace ShiftUI +{ + [ClassInterface (ClassInterfaceType.AutoDispatch)] + [ComVisible (true)] + public class HScrollBar : ScrollBar + { + public HScrollBar() + { + vert = false; + } + + protected override Size DefaultSize { + get { return ThemeEngine.Current.HScrollBarDefaultSize;} + } + + protected override CreateParams CreateParams { + get { return base.CreateParams; } + } + } +} diff --git a/source/ShiftUI/Widgets/IRootGridEntry.cs b/source/ShiftUI/Widgets/IRootGridEntry.cs new file mode 100644 index 0000000..25142bb --- /dev/null +++ b/source/ShiftUI/Widgets/IRootGridEntry.cs @@ -0,0 +1,40 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2005 Novell, Inc. +// +// Authors: +// Jonathan Chambers [email protected] +// + + +// COMPLETE + +using System; +namespace ShiftUI.PropertyGridInternal +{ + public interface IRootGridEntry + { + System.ComponentModel.AttributeCollection BrowsableAttributes { get; set; } + + void ShowCategories ( bool showCategories ); + + void ResetBrowsableAttributes (); + } +} diff --git a/source/ShiftUI/Widgets/ImplicitHScrollBar.cs b/source/ShiftUI/Widgets/ImplicitHScrollBar.cs new file mode 100644 index 0000000..3e2acfa --- /dev/null +++ b/source/ShiftUI/Widgets/ImplicitHScrollBar.cs @@ -0,0 +1,48 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2004-2005 Novell, Inc. +// +// Authors: +// Peter Bartok [email protected] +// +// Partially based on work by: +// Aleksey Ryabchuk [email protected] +// Alexandre Pigolkine [email protected] +// Dennis Hayes [email protected] +// Jaak Simm [email protected] +// John Sohn [email protected] +// + +// COMPLETE + +using System; + +namespace ShiftUI { + + internal class ImplicitHScrollBar : HScrollBar { + + public ImplicitHScrollBar () + { + implicit_Widget = true; + SetStyle (Widgetstyles.Selectable, false); + } + } +} + diff --git a/source/ShiftUI/Widgets/ImplicitVScrollBar.cs b/source/ShiftUI/Widgets/ImplicitVScrollBar.cs new file mode 100644 index 0000000..618cc53 --- /dev/null +++ b/source/ShiftUI/Widgets/ImplicitVScrollBar.cs @@ -0,0 +1,48 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2004-2005 Novell, Inc. +// +// Authors: +// Peter Bartok [email protected] +// +// Partially based on work by: +// Aleksey Ryabchuk [email protected] +// Alexandre Pigolkine [email protected] +// Dennis Hayes [email protected] +// Jaak Simm [email protected] +// John Sohn [email protected] +// + +// COMPLETE + +using System; + +namespace ShiftUI { + + internal class ImplicitVScrollBar : VScrollBar { + + public ImplicitVScrollBar () + { + implicit_Widget = true; + SetStyle (Widgetstyles.Selectable, false); + } + } +} + diff --git a/source/ShiftUI/Widgets/Label.cs b/source/ShiftUI/Widgets/Label.cs new file mode 100644 index 0000000..4bd4515 --- /dev/null +++ b/source/ShiftUI/Widgets/Label.cs @@ -0,0 +1,729 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2004-2006 Novell, Inc. +// +// Authors: +// Jordi Mas i Hernandez, [email protected] +// Peter Bartok, [email protected] +// +// + +// COMPLETE + +using System.ComponentModel; +using System.ComponentModel.Design; +using System.Drawing; +using System.Drawing.Text; +using System.Drawing.Imaging; +using System.Runtime.InteropServices; +using ShiftUI.Theming; +using System; + +namespace ShiftUI +{ + [DefaultProperty ("Text")] + //[Designer ("ShiftUI.Design.LabelDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")] + [ClassInterface (ClassInterfaceType.AutoDispatch)] + [ComVisible (true)] + [ToolboxItem ("ShiftUI.Design.AutoSizeToolboxItem," + Consts.AssemblySystem_Design)] + [DefaultBindingProperty ("Text")] + [ToolboxWidget] + public class Label : Widget + { + private bool autosize; + private bool auto_ellipsis; + private Image image; + private bool render_transparent; + private FlatStyle flat_style; + private bool use_mnemonic; + private int image_index = -1; + private string image_key = string.Empty; + private ImageList image_list; + internal ContentAlignment image_align; + internal StringFormat string_format; + internal ContentAlignment text_align; + static SizeF req_witdthsize = new SizeF (0,0); + + #region Events + static object AutoSizeChangedEvent = new object (); + static object TextAlignChangedEvent = new object (); + + [Browsable (true)] + //[EditorBrowsable (EditorBrowsableState.Always)] + public new event EventHandler AutoSizeChanged { + add { Events.AddHandler (AutoSizeChangedEvent, value); } + remove { Events.RemoveHandler (AutoSizeChangedEvent, value); } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler BackgroundImageChanged { + add { base.BackgroundImageChanged += value; } + remove { base.BackgroundImageChanged -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler BackgroundImageLayoutChanged { + add { base.BackgroundImageLayoutChanged += value; } + remove { base.BackgroundImageLayoutChanged -= value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler ImeModeChanged { + add { base.ImeModeChanged += value; } + remove { base.ImeModeChanged -= value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event KeyEventHandler KeyDown { + add { base.KeyDown += value; } + remove { base.KeyDown -= value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event KeyPressEventHandler KeyPress { + add { base.KeyPress += value; } + remove { base.KeyPress -= value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event KeyEventHandler KeyUp { + add { base.KeyUp += value; } + remove { base.KeyUp -= value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler TabStopChanged { + add { base.TabStopChanged += value; } + remove { base.TabStopChanged -= value; } + } + + public event EventHandler TextAlignChanged { + add { Events.AddHandler (TextAlignChangedEvent, value); } + remove { Events.RemoveHandler (TextAlignChangedEvent, value); } + } + #endregion + + public Label () + { + // Defaults in the Spec + autosize = false; + TabStop = false; + string_format = new StringFormat(); + string_format.FormatFlags = StringFormatFlags.LineLimit; + TextAlign = ContentAlignment.TopLeft; + image = null; + UseMnemonic = true; + image_list = null; + image_align = ContentAlignment.MiddleCenter; + SetUseMnemonic (UseMnemonic); + flat_style = FlatStyle.Standard; + + SetStyle (Widgetstyles.Selectable, false); + SetStyle (Widgetstyles.ResizeRedraw | + Widgetstyles.UserPaint | + Widgetstyles.AllPaintingInWmPaint | + Widgetstyles.SupportsTransparentBackColor | + Widgetstyles.OptimizedDoubleBuffer + , true); + + HandleCreated += new EventHandler (OnHandleCreatedLB); + } + + #region Public Properties + + [DefaultValue (false)] + [Browsable (true)] + //[EditorBrowsable (EditorBrowsableState.Always)] + public bool AutoEllipsis { + get { return this.auto_ellipsis; } + set + { + if (this.auto_ellipsis != value) { + this.auto_ellipsis = value; + + if (this.auto_ellipsis) + string_format.Trimming = StringTrimming.EllipsisCharacter; + else + string_format.Trimming = StringTrimming.Character; + + if (Parent != null) + Parent.PerformLayout (this, "AutoEllipsis"); + this.Invalidate (); + } + } + } + + [Browsable (true)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Visible)] + //[EditorBrowsable (EditorBrowsableState.Always)] + [DefaultValue(false)] + [Localizable(true)] + [RefreshProperties(RefreshProperties.All)] + public override bool AutoSize { + get { return autosize; } + set { + if (autosize == value) + return; + + base.SetAutoSizeMode (AutoSizeMode.GrowAndShrink); + base.AutoSize = value; + autosize = value; + CalcAutoSize (); + Invalidate (); + + OnAutoSizeChanged (EventArgs.Empty); + } + } + + [Browsable(false)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public override Image BackgroundImage { + get { return base.BackgroundImage; } + set { + base.BackgroundImage = value; + Invalidate (); + } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public override ImageLayout BackgroundImageLayout { + get { return base.BackgroundImageLayout; } + set { base.BackgroundImageLayout = value; } + } + + [DefaultValue(BorderStyle.None)] + [DispId(-504)] + public virtual BorderStyle BorderStyle { + get { return InternalBorderStyle; } + set { InternalBorderStyle = value; } + } + + protected override CreateParams CreateParams { + get { + CreateParams create_params = base.CreateParams; + + if (BorderStyle != BorderStyle.Fixed3D) + return create_params; + + create_params.ExStyle &= ~(int) WindowExStyles.WS_EX_CLIENTEDGE; + create_params.ExStyle |= (int)WindowExStyles.WS_EX_STATICEDGE; + + return create_params; + } + } + + protected override ImeMode DefaultImeMode { + get { return ImeMode.Disable;} + } + + protected override Padding DefaultMargin { + get { return new Padding (3, 0, 3, 0); } + } + + protected override Size DefaultSize { + get { return ThemeElements.LabelPainter.DefaultSize; } + } + + [DefaultValue(FlatStyle.Standard)] + public FlatStyle FlatStyle { + get { return flat_style; } + set { + if (!Enum.IsDefined (typeof (FlatStyle), value)) + throw new InvalidEnumArgumentException (string.Format("Enum argument value '{0}' is not valid for FlatStyle", value)); + + if (flat_style == value) + return; + + flat_style = value; + if (Parent != null) + Parent.PerformLayout (this, "FlatStyle"); + Invalidate (); + } + } + + [Localizable(true)] + public Image Image { + get { + if (this.image != null) + return this.image; + + if (this.image_index >= 0) + if (this.image_list != null) + return this.image_list.Images[this.image_index]; + + if (!string.IsNullOrEmpty (this.image_key)) + if (this.image_list != null) + return this.image_list.Images[this.image_key]; + + return null; + } + set { + if (this.image != value) { + this.image = value; + this.image_index = -1; + this.image_key = string.Empty; + this.image_list = null; + + if (this.AutoSize && this.Parent != null) + this.Parent.PerformLayout (this, "Image"); + + Invalidate (); + } + } + } + + [DefaultValue(ContentAlignment.MiddleCenter)] + [Localizable(true)] + public ContentAlignment ImageAlign { + get { return image_align; } + set { + if (!Enum.IsDefined (typeof (ContentAlignment), value)) + throw new InvalidEnumArgumentException (string.Format("Enum argument value '{0}' is not valid for ContentAlignment", value)); + + if (image_align == value) + return; + + image_align = value; + Invalidate (); + } + } + + [DefaultValue (-1)] + //[Editor ("ShiftUI.Design.ImageIndexEditor, " + Consts.AssemblySystem_Design, typeof (System.Drawing.Design.UITypeEditor))] + [Localizable (true)] + [TypeConverter (typeof (ImageIndexConverter))] + [RefreshProperties (RefreshProperties.Repaint)] + public int ImageIndex { + get { + if (ImageList == null) { + return -1; + } + + if (image_index >= image_list.Images.Count) { + return image_list.Images.Count - 1; + } + + return image_index; + } + set { + + if (value < -1) + throw new ArgumentException (); + + if (this.image_index != value) { + this.image_index = value; + this.image = null; + this.image_key = string.Empty; + Invalidate (); + } + } + } + + [Localizable (true)] + [DefaultValue ("")] + //[Editor ("ShiftUI.Design.ImageIndexEditor, " + Consts.AssemblySystem_Design, typeof (System.Drawing.Design.UITypeEditor))] + [RefreshProperties (RefreshProperties.Repaint)] + [TypeConverter (typeof (ImageKeyConverter))] + public string ImageKey { + get { return this.image_key; } + set { + if (this.image_key != value) { + this.image = null; + this.image_index = -1; + this.image_key = value; + this.Invalidate (); + } + } + } + + [DefaultValue(null)] + [RefreshProperties (RefreshProperties.Repaint)] + public ImageList ImageList { + get { return image_list;} + set { + if (image_list == value) + return; + + image_list = value; + + if (image_list != null && image_index !=-1) + Image = null; + + Invalidate (); + } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new ImeMode ImeMode { + get { return base.ImeMode; } + set { base.ImeMode = value; } + } + + internal virtual Size InternalGetPreferredSize (Size proposed) + { + Size size; + + if (Text == string.Empty) { + size = new Size (0, Font.Height); + } else { + size = Size.Ceiling (TextRenderer.MeasureString (Text, Font, req_witdthsize, string_format)); + size.Width += 3; + } + + size.Width += Padding.Horizontal; + size.Height += Padding.Vertical; + + if (!use_compatible_text_rendering) + return size; + + if (border_style == BorderStyle.None) + size.Height += 3; + else + size.Height += 6; + + return size; + } + + public override Size GetPreferredSize (Size proposedSize) + { + return InternalGetPreferredSize (proposedSize); + } + + [Browsable(false)] + //[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden)] + //[EditorBrowsable(EditorBrowsableState.Advanced)] + public virtual int PreferredHeight { + get { return InternalGetPreferredSize (Size.Empty).Height; } + } + + [Browsable(false)] + //[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden)] + //[EditorBrowsable(EditorBrowsableState.Advanced)] + public virtual int PreferredWidth { + get { return InternalGetPreferredSize (Size.Empty).Width; } + } + + [Obsolete ("This property has been deprecated. Use BackColor instead.")] + protected virtual bool RenderTransparent { + get { return render_transparent; } + set { render_transparent = value;} + } + + [Browsable(false)] + [DefaultValue(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new bool TabStop { + get { return base.TabStop; } + set { base.TabStop = value; } + } + + [DefaultValue(ContentAlignment.TopLeft)] + [Localizable(true)] + public virtual ContentAlignment TextAlign { + get { return text_align; } + + set { + if (!Enum.IsDefined (typeof (ContentAlignment), value)) + throw new InvalidEnumArgumentException (string.Format("Enum argument value '{0}' is not valid for ContentAlignment", value)); + + if (text_align != value) { + text_align = value; + switch (value) { + case ContentAlignment.BottomLeft: + string_format.LineAlignment = StringAlignment.Far; + string_format.Alignment = StringAlignment.Near; + break; + case ContentAlignment.BottomCenter: + string_format.LineAlignment = StringAlignment.Far; + string_format.Alignment = StringAlignment.Center; + break; + case ContentAlignment.BottomRight: + string_format.LineAlignment = StringAlignment.Far; + string_format.Alignment = StringAlignment.Far; + break; + case ContentAlignment.TopLeft: + string_format.LineAlignment = StringAlignment.Near; + string_format.Alignment = StringAlignment.Near; + break; + case ContentAlignment.TopCenter: + string_format.LineAlignment = StringAlignment.Near; + string_format.Alignment = StringAlignment.Center; + break; + case ContentAlignment.TopRight: + string_format.LineAlignment = StringAlignment.Near; + string_format.Alignment = StringAlignment.Far; + break; + case ContentAlignment.MiddleLeft: + string_format.LineAlignment = StringAlignment.Center; + string_format.Alignment = StringAlignment.Near; + break; + case ContentAlignment.MiddleRight: + string_format.LineAlignment = StringAlignment.Center; + string_format.Alignment = StringAlignment.Far; + break; + case ContentAlignment.MiddleCenter: + string_format.LineAlignment = StringAlignment.Center; + string_format.Alignment = StringAlignment.Center; + break; + default: + break; + } + + OnTextAlignChanged (EventArgs.Empty); + Invalidate (); + } + } + } + + [DefaultValue(true)] + public bool UseMnemonic { + get { return use_mnemonic; } + set { + if (use_mnemonic != value) { + use_mnemonic = value; + SetUseMnemonic (use_mnemonic); + Invalidate (); + } + } + } + + #endregion + + #region Public Methods + + protected Rectangle CalcImageRenderBounds (Image image, Rectangle r, ContentAlignment align) + { + Rectangle rcImageClip = r; + rcImageClip.Inflate (-2,-2); + + int X = r.X; + int Y = r.Y; + + if (align == ContentAlignment.TopCenter || + align == ContentAlignment.MiddleCenter || + align == ContentAlignment.BottomCenter) { + X += (r.Width - image.Width) / 2; + } else if (align == ContentAlignment.TopRight || + align == ContentAlignment.MiddleRight|| + align == ContentAlignment.BottomRight) { + X += (r.Width - image.Width); + } + + if( align == ContentAlignment.BottomCenter || + align == ContentAlignment.BottomLeft || + align == ContentAlignment.BottomRight) { + Y += r.Height - image.Height; + } else if(align == ContentAlignment.MiddleCenter || + align == ContentAlignment.MiddleLeft || + align == ContentAlignment.MiddleRight) { + Y += (r.Height - image.Height) / 2; + } + + rcImageClip.X = X; + rcImageClip.Y = Y; + rcImageClip.Width = image.Width; + rcImageClip.Height = image.Height; + + return rcImageClip; + } + + protected override AccessibleObject CreateAccessibilityInstance () + { + return base.CreateAccessibilityInstance (); + } + + protected override void Dispose(bool disposing) + { + base.Dispose (disposing); + + if (disposing) + string_format.Dispose (); + } + + protected internal void DrawImage (Graphics g, Image image, Rectangle r, ContentAlignment align) + { + if (image == null || g == null) + return; + + Rectangle rcImageClip = CalcImageRenderBounds (image, r, align); + + if (Enabled) + g.DrawImage (image, rcImageClip.X, rcImageClip.Y, rcImageClip.Width, rcImageClip.Height); + else + WidgetPaint.DrawImageDisabled (g, image, rcImageClip.X, rcImageClip.Y, BackColor); + } + + protected override void OnEnabledChanged (EventArgs e) + { + base.OnEnabledChanged (e); + } + + protected override void OnFontChanged (EventArgs e) + { + base.OnFontChanged (e); + if (autosize) + CalcAutoSize(); + Invalidate (); + } + + protected override void OnPaddingChanged (EventArgs e) + { + base.OnPaddingChanged (e); + } + + protected override void OnPaint (PaintEventArgs e) + { + ThemeElements.LabelPainter.Draw (e.Graphics, ClientRectangle, this); + base.OnPaint(e); + } + + protected override void OnParentChanged (EventArgs e) + { + base.OnParentChanged (e); + } + + protected override void OnRightToLeftChanged (EventArgs e) + { + base.OnRightToLeftChanged (e); + } + + protected virtual void OnTextAlignChanged (EventArgs e) + { + EventHandler eh = (EventHandler)(Events [TextAlignChangedEvent]); + if (eh != null) + eh (this, e); + } + + protected override void OnTextChanged (EventArgs e) + { + base.OnTextChanged (e); + if (autosize) + CalcAutoSize (); + Invalidate (); + } + + protected override void OnVisibleChanged (EventArgs e) + { + base.OnVisibleChanged (e); + } + + protected override bool ProcessMnemonic (char charCode) + { + if (IsMnemonic (charCode, Text)) { + // Select item next in line in tab order + if (this.Parent != null) + Parent.SelectNextWidget(this, true, false, false, false); + return true; + } + + return base.ProcessMnemonic (charCode); + } + + protected override void SetBoundsCore (int x, int y, int width, int height, BoundsSpecified specified) + { + base.SetBoundsCore (x, y, width, height, specified); + } + + public override string ToString() + { + return base.ToString () + ", Text: " + Text; + } + + protected override void WndProc (ref Message m) + { + switch ((Msg) m.Msg) { + case Msg.WM_DRAWITEM: + m.Result = (IntPtr)1; + break; + default: + base.WndProc (ref m); + break; + } + } + + #endregion Public Methods + + #region Private Methods + + private void CalcAutoSize () + { + if (!AutoSize) + return; + + Size s = InternalGetPreferredSize (Size.Empty); + + SetBounds (Left, Top, s.Width, s.Height, BoundsSpecified.Size); + } + + private void OnHandleCreatedLB (Object o, EventArgs e) + { + if (autosize) + CalcAutoSize (); + } + + private void SetUseMnemonic (bool use) + { + if (use) + string_format.HotkeyPrefix = HotkeyPrefix.Show; + else + string_format.HotkeyPrefix = HotkeyPrefix.None; + } + + #endregion Private Methods + [DefaultValue (false)] + public bool UseCompatibleTextRendering { + get { return use_compatible_text_rendering; } + set { use_compatible_text_rendering = value; } + } + + [SettingsBindable (true)] + //[Editor ("System.ComponentModel.Design.MultilineStringEditor, " + Consts.AssemblySystem_Design, + // typeof (System.Drawing.Design.UITypeEditor))] + public override string Text { + get { return base.Text; } + set { base.Text = value; } + } + + protected override void OnMouseEnter (EventArgs e) + { + base.OnMouseEnter (e); + } + + protected override void OnMouseLeave (EventArgs e) + { + base.OnMouseLeave (e); + } + + protected override void OnHandleDestroyed (EventArgs e) + { + base.OnHandleDestroyed (e); + } + } +} diff --git a/source/ShiftUI/Widgets/LabelEditTextBox.cs b/source/ShiftUI/Widgets/LabelEditTextBox.cs new file mode 100644 index 0000000..8e03bd0 --- /dev/null +++ b/source/ShiftUI/Widgets/LabelEditTextBox.cs @@ -0,0 +1,108 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2006 Novell, Inc. +// +// Authors: +// Jackson Harper ([email protected]) +// +// + +// This is an internal class that allows us to use a textbox for label editing +// in the tree and in listview. The textbox will make itself invisible when +// the user pressed the enter key +using System; + +namespace ShiftUI { + + internal class LabelEditTextBox : FixedSizeTextBox { + + public LabelEditTextBox () : base (true, true) + { + } + + protected override bool IsInputKey (Keys key_data) + { + if ((key_data & Keys.Alt) == 0) { + switch (key_data & Keys.KeyCode) { + case Keys.Enter: + return true; + case Keys.Escape: + return true; + } + } + return base.IsInputKey (key_data); + } + + protected override void OnKeyDown (KeyEventArgs e) + { + if (!Visible) + return; + + switch (e.KeyCode) { + case Keys.Return: + Visible = false; + Parent.Focus (); + e.Handled = true; + OnEditingFinished (e); + break; + case Keys.Escape: + Visible = false; + Parent.Focus (); + e.Handled = true; + OnEditingCancelled (e); + break; + } + } + + protected override void OnLostFocus (EventArgs e) + { + if (Visible) { + OnEditingFinished (e); + } + } + + protected void OnEditingCancelled (EventArgs e) + { + EventHandler eh = (EventHandler)(Events[EditingCancelledEvent]); + if (eh != null) + eh (this, e); + } + + protected void OnEditingFinished (EventArgs e) + { + EventHandler eh = (EventHandler)(Events [EditingFinishedEvent]); + if (eh != null) + eh (this, e); + } + + static object EditingCancelledEvent = new object (); + public event EventHandler EditingCancelled { + add { Events.AddHandler (EditingCancelledEvent, value); } + remove { Events.RemoveHandler (EditingCancelledEvent, value); } + } + + static object EditingFinishedEvent = new object (); + public event EventHandler EditingFinished { + add { Events.AddHandler (EditingFinishedEvent, value); } + remove { Events.AddHandler (EditingFinishedEvent, value); } + } + } +} + diff --git a/source/ShiftUI/Widgets/LinkLabel.cs b/source/ShiftUI/Widgets/LinkLabel.cs new file mode 100644 index 0000000..3654449 --- /dev/null +++ b/source/ShiftUI/Widgets/LinkLabel.cs @@ -0,0 +1,1120 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2004-2005 Novell, Inc. +// +// Authors: +// Jordi Mas i Hernandez, [email protected] +// Chris Toshok <[email protected]> +// Everaldo Canuto <[email protected]> +// +// Based on work by: +// Daniel Carrera, [email protected] (stubbed out) +// Jaak Simm ([email protected]) (stubbed out) +// + +using System.ComponentModel; +using System.Collections; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Runtime.InteropServices; +using ShiftUI.Theming; +using System; + +namespace ShiftUI +{ + [DefaultEvent("LinkClicked")] + [ClassInterface (ClassInterfaceType.AutoDispatch)] + [ComVisible (true)] + [ToolboxItem ("ShiftUI.Design.AutoSizeToolboxItem," + Consts.AssemblySystem_Design)] + [ToolboxWidget] + public class LinkLabel : Label, IButtonWidget + { + /* Encapsulates a piece of text (regular or link)*/ + internal class Piece + { + public string text; + public int start; + public int length; + public LinkLabel.Link link; // Empty link indicates regular text + public Region region; + + public Piece (int start, int length, string text, Link link) + { + this.start = start; + this.length = length; + this.text = text; + this.link = link; + } + } + + private Color active_link_color; + private Color disabled_link_color; + private Color link_color; + private Color visited_color; + private LinkArea link_area; + private LinkBehavior link_behavior; + private LinkCollection link_collection; + private ArrayList links = new ArrayList(); + internal Link[] sorted_links; + private bool link_visited; + internal Piece[] pieces; + private Cursor override_cursor; + private DialogResult dialog_result; + + private Link active_link; + private Link hovered_link; + /* this is an index instead of a Link because we have + * to search through sorted links for the new one */ + private int focused_index; + + #region Events + static object LinkClickedEvent = new object (); + + public event LinkLabelLinkClickedEventHandler LinkClicked { + add { Events.AddHandler (LinkClickedEvent, value); } + remove { Events.RemoveHandler (LinkClickedEvent, value); } + } + + [Browsable (true)] + //[EditorBrowsable (EditorBrowsableState.Always)] + public new event EventHandler TabStopChanged { + add { base.TabStopChanged += value; } + remove { base.TabStopChanged -= value; } + } + #endregion // Events + + public LinkLabel () + { + LinkArea = new LinkArea (0, -1); + link_behavior = LinkBehavior.SystemDefault; + link_visited = false; + pieces = null; + focused_index = -1; + + string_format.FormatFlags |= StringFormatFlags.NoClip; + + ActiveLinkColor = Color.Red; + DisabledLinkColor = ThemeEngine.Current.ColorGrayText; + LinkColor = Color.FromArgb (255, 0, 0, 255); + VisitedLinkColor = Color.FromArgb (255, 128, 0, 128); + SetStyle (Widgetstyles.Selectable, false); + SetStyle (Widgetstyles.ResizeRedraw | + Widgetstyles.UserPaint | + Widgetstyles.AllPaintingInWmPaint | + Widgetstyles.SupportsTransparentBackColor | + Widgetstyles.Opaque | + Widgetstyles.OptimizedDoubleBuffer + , true); + CreateLinkPieces (); + } + + #region Public Properties + + public Color ActiveLinkColor { + get { return active_link_color; } + set { + if (active_link_color == value) + return; + + active_link_color = value; + Invalidate (); + } + } + + public Color DisabledLinkColor { + + get { return disabled_link_color; } + set { + if (disabled_link_color == value) + return; + + disabled_link_color = value; + Invalidate (); + } + } + + public Color LinkColor { + get { return link_color; } + set { + if (link_color == value) + return; + + link_color = value; + Invalidate (); + } + } + + public Color VisitedLinkColor { + get { return visited_color;} + set { + if (visited_color == value) + return; + + visited_color = value; + Invalidate (); + } + } + + [Localizable (true)] + //[Editor ("ShiftUI.Design.LinkAreaEditor, " + Consts.AssemblySystem_Design, typeof (System.Drawing.Design.UITypeEditor))] + [RefreshProperties (RefreshProperties.Repaint)] + public LinkArea LinkArea { + get { return link_area;} + set { + + if (value.Start <0 || value.Length < -1) + throw new ArgumentException (); + + Links.Clear (); + + if (!value.IsEmpty) { + Links.Add (value.Start, value.Length); + + link_area = value; + Invalidate (); + } + } + } + + [DefaultValue (LinkBehavior.SystemDefault)] + public LinkBehavior LinkBehavior { + + get { return link_behavior;} + set { + if (link_behavior == value) + return; + + link_behavior = value; + Invalidate (); + } + } + + [Browsable (false)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public LinkLabel.LinkCollection Links { + get { + if (link_collection == null) + link_collection = new LinkCollection (this); + return link_collection; + } + } + + [DefaultValue (false)] + public bool LinkVisited { + get { return link_visited;} + set { + if (link_visited == value) + return; + + link_visited = value; + Invalidate (); + } + } + + protected Cursor OverrideCursor { + get { + if (override_cursor == null) + override_cursor = Cursors.Hand; + return override_cursor; + } + set { override_cursor = value; } + } + + [RefreshProperties(RefreshProperties.Repaint)] + public override string Text { + get { return base.Text; } + set { + if (base.Text == value) + return; + + base.Text = value; + CreateLinkPieces (); + } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new FlatStyle FlatStyle { + get { return base.FlatStyle; } + set { + if (base.FlatStyle == value) + return; + + base.FlatStyle = value; + } + } + + [RefreshProperties (RefreshProperties.Repaint)] + public new Padding Padding { + get { return base.Padding; } + set { + if (base.Padding == value) + return; + + base.Padding = value; + CreateLinkPieces (); + } + } + + #endregion // Public Properties + + DialogResult IButtonWidget.DialogResult { + get { return dialog_result; } + set { dialog_result = value; } + } + + + void IButtonWidget.NotifyDefault (bool value) + { + } + + void IButtonWidget.PerformClick () + { + } + + #region Public Methods + protected override AccessibleObject CreateAccessibilityInstance () + { + return base.CreateAccessibilityInstance(); + } + + protected override void CreateHandle () + { + base.CreateHandle (); + CreateLinkPieces (); + } + + protected override void OnAutoSizeChanged (EventArgs e) + { + base.OnAutoSizeChanged (e); + } + + protected override void OnEnabledChanged (EventArgs e) + { + base.OnEnabledChanged (e); + Invalidate (); + } + + protected override void OnFontChanged (EventArgs e) + { + base.OnFontChanged (e); + CreateLinkPieces (); + } + + protected override void OnGotFocus (EventArgs e) + { + base.OnGotFocus (e); + + // And yes it can actually be null..... arghh.. + if (sorted_links == null) + return; + + // Set focus to the first enabled link piece + if (focused_index == -1) { + if ((Widget.ModifierKeys & Keys.Shift) == 0) { + for (int i = 0; i < sorted_links.Length; i ++) { + if (sorted_links[i].Enabled) { + focused_index = i; + break; + } + } + } else { + if (focused_index == -1) + focused_index = sorted_links.Length; + + for (int n = focused_index - 1; n >= 0; n--) { + if (sorted_links[n].Enabled) { + sorted_links[n].Focused = true; + focused_index = n; + return; + } + } + } + } + + if (focused_index != -1) + sorted_links[focused_index].Focused = true; + } + + protected override void OnKeyDown (KeyEventArgs e) + { + if (e.KeyCode == Keys.Return) { + if (focused_index != -1) + OnLinkClicked (new LinkLabelLinkClickedEventArgs (sorted_links[focused_index])); + } + + base.OnKeyDown(e); + } + + protected virtual void OnLinkClicked (LinkLabelLinkClickedEventArgs e) + { + LinkLabelLinkClickedEventHandler eh = (LinkLabelLinkClickedEventHandler)(Events [LinkClickedEvent]); + if (eh != null) + eh (this, e); + } + + protected override void OnLostFocus (EventArgs e) + { + base.OnLostFocus (e); + + // Clean focus in link pieces + if (focused_index != -1) + sorted_links[focused_index].Focused = false; + } + + protected override void OnMouseDown (MouseEventArgs e) + { + if (!Enabled) return; + + base.OnMouseDown (e); + + for (int i = 0; i < sorted_links.Length; i ++) { + if (sorted_links[i].Contains (e.X, e.Y) && sorted_links[i].Enabled) { + sorted_links[i].Active = true; + if (focused_index != -1) + sorted_links[focused_index].Focused = false; + active_link = sorted_links[i]; + focused_index = i; + sorted_links[focused_index].Focused = true; + break; + } + } + } + + protected override void OnMouseLeave(EventArgs e) + { + if (!Enabled) return; + base.OnMouseLeave (e); + UpdateHover (null); + } + + protected override void OnPaddingChanged (EventArgs e) + { + base.OnPaddingChanged (e); + } + + private void UpdateHover (Link link) + { + if (link == hovered_link) + return; + + if (hovered_link != null) + hovered_link.Hovered = false; + + hovered_link = link; + + if (hovered_link != null) + hovered_link.Hovered = true; + + Cursor = (hovered_link != null) ? OverrideCursor : Cursors.Default; + + /* XXX this shouldn't be here. the + * Link.Invalidate machinery should be enough, + * but it seems the piece regions don't + * contain the area with the underline. this + * can be seen easily when you click on a link + * and the focus rectangle shows up (it's too + * far up), and also the bottom few pixels of + * a linklabel aren't active when it comes to + * hovering */ + Invalidate (); + } + + protected override void OnMouseMove (MouseEventArgs e) + { + UpdateHover (PointInLink (e.X, e.Y)); + base.OnMouseMove (e); + } + + protected override void OnMouseUp (MouseEventArgs e) + { + if (!Enabled) return; + + base.OnMouseUp (e); + + if (active_link == null) + return; + + Link clicked_link = (PointInLink (e.X, e.Y) == active_link) ? active_link : null; + + active_link.Active = false; + active_link = null; + + if (clicked_link != null) + OnLinkClicked (new LinkLabelLinkClickedEventArgs (clicked_link, e.Button)); + } + + protected override void OnClick (EventArgs e) + { + if (active_link != null && this.Capture) { + this.Capture = false; + } + base.OnClick (e); + } + + protected override void OnPaint (PaintEventArgs e) + { + // We need to invoke paintbackground because control is opaque + // and can have transparent colors. + base.InvokePaintBackground (this, e); + + ThemeElements.LinkLabelPainter.Draw (e.Graphics, e.ClipRectangle, this); + // Do not call base.OnPaint since it's the Label class + } + + protected override void OnPaintBackground (PaintEventArgs e) + { + base.OnPaintBackground (e); + } + + protected override void OnTextAlignChanged (EventArgs e) + { + CreateLinkPieces (); + base.OnTextAlignChanged (e); + } + + protected override void OnTextChanged (EventArgs e) + { + CreateLinkPieces (); + base.OnTextChanged (e); + } + + protected Link PointInLink (int x, int y) + { + for (int i = 0; i < sorted_links.Length; i ++) + if (sorted_links[i].Contains (x, y)) + return sorted_links[i]; + + return null; + } + + protected override bool ProcessDialogKey (Keys keyData) + { + if ((keyData & Keys.KeyCode) == Keys.Tab) { + Select (true, (keyData & Keys.Shift) == 0); + return true; + } + return base.ProcessDialogKey (keyData); + } + + protected override void Select (bool directed, bool forward) + { + if (directed) { + if (focused_index != -1) { + sorted_links[focused_index].Focused = false; + focused_index = -1; + } + + if (forward) { + for (int n = focused_index + 1; n < sorted_links.Length; n++) { + if (sorted_links[n].Enabled) { + sorted_links[n].Focused = true; + focused_index = n; + base.Select (directed, forward); + return; + } + } + } else { + if (focused_index == -1) + focused_index = sorted_links.Length; + + for (int n = focused_index - 1; n >= 0; n--) { + if (sorted_links[n].Enabled) { + sorted_links[n].Focused = true; + focused_index = n; + base.Select (directed, forward); + return; + } + } + } + + focused_index = -1; + + if (Parent != null) + Parent.SelectNextWidget (this, forward, false, true, true); + } + } + + protected override void SetBoundsCore (int x, int y, int width, int height, BoundsSpecified specified) + { + base.SetBoundsCore (x, y, width, height, specified); + CreateLinkPieces(); + } + + protected override void WndProc (ref Message msg) + { + base.WndProc (ref msg); + } + + #endregion //Public Methods + + #region Private Methods + + private ArrayList CreatePiecesFromText (int start, int len, Link link) + { + ArrayList rv = new ArrayList (); + + if (start + len > Text.Length) + len = Text.Length - start; + if (len < 0) + return rv; + + string t = Text.Substring (start, len); + + int ps = 0; + for (int i = 0; i < t.Length; i ++) { + if (t[i] == '\n') { + if (i != 0) { + Piece p = new Piece (start + ps, i + 1 - ps, t.Substring (ps, i+1-ps), link); + rv.Add (p); + } + ps = i+1; + } + } + if (ps < t.Length) { + Piece p = new Piece (start + ps, t.Length - ps, t.Substring (ps, t.Length-ps), link); + rv.Add (p); + } + + return rv; + } + + private void CreateLinkPieces () + { + if (Text.Length == 0) { + SetStyle (Widgetstyles.Selectable, false); + TabStop = false; + link_area.Start = 0; + link_area.Length = 0; + return; + } + + if (Links.Count == 1 && Links[0].Start == 0 && Links[0].Length == -1) + Links[0].Length = Text.Length; + + SortLinks (); + + // Set the LinkArea values based on first link. + if (Links.Count > 0) { + link_area.Start = Links[0].Start; + link_area.Length = Links[0].Length; + } else { + link_area.Start = 0; + link_area.Length = 0; + } + + TabStop = (LinkArea.Length > 0); + SetStyle (Widgetstyles.Selectable, TabStop); + + /* don't bother doing the rest if our handle hasn't been created */ + if (!IsHandleCreated) + return; + + ArrayList pieces_list = new ArrayList (); + + int current_end = 0; + + for (int l = 0; l < sorted_links.Length; l ++) { + int new_link_start = sorted_links[l].Start; + + if (new_link_start > current_end) { + /* create/push a piece + * containing the text between + * the previous/new link */ + ArrayList text_pieces = CreatePiecesFromText (current_end, new_link_start - current_end, null); + pieces_list.AddRange (text_pieces); + } + + /* now create a group of pieces for + * the new link (split up by \n's) */ + ArrayList link_pieces = CreatePiecesFromText (new_link_start, sorted_links[l].Length, sorted_links[l]); + pieces_list.AddRange (link_pieces); + sorted_links[l].pieces.AddRange (link_pieces); + + current_end = sorted_links[l].Start + sorted_links[l].Length; + } + if (current_end < Text.Length) { + ArrayList text_pieces = CreatePiecesFromText (current_end, Text.Length - current_end, null); + pieces_list.AddRange (text_pieces); + } + + pieces = new Piece[pieces_list.Count]; + pieces_list.CopyTo (pieces, 0); + + CharacterRange[] ranges = new CharacterRange[pieces.Length]; + + for(int i = 0; i < pieces.Length; i++) + ranges[i] = new CharacterRange (pieces[i].start, pieces[i].length); + + string_format.SetMeasurableCharacterRanges (ranges); + + Region[] regions = TextRenderer.MeasureCharacterRanges (Text, + ThemeEngine.Current.GetLinkFont (this), + PaddingClientRectangle, + string_format); + + for (int i = 0; i < pieces.Length; i ++) { + pieces[i].region = regions[i]; + pieces[i].region.Translate (Padding.Left, Padding.Top); + } + + Invalidate (); + } + + private void SortLinks () + { + if (sorted_links != null) + return; + + sorted_links = new Link [Links.Count]; + ((ICollection)Links).CopyTo (sorted_links, 0); + + Array.Sort (sorted_links, new LinkComparer ()); + } + + /* Check if the links overlap */ + private void CheckLinks () + { + SortLinks (); + + int current_end = 0; + + for (int i = 0; i < sorted_links.Length; i++) { + if (sorted_links[i].Start < current_end) + throw new InvalidOperationException ("Overlapping link regions."); + current_end = sorted_links[i].Start + sorted_links[i].Length; + } + } + + #endregion // Private Methods + + // + // ShiftUI.LinkLabel.Link + // + [TypeConverter (typeof (LinkConverter))] + public class Link + { + private bool enabled; + internal int length; + private object linkData; + private int start; + private bool visited; + private LinkLabel owner; + private bool hovered; + internal ArrayList pieces; + private bool focused; + private bool active; + private string description; + private string name; + private object tag; + + internal Link (LinkLabel owner) + { + focused = false; + enabled = true; + visited = false; + length = start = 0; + linkData = null; + this.owner = owner; + pieces = new ArrayList (); + name = string.Empty; + } + + public Link () + { + this.enabled = true; + this.name = string.Empty; + this.pieces = new ArrayList (); + } + + public Link (int start, int length) : this () + { + this.start = start; + this.length = length; + } + + public Link (int start, int length, Object linkData) : this (start, length) + { + this.linkData = linkData; + } + + #region Public Properties + public string Description { + get { return this.description; } + set { this.description = value; } + } + + [DefaultValue ("")] + public string Name { + get { return this.name; } + set { this.name = value; } + } + + [Bindable (true)] + [Localizable (false)] + [DefaultValue (null)] + [TypeConverter (typeof (StringConverter))] + public Object Tag { + get { return this.tag; } + set { this.tag = value; } + } + + [DefaultValue (true)] + public bool Enabled { + get { return enabled; } + set { + if (enabled != value) + Invalidate (); + + enabled = value; + } + } + + public int Length { + get { + if (length == -1) { + return owner.Text.Length; + } + + return length; + } + set { + if (length == value) + return; + + length = value; + + owner.CreateLinkPieces (); + } + } + + [DefaultValue (null)] + public object LinkData { + get { return linkData; } + set { linkData = value; } + } + + public int Start { + get { return start; } + set { + if (start == value) + return; + + start = value; + + owner.sorted_links = null; + owner.CreateLinkPieces (); + } + } + + [DefaultValue (false)] + public bool Visited { + get { return visited; } + set { + if (visited != value) + Invalidate (); + + visited = value; + } + } + + internal bool Hovered { + get { return hovered; } + set { + if (hovered != value) + Invalidate (); + hovered = value; + } + } + + internal bool Focused { + get { return focused; } + set { + if (focused != value) + Invalidate (); + focused = value; + } + } + + internal bool Active { + get { return active; } + set { + if (active != value) + Invalidate (); + active = value; + } + } + + internal LinkLabel Owner { + set { owner = value; } + } + #endregion + + private void Invalidate () + { + for (int i = 0; i < pieces.Count; i ++) + owner.Invalidate (((Piece)pieces[i]).region); + } + + internal bool Contains (int x, int y) + { + foreach (Piece p in pieces) { + if (p.region.IsVisible (new Point (x,y))) + return true; + } + return false; + } + } + + class LinkComparer : IComparer + { + public int Compare (object x, object y) + { + Link l1 = (Link)x; + Link l2 = (Link)y; + + return l1.Start - l2.Start; + } + } + + // + // ShiftUI.LinkLabel.LinkCollection + // + public class LinkCollection : IList, ICollection, IEnumerable + { + private LinkLabel owner; + private bool links_added; + + public LinkCollection (LinkLabel owner) + { + if (owner == null) + throw new ArgumentNullException ("owner"); + + this.owner = owner; + } + + [Browsable (false)] + public int Count { + get { return owner.links.Count; } + } + + public bool IsReadOnly { + get { return false; } + } + + public virtual LinkLabel.Link this[int index] { + get { + if (index < 0 || index >= Count) + throw new ArgumentOutOfRangeException(); + + return (LinkLabel.Link) owner.links[index]; + } + set { + if (index < 0 || index >= Count) + throw new ArgumentOutOfRangeException(); + + owner.links[index] = value; + } + } + + public virtual Link this[string key] { + get { + if (string.IsNullOrEmpty (key)) + return null; + + foreach (Link l in owner.links) + if (string.Compare (l.Name, key, true) == 0) + return l; + + return null; + } + } + + public int Add (Link value) + { + value.Owner = owner; + /* remove the default 0,-1 link */ + if (IsDefault) { + /* don't call Clear() here to save the additional CreateLinkPieces */ + owner.links.Clear (); + } + + int idx = owner.links.Add (value); + links_added = true; + + owner.sorted_links = null; + owner.CheckLinks (); + owner.CreateLinkPieces (); + + return idx; + } + + public Link Add (int start, int length) + { + return Add (start, length, null); + } + + internal bool IsDefault { + get { + return (Count == 1 + && this[0].Start == 0 + && this[0].length == -1); + } + } + + public Link Add (int start, int length, object linkData) + { + Link link = new Link (owner); + link.Length = length; + link.Start = start; + link.LinkData = linkData; + + int idx = Add (link); + + return (Link) owner.links[idx]; + } + + public virtual void Clear () + { + owner.links.Clear(); + owner.sorted_links = null; + owner.CreateLinkPieces (); + } + + public bool Contains (Link link) + { + return owner.links.Contains (link); + } + + public virtual bool ContainsKey (string key) + { + return !(this[key] == null); + } + + public IEnumerator GetEnumerator () + { + return owner.links.GetEnumerator (); + } + + public int IndexOf (Link link) + { + return owner.links.IndexOf (link); + } + + public virtual int IndexOfKey (string key) + { + if (string.IsNullOrEmpty (key)) + return -1; + + return IndexOf (this[key]); + } + + public bool LinksAdded { + get { return this.links_added; } + } + + public void Remove (LinkLabel.Link value) + { + owner.links.Remove (value); + owner.sorted_links = null; + owner.CreateLinkPieces (); + } + + public virtual void RemoveByKey (string key) + { + Remove (this[key]); + } + + public void RemoveAt (int index) + { + if (index >= Count) + throw new ArgumentOutOfRangeException ("Invalid value for array index"); + + owner.links.Remove (owner.links[index]); + owner.sorted_links = null; + owner.CreateLinkPieces (); + } + + bool IList.IsFixedSize { + get {return false;} + } + + object IList.this[int index] { + get { return owner.links[index]; } + set { owner.links[index] = value; } + } + + object ICollection.SyncRoot { + get {return this;} + } + + bool ICollection.IsSynchronized { + get {return false;} + } + + void ICollection.CopyTo (Array dest, int index) + { + owner.links.CopyTo (dest, index); + } + + int IList.Add (object value) + { + int idx = owner.links.Add (value); + owner.sorted_links = null; + owner.CheckLinks (); + owner.CreateLinkPieces (); + return idx; + } + + bool IList.Contains (object link) + { + return Contains ((Link) link); + } + + int IList.IndexOf (object link) + { + return owner.links.IndexOf (link); + } + + void IList.Insert (int index, object value) + { + owner.links.Insert (index, value); + owner.sorted_links = null; + owner.CheckLinks (); + owner.CreateLinkPieces (); + } + + void IList.Remove (object value) + { + Remove ((Link) value); + } + } + + [RefreshProperties (RefreshProperties.Repaint)] + public new bool UseCompatibleTextRendering { + get { + return use_compatible_text_rendering; + } + set { + use_compatible_text_rendering = value; + } + } + } +} diff --git a/source/ShiftUI/Widgets/ListBox.cs b/source/ShiftUI/Widgets/ListBox.cs new file mode 100644 index 0000000..ed705f6 --- /dev/null +++ b/source/ShiftUI/Widgets/ListBox.cs @@ -0,0 +1,3093 @@ +/// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2004-2006 Novell, Inc. +// +// Authors: +// Jordi Mas i Hernandez, [email protected] +// Mike Kestner <[email protected]> +// + +// COMPLETE + +using System; +using System.Drawing; +using System.Collections; +using System.ComponentModel; +using System.ComponentModel.Design; +using System.ComponentModel.Design.Serialization; +using System.Globalization; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Collections.Generic; + +namespace ShiftUI +{ + [DefaultProperty("Items")] + [DefaultEvent("SelectedIndexChanged")] + //[Designer ("ShiftUI.Design.ListBoxDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")] + [DefaultBindingProperty ("SelectedValue")] + [ClassInterface (ClassInterfaceType.AutoDispatch)] + [ComVisible (true)] + [ToolboxWidget] + public class ListBox : ListWidget + { + public const int DefaultItemHeight = 13; + public const int NoMatches = -1; + + internal enum ItemNavigation + { + First, + Last, + Next, + Previous, + NextPage, + PreviousPage, + PreviousColumn, + NextColumn + } + + Hashtable item_heights; + private int item_height = -1; + private int column_width = 0; + private int requested_height; + private DrawMode draw_mode = DrawMode.Normal; + private int horizontal_extent = 0; + private bool horizontal_scrollbar = false; + private bool integral_height = true; + private bool multicolumn = false; + private bool scroll_always_visible = false; + private SelectedIndexCollection selected_indices; + private SelectedObjectCollection selected_items; + private SelectionMode selection_mode = SelectionMode.One; + private bool sorted = false; + private bool use_tabstops = true; + private int column_width_internal = 120; + private ImplicitVScrollBar vscrollbar; + private ImplicitHScrollBar hscrollbar; + private int hbar_offset; + private bool suspend_layout; + private bool ctrl_pressed = false; + private bool shift_pressed = false; + private bool explicit_item_height = false; + private int top_index = 0; + private int last_visible_index = 0; + private Rectangle items_area; + private int focused_item = -1; + private ObjectCollection items; + private IntegerCollection custom_tab_offsets; + private Padding padding; + private bool use_custom_tab_offsets; + + public ListBox () + { + items = CreateItemCollection (); + selected_indices = new SelectedIndexCollection (this); + selected_items = new SelectedObjectCollection (this); + + requested_height = bounds.Height; + InternalBorderStyle = BorderStyle.Fixed3D; + BackColor = ThemeEngine.Current.ColorControl; + + /* Vertical scrollbar */ + vscrollbar = new ImplicitVScrollBar (); + vscrollbar.Minimum = 0; + vscrollbar.SmallChange = 1; + vscrollbar.LargeChange = 1; + vscrollbar.Maximum = 0; + vscrollbar.ValueChanged += new EventHandler (VerticalScrollEvent); + vscrollbar.Visible = false; + + /* Horizontal scrollbar */ + hscrollbar = new ImplicitHScrollBar (); + hscrollbar.Minimum = 0; + hscrollbar.SmallChange = 1; + hscrollbar.LargeChange = 1; + hscrollbar.Maximum = 0; + hscrollbar.Visible = false; + hscrollbar.ValueChanged += new EventHandler (HorizontalScrollEvent); + + Widgets.AddImplicit (vscrollbar); + Widgets.AddImplicit (hscrollbar); + + /* Events */ + MouseDown += new MouseEventHandler (OnMouseDownLB); + MouseMove += new MouseEventHandler (OnMouseMoveLB); + MouseUp += new MouseEventHandler (OnMouseUpLB); + MouseWheel += new MouseEventHandler (OnMouseWheelLB); + KeyUp += new KeyEventHandler (OnKeyUpLB); + GotFocus += new EventHandler (OnGotFocus); + LostFocus += new EventHandler (OnLostFocus); + + SetStyle (Widgetstyles.UserPaint, false); + + custom_tab_offsets = new IntegerCollection (this); + } + + #region Events + static object DrawItemEvent = new object (); + static object MeasureItemEvent = new object (); + static object SelectedIndexChangedEvent = new object (); + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler BackgroundImageChanged { + add { base.BackgroundImageChanged += value; } + remove { base.BackgroundImageChanged -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler BackgroundImageLayoutChanged { + add { base.BackgroundImageLayoutChanged += value; } + remove { base.BackgroundImageLayoutChanged -= value; } + } + + [Browsable (true)] + //[EditorBrowsable (EditorBrowsableState.Always)] + public new event EventHandler Click { + add { base.Click += value; } + remove { base.Click -= value; } + } + + public event DrawItemEventHandler DrawItem { + add { Events.AddHandler (DrawItemEvent, value); } + remove { Events.RemoveHandler (DrawItemEvent, value); } + } + + public event MeasureItemEventHandler MeasureItem { + add { Events.AddHandler (MeasureItemEvent, value); } + remove { Events.RemoveHandler (MeasureItemEvent, value); } + } + + [Browsable (true)] + //[EditorBrowsable (EditorBrowsableState.Always)] + public new event MouseEventHandler MouseClick { + add { base.MouseClick += value; } + remove { base.MouseClick -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler PaddingChanged { + add { base.PaddingChanged += value; } + remove { base.PaddingChanged -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event PaintEventHandler Paint { + add { base.Paint += value; } + remove { base.Paint -= value; } + } + + public event EventHandler SelectedIndexChanged { + add { Events.AddHandler (SelectedIndexChangedEvent, value); } + remove { Events.RemoveHandler (SelectedIndexChangedEvent, value); } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Advanced)] + public new event EventHandler TextChanged { + add { base.TextChanged += value; } + remove { base.TextChanged -= value; } + } + #endregion // Events + + #region UIA Framework Events + //NOTE: + // We are using Reflection to add/remove internal events. + // Class ListProvider uses the events. + // + //Event used to generate UIA Selection Pattern + static object UIASelectionModeChangedEvent = new object (); + + internal event EventHandler UIASelectionModeChanged { + add { Events.AddHandler (UIASelectionModeChangedEvent, value); } + remove { Events.RemoveHandler (UIASelectionModeChangedEvent, value); } + } + + internal void OnUIASelectionModeChangedEvent () + { + EventHandler eh = (EventHandler) Events [UIASelectionModeChangedEvent]; + if (eh != null) + eh (this, EventArgs.Empty); + } + + static object UIAFocusedItemChangedEvent = new object (); + + internal event EventHandler UIAFocusedItemChanged { + add { Events.AddHandler (UIAFocusedItemChangedEvent, value); } + remove { Events.RemoveHandler (UIAFocusedItemChangedEvent, value); } + } + + internal void OnUIAFocusedItemChangedEvent () + { + EventHandler eh = (EventHandler) Events [UIAFocusedItemChangedEvent]; + if (eh != null) + eh (this, EventArgs.Empty); + } + #endregion UIA Framework Events + + #region Public Properties + public override Color BackColor { + get { return base.BackColor; } + set { + if (base.BackColor == value) + return; + + base.BackColor = value; + base.Refresh (); // Careful. Calling the base method is not the same that calling + } // the overriden one that refresh also all the items + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public override Image BackgroundImage { + get { return base.BackgroundImage; } + set { + base.BackgroundImage = value; + base.Refresh (); + } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public override ImageLayout BackgroundImageLayout { + get { return base.BackgroundImageLayout; } + set { base.BackgroundImageLayout = value; } + } + + [DefaultValue (BorderStyle.Fixed3D)] + [DispId(-504)] + public BorderStyle BorderStyle { + get { return InternalBorderStyle; } + set { + InternalBorderStyle = value; + UpdateListBoxBounds (); + } + } + + [DefaultValue (0)] + [Localizable (true)] + public int ColumnWidth { + get { return column_width; } + set { + if (value < 0) + throw new ArgumentException ("A value less than zero is assigned to the property."); + + column_width = value; + + if (value == 0) + ColumnWidthInternal = 120; + else + ColumnWidthInternal = value; + + base.Refresh (); + } + } + + protected override CreateParams CreateParams { + get { return base.CreateParams;} + } + + [Browsable (false)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Content)] + public IntegerCollection CustomTabOffsets { + get { return custom_tab_offsets; } + } + + protected override Size DefaultSize { + get { return new Size (120, 96); } + } + + [RefreshProperties(RefreshProperties.Repaint)] + [DefaultValue (DrawMode.Normal)] + public virtual DrawMode DrawMode { + get { return draw_mode; } + set { + if (!Enum.IsDefined (typeof (DrawMode), value)) + throw new InvalidEnumArgumentException (string.Format("Enum argument value '{0}' is not valid for DrawMode", value)); + + if (value == DrawMode.OwnerDrawVariable && multicolumn == true) + throw new ArgumentException ("Cannot have variable height and multicolumn"); + + if (draw_mode == value) + return; + + draw_mode = value; + + if (draw_mode == DrawMode.OwnerDrawVariable) + item_heights = new Hashtable (); + else + item_heights = null; + + if (Parent != null) + Parent.PerformLayout (this, "DrawMode"); + base.Refresh (); + } + } + + public override Font Font { + get { return base.Font; } + set { base.Font = value; } + } + + public override Color ForeColor { + get { return base.ForeColor; } + set { + if (base.ForeColor == value) + return; + + base.ForeColor = value; + base.Refresh (); + } + } + + [DefaultValue (0)] + [Localizable (true)] + public int HorizontalExtent { + get { return horizontal_extent; } + set { + if (horizontal_extent == value) + return; + + horizontal_extent = value; + base.Refresh (); + } + } + + [DefaultValue (false)] + [Localizable (true)] + public bool HorizontalScrollbar { + get { return horizontal_scrollbar; } + set { + if (horizontal_scrollbar == value) + return; + + horizontal_scrollbar = value; + UpdateScrollBars (); + base.Refresh (); + } + } + + [DefaultValue (true)] + [Localizable (true)] + [RefreshProperties(RefreshProperties.Repaint)] + public bool IntegralHeight { + get { return integral_height; } + set { + if (integral_height == value) + return; + + integral_height = value; + UpdateListBoxBounds (); + } + } + + [DefaultValue (13)] + [Localizable (true)] + [RefreshProperties(RefreshProperties.Repaint)] + public virtual int ItemHeight { + get { + if (item_height == -1) { + SizeF sz = TextRenderer.MeasureString ("The quick brown Fox", Font); + item_height = (int) sz.Height; + } + return item_height; + } + set { + if (value > 255) + throw new ArgumentOutOfRangeException ("The ItemHeight property was set beyond 255 pixels"); + + explicit_item_height = true; + if (item_height == value) + return; + + item_height = value; + if (IntegralHeight) + UpdateListBoxBounds (); + LayoutListBox (); + } + } + + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Content)] + [Localizable (true)] + //[Editor ("ShiftUI.Design.ListWidgetstringCollectionEditor, " + Consts.AssemblySystem_Design, typeof (System.Drawing.Design.UITypeEditor))] + [MergableProperty (false)] + public ObjectCollection Items { + get { return items; } + } + + [DefaultValue (false)] + public bool MultiColumn { + get { return multicolumn; } + set { + if (multicolumn == value) + return; + + if (value == true && DrawMode == DrawMode.OwnerDrawVariable) + throw new ArgumentException ("A multicolumn ListBox cannot have a variable-sized height."); + + multicolumn = value; + LayoutListBox (); + Invalidate (); + } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public new Padding Padding { + get { return padding; } + set { padding = value; } + } + + [Browsable (false)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + //[EditorBrowsable (EditorBrowsableState.Advanced)] + public int PreferredHeight { + get { + int itemsHeight = 0; + if (draw_mode == DrawMode.Normal) + itemsHeight = FontHeight * items.Count; + else if (draw_mode == DrawMode.OwnerDrawFixed) + itemsHeight = ItemHeight * items.Count; + else if (draw_mode == DrawMode.OwnerDrawVariable) { + for (int i = 0; i < items.Count; i++) + itemsHeight += (int) item_heights [Items [i]]; + } + + return itemsHeight; + } + } + + public override RightToLeft RightToLeft { + get { return base.RightToLeft; } + set { + base.RightToLeft = value; + if (base.RightToLeft == RightToLeft.Yes) + StringFormat.Alignment = StringAlignment.Far; + else + StringFormat.Alignment = StringAlignment.Near; + base.Refresh (); + } + } + + // Only affects the Vertical ScrollBar + [DefaultValue (false)] + [Localizable (true)] + public bool ScrollAlwaysVisible { + get { return scroll_always_visible; } + set { + if (scroll_always_visible == value) + return; + + scroll_always_visible = value; + UpdateScrollBars (); + } + } + + [Bindable(true)] + [Browsable (false)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public override int SelectedIndex { + get { + if (selected_indices == null) + return -1; + + return selected_indices.Count > 0 ? selected_indices [0] : -1; + } + set { + if (value < -1 || value >= Items.Count) + throw new ArgumentOutOfRangeException ("Index of out range"); + + if (SelectionMode == SelectionMode.None) + throw new ArgumentException ("cannot call this method if SelectionMode is SelectionMode.None"); + + if (value == -1) + selected_indices.Clear (); + else + selected_indices.Add (value); + } + } + + [Browsable (false)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public SelectedIndexCollection SelectedIndices { + get { return selected_indices; } + } + + [Bindable(true)] + [Browsable (false)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public object SelectedItem { + get { + if (SelectedItems.Count > 0) + return SelectedItems[0]; + else + return null; + } + set { + if (value != null && !Items.Contains (value)) + return; // FIXME: this is probably an exception + + SelectedIndex = value == null ? - 1 : Items.IndexOf (value); + } + } + + [Browsable (false)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public SelectedObjectCollection SelectedItems { + get {return selected_items;} + } + + [DefaultValue (SelectionMode.One)] + public virtual SelectionMode SelectionMode { + get { return selection_mode; } + set { + if (!Enum.IsDefined (typeof (SelectionMode), value)) + throw new InvalidEnumArgumentException (string.Format("Enum argument value '{0}' is not valid for SelectionMode", value)); + + if (selection_mode == value) + return; + + selection_mode = value; + + switch (selection_mode) { + case SelectionMode.None: + SelectedIndices.Clear (); + break; + + case SelectionMode.One: + // FIXME: Probably this can be improved + ArrayList old_selection = (ArrayList) SelectedIndices.List.Clone (); + for (int i = 1; i < old_selection.Count; i++) + SelectedIndices.Remove ((int)old_selection [i]); + break; + + default: + break; + } + + // UIA Framework: Generates SelectionModeChanged event. + OnUIASelectionModeChangedEvent (); + } + } + + [DefaultValue (false)] + public bool Sorted { + get { return sorted; } + set { + if (sorted == value) + return; + + sorted = value; + if (sorted) + Sort (); + } + } + + [Bindable (false)] + [Browsable (false)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + //[EditorBrowsable (EditorBrowsableState.Advanced)] + public override string Text { + get { + if (SelectionMode != SelectionMode.None && SelectedIndex != -1) + return GetItemText (SelectedItem); + + return base.Text; + } + set { + + base.Text = value; + + if (SelectionMode == SelectionMode.None) + return; + + int index; + + index = FindStringExact (value); + + if (index == -1) + return; + + SelectedIndex = index; + } + } + + [Browsable (false)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public int TopIndex { + get { return top_index; } + set { + if (value == top_index) + return; + + if (value < 0 || value >= Items.Count) + return; + + int page_size = (items_area.Height / ItemHeight); + + if (Items.Count < page_size) + value = 0; + else if (!multicolumn) + top_index = Math.Min (value, Items.Count - page_size); + else + top_index = value; + + UpdateTopItem (); + base.Refresh (); + } + } + + [Browsable (false)] + [DefaultValue (false)] + public bool UseCustomTabOffsets { + get { return use_custom_tab_offsets; } + set { + if (use_custom_tab_offsets != value) { + use_custom_tab_offsets = value; + CalculateTabStops (); + } + } + } + + [DefaultValue (true)] + public bool UseTabStops { + get { return use_tabstops; } + set { + if (use_tabstops == value) + return; + + use_tabstops = value; + CalculateTabStops (); + } + } + + protected override bool AllowSelection { + get { + return SelectionMode != SelectionMode.None; + } + } + #endregion Public Properties + + #region Private Properties + + private int ColumnWidthInternal { + get { return column_width_internal; } + set { column_width_internal = value; } + } + + private int row_count = 1; + private int RowCount { + get { + return MultiColumn ? row_count : Items.Count; + } + } + + #endregion Private Properties + + #region UIA Framework Properties + + internal ScrollBar UIAHScrollBar { + get { return hscrollbar; } + } + + internal ScrollBar UIAVScrollBar { + get { return vscrollbar; } + } + + #endregion UIA Framework Properties + + #region Public Methods + [Obsolete ("this method has been deprecated")] + protected virtual void AddItemsCore (object[] value) + { + Items.AddRange (value); + } + + public void BeginUpdate () + { + suspend_layout = true; + } + + public void ClearSelected () + { + selected_indices.Clear (); + } + + protected virtual ObjectCollection CreateItemCollection () + { + return new ObjectCollection (this); + } + + public void EndUpdate () + { + suspend_layout = false; + LayoutListBox (); + base.Refresh (); + } + + public int FindString (String s) + { + return FindString (s, -1); + } + + public int FindString (string s, int startIndex) + { + if (Items.Count == 0) + return -1; // No exception throwing if empty + + if (startIndex < -1 || startIndex >= Items.Count) + throw new ArgumentOutOfRangeException ("Index of out range"); + + startIndex = (startIndex == Items.Count - 1) ? 0 : startIndex + 1; + + int i = startIndex; + while (true) { + string text = GetItemText (Items [i]); + if (CultureInfo.CurrentCulture.CompareInfo.IsPrefix (text, s, + CompareOptions.IgnoreCase)) + return i; + + i = (i == Items.Count - 1) ? 0 : i + 1; + if (i == startIndex) + break; + } + + return NoMatches; + } + + public int FindStringExact (string s) + { + return FindStringExact (s, -1); + } + + public int FindStringExact (string s, int startIndex) + { + if (Items.Count == 0) + return -1; // No exception throwing if empty + + if (startIndex < -1 || startIndex >= Items.Count) + throw new ArgumentOutOfRangeException ("Index of out range"); + + startIndex = (startIndex + 1 == Items.Count) ? 0 : startIndex + 1; + + int i = startIndex; + while (true) { + if (String.Compare (GetItemText (Items[i]), s, true) == 0) + return i; + + i = (i + 1 == Items.Count) ? 0 : i + 1; + if (i == startIndex) + break; + } + + return NoMatches; + } + + public int GetItemHeight (int index) + { + if (index < 0 || index >= Items.Count) + throw new ArgumentOutOfRangeException ("Index of out range"); + + if (DrawMode == DrawMode.OwnerDrawVariable && IsHandleCreated == true) { + + object o = Items [index]; + if (item_heights.Contains (o)) + return (int) item_heights [o]; + + MeasureItemEventArgs args = new MeasureItemEventArgs (DeviceContext, index, ItemHeight); + OnMeasureItem (args); + item_heights [o] = args.ItemHeight; + return args.ItemHeight; + } + + return ItemHeight; + } + + public Rectangle GetItemRectangle (int index) + { + if (index < 0 || index >= Items.Count) + throw new ArgumentOutOfRangeException ("GetItemRectangle index out of range."); + + Rectangle rect = new Rectangle (); + + if (MultiColumn) { + int col = index / RowCount; + int y = index; + if (y < 0) // We convert it to valid positive value + y += RowCount * (top_index / RowCount); + rect.Y = (y % RowCount) * ItemHeight; + rect.X = (col - (top_index / RowCount)) * ColumnWidthInternal; + rect.Height = ItemHeight; + rect.Width = ColumnWidthInternal; + } else { + rect.X = 0; + rect.Height = GetItemHeight (index); + rect.Width = items_area.Width; + + if (DrawMode == DrawMode.OwnerDrawVariable) { + rect.Y = 0; + if (index >= top_index) { + for (int i = top_index; i < index; i++) { + rect.Y += GetItemHeight (i); + } + } else { + for (int i = index; i < top_index; i++) { + rect.Y -= GetItemHeight (i); + } + } + } else { + rect.Y = ItemHeight * (index - top_index); + } + } + + if (this is CheckedListBox) + rect.Width += 15; + + return rect; + } + + //[EditorBrowsable (EditorBrowsableState.Advanced)] + protected override Rectangle GetScaledBounds (Rectangle bounds, SizeF factor, BoundsSpecified specified) + { + bounds.Height = requested_height; + + return base.GetScaledBounds (bounds, factor, specified); + } + + public bool GetSelected (int index) + { + if (index < 0 || index >= Items.Count) + throw new ArgumentOutOfRangeException ("Index of out range"); + + return SelectedIndices.Contains (index); + } + + public int IndexFromPoint (Point p) + { + return IndexFromPoint (p.X, p.Y); + } + + // Only returns visible points + public int IndexFromPoint (int x, int y) + { + + if (Items.Count == 0) { + return -1; + } + + for (int i = top_index; i <= last_visible_index; i++) { + if (GetItemRectangle (i).Contains (x,y) == true) + return i; + } + + return -1; + } + + protected override void OnChangeUICues (UICuesEventArgs e) + { + base.OnChangeUICues (e); + } + + protected override void OnDataSourceChanged (EventArgs e) + { + base.OnDataSourceChanged (e); + BindDataItems (); + + if (DataSource == null || DataManager == null) { + SelectedIndex = -1; + } else { + SelectedIndex = DataManager.Position; + } + } + + protected override void OnDisplayMemberChanged (EventArgs e) + { + base.OnDisplayMemberChanged (e); + + if (DataManager == null || !IsHandleCreated) + return; + + BindDataItems (); + base.Refresh (); + } + + protected virtual void OnDrawItem (DrawItemEventArgs e) + { + switch (DrawMode) { + case DrawMode.OwnerDrawFixed: + case DrawMode.OwnerDrawVariable: + DrawItemEventHandler eh = (DrawItemEventHandler)(Events [DrawItemEvent]); + if (eh != null) + eh (this, e); + + break; + + default: + ThemeEngine.Current.DrawListBoxItem (this, e); + break; + } + } + + protected override void OnFontChanged (EventArgs e) + { + base.OnFontChanged (e); + + if (use_tabstops) + StringFormat.SetTabStops (0, new float [] {(float)(Font.Height * 3.7)}); + + if (explicit_item_height) { + base.Refresh (); + } else { + SizeF sz = TextRenderer.MeasureString ("The quick brown Fox", Font); + item_height = (int) sz.Height; + if (IntegralHeight) + UpdateListBoxBounds (); + LayoutListBox (); + } + } + + protected override void OnHandleCreated (EventArgs e) + { + base.OnHandleCreated (e); + + if (IntegralHeight) + UpdateListBoxBounds (); + + LayoutListBox (); + EnsureVisible (focused_item); + } + + protected override void OnHandleDestroyed (EventArgs e) + { + base.OnHandleDestroyed (e); + } + + protected virtual void OnMeasureItem (MeasureItemEventArgs e) + { + if (draw_mode != DrawMode.OwnerDrawVariable) + return; + + MeasureItemEventHandler eh = (MeasureItemEventHandler)(Events [MeasureItemEvent]); + if (eh != null) + eh (this, e); + } + + protected override void OnParentChanged (EventArgs e) + { + base.OnParentChanged (e); + } + + protected override void OnResize (EventArgs e) + { + base.OnResize (e); + if (canvas_size.IsEmpty || MultiColumn) + LayoutListBox (); + + Invalidate (); + } + + protected override void OnSelectedIndexChanged (EventArgs e) + { + base.OnSelectedIndexChanged (e); + + EventHandler eh = (EventHandler)(Events [SelectedIndexChangedEvent]); + if (eh != null) + eh (this, e); + } + + protected override void OnSelectedValueChanged (EventArgs e) + { + base.OnSelectedValueChanged (e); + } + + public override void Refresh () + { + if (draw_mode == DrawMode.OwnerDrawVariable) + item_heights.Clear (); + + base.Refresh (); + } + + protected override void RefreshItem (int index) + { + if (index < 0 || index >= Items.Count) + throw new ArgumentOutOfRangeException ("Index of out range"); + + if (draw_mode == DrawMode.OwnerDrawVariable) + item_heights.Remove (Items [index]); + } + + protected override void RefreshItems () + { + for (int i = 0; i < Items.Count; i++) { + RefreshItem (i); + } + + LayoutListBox (); + Refresh (); + } + + public override void ResetBackColor () + { + base.ResetBackColor (); + } + + public override void ResetForeColor () + { + base.ResetForeColor (); + } + + protected override void ScaleWidget (SizeF factor, BoundsSpecified specified) + { + base.ScaleWidget (factor, specified); + } + + private int SnapHeightToIntegral (int height) + { + int border; + + switch (border_style) { + case BorderStyle.Fixed3D: + border = ThemeEngine.Current.Border3DSize.Height; + break; + case BorderStyle.FixedSingle: + border = ThemeEngine.Current.BorderSize.Height; + break; + case BorderStyle.None: + default: + border = 0; + break; + } + + height -= (2 * border); + height -= height % ItemHeight; + height += (2 * border); + + return height; + } + + protected override void SetBoundsCore (int x, int y, int width, int height, BoundsSpecified specified) + { + if ((specified & BoundsSpecified.Height) == BoundsSpecified.Height) + requested_height = height; + + if (IntegralHeight && IsHandleCreated) + height = SnapHeightToIntegral (height); + + base.SetBoundsCore (x, y, width, height, specified); + UpdateScrollBars (); + last_visible_index = LastVisibleItem (); + } + + protected override void SetItemCore (int index, object value) + { + if (index < 0 || index >= Items.Count) + return; + + Items[index] = value; + } + + protected override void SetItemsCore (IList value) + { + BeginUpdate (); + try { + Items.Clear (); + Items.AddItems (value); + } finally { + EndUpdate (); + } + } + + public void SetSelected (int index, bool value) + { + if (index < 0 || index >= Items.Count) + throw new ArgumentOutOfRangeException ("Index of out range"); + + if (SelectionMode == SelectionMode.None) + throw new InvalidOperationException (); + + if (value) + SelectedIndices.Add (index); + else + SelectedIndices.Remove (index); + } + + protected virtual void Sort () + { + Sort (true); + } + + // + // Sometimes we could need to Sort, and request a Refresh + // in a different place, to not have the painting done twice + // + void Sort (bool paint) + { + if (Items.Count == 0) + return; + + Items.Sort (); + + if (paint) + base.Refresh (); + } + + public override string ToString () + { + return base.ToString (); + } + + protected virtual void WmReflectCommand (ref Message m) + { + } + + protected override void WndProc (ref Message m) + { + if ((Msg)m.Msg == Msg.WM_KEYDOWN) { + if (ProcessKeyMessage (ref m)) + m.Result = IntPtr.Zero; + else { + HandleKeyDown ((Keys)m.WParam.ToInt32 ()); + DefWndProc (ref m); + } + + return; + } + + base.WndProc (ref m); + } + + #endregion Public Methods + + #region Private Methods + + private void CalculateTabStops () + { + if (use_tabstops) { + if (use_custom_tab_offsets) { + float[] f = new float[custom_tab_offsets.Count]; + custom_tab_offsets.CopyTo (f, 0); + StringFormat.SetTabStops (0, f); + } + else + StringFormat.SetTabStops (0, new float[] { (float)(Font.Height * 3.7) }); + } else + StringFormat.SetTabStops (0, new float[0]); + + this.Invalidate (); + } + + private Size canvas_size; + + private void LayoutListBox () + { + if (!IsHandleCreated || suspend_layout) + return; + + if (MultiColumn) + LayoutMultiColumn (); + else + LayoutSingleColumn (); + + last_visible_index = LastVisibleItem (); + UpdateScrollBars (); + } + + private void LayoutSingleColumn () + { + int height, width; + + switch (DrawMode) { + case DrawMode.OwnerDrawVariable: + height = 0; + width = HorizontalExtent; + for (int i = 0; i < Items.Count; i++) { + height += GetItemHeight (i); + } + break; + + case DrawMode.OwnerDrawFixed: + height = Items.Count * ItemHeight; + width = HorizontalExtent; + break; + + case DrawMode.Normal: + default: + height = Items.Count * ItemHeight; + width = 0; + for (int i = 0; i < Items.Count; i++) { + SizeF sz = TextRenderer.MeasureString (GetItemText (Items[i]), Font); + int t = (int)sz.Width; + + if (this is CheckedListBox) + t += 15; + + if (t > width) + width = t; + } + break; + } + + canvas_size = new Size (width, height); + } + + private void LayoutMultiColumn () + { + int usable_height = ClientRectangle.Height - (ScrollAlwaysVisible ? hscrollbar.Height : 0); + row_count = Math.Max (1, usable_height / ItemHeight); + + int cols = (int) Math.Ceiling ((float)Items.Count / (float) row_count); + Size sz = new Size (cols * ColumnWidthInternal, row_count * ItemHeight); + if (!ScrollAlwaysVisible && sz.Width > ClientRectangle.Width && row_count > 1) { + usable_height = ClientRectangle.Height - hscrollbar.Height; + row_count = Math.Max (1, usable_height / ItemHeight); + cols = (int) Math.Ceiling ((float)Items.Count / (float) row_count); + sz = new Size (cols * ColumnWidthInternal, row_count * ItemHeight); + } + canvas_size = sz; + } + + internal void Draw (Rectangle clip, Graphics dc) + { + Theme theme = ThemeEngine.Current; + + if (hscrollbar.Visible && vscrollbar.Visible) { + // Paint the dead space in the bottom right corner + Rectangle rect = new Rectangle (hscrollbar.Right, vscrollbar.Bottom, vscrollbar.Width, hscrollbar.Height); + if (rect.IntersectsWith (clip)) + dc.FillRectangle (theme.ResPool.GetSolidBrush (theme.ColorControl), rect); + } + + dc.FillRectangle (theme.ResPool.GetSolidBrush (BackColor), items_area); + + if (Items.Count == 0) + return; + + for (int i = top_index; i <= last_visible_index; i++) { + Rectangle rect = GetItemDisplayRectangle (i, top_index); + + if (!clip.IntersectsWith (rect)) + continue; + + DrawItemState state = DrawItemState.None; + + if (SelectedIndices.Contains (i)) + state |= DrawItemState.Selected; + + if (has_focus && FocusedItem == i) + state |= DrawItemState.Focus; + + if (MultiColumn == false && hscrollbar != null && hscrollbar.Visible) { + rect.X -= hscrollbar.Value; + rect.Width += hscrollbar.Value; + } + + Color fore_color = !Enabled ? ThemeEngine.Current.ColorGrayText : + (state & DrawItemState.Selected) != 0 ? ThemeEngine.Current.ColorHighlightText : ForeColor; + OnDrawItem (new DrawItemEventArgs (dc, Font, rect, i, state, fore_color, BackColor)); + } + } + + // Converts a GetItemRectangle to a one that we can display + internal Rectangle GetItemDisplayRectangle (int index, int first_displayble) + { + Rectangle item_rect; + Rectangle first_item_rect = GetItemRectangle (first_displayble); + item_rect = GetItemRectangle (index); + item_rect.X -= first_item_rect.X; + item_rect.Y -= first_item_rect.Y; + + // Subtract the checkboxes from the width + if (this is CheckedListBox) + item_rect.Width -= 14; + + return item_rect; + } + + // Value Changed + private void HorizontalScrollEvent (object sender, EventArgs e) + { + if (multicolumn) { + int top_item = top_index; + int last_item = last_visible_index; + + top_index = RowCount * hscrollbar.Value; + last_visible_index = LastVisibleItem (); + + if (top_item != top_index || last_item != last_visible_index) + Invalidate (items_area); + } + else { + int old_offset = hbar_offset; + hbar_offset = hscrollbar.Value; + + if (hbar_offset < 0) + hbar_offset = 0; + + if (IsHandleCreated) { + XplatUI.ScrollWindow (Handle, items_area, old_offset - hbar_offset, 0, false); + + // Invalidate the previous selection border, to keep it properly updated. + Rectangle selection_border_area = new Rectangle (items_area.Width - (hbar_offset - old_offset) - 3, 0, + 3, items_area.Height); + Invalidate (selection_border_area); + } + } + } + + // Only returns visible points. The diference of with IndexFromPoint is that the rectangle + // has screen coordinates + private int IndexAtClientPoint (int x, int y) + { + if (Items.Count == 0) + return -1; + + if (x < 0) + x = 0; + else if (x > ClientRectangle.Right) + x = ClientRectangle.Right; + + if (y < 0) + y = 0; + else if (y > ClientRectangle.Bottom) + y = ClientRectangle.Bottom; + + for (int i = top_index; i <= last_visible_index; i++) + if (GetItemDisplayRectangle (i, top_index).Contains (x, y)) + return i; + + return -1; + } + + internal override bool IsInputCharInternal (char charCode) + { + return true; + } + + private int LastVisibleItem () + { + Rectangle item_rect; + int top_y = items_area.Y + items_area.Height; + int i = 0; + + if (top_index >= Items.Count) + return top_index; + + for (i = top_index; i < Items.Count; i++) { + item_rect = GetItemDisplayRectangle (i, top_index); + if (MultiColumn) { + if (item_rect.X > items_area.Width) + return i - 1; + } else { + if (item_rect.Y + item_rect.Height > top_y) + return i; + } + } + return i - 1; + } + + private void UpdateTopItem () + { + if (MultiColumn) { + int col = top_index / RowCount; + + if (col > hscrollbar.Maximum) + hscrollbar.Value = hscrollbar.Maximum; + else + hscrollbar.Value = col; + } else { + if (top_index > vscrollbar.Maximum) + vscrollbar.Value = vscrollbar.Maximum; + else + vscrollbar.Value = top_index; + Scroll (vscrollbar, vscrollbar.Value - top_index); + } + } + + // Navigates to the indicated item and returns the new item + private int NavigateItemVisually (ItemNavigation navigation) + { + int page_size, columns, selected_index = -1; + + if (multicolumn) { + columns = items_area.Width / ColumnWidthInternal; + page_size = columns * RowCount; + if (page_size == 0) { + page_size = RowCount; + } + } else { + page_size = items_area.Height / ItemHeight; + } + + switch (navigation) { + + case ItemNavigation.PreviousColumn: { + if (SelectedIndex - RowCount < 0) { + return -1; + } + + if (SelectedIndex - RowCount < top_index) { + top_index = SelectedIndex - RowCount; + UpdateTopItem (); + } + + selected_index = SelectedIndex - RowCount; + break; + } + + case ItemNavigation.NextColumn: { + if (SelectedIndex + RowCount >= Items.Count) { + break; + } + + if (SelectedIndex + RowCount > last_visible_index) { + top_index = SelectedIndex; + UpdateTopItem (); + } + + selected_index = SelectedIndex + RowCount; + break; + } + + case ItemNavigation.First: { + top_index = 0; + selected_index = 0; + UpdateTopItem (); + break; + } + + case ItemNavigation.Last: { + + int rows = items_area.Height / ItemHeight; + + if (multicolumn) { + selected_index = Items.Count - 1; + break; + } + if (Items.Count < rows) { + top_index = 0; + selected_index = Items.Count - 1; + UpdateTopItem (); + } else { + top_index = Items.Count - rows; + selected_index = Items.Count - 1; + UpdateTopItem (); + } + break; + } + + case ItemNavigation.Next: { + if (FocusedItem == Items.Count - 1) + return -1; + + if (multicolumn) { + selected_index = FocusedItem + 1; + break; + } + + int bottom = 0; + ArrayList heights = new ArrayList (); + if (draw_mode == DrawMode.OwnerDrawVariable) { + for (int i = top_index; i <= FocusedItem + 1; i++) { + int h = GetItemHeight (i); + bottom += h; + heights.Add (h); + } + } else { + bottom = ((FocusedItem + 1) - top_index + 1) * ItemHeight; + } + + if (bottom >= items_area.Height) { + int overhang = bottom - items_area.Height; + + int offset = 0; + if (draw_mode == DrawMode.OwnerDrawVariable) + while (overhang > 0) + overhang -= (int) heights [offset]; + else + offset = (int) Math.Ceiling ((float)overhang / (float) ItemHeight); + top_index += offset; + UpdateTopItem (); + } + selected_index = FocusedItem + 1; + break; + } + + case ItemNavigation.Previous: { + if (FocusedItem > 0) { + if (FocusedItem - 1 < top_index) { + top_index--; + UpdateTopItem (); + } + selected_index = FocusedItem - 1; + } + break; + } + + case ItemNavigation.NextPage: { + if (Items.Count < page_size) { + NavigateItemVisually (ItemNavigation.Last); + break; + } + + if (FocusedItem + page_size - 1 >= Items.Count) { + top_index = Items.Count - page_size; + UpdateTopItem (); + selected_index = Items.Count - 1; + } + else { + if (FocusedItem + page_size - 1 > last_visible_index) { + top_index = FocusedItem; + UpdateTopItem (); + } + + selected_index = FocusedItem + page_size - 1; + } + + break; + } + + case ItemNavigation.PreviousPage: { + + int rows = items_area.Height / ItemHeight; + if (FocusedItem - (rows - 1) <= 0) { + top_index = 0; + UpdateTopItem (); + selected_index = 0; + } + else { + if (SelectedIndex - (rows - 1) < top_index) { + top_index = FocusedItem - (rows - 1); + UpdateTopItem (); + } + + selected_index = FocusedItem - (rows - 1); + } + + break; + } + default: + break; + } + + return selected_index; + } + + + private void OnGotFocus (object sender, EventArgs e) + { + if (Items.Count == 0) + return; + + if (FocusedItem == -1) + FocusedItem = 0; + + InvalidateItem (FocusedItem); + } + + private void OnLostFocus (object sender, EventArgs e) + { + if (FocusedItem != -1) + InvalidateItem (FocusedItem); + } + + private bool KeySearch (Keys key) + { + char c = (char) key; + if (!Char.IsLetterOrDigit (c)) + return false; + + int idx = FindString (c.ToString (), SelectedIndex); + if (idx != ListBox.NoMatches) + SelectedIndex = idx; + + return true; + } + + internal void HandleKeyDown (Keys key) + { + int new_item = -1; + + if (Items.Count == 0) + return; + + if (KeySearch (key)) + return; + + switch (key) { + + case Keys.ControlKey: + ctrl_pressed = true; + break; + + case Keys.ShiftKey: + shift_pressed = true; + break; + + case Keys.Home: + new_item = NavigateItemVisually (ItemNavigation.First); + break; + + case Keys.End: + new_item = NavigateItemVisually (ItemNavigation.Last); + break; + + case Keys.Up: + new_item = NavigateItemVisually (ItemNavigation.Previous); + break; + + case Keys.Down: + new_item = NavigateItemVisually (ItemNavigation.Next); + break; + + case Keys.PageUp: + new_item = NavigateItemVisually (ItemNavigation.PreviousPage); + break; + + case Keys.PageDown: + new_item = NavigateItemVisually (ItemNavigation.NextPage); + break; + + case Keys.Right: + if (multicolumn == true) { + new_item = NavigateItemVisually (ItemNavigation.NextColumn); + } + break; + + case Keys.Left: + if (multicolumn == true) { + new_item = NavigateItemVisually (ItemNavigation.PreviousColumn); + } + break; + + case Keys.Space: + if (selection_mode == SelectionMode.MultiSimple) { + SelectedItemFromNavigation (FocusedItem); + } + break; + + + default: + break; + } + + if (new_item != -1) { + FocusedItem = new_item; + + if (selection_mode != SelectionMode.MultiSimple) + SelectedItemFromNavigation (new_item); + } + } + + private void OnKeyUpLB (object sender, KeyEventArgs e) + { + switch (e.KeyCode) { + case Keys.ControlKey: + ctrl_pressed = false; + break; + case Keys.ShiftKey: + shift_pressed = false; + break; + default: + break; + } + } + + internal void InvalidateItem (int index) + { + if (!IsHandleCreated) + return; + Rectangle bounds = GetItemDisplayRectangle (index, top_index); + if (ClientRectangle.IntersectsWith (bounds)) + Invalidate (bounds); + } + + internal virtual void OnItemClick (int index) + { + OnSelectedIndexChanged (EventArgs.Empty); + OnSelectedValueChanged (EventArgs.Empty); + } + + int anchor = -1; + int[] prev_selection; + bool button_pressed = false; + Point button_pressed_loc = new Point (-1, -1); + + private void SelectExtended (int index) + { + SuspendLayout (); + + ArrayList new_selection = new ArrayList (); + int start = anchor < index ? anchor : index; + int end = anchor > index ? anchor : index; + for (int i = start; i <= end; i++) + new_selection.Add (i); + + if (ctrl_pressed) + foreach (int i in prev_selection) + if (!new_selection.Contains (i)) + new_selection.Add (i); + + // Need to make a copy since we can't enumerate and modify the collection + // at the same time + ArrayList sel_indices = (ArrayList) selected_indices.List.Clone (); + foreach (int i in sel_indices) + if (!new_selection.Contains (i)) + selected_indices.Remove (i); + + foreach (int i in new_selection) + if (!sel_indices.Contains (i)) + selected_indices.AddCore (i); + ResumeLayout (); + } + + private void OnMouseDownLB (object sender, MouseEventArgs e) + { + // Only do stuff with the left mouse button + if ((e.Button & MouseButtons.Left) == 0) + return; + + int index = IndexAtClientPoint (e.X, e.Y); + if (index == -1) + return; + + switch (SelectionMode) { + case SelectionMode.One: + SelectedIndices.AddCore (index); // Unselects previous one + break; + + case SelectionMode.MultiSimple: + if (SelectedIndices.Contains (index)) + SelectedIndices.RemoveCore (index); + else + SelectedIndices.AddCore (index); + break; + + case SelectionMode.MultiExtended: + shift_pressed = (XplatUI.State.ModifierKeys & Keys.Shift) != 0; + ctrl_pressed = (XplatUI.State.ModifierKeys & Keys.Widget) != 0; + + if (shift_pressed) { + SelectedIndices.ClearCore (); + SelectExtended (index); + break; + } + + anchor = index; + + if (ctrl_pressed) { + prev_selection = new int [SelectedIndices.Count]; + SelectedIndices.CopyTo (prev_selection, 0); + + if (SelectedIndices.Contains (index)) + SelectedIndices.RemoveCore (index); + else + SelectedIndices.AddCore (index); + + break; + } + + SelectedIndices.ClearCore (); + SelectedIndices.AddCore (index); + break; + + case SelectionMode.None: + break; + default: + return; + } + + button_pressed = true; + button_pressed_loc = new Point (e.X, e.Y); + FocusedItem = index; + } + + private void OnMouseMoveLB (object sender, MouseEventArgs e) + { + // Don't take into account MouseMove events generated with MouseDown + if (!button_pressed || button_pressed_loc == new Point (e.X, e.Y)) + return; + + int index = IndexAtClientPoint (e.X, e.Y); + if (index == -1) + return; + + switch (SelectionMode) { + case SelectionMode.One: + SelectedIndices.AddCore (index); // Unselects previous one + break; + + case SelectionMode.MultiSimple: + break; + + case SelectionMode.MultiExtended: + SelectExtended (index); + break; + + case SelectionMode.None: + break; + default: + return; + } + + FocusedItem = index; + } + + internal override void OnDragDropEnd (DragDropEffects effects) + { + // In the case of a DnD operation (started on MouseDown) + // there will be no MouseUp event, so we need to reset + // the state here + button_pressed = false; + } + + private void OnMouseUpLB (object sender, MouseEventArgs e) + { + // Only do stuff with the left mouse button + if ((e.Button & MouseButtons.Left) == 0) + return; + + if (e.Clicks > 1) { + OnDoubleClick (EventArgs.Empty); + OnMouseDoubleClick (e); + } + else if (e.Clicks == 1) { + OnClick (EventArgs.Empty); + OnMouseClick (e); + } + + if (!button_pressed) + return; + + int index = IndexAtClientPoint (e.X, e.Y); + OnItemClick (index); + button_pressed = ctrl_pressed = shift_pressed = false; + } + + private void Scroll (ScrollBar scrollbar, int delta) + { + if (delta == 0 || !scrollbar.Visible || !scrollbar.Enabled) + return; + + int max; + if (scrollbar == hscrollbar) + max = hscrollbar.Maximum - (items_area.Width / ColumnWidthInternal) + 1; + else + max = vscrollbar.Maximum - (items_area.Height / ItemHeight) + 1; + + int val = scrollbar.Value + delta; + if (val > max) + val = max; + else if (val < scrollbar.Minimum) + val = scrollbar.Minimum; + scrollbar.Value = val; + } + + private void OnMouseWheelLB (object sender, MouseEventArgs me) + { + if (Items.Count == 0) + return; + + int lines = me.Delta / 120; + + if (MultiColumn) + Scroll (hscrollbar, -SystemInformation.MouseWheelScrollLines * lines); + else + Scroll (vscrollbar, -lines); + } + + internal override void OnPaintInternal (PaintEventArgs pevent) + { + if (suspend_layout) + return; + + Draw (pevent.ClipRectangle, pevent.Graphics); + } + + internal void RepositionScrollBars () + { + if (vscrollbar.is_visible) { + vscrollbar.Size = new Size (vscrollbar.Width, items_area.Height); + vscrollbar.Location = new Point (items_area.Width, 0); + } + + if (hscrollbar.is_visible) { + hscrollbar.Size = new Size (items_area.Width, hscrollbar.Height); + hscrollbar.Location = new Point (0, items_area.Height); + } + } + + // An item navigation operation (mouse or keyboard) has caused to select a new item + internal void SelectedItemFromNavigation (int index) + { + switch (SelectionMode) { + case SelectionMode.None: + // .Net doesn't select the item, only ensures that it's visible + // and fires the selection related events + EnsureVisible (index); + OnSelectedIndexChanged (EventArgs.Empty); + OnSelectedValueChanged (EventArgs.Empty); + break; + case SelectionMode.One: { + SelectedIndex = index; + break; + } + case SelectionMode.MultiSimple: { + if (SelectedIndex == -1) { + SelectedIndex = index; + } else { + + if (SelectedIndices.Contains (index)) + SelectedIndices.Remove (index); + else { + SelectedIndices.AddCore (index); + + OnSelectedIndexChanged (EventArgs.Empty); + OnSelectedValueChanged (EventArgs.Empty); + } + } + break; + } + + case SelectionMode.MultiExtended: { + if (SelectedIndex == -1) { + SelectedIndex = index; + } else { + + if (ctrl_pressed == false && shift_pressed == false) { + SelectedIndices.Clear (); + } + + if (shift_pressed == true) { + ShiftSelection (index); + } else { // ctrl_pressed or single item + SelectedIndices.AddCore (index); + + } + + OnSelectedIndexChanged (EventArgs.Empty); + OnSelectedValueChanged (EventArgs.Empty); + } + break; + } + + default: + break; + } + } + + private void ShiftSelection (int index) + { + int shorter_item = -1, dist = Items.Count + 1, cur_dist; + + foreach (int idx in selected_indices) { + if (idx > index) { + cur_dist = idx - index; + } else { + cur_dist = index - idx; + } + + if (cur_dist < dist) { + dist = cur_dist; + shorter_item = idx; + } + } + + if (shorter_item != -1) { + int start, end; + + if (shorter_item > index) { + start = index; + end = shorter_item; + } else { + start = shorter_item; + end = index; + } + + selected_indices.Clear (); + for (int idx = start; idx <= end; idx++) { + selected_indices.AddCore (idx); + } + } + } + + internal int FocusedItem { + get { return focused_item; } + set { + if (focused_item == value) + return; + + int prev = focused_item; + + focused_item = value; + + if (has_focus == false) + return; + + if (prev != -1) + InvalidateItem (prev); + + if (value != -1) + InvalidateItem (value); + + // UIA Framework: Generates FocusedItemChanged event. + OnUIAFocusedItemChangedEvent (); + } + } + + StringFormat string_format; + internal StringFormat StringFormat { + get { + if (string_format == null) { + string_format = new StringFormat (); + string_format.FormatFlags = StringFormatFlags.NoWrap; + + if (RightToLeft == RightToLeft.Yes) + string_format.Alignment = StringAlignment.Far; + else + string_format.Alignment = StringAlignment.Near; + CalculateTabStops (); + } + return string_format; + } + } + + internal virtual void CollectionChanged () + { + if (sorted) + Sort (false); + + if (Items.Count == 0) { + selected_indices.List.Clear (); + focused_item = -1; + top_index = 0; + } + if (Items.Count <= focused_item) + focused_item = Items.Count - 1; + + if (!IsHandleCreated || suspend_layout) + return; + + LayoutListBox (); + + base.Refresh (); + } + + void EnsureVisible (int index) + { + if (!IsHandleCreated || index == -1) + return; + + if (index < top_index) { + top_index = index; + UpdateTopItem (); + Invalidate (); + } else if (!multicolumn) { + int rows = items_area.Height / ItemHeight; + if (index >= (top_index + rows)) + top_index = index - rows + 1; + + UpdateTopItem (); + } else { + int rows = Math.Max (1, items_area.Height / ItemHeight); + int cols = Math.Max (1, items_area.Width / ColumnWidthInternal); + + if (index >= (top_index + (rows * cols))) { + int incolumn = index / rows; + top_index = (incolumn - (cols - 1)) * rows; + + UpdateTopItem (); + Invalidate (); + } + } + } + + private void UpdateListBoxBounds () + { + if (IsHandleCreated) + SetBoundsInternal (bounds.X, bounds.Y, bounds.Width, IntegralHeight ? SnapHeightToIntegral (requested_height) : requested_height, BoundsSpecified.None); + } + + private void UpdateScrollBars () + { + items_area = ClientRectangle; + if (UpdateHorizontalScrollBar ()) { + items_area.Height -= hscrollbar.Height; + if (UpdateVerticalScrollBar ()) { + items_area.Width -= vscrollbar.Width; + UpdateHorizontalScrollBar (); + } + } else if (UpdateVerticalScrollBar ()) { + items_area.Width -= vscrollbar.Width; + if (UpdateHorizontalScrollBar ()) { + items_area.Height -= hscrollbar.Height; + UpdateVerticalScrollBar (); + } + } + + RepositionScrollBars (); + } + + /* Determines if the horizontal scrollbar has to be displyed */ + private bool UpdateHorizontalScrollBar () + { + bool show = false; + bool enabled = true; + + if (MultiColumn) { + if (canvas_size.Width > items_area.Width) { + show = true; + hscrollbar.Maximum = canvas_size.Width / ColumnWidthInternal - 1; + } else if (ScrollAlwaysVisible == true) { + enabled = false; + show = true; + hscrollbar.Maximum = 0; + } + } else if (canvas_size.Width > ClientRectangle.Width && HorizontalScrollbar) { + show = true; + hscrollbar.Maximum = canvas_size.Width; + hscrollbar.LargeChange = Math.Max (0, items_area.Width); + } else if (scroll_always_visible && horizontal_scrollbar) { + show = true; + enabled = false; + hscrollbar.Maximum = 0; + } + + hbar_offset = hscrollbar.Value; + hscrollbar.Enabled = enabled; + hscrollbar.Visible = show; + + return show; + } + + /* Determines if the vertical scrollbar has to be displyed */ + private bool UpdateVerticalScrollBar () + { + if (MultiColumn || (Items.Count == 0 && !scroll_always_visible)) { + vscrollbar.Visible = false; + return false; + } else if (Items.Count == 0) { + vscrollbar.Visible = true; + vscrollbar.Enabled = false; + vscrollbar.Maximum = 0; + return true; + } + + bool show = false; + bool enabled = true; + if (canvas_size.Height > items_area.Height) { + show = true; + vscrollbar.Maximum = Items.Count - 1; + vscrollbar.LargeChange = Math.Max (items_area.Height / ItemHeight, 0); + } else if (ScrollAlwaysVisible) { + show = true; + enabled = false; + vscrollbar.Maximum = 0; + } + + vscrollbar.Enabled = enabled; + vscrollbar.Visible = show; + + return show; + } + + // Value Changed + private void VerticalScrollEvent (object sender, EventArgs e) + { + int top_item = top_index; + + top_index = /*row_count + */ vscrollbar.Value; + last_visible_index = LastVisibleItem (); + + int delta = (top_item - top_index) * ItemHeight; + if (DrawMode == DrawMode.OwnerDrawVariable) { + delta = 0; + + if (top_index < top_item) + for (int i = top_index; i < top_item; i++) + delta += GetItemHeight (i); + else + for (int i = top_item; i < top_index; i++) + delta -= GetItemHeight (i); + } + + if (IsHandleCreated) + XplatUI.ScrollWindow (Handle, items_area, 0, delta, false); + } + + #endregion Private Methods + + public class IntegerCollection : IList, ICollection, IEnumerable + { + private ListBox owner; + private List<int> list; + + #region Public Constructor + public IntegerCollection (ListBox owner) + { + this.owner = owner; + list = new List<int> (); + } + #endregion + + #region Public Properties + [Browsable (false)] + public int Count { + get { return list.Count; } + } + + public int this [int index] { + get { return list[index]; } + set { list[index] = value; owner.CalculateTabStops (); } + } + #endregion + + #region Public Methods + public int Add (int item) + { + // This collection does not allow duplicates + if (!list.Contains (item)) { + list.Add (item); + list.Sort (); + owner.CalculateTabStops (); + } + + return list.IndexOf (item); + } + + public void AddRange (int[] items) + { + AddItems (items); + } + + public void AddRange (IntegerCollection value) + { + AddItems (value); + } + + void AddItems (IList items) + { + if (items == null) + throw new ArgumentNullException ("items"); + + foreach (int i in items) + if (!list.Contains (i)) + list.Add (i); + + list.Sort (); + } + + public void Clear () + { + list.Clear (); + owner.CalculateTabStops (); + } + + public bool Contains (int item) + { + return list.Contains (item); + } + + public void CopyTo (Array destination, int index) + { + for (int i = 0; i < list.Count; i++) + destination.SetValue (list[i], index++); + } + + public int IndexOf (int item) + { + return list.IndexOf (item); + } + + public void Remove (int item) + { + list.Remove (item); + list.Sort (); + owner.CalculateTabStops (); + } + + public void RemoveAt (int index) + { + if (index < 0) + throw new IndexOutOfRangeException (); + + list.RemoveAt (index); + list.Sort (); + owner.CalculateTabStops (); + } + #endregion + + #region IEnumerable Members + IEnumerator IEnumerable.GetEnumerator () + { + return list.GetEnumerator (); + } + #endregion + + #region IList Members + int IList.Add (object item) + { + int? intValue = item as int?; + if (!intValue.HasValue) + throw new ArgumentException ("item"); + return Add (intValue.Value); + } + + void IList.Clear () + { + Clear (); + } + + bool IList.Contains (object item) + { + int? intValue = item as int?; + if (!intValue.HasValue) + return false; + return Contains (intValue.Value); + } + + int IList.IndexOf (object item) + { + int? intValue = item as int?; + if (!intValue.HasValue) + return -1; + return IndexOf (intValue.Value); + } + + void IList.Insert (int index, object value) + { + throw new NotSupportedException (string.Format ( + CultureInfo.InvariantCulture, "No items " + + "can be inserted into {0}, since it is" + + " a sorted collection.", this.GetType ())); + } + + bool IList.IsFixedSize + { + get { return false; } + } + + bool IList.IsReadOnly + { + get { return false; } + } + + void IList.Remove (object value) + { + int? intValue = value as int?; + if (!intValue.HasValue) + throw new ArgumentException ("value"); + + Remove (intValue.Value); + } + + void IList.RemoveAt (int index) + { + RemoveAt (index); + } + + object IList.this[int index] { + get { return this[index]; } + set { this[index] = (int)value; } + } + #endregion + + #region ICollection Members + bool ICollection.IsSynchronized { + get { return true; } + } + + object ICollection.SyncRoot { + get { return this; } + } + #endregion + } + + [ListBindable (false)] + public class ObjectCollection : IList, ICollection, IEnumerable + { + internal class ListObjectComparer : IComparer + { + public int Compare (object a, object b) + { + string str1 = a.ToString (); + string str2 = b.ToString (); + return str1.CompareTo (str2); + } + } + + private ListBox owner; + internal ArrayList object_items = new ArrayList (); + + #region UIA Framework Events + //NOTE: + // We are using Reflection to add/remove internal events. + // Class ListProvider uses the events. + // + //Event used to generate UIA StructureChangedEvent + static object UIACollectionChangedEvent = new object (); + + internal event CollectionChangeEventHandler UIACollectionChanged { + add { owner.Events.AddHandler (UIACollectionChangedEvent, value); } + remove { owner.Events.RemoveHandler (UIACollectionChangedEvent, value); } + } + + internal void OnUIACollectionChangedEvent (CollectionChangeEventArgs args) + { + CollectionChangeEventHandler eh + = (CollectionChangeEventHandler) owner.Events [UIACollectionChangedEvent]; + if (eh != null) + eh (owner, args); + } + #endregion UIA Framework Events + + public ObjectCollection (ListBox owner) + { + this.owner = owner; + } + + public ObjectCollection (ListBox owner, object[] value) + { + this.owner = owner; + AddRange (value); + } + + public ObjectCollection (ListBox owner, ObjectCollection value) + { + this.owner = owner; + AddRange (value); + } + + #region Public Properties + public int Count { + get { return object_items.Count; } + } + + public bool IsReadOnly { + get { return false; } + } + + [Browsable(false)] + //[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual object this [int index] { + get { + if (index < 0 || index >= Count) + throw new ArgumentOutOfRangeException ("Index of out range"); + + return object_items[index]; + } + set { + if (index < 0 || index >= Count) + throw new ArgumentOutOfRangeException ("Index of out range"); + if (value == null) + throw new ArgumentNullException ("value"); + + //UIA Framework event: Item Removed + OnUIACollectionChangedEvent (new CollectionChangeEventArgs (CollectionChangeAction.Remove, object_items [index])); + + object_items[index] = value; + + //UIA Framework event: Item Added + OnUIACollectionChangedEvent (new CollectionChangeEventArgs (CollectionChangeAction.Add, value)); + + owner.CollectionChanged (); + } + } + + bool ICollection.IsSynchronized { + get { return false; } + } + + object ICollection.SyncRoot { + get { return this; } + } + + bool IList.IsFixedSize { + get { return false; } + } + + #endregion Public Properties + + #region Public Methods + public int Add (object item) + { + int idx; + object[] selectedItems = null; + + // we need to remember the original selected items so that we can update the indices + if (owner.sorted) { + selectedItems = new object[owner.SelectedItems.Count]; + owner.SelectedItems.CopyTo (selectedItems, 0); + } + + idx = AddItem (item); + owner.CollectionChanged (); + + // If we are sorted, the item probably moved indexes, get the real one + if (owner.sorted) { + // update indices of selected items + owner.SelectedIndices.Clear (); + for (int i = 0; i < selectedItems.Length; i++) { + owner.SelectedIndex = this.IndexOf (selectedItems [i]); + } + return this.IndexOf (item); + } + + return idx; + } + + public void AddRange (object[] items) + { + AddItems (items); + } + + public void AddRange (ObjectCollection value) + { + AddItems (value); + } + + internal void AddItems (IList items) + { + if (items == null) + throw new ArgumentNullException ("items"); + + foreach (object mi in items) + AddItem (mi); + + owner.CollectionChanged (); + } + + public virtual void Clear () + { + owner.selected_indices.ClearCore (); + object_items.Clear (); + owner.CollectionChanged (); + + //UIA Framework event: Items list cleared + OnUIACollectionChangedEvent (new CollectionChangeEventArgs (CollectionChangeAction.Refresh, null)); + } + + public bool Contains (object value) + { + if (value == null) + throw new ArgumentNullException ("value"); + + return object_items.Contains (value); + } + + public void CopyTo (object[] destination, int arrayIndex) + { + object_items.CopyTo (destination, arrayIndex); + } + + void ICollection.CopyTo (Array destination, int index) + { + object_items.CopyTo (destination, index); + } + + public IEnumerator GetEnumerator () + { + return object_items.GetEnumerator (); + } + + int IList.Add (object item) + { + return Add (item); + } + + public int IndexOf (object value) + { + if (value == null) + throw new ArgumentNullException ("value"); + + return object_items.IndexOf (value); + } + + public void Insert (int index, object item) + { + if (index < 0 || index > Count) + throw new ArgumentOutOfRangeException ("Index of out range"); + if (item == null) + throw new ArgumentNullException ("item"); + + owner.BeginUpdate (); + object_items.Insert (index, item); + owner.CollectionChanged (); + owner.EndUpdate (); + + //UIA Framework event: Item Added + OnUIACollectionChangedEvent (new CollectionChangeEventArgs (CollectionChangeAction.Add, item)); + } + + public void Remove (object value) + { + if (value == null) + return; + + int index = IndexOf (value); + if (index != -1) + RemoveAt (index); + } + + public void RemoveAt (int index) + { + if (index < 0 || index >= Count) + throw new ArgumentOutOfRangeException ("Index of out range"); + + //UIA Framework element removed + object removed = object_items [index]; + UpdateSelection (index); + object_items.RemoveAt (index); + owner.CollectionChanged (); + + //UIA Framework event: Item Removed + OnUIACollectionChangedEvent (new CollectionChangeEventArgs (CollectionChangeAction.Remove, removed)); + } + #endregion Public Methods + + #region Private Methods + internal int AddItem (object item) + { + if (item == null) + throw new ArgumentNullException ("item"); + + int cnt = object_items.Count; + object_items.Add (item); + + //UIA Framework event: Item Added + OnUIACollectionChangedEvent (new CollectionChangeEventArgs (CollectionChangeAction.Add, item)); + + return cnt; + } + + // we receive the index to be removed + void UpdateSelection (int removed_index) + { + owner.selected_indices.Remove (removed_index); + + if (owner.selection_mode != SelectionMode.None) { + int last_idx = object_items.Count - 1; + + // if the last item was selected, remove it from selection, + // since it will become invalid after the removal + if (owner.selected_indices.Contains (last_idx)) { + owner.selected_indices.Remove (last_idx); + + // in SelectionMode.One try to put the selection on the new last item + int new_idx = last_idx - 1; + if (owner.selection_mode == SelectionMode.One && new_idx > -1) + owner.selected_indices.Add (new_idx); + } + } + + } + + internal void Sort () + { + object_items.Sort (new ListObjectComparer ()); + } + + #endregion Private Methods + } + + public class SelectedIndexCollection : IList, ICollection, IEnumerable + { + private ListBox owner; + ArrayList selection; + bool sorting_needed; // Selection state retrieval is done sorted - we do it lazyly + + #region UIA Framework Events + + //NOTE: + // We are using Reflection to add/remove internal events. + // Class ListProvider uses the events. + // + //Event used to generate UIA StructureChangedEvent + static object UIACollectionChangedEvent = new object (); + + internal event CollectionChangeEventHandler UIACollectionChanged { + add { owner.Events.AddHandler (UIACollectionChangedEvent, value); } + remove { owner.Events.RemoveHandler (UIACollectionChangedEvent, value); } + } + + internal void OnUIACollectionChangedEvent (CollectionChangeEventArgs args) + { + CollectionChangeEventHandler eh + = (CollectionChangeEventHandler) owner.Events [UIACollectionChangedEvent]; + if (eh != null) + eh (owner, args); + } + + #endregion UIA Framework Events + + + public SelectedIndexCollection (ListBox owner) + { + this.owner = owner; + selection = new ArrayList (); + } + + #region Public Properties + [Browsable (false)] + public int Count { + get { return selection.Count; } + } + + public bool IsReadOnly { + get { return true; } + } + + public int this [int index] { + get { + if (index < 0 || index >= Count) + throw new ArgumentOutOfRangeException ("Index of out range"); + + CheckSorted (); + return (int)selection [index]; + } + } + + bool ICollection.IsSynchronized { + get { return true; } + } + + bool IList.IsFixedSize{ + get { return true; } + } + + object ICollection.SyncRoot { + get { return selection; } + } + + #endregion Public Properties + + #region Public Methods + public void Add (int index) + { + if (AddCore (index)) { + owner.OnSelectedIndexChanged (EventArgs.Empty); + owner.OnSelectedValueChanged (EventArgs.Empty); + } + } + + // Need to separate selection logic from events, + // since selection changes using keys/mouse handle them their own way + internal bool AddCore (int index) + { + if (selection.Contains (index)) + return false; + + if (index == -1) // Weird MS behaviour + return false; + if (index < -1 || index >= owner.Items.Count) + throw new ArgumentOutOfRangeException ("index"); + if (owner.selection_mode == SelectionMode.None) + throw new InvalidOperationException ("Cannot call this method when selection mode is SelectionMode.None"); + + if (owner.selection_mode == SelectionMode.One && Count > 0) // Unselect previously selected item + RemoveCore ((int)selection [0]); + + selection.Add (index); + sorting_needed = true; + owner.EnsureVisible (index); + owner.FocusedItem = index; + owner.InvalidateItem (index); + + // UIA Framework event: Selected item added + OnUIACollectionChangedEvent (new CollectionChangeEventArgs (CollectionChangeAction.Add, index)); + + return true; + } + + public void Clear () + { + if (ClearCore ()) { + owner.OnSelectedIndexChanged (EventArgs.Empty); + owner.OnSelectedValueChanged (EventArgs.Empty); + } + } + + internal bool ClearCore () + { + if (selection.Count == 0) + return false; + + foreach (int index in selection) + owner.InvalidateItem (index); + + selection.Clear (); + + // UIA Framework event: Selected items list updated + OnUIACollectionChangedEvent (new CollectionChangeEventArgs (CollectionChangeAction.Refresh, -1)); + + return true; + } + + public bool Contains (int selectedIndex) + { + foreach (int index in selection) + if (index == selectedIndex) + return true; + return false; + } + + public void CopyTo (Array destination, int index) + { + CheckSorted (); + selection.CopyTo (destination, index); + } + + public IEnumerator GetEnumerator () + { + CheckSorted (); + return selection.GetEnumerator (); + } + + // FIXME: Probably we can avoid sorting when calling + // IndexOf (imagine a scenario where multiple removal of items + // happens) + public void Remove (int index) + { + // Separate logic from events here too + if (RemoveCore (index)) { + owner.OnSelectedIndexChanged (EventArgs.Empty); + owner.OnSelectedValueChanged (EventArgs.Empty); + } + } + + internal bool RemoveCore (int index) + { + int idx = IndexOf (index); + if (idx == -1) + return false; + + selection.RemoveAt (idx); + owner.InvalidateItem (index); + + // UIA Framework event: Selected item removed from selection + OnUIACollectionChangedEvent (new CollectionChangeEventArgs (CollectionChangeAction.Remove, index)); + + return true; + } + + int IList.Add (object value) + { + throw new NotSupportedException (); + } + + void IList.Clear () + { + throw new NotSupportedException (); + } + + bool IList.Contains (object selectedIndex) + { + return Contains ((int)selectedIndex); + } + + int IList.IndexOf (object selectedIndex) + { + return IndexOf ((int) selectedIndex); + } + + void IList.Insert (int index, object value) + { + throw new NotSupportedException (); + } + + void IList.Remove (object value) + { + throw new NotSupportedException (); + } + + void IList.RemoveAt (int index) + { + throw new NotSupportedException (); + } + + object IList.this[int index]{ + get { return this [index]; } + set {throw new NotImplementedException (); } + } + + public int IndexOf (int selectedIndex) + { + CheckSorted (); + + for (int i = 0; i < selection.Count; i++) + if ((int)selection [i] == selectedIndex) + return i; + + return -1; + } + #endregion Public Methods + internal ArrayList List { + get { + CheckSorted (); + return selection; + } + } + + void CheckSorted () + { + if (sorting_needed) { + sorting_needed = false; + selection.Sort (); + } + } + } + + public class SelectedObjectCollection : IList, ICollection, IEnumerable + { + private ListBox owner; + + public SelectedObjectCollection (ListBox owner) + { + this.owner = owner; + } + + #region Public Properties + public int Count { + get { return owner.selected_indices.Count; } + } + + public bool IsReadOnly { + get { return true; } + } + + [Browsable(false)] + //[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public object this [int index] { + get { + if (index < 0 || index >= Count) + throw new ArgumentOutOfRangeException ("Index of out range"); + + return owner.items [owner.selected_indices [index]]; + } + set {throw new NotSupportedException ();} + } + + bool ICollection.IsSynchronized { + get { return true; } + } + + object ICollection.SyncRoot { + get { return this; } + } + + bool IList.IsFixedSize { + get { return true; } + } + + #endregion Public Properties + + #region Public Methods + public void Add (object value) + { + if (owner.selection_mode == SelectionMode.None) + throw new ArgumentException ("Cannot call this method if SelectionMode is SelectionMode.None"); + + int idx = owner.items.IndexOf (value); + if (idx == -1) + return; + + owner.selected_indices.Add (idx); + } + + public void Clear () + { + owner.selected_indices.Clear (); + } + + public bool Contains (object selectedObject) + { + int idx = owner.items.IndexOf (selectedObject); + return idx == -1 ? false : owner.selected_indices.Contains (idx); + } + + public void CopyTo (Array destination, int index) + { + for (int i = 0; i < Count; i++) + destination.SetValue (this [i], index++); + } + + public void Remove (object value) + { + if (value == null) + return; + + int idx = owner.items.IndexOf (value); + if (idx == -1) + return; + + owner.selected_indices.Remove (idx); + } + + int IList.Add (object value) + { + throw new NotSupportedException (); + } + + void IList.Clear () + { + throw new NotSupportedException (); + } + + void IList.Insert (int index, object value) + { + throw new NotSupportedException (); + } + + void IList.Remove (object value) + { + throw new NotSupportedException (); + } + + void IList.RemoveAt (int index) + { + throw new NotSupportedException (); + } + + public int IndexOf (object selectedObject) + { + int idx = owner.items.IndexOf (selectedObject); + return idx == -1 ? -1 : owner.selected_indices.IndexOf (idx); + } + + public IEnumerator GetEnumerator () + { + //FIXME: write an enumerator that uses selection.GetEnumerator + // so that invalidation is write on selection changes + object [] items = new object [Count]; + for (int i = 0; i < Count; i++) { + items [i] = owner.items [owner.selected_indices [i]]; + } + + return items.GetEnumerator (); + } + + #endregion Public Methods + } + } +} diff --git a/source/ShiftUI/Widgets/ListControl.cs b/source/ShiftUI/Widgets/ListControl.cs new file mode 100644 index 0000000..4b01499 --- /dev/null +++ b/source/ShiftUI/Widgets/ListControl.cs @@ -0,0 +1,509 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2004-2005 Novell, Inc. +// +// Authors: +// Jordi Mas i Hernandez, [email protected] +// +// + +// COMPLETE + +using System; +using System.Drawing; +using System.Collections; +using System.ComponentModel; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace ShiftUI +{ + [ClassInterface (ClassInterfaceType.AutoDispatch)] + [ComVisible (true)] + [LookupBindingProperties ("DataSource", "DisplayMember", "ValueMember", "SelectedValue")] + public abstract class ListWidget : Widget + { + + private object data_source; + private BindingMemberInfo value_member; + private string display_member; + private CurrencyManager data_manager; + private BindingContext last_binding_context; + private IFormatProvider format_info; + private string format_string = string.Empty; + private bool formatting_enabled; + + protected ListWidget () + { + value_member = new BindingMemberInfo (string.Empty); + display_member = string.Empty; + SetStyle (Widgetstyles.StandardClick | Widgetstyles.UserPaint | Widgetstyles.UseTextForAccessibility, false); + } + + #region Events + static object DataSourceChangedEvent = new object (); + static object DisplayMemberChangedEvent = new object (); + static object FormatEvent = new object (); + static object FormatInfoChangedEvent = new object (); + static object FormatStringChangedEvent = new object (); + static object FormattingEnabledChangedEvent = new object (); + static object SelectedValueChangedEvent = new object (); + static object ValueMemberChangedEvent = new object (); + + public event EventHandler DataSourceChanged { + add { Events.AddHandler (DataSourceChangedEvent, value); } + remove { Events.RemoveHandler (DataSourceChangedEvent, value); } + } + + public event EventHandler DisplayMemberChanged { + add { Events.AddHandler (DisplayMemberChangedEvent, value); } + remove { Events.RemoveHandler (DisplayMemberChangedEvent, value); } + } + + public event ListWidgetConvertEventHandler Format { + add { Events.AddHandler (FormatEvent, value); } + remove { Events.RemoveHandler (FormatEvent, value); } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Advanced)] + public event EventHandler FormatInfoChanged { + add { Events.AddHandler (FormatInfoChangedEvent, value); } + remove { Events.RemoveHandler (FormatInfoChangedEvent, value); } + } + + public event EventHandler FormatStringChanged { + add { Events.AddHandler (FormatStringChangedEvent, value); } + remove { Events.RemoveHandler (FormatStringChangedEvent, value); } + } + + public event EventHandler FormattingEnabledChanged { + add { Events.AddHandler (FormattingEnabledChangedEvent, value); } + remove { Events.RemoveHandler (FormattingEnabledChangedEvent, value); } + } + + public event EventHandler SelectedValueChanged { + add { Events.AddHandler (SelectedValueChangedEvent, value); } + remove { Events.RemoveHandler (SelectedValueChangedEvent, value); } + } + + public event EventHandler ValueMemberChanged { + add { Events.AddHandler (ValueMemberChangedEvent, value); } + remove { Events.RemoveHandler (ValueMemberChangedEvent, value); } + } + + #endregion // Events + + #region .NET 2.0 Public Properties + [Browsable (false)] + [DefaultValue (null)] + //[EditorBrowsable (EditorBrowsableState.Advanced)] + public IFormatProvider FormatInfo { + get { return format_info; } + set { + if (format_info != value) { + format_info = value; + RefreshItems (); + OnFormatInfoChanged (EventArgs.Empty); + } + } + } + + [DefaultValue ("")] + [MergableProperty (false)] + //[Editor ("ShiftUI.Design.FormatStringEditor, " + Consts.AssemblySystem_Design, typeof (System.Drawing.Design.UITypeEditor))] + public string FormatString { + get { return format_string; } + set { + if (format_string != value) { + format_string = value; + RefreshItems (); + OnFormatStringChanged (EventArgs.Empty); + } + } + } + + [DefaultValue (false)] + public bool FormattingEnabled { + get { return formatting_enabled; } + set { + if (formatting_enabled != value) { + formatting_enabled = value; + RefreshItems (); + OnFormattingEnabledChanged (EventArgs.Empty); + } + } + } + #endregion + + #region Public Properties + + [DefaultValue(null)] + [RefreshProperties(RefreshProperties.Repaint)] + [AttributeProvider (typeof (IListSource))] + [MWFCategory("Data")] + public object DataSource { + get { return data_source; } + set { + if (data_source == value) + return; + + if (value == null) + display_member = String.Empty; + else if (!(value is IList || value is IListSource)) + throw new Exception ("Complex DataBinding accepts as a data source " + + "either an IList or an IListSource"); + + data_source = value; + ConnectToDataSource (); + OnDataSourceChanged (EventArgs.Empty); + } + } + + [DefaultValue("")] + //[Editor("ShiftUI.Design.DataMemberFieldEditor, " + Consts.AssemblySystem_Design, typeof(System.Drawing.Design.UITypeEditor))] + [TypeConverter("ShiftUI.Design.DataMemberFieldConverter, " + Consts.AssemblySystem_Design)] + [MWFCategory("Data")] + public string DisplayMember { + get { + return display_member; + } + set { + if (value == null) + value = String.Empty; + + if (display_member == value) { + return; + } + + display_member = value; + ConnectToDataSource (); + OnDisplayMemberChanged (EventArgs.Empty); + } + } + + public abstract int SelectedIndex { + get; + set; + } + + [Bindable(BindableSupport.Yes)] + [Browsable(false)] + [DefaultValue(null)] + //[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public object SelectedValue { + get { + if (data_manager == null || SelectedIndex == -1) + return null; + + object item = data_manager [SelectedIndex]; + object fil = FilterItemOnProperty (item, ValueMember); + return fil; + } + set { + if (data_manager == null) + return; + + if (value == null) + throw new ArgumentNullException ("value"); + + PropertyDescriptorCollection col = data_manager.GetItemProperties (); + PropertyDescriptor prop = col.Find (ValueMember, true); + + for (int i = 0; i < data_manager.Count; i++) { + if (value.Equals (prop.GetValue (data_manager [i]))) { + SelectedIndex = i; + return; + } + } + SelectedIndex = -1; + } + } + + [DefaultValue("")] + //[Editor("ShiftUI.Design.DataMemberFieldEditor, " + Consts.AssemblySystem_Design, typeof(System.Drawing.Design.UITypeEditor))] + [MWFCategory("Data")] + public string ValueMember { + get { return value_member.BindingMember; } + set { + BindingMemberInfo new_value = new BindingMemberInfo (value); + + if (value_member.Equals (new_value)) + return; + + value_member = new_value; + + if (display_member == string.Empty) + DisplayMember = value_member.BindingMember; + + ConnectToDataSource (); + OnValueMemberChanged (EventArgs.Empty); + } + } + + protected virtual bool AllowSelection { + get { return true; } + } + + #endregion Public Properties + + #region Private Properties + + internal override bool ScaleChildrenInternal { + get { return false; } + } + + #endregion Private Properties + + #region Public Methods + + protected object FilterItemOnProperty (object item) + { + return FilterItemOnProperty (item, string.Empty); + } + + protected object FilterItemOnProperty (object item, string field) + { + if (item == null) + return null; + + if (field == null || field == string.Empty) + return item; + + PropertyDescriptor prop = null; + + if (data_manager != null) { + PropertyDescriptorCollection col = data_manager.GetItemProperties (); + prop = col.Find (field, true); + } else { + PropertyDescriptorCollection properties = TypeDescriptor.GetProperties (item); + prop = properties.Find (field, true); + } + + if (prop == null) + return item; + + return prop.GetValue (item); + } + + public string GetItemText (object item) + { + object o = FilterItemOnProperty (item, DisplayMember); + + if (o == null) + o = item; + + string retval = o.ToString (); + + if (FormattingEnabled) { + ListWidgetConvertEventArgs e = new ListWidgetConvertEventArgs (o, typeof (string), item); + OnFormat (e); + + // The user provided their own value + if (e.Value.ToString () != retval) + return e.Value.ToString (); + + if (o is IFormattable) + return ((IFormattable)o).ToString (string.IsNullOrEmpty (FormatString) ? null : FormatString, FormatInfo); + } + + return retval; + } + + protected CurrencyManager DataManager { + get { return data_manager; } + } + + // Used only by ListBox to avoid to break Listbox's member signature + protected override bool IsInputKey (Keys keyData) + { + switch (keyData) { + case Keys.Up: + case Keys.Down: + case Keys.PageUp: + case Keys.PageDown: + case Keys.Right: + case Keys.Left: + case Keys.End: + case Keys.Home: + case Keys.ControlKey: + case Keys.Space: + case Keys.ShiftKey: + return true; + + default: + return false; + } + } + + // Since this event is fired twice for the same binding context instance + // (when the Widget is added to the form and when the form is shown), + // we only take into account the first time it happens + protected override void OnBindingContextChanged (EventArgs e) + { + base.OnBindingContextChanged (e); + if (last_binding_context == BindingContext) + return; + + last_binding_context = BindingContext; + ConnectToDataSource (); + + if (DataManager != null) { + SetItemsCore (DataManager.List); + if (AllowSelection) + SelectedIndex = DataManager.Position; + } + } + + protected virtual void OnDataSourceChanged (EventArgs e) + { + EventHandler eh = (EventHandler)(Events [DataSourceChangedEvent]); + if (eh != null) + eh (this, e); + } + + protected virtual void OnDisplayMemberChanged (EventArgs e) + { + EventHandler eh = (EventHandler)(Events [DisplayMemberChangedEvent]); + if (eh != null) + eh (this, e); + } + + protected virtual void OnFormat (ListWidgetConvertEventArgs e) + { + ListWidgetConvertEventHandler eh = (ListWidgetConvertEventHandler)(Events[FormatEvent]); + if (eh != null) + eh (this, e); + } + + protected virtual void OnFormatInfoChanged (EventArgs e) + { + EventHandler eh = (EventHandler)(Events[FormatInfoChangedEvent]); + if (eh != null) + eh (this, e); + } + + protected virtual void OnFormatStringChanged (EventArgs e) + { + EventHandler eh = (EventHandler)(Events[FormatStringChangedEvent]); + if (eh != null) + eh (this, e); + } + + protected virtual void OnFormattingEnabledChanged (EventArgs e) + { + EventHandler eh = (EventHandler)(Events[FormattingEnabledChangedEvent]); + if (eh != null) + eh (this, e); + } + + protected virtual void OnSelectedIndexChanged (EventArgs e) + { + if (data_manager == null) + return; + if (data_manager.Position == SelectedIndex) + return; + data_manager.Position = SelectedIndex; + } + + protected virtual void OnSelectedValueChanged (EventArgs e) + { + EventHandler eh = (EventHandler)(Events [SelectedValueChangedEvent]); + if (eh != null) + eh (this, e); + } + + protected virtual void OnValueMemberChanged (EventArgs e) + { + EventHandler eh = (EventHandler)(Events [ValueMemberChangedEvent]); + if (eh != null) + eh (this, e); + } + + protected abstract void RefreshItem (int index); + + protected virtual void RefreshItems () + { + } + + protected virtual void SetItemCore (int index, object value) + { + } + + protected abstract void SetItemsCore (IList items); + + #endregion Public Methods + + #region Private Methods + + internal void BindDataItems () + { + SetItemsCore (data_manager != null ? data_manager.List : new object [0]); + } + + private void ConnectToDataSource () + { + if (BindingContext == null) + return; + + CurrencyManager newDataMgr = null; + if (data_source != null) + newDataMgr = (CurrencyManager) BindingContext [data_source]; + if (newDataMgr != data_manager) { + if (data_manager != null) { + // Disconnect handlers from previous manager + data_manager.PositionChanged -= new EventHandler (OnPositionChanged); + data_manager.ItemChanged -= new ItemChangedEventHandler (OnItemChanged); + } + if (newDataMgr != null) { + newDataMgr.PositionChanged += new EventHandler (OnPositionChanged); + newDataMgr.ItemChanged += new ItemChangedEventHandler (OnItemChanged); + } + data_manager = newDataMgr; + } + } + + private void OnItemChanged (object sender, ItemChangedEventArgs e) + { + /* if the list has changed, tell our subclass to re-bind */ + if (e.Index == -1) + SetItemsCore (data_manager.List); + else + RefreshItem (e.Index); + + /* For the first added item, ItemChanged is fired _after_ PositionChanged, + * so we need to set Index _only_ for that case - normally we would do that + * in PositionChanged handler */ + if (AllowSelection && SelectedIndex == -1 && data_manager.Count == 1) + SelectedIndex = data_manager.Position; + } + + private void OnPositionChanged (object sender, EventArgs e) + { + /* For the first added item, PositionChanged is fired + * _before_ ItemChanged (items not yet added), which leave us in a temporary + * invalid state */ + if (AllowSelection && data_manager.Count > 1) + SelectedIndex = data_manager.Position; + } + + #endregion Private Methods + } +} diff --git a/source/ShiftUI/Widgets/ListView.cs b/source/ShiftUI/Widgets/ListView.cs new file mode 100644 index 0000000..a2706a3 --- /dev/null +++ b/source/ShiftUI/Widgets/ListView.cs @@ -0,0 +1,6155 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2004-2005 Novell, Inc. (http://www.novell.com) +// +// Authors: +// Ravindra Kumar ([email protected]) +// Jordi Mas i Hernandez, [email protected] +// Mike Kestner ([email protected]) +// Daniel Nauck (dna(at)mono-project(dot)de) +// Carlos Alberto Cortez <[email protected]> +// + + +// NOT COMPLETE + + +using System.Collections; +using System.ComponentModel; +using System.ComponentModel.Design; +using System.Drawing; +using System.Runtime.InteropServices; +using System.Globalization; +using System.Collections.Generic; +using System; + +namespace ShiftUI +{ + [DefaultEvent ("SelectedIndexChanged")] + [DefaultProperty ("Items")] + //[Designer ("ShiftUI.Design.ListViewDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")] + [ClassInterface (ClassInterfaceType.AutoDispatch)] + [ComVisible (true)] + [Docking (DockingBehavior.Ask)] + [ToolboxWidget] + public class ListView : Widget + { + private ItemActivation activation = ItemActivation.Standard; + private ListViewAlignment alignment = ListViewAlignment.Top; + private bool allow_column_reorder; + private bool auto_arrange = true; + private bool check_boxes; + private readonly CheckedIndexCollection checked_indices; + private readonly CheckedListViewItemCollection checked_items; + private readonly ColumnHeaderCollection columns; + internal int focused_item_index = -1; + private bool full_row_select; + private bool grid_lines; + private ColumnHeaderStyle header_style = ColumnHeaderStyle.Clickable; + private bool hide_selection = true; + private bool hover_selection; + private IComparer item_sorter; + private readonly ListViewItemCollection items; + private readonly ListViewGroupCollection groups; + private bool owner_draw; + private bool show_groups = true; + private bool label_edit; + private bool label_wrap = true; + private bool multiselect = true; + private bool scrollable = true; + private bool hover_pending; + private readonly SelectedIndexCollection selected_indices; + private readonly SelectedListViewItemCollection selected_items; + private SortOrder sort_order = SortOrder.None; + private ImageList state_image_list; + internal bool updating; + private View view = View.LargeIcon; + private int layout_wd; // We might draw more than our client area + private int layout_ht; // therefore we need to have these two. + internal HeaderControl header_control; + internal ItemControl item_control; + internal ScrollBar h_scroll; // used for scrolling horizontally + internal ScrollBar v_scroll; // used for scrolling vertically + internal int h_marker; // Position markers for scrolling + internal int v_marker; + private int keysearch_tickcnt; + private string keysearch_text; + static private readonly int keysearch_keydelay = 1000; + private int[] reordered_column_indices; + private int[] reordered_items_indices; + private Point [] items_location; + private ItemMatrixLocation [] items_matrix_location; + private Size item_size; // used for caching item size + private int custom_column_width; // used when using Columns with SmallIcon/List views + private int hot_item_index = -1; + private bool hot_tracking; + private ListViewInsertionMark insertion_mark; + private bool show_item_tooltips; + private ToolTip item_tooltip; + private Size tile_size; + private bool virtual_mode; + private int virtual_list_size; + private bool right_to_left_layout; + // selection is available after the first time the handle is created, *even* if later + // the handle is either recreated or destroyed - so keep this info around. + private bool is_selection_available; + + // internal variables + internal ImageList large_image_list; + internal ImageList small_image_list; + internal Size text_size = Size.Empty; + + #region Events + static object AfterLabelEditEvent = new object (); + static object BeforeLabelEditEvent = new object (); + static object ColumnClickEvent = new object (); + static object ItemActivateEvent = new object (); + static object ItemCheckEvent = new object (); + static object ItemDragEvent = new object (); + static object SelectedIndexChangedEvent = new object (); + static object DrawColumnHeaderEvent = new object(); + static object DrawItemEvent = new object(); + static object DrawSubItemEvent = new object(); + static object ItemCheckedEvent = new object (); + static object ItemMouseHoverEvent = new object (); + static object ItemSelectionChangedEvent = new object (); + static object CacheVirtualItemsEvent = new object (); + static object RetrieveVirtualItemEvent = new object (); + static object RightToLeftLayoutChangedEvent = new object (); + static object SearchForVirtualItemEvent = new object (); + static object VirtualItemsSelectionRangeChangedEvent = new object (); + + public event LabelEditEventHandler AfterLabelEdit { + add { Events.AddHandler (AfterLabelEditEvent, value); } + remove { Events.RemoveHandler (AfterLabelEditEvent, value); } + } + + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler BackgroundImageLayoutChanged { + add { base.BackgroundImageLayoutChanged += value; } + remove { base.BackgroundImageLayoutChanged -= value; } + } + + public event LabelEditEventHandler BeforeLabelEdit { + add { Events.AddHandler (BeforeLabelEditEvent, value); } + remove { Events.RemoveHandler (BeforeLabelEditEvent, value); } + } + + public event ColumnClickEventHandler ColumnClick { + add { Events.AddHandler (ColumnClickEvent, value); } + remove { Events.RemoveHandler (ColumnClickEvent, value); } + } + + public event DrawListViewColumnHeaderEventHandler DrawColumnHeader { + add { Events.AddHandler(DrawColumnHeaderEvent, value); } + remove { Events.RemoveHandler(DrawColumnHeaderEvent, value); } + } + + public event DrawListViewItemEventHandler DrawItem { + add { Events.AddHandler(DrawItemEvent, value); } + remove { Events.RemoveHandler(DrawItemEvent, value); } + } + + public event DrawListViewSubItemEventHandler DrawSubItem { + add { Events.AddHandler(DrawSubItemEvent, value); } + remove { Events.RemoveHandler(DrawSubItemEvent, value); } + } + + public event EventHandler ItemActivate { + add { Events.AddHandler (ItemActivateEvent, value); } + remove { Events.RemoveHandler (ItemActivateEvent, value); } + } + + public event ItemCheckEventHandler ItemCheck { + add { Events.AddHandler (ItemCheckEvent, value); } + remove { Events.RemoveHandler (ItemCheckEvent, value); } + } + + public event ItemCheckedEventHandler ItemChecked { + add { Events.AddHandler (ItemCheckedEvent, value); } + remove { Events.RemoveHandler (ItemCheckedEvent, value); } + } + + public event ItemDragEventHandler ItemDrag { + add { Events.AddHandler (ItemDragEvent, value); } + remove { Events.RemoveHandler (ItemDragEvent, value); } + } + + public event ListViewItemMouseHoverEventHandler ItemMouseHover { + add { Events.AddHandler (ItemMouseHoverEvent, value); } + remove { Events.RemoveHandler (ItemMouseHoverEvent, value); } + } + + public event ListViewItemSelectionChangedEventHandler ItemSelectionChanged { + add { Events.AddHandler (ItemSelectionChangedEvent, value); } + remove { Events.RemoveHandler (ItemSelectionChangedEvent, value); } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler PaddingChanged { + add { base.PaddingChanged += value; } + remove { base.PaddingChanged -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event PaintEventHandler Paint { + add { base.Paint += value; } + remove { base.Paint -= value; } + } + + public event EventHandler SelectedIndexChanged { + add { Events.AddHandler (SelectedIndexChangedEvent, value); } + remove { Events.RemoveHandler (SelectedIndexChangedEvent, value); } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler TextChanged { + add { base.TextChanged += value; } + remove { base.TextChanged -= value; } + } + + public event CacheVirtualItemsEventHandler CacheVirtualItems { + add { Events.AddHandler (CacheVirtualItemsEvent, value); } + remove { Events.RemoveHandler (CacheVirtualItemsEvent, value); } + } + + public event RetrieveVirtualItemEventHandler RetrieveVirtualItem { + add { Events.AddHandler (RetrieveVirtualItemEvent, value); } + remove { Events.RemoveHandler (RetrieveVirtualItemEvent, value); } + } + + public event EventHandler RightToLeftLayoutChanged { + add { Events.AddHandler (RightToLeftLayoutChangedEvent, value); } + remove { Events.RemoveHandler (RightToLeftLayoutChangedEvent, value); } + } + + public event SearchForVirtualItemEventHandler SearchForVirtualItem { + add { Events.AddHandler (SearchForVirtualItemEvent, value); } + remove { Events.AddHandler (SearchForVirtualItemEvent, value); } + } + + public event ListViewVirtualItemsSelectionRangeChangedEventHandler VirtualItemsSelectionRangeChanged { + add { Events.AddHandler (VirtualItemsSelectionRangeChangedEvent, value); } + remove { Events.RemoveHandler (VirtualItemsSelectionRangeChangedEvent, value); } + } + + #endregion // Events + + #region Public Constructors + public ListView () + { + background_color = ThemeEngine.Current.ColorWindow; + groups = new ListViewGroupCollection (this); + items = new ListViewItemCollection (this); + items.Changed += new CollectionChangedHandler (OnItemsChanged); + checked_indices = new CheckedIndexCollection (this); + checked_items = new CheckedListViewItemCollection (this); + columns = new ColumnHeaderCollection (this); + foreground_color = SystemColors.WindowText; + selected_indices = new SelectedIndexCollection (this); + selected_items = new SelectedListViewItemCollection (this); + items_location = new Point [16]; + items_matrix_location = new ItemMatrixLocation [16]; + reordered_items_indices = new int [16]; + item_tooltip = new ToolTip (); + item_tooltip.Active = false; + insertion_mark = new ListViewInsertionMark (this); + + InternalBorderStyle = BorderStyle.Fixed3D; + + header_control = new HeaderControl (this); + header_control.Visible = false; + Widgets.AddImplicit (header_control); + + item_control = new ItemControl (this); + Widgets.AddImplicit (item_control); + + h_scroll = new ImplicitHScrollBar (); + Widgets.AddImplicit (this.h_scroll); + + v_scroll = new ImplicitVScrollBar (); + Widgets.AddImplicit (this.v_scroll); + + h_marker = v_marker = 0; + keysearch_tickcnt = 0; + + // scroll bars are disabled initially + h_scroll.Visible = false; + h_scroll.ValueChanged += new EventHandler(HorizontalScroller); + v_scroll.Visible = false; + v_scroll.ValueChanged += new EventHandler(VerticalScroller); + + // event handlers + base.KeyDown += new KeyEventHandler(ListView_KeyDown); + SizeChanged += new EventHandler (ListView_SizeChanged); + GotFocus += new EventHandler (FocusChanged); + LostFocus += new EventHandler (FocusChanged); + MouseWheel += new MouseEventHandler(ListView_MouseWheel); + MouseEnter += new EventHandler (ListView_MouseEnter); + Invalidated += new InvalidateEventHandler (ListView_Invalidated); + + BackgroundImageTiled = false; + + this.SetStyle (Widgetstyles.UserPaint | Widgetstyles.StandardClick + | Widgetstyles.UseTextForAccessibility + , false); + } + #endregion // Public Constructors + + #region Private Internal Properties + internal Size CheckBoxSize { + get { + if (this.check_boxes) { + if (this.state_image_list != null) + return this.state_image_list.ImageSize; + else + return ThemeEngine.Current.ListViewCheckBoxSize; + } + return Size.Empty; + } + } + + internal Size ItemSize { + get { + if (view != View.Details) + return item_size; + + Size size = new Size (); + size.Height = item_size.Height; + for (int i = 0; i < columns.Count; i++) + size.Width += columns [i].Wd; + + return size; + } + set { + item_size = value; + } + } + + internal int HotItemIndex { + get { + return hot_item_index; + } + set { + hot_item_index = value; + } + } + + internal bool UsingGroups { + get { + return show_groups && groups.Count > 0 && view != View.List && + Application.VisualStylesEnabled; + } + } + + internal override bool ScaleChildrenInternal { + get { return false; } + } + + internal bool UseCustomColumnWidth { + get { + return (view == View.List || view == View.SmallIcon) && columns.Count > 0; + } + } + + internal ColumnHeader EnteredColumnHeader { + get { + return header_control.EnteredColumnHeader; + } + } + #endregion // Private Internal Properties + + #region Protected Properties + protected override CreateParams CreateParams { + get { return base.CreateParams; } + } + + protected override Size DefaultSize { + get { return ThemeEngine.Current.ListViewDefaultSize; } + } + protected override bool DoubleBuffered { + get { + return base.DoubleBuffered; + } + set { + base.DoubleBuffered = value; + } + } + #endregion // Protected Properties + + #region Public Instance Properties + [DefaultValue (ItemActivation.Standard)] + public ItemActivation Activation { + get { return activation; } + set { + if (value != ItemActivation.Standard && value != ItemActivation.OneClick && + value != ItemActivation.TwoClick) { + throw new InvalidEnumArgumentException (string.Format + ("Enum argument value '{0}' is not valid for Activation", value)); + } + if (hot_tracking && value != ItemActivation.OneClick) + throw new ArgumentException ("When HotTracking is on, activation must be ItemActivation.OneClick"); + + activation = value; + } + } + + [DefaultValue (ListViewAlignment.Top)] + [Localizable (true)] + public ListViewAlignment Alignment { + get { return alignment; } + set { + if (value != ListViewAlignment.Default && value != ListViewAlignment.Left && + value != ListViewAlignment.SnapToGrid && value != ListViewAlignment.Top) { + throw new InvalidEnumArgumentException (string.Format + ("Enum argument value '{0}' is not valid for Alignment", value)); + } + + if (this.alignment != value) { + alignment = value; + // alignment does not matter in Details/List views + if (this.view == View.LargeIcon || this.View == View.SmallIcon) + this.Redraw (true); + } + } + } + + [DefaultValue (false)] + public bool AllowColumnReorder { + get { return allow_column_reorder; } + set { allow_column_reorder = value; } + } + + [DefaultValue (true)] + public bool AutoArrange { + get { return auto_arrange; } + set { + if (auto_arrange != value) { + auto_arrange = value; + // autoarrange does not matter in Details/List views + if (this.view == View.LargeIcon || this.View == View.SmallIcon) + this.Redraw (true); + } + } + } + + public override Color BackColor { + get { + if (background_color.IsEmpty) + return ThemeEngine.Current.ColorWindow; + else + return background_color; + } + set { + background_color = value; + item_control.BackColor = value; + } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public override ImageLayout BackgroundImageLayout { + get { + return base.BackgroundImageLayout; + } + set { + base.BackgroundImageLayout = value; + } + } + + [DefaultValue (false)] + public bool BackgroundImageTiled { + get { + return item_control.BackgroundImageLayout == ImageLayout.Tile; + } + set { + ImageLayout new_image_layout = value ? ImageLayout.Tile : ImageLayout.None; + if (new_image_layout == item_control.BackgroundImageLayout) + return; + + item_control.BackgroundImageLayout = new_image_layout; + } + } + + [DefaultValue (BorderStyle.Fixed3D)] + [DispId (-504)] + public BorderStyle BorderStyle { + get { return InternalBorderStyle; } + set { InternalBorderStyle = value; } + } + + [DefaultValue (false)] + public bool CheckBoxes { + get { return check_boxes; } + set { + if (check_boxes != value) { + if (value && View == View.Tile) + throw new NotSupportedException ("CheckBoxes are not" + + " supported in Tile view. Choose a different" + + " view or set CheckBoxes to false."); + + check_boxes = value; + this.Redraw (true); + + //UIA Framework: Event used by ListView to set/unset Toggle Pattern + OnUIACheckBoxesChanged (); + } + } + } + + [Browsable (false)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public CheckedIndexCollection CheckedIndices { + get { return checked_indices; } + } + + [Browsable (false)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public CheckedListViewItemCollection CheckedItems { + get { return checked_items; } + } + + //[Editor ("ShiftUI.Design.ColumnHeaderCollectionEditor, " + Consts.AssemblySystem_Design, typeof (System.Drawing.Design.UITypeEditor))] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Content)] + [Localizable (true)] + [MergableProperty (false)] + public ColumnHeaderCollection Columns { + get { return columns; } + } + + [Browsable (false)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public ListViewItem FocusedItem { + get { + if (focused_item_index == -1) + return null; + + return GetItemAtDisplayIndex (focused_item_index); + } + set { + if (value == null || value.ListView != this || + !IsHandleCreated) + return; + + SetFocusedItem (value.DisplayIndex); + } + } + + public override Color ForeColor { + get { + if (foreground_color.IsEmpty) + return ThemeEngine.Current.ColorWindowText; + else + return foreground_color; + } + set { foreground_color = value; } + } + + [DefaultValue (false)] + public bool FullRowSelect { + get { return full_row_select; } + set { + if (full_row_select != value) { + full_row_select = value; + InvalidateSelection (); + } + } + } + + [DefaultValue (false)] + public bool GridLines { + get { return grid_lines; } + set { + if (grid_lines != value) { + grid_lines = value; + this.Redraw (false); + } + } + } + + [DefaultValue (ColumnHeaderStyle.Clickable)] + public ColumnHeaderStyle HeaderStyle { + get { return header_style; } + set { + if (header_style == value) + return; + + switch (value) { + case ColumnHeaderStyle.Clickable: + case ColumnHeaderStyle.Nonclickable: + case ColumnHeaderStyle.None: + break; + default: + throw new InvalidEnumArgumentException (string.Format + ("Enum argument value '{0}' is not valid for ColumnHeaderStyle", value)); + } + + header_style = value; + if (view == View.Details) + Redraw (true); + } + } + + [DefaultValue (true)] + public bool HideSelection { + get { return hide_selection; } + set { + if (hide_selection != value) { + hide_selection = value; + InvalidateSelection (); + } + } + } + + [DefaultValue (false)] + public bool HotTracking { + get { + return hot_tracking; + } + set { + if (hot_tracking == value) + return; + + hot_tracking = value; + if (hot_tracking) { + hover_selection = true; + activation = ItemActivation.OneClick; + } + } + } + + [DefaultValue (false)] + public bool HoverSelection { + get { return hover_selection; } + set { + if (hot_tracking && value == false) + throw new ArgumentException ("When HotTracking is on, hover selection must be true"); + hover_selection = value; + } + } + + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + [Browsable (false)] + public ListViewInsertionMark InsertionMark { + get { + return insertion_mark; + } + } + + //[Editor ("ShiftUI.Design.ListViewItemCollectionEditor, " + Consts.AssemblySystem_Design, typeof (System.Drawing.Design.UITypeEditor))] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Content)] + [Localizable (true)] + [MergableProperty (false)] + public ListViewItemCollection Items { + get { return items; } + } + + [DefaultValue (false)] + public bool LabelEdit { + get { return label_edit; } + set { + if (value != label_edit) { + label_edit = value; + + // UIA Framework: Event used by Value Pattern in ListView.ListItem provider + OnUIALabelEditChanged (); + } + + } + } + + [DefaultValue (true)] + [Localizable (true)] + public bool LabelWrap { + get { return label_wrap; } + set { + if (label_wrap != value) { + label_wrap = value; + this.Redraw (true); + } + } + } + + [DefaultValue (null)] + public ImageList LargeImageList { + get { return large_image_list; } + set { + large_image_list = value; + this.Redraw (true); + } + } + + [Browsable (false)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public IComparer ListViewItemSorter { + get { + if (View != View.SmallIcon && View != View.LargeIcon && item_sorter is ItemComparer) + return null; + return item_sorter; + } + set { + if (item_sorter != value) { + item_sorter = value; + Sort (); + } + } + } + + [DefaultValue (true)] + public bool MultiSelect { + get { return multiselect; } + set { + if (value != multiselect) { + multiselect = value; + + // UIA Framework: Event used by Selection Pattern in ListView.ListItem provider + OnUIAMultiSelectChanged (); + } + } + } + + + [DefaultValue(false)] + public bool OwnerDraw { + get { return owner_draw; } + set { + owner_draw = value; + Redraw (true); + } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public new Padding Padding { + get { + return base.Padding; + } + set { + base.Padding = value; + } + } + + [MonoTODO ("RTL not supported")] + [Localizable (true)] + [DefaultValue (false)] + public virtual bool RightToLeftLayout { + get { return right_to_left_layout; } + set { + if (right_to_left_layout != value) { + right_to_left_layout = value; + OnRightToLeftLayoutChanged (EventArgs.Empty); + } + } + } + + [DefaultValue (true)] + public bool Scrollable { + get { return scrollable; } + set { + if (scrollable != value) { + scrollable = value; + this.Redraw (true); + } + } + } + + [Browsable (false)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public SelectedIndexCollection SelectedIndices { + get { return selected_indices; } + } + + [Browsable (false)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public SelectedListViewItemCollection SelectedItems { + get { return selected_items; } + } + + [DefaultValue(true)] + public bool ShowGroups { + get { return show_groups; } + set { + if (show_groups != value) { + show_groups = value; + Redraw(true); + + // UIA Framework: Used to update a11y Tree + OnUIAShowGroupsChanged (); + } + } + } + + [LocalizableAttribute (true)] + [MergableProperty (false)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Content)] + //[Editor ("ShiftUI.Design.ListViewGroupCollectionEditor, " + Consts.AssemblySystem_Design, typeof (System.Drawing.Design.UITypeEditor))] + public ListViewGroupCollection Groups { + get { return groups; } + } + + [DefaultValue (false)] + public bool ShowItemToolTips { + get { + return show_item_tooltips; + } + set { + show_item_tooltips = value; + item_tooltip.Active = false; + } + } + + [DefaultValue (null)] + public ImageList SmallImageList { + get { return small_image_list; } + set { + small_image_list = value; + this.Redraw (true); + } + } + + [DefaultValue (SortOrder.None)] + public SortOrder Sorting { + get { return sort_order; } + set { + if (!Enum.IsDefined (typeof (SortOrder), value)) { + throw new InvalidEnumArgumentException ("value", (int) value, + typeof (SortOrder)); + } + + if (sort_order == value) + return; + + sort_order = value; + + if (virtual_mode) // Sorting is not allowed in virtual mode + return; + + if (value == SortOrder.None) { + if (item_sorter != null) { + // ListViewItemSorter should never be reset for SmallIcon + // and LargeIcon view + if (View != View.SmallIcon && View != View.LargeIcon) + item_sorter = null; + } + this.Redraw (false); + } else { + if (item_sorter == null) + item_sorter = new ItemComparer (value); + if (item_sorter is ItemComparer) { + item_sorter = new ItemComparer (value); + } + Sort (); + } + } + } + + private void OnImageListChanged (object sender, EventArgs args) + { + item_control.Invalidate (); + } + + [DefaultValue (null)] + public ImageList StateImageList { + get { return state_image_list; } + set { + if (state_image_list == value) + return; + + if (state_image_list != null) + state_image_list.Images.Changed -= new EventHandler (OnImageListChanged); + + state_image_list = value; + + if (state_image_list != null) + state_image_list.Images.Changed += new EventHandler (OnImageListChanged); + + this.Redraw (true); + } + } + + [Bindable (false)] + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public override string Text { + get { return base.Text; } + set { + if (value == base.Text) + return; + + base.Text = value; + this.Redraw (true); + } + } + + [Browsable (true)] + public Size TileSize { + get { + return tile_size; + } + set { + if (value.Width <= 0 || value.Height <= 0) + throw new ArgumentOutOfRangeException ("value"); + + tile_size = value; + if (view == View.Tile) + Redraw (true); + } + } + + [Browsable (false)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public ListViewItem TopItem { + get { + if (view == View.LargeIcon || view == View.SmallIcon || view == View.Tile) + throw new InvalidOperationException ("Cannot get the top item in LargeIcon, SmallIcon or Tile view."); + // there is no item + if (this.items.Count == 0) + return null; + // if contents are not scrolled + // it is the first item + else if (h_marker == 0 && v_marker == 0) + return this.items [0]; + // do a hit test for the scrolled position + else { + int header_offset = header_control.Height; + for (int i = 0; i < items.Count; i++) { + Point item_loc = GetItemLocation (i); + if (item_loc.X >= 0 && item_loc.Y - header_offset >= 0) + return items [i]; + } + return null; + } + } + set { + if (view == View.LargeIcon || view == View.SmallIcon || view == View.Tile) + throw new InvalidOperationException ("Cannot set the top item in LargeIcon, SmallIcon or Tile view."); + + // .Net doesn't throw any exception in the cases below + if (value == null || value.ListView != this) + return; + + // Take advantage this property is only valid for Details view. + SetScrollValue (v_scroll, item_size.Height * value.Index); + } + } + + //[EditorBrowsable (EditorBrowsableState.Advanced)] + [DefaultValue (true)] + [Browsable (false)] + [MonoInternalNote ("Stub, not implemented")] + public bool UseCompatibleStateImageBehavior { + get { + return false; + } + set { + } + } + + [DefaultValue (View.LargeIcon)] + public View View { + get { return view; } + set { + if (!Enum.IsDefined (typeof (View), value)) + throw new InvalidEnumArgumentException ("value", (int) value, + typeof (View)); + + if (view != value) { + if (CheckBoxes && value == View.Tile) + throw new NotSupportedException ("CheckBoxes are not" + + " supported in Tile view. Choose a different" + + " view or set CheckBoxes to false."); + if (VirtualMode && value == View.Tile) + throw new NotSupportedException ("VirtualMode is" + + " not supported in Tile view. Choose a different" + + " view or set ViewMode to false."); + + h_scroll.Value = v_scroll.Value = 0; + view = value; + Redraw (true); + + // UIA Framework: Event used to update UIA Tree. + OnUIAViewChanged (); + } + } + } + + [DefaultValue (false)] + [RefreshProperties (RefreshProperties.Repaint)] + public bool VirtualMode { + get { + return virtual_mode; + } + set { + if (virtual_mode == value) + return; + + if (!virtual_mode && items.Count > 0) + throw new InvalidOperationException (); + if (value && view == View.Tile) + throw new NotSupportedException ("VirtualMode is" + + " not supported in Tile view. Choose a different" + + " view or set ViewMode to false."); + + virtual_mode = value; + Redraw (true); + } + } + + [DefaultValue (0)] + [RefreshProperties (RefreshProperties.Repaint)] + public int VirtualListSize { + get { + return virtual_list_size; + } + set { + if (value < 0) + throw new ArgumentException ("value"); + + if (virtual_list_size == value) + return; + + virtual_list_size = value; + if (virtual_mode) { + focused_item_index = -1; + selected_indices.Reset (); + Redraw (true); + } + } + } + #endregion // Public Instance Properties + + #region Internal Methods Properties + + internal int FirstVisibleIndex { + get { + // there is no item + if (this.items.Count == 0) + return 0; + + if (h_marker == 0 && v_marker == 0) + return 0; + + Size item_size = ItemSize; + // In virtual mode we always have fixed positions, and we can infer the positon easily + if (virtual_mode) { + int first = 0; + switch (view) { + case View.Details: + first = v_marker / item_size.Height; + break; + case View.LargeIcon: + case View.SmallIcon: + first = (v_marker / (item_size.Height + y_spacing)) * cols; + break; + case View.List: + first = (h_marker / (item_size.Width * x_spacing)) * rows; + break; + } + + if (first >= items.Count) + first = items.Count; + + return first; + } + for (int i = 0; i < items.Count; i++) { + Rectangle item_rect = new Rectangle (GetItemLocation (i), item_size); + if (item_rect.Right >= 0 && item_rect.Bottom >= 0) + return i; + } + + return 0; + } + } + + + internal int LastVisibleIndex { + get { + for (int i = FirstVisibleIndex; i < Items.Count; i++) { + if (View == View.List || Alignment == ListViewAlignment.Left) { + if (GetItemLocation (i).X > item_control.ClientRectangle.Right) + return i - 1; + } else { + if (GetItemLocation (i).Y > item_control.ClientRectangle.Bottom) + return i - 1; + } + } + + return Items.Count - 1; + } + } + + internal void OnSelectedIndexChanged () + { + if (is_selection_available) + OnSelectedIndexChanged (EventArgs.Empty); + } + + internal int TotalWidth { + get { return Math.Max (this.Width, this.layout_wd); } + } + + internal int TotalHeight { + get { return Math.Max (this.Height, this.layout_ht); } + } + + internal void Redraw (bool recalculate) + { + // Avoid calculations when control is being updated + if (updating) + return; + // VirtualMode doesn't do any calculations until handle is created + if (virtual_mode && !IsHandleCreated) + return; + + + if (recalculate) + CalculateListView (this.alignment); + + Invalidate (true); + } + + void InvalidateSelection () + { + foreach (int selected_index in SelectedIndices) + items [selected_index].Invalidate (); + } + + const int text_padding = 15; + + internal Size GetChildColumnSize (int index) + { + Size ret_size = Size.Empty; + ColumnHeader col = this.columns [index]; + + if (col.Width == -2) { // autosize = max(items, columnheader) + Size size = Size.Ceiling (TextRenderer.MeasureString + (col.Text, this.Font)); + size.Width += text_padding; + ret_size = BiggestItem (index); + if (size.Width > ret_size.Width) + ret_size = size; + } + else { // -1 and all the values < -2 are put under one category + ret_size = BiggestItem (index); + // fall back to empty columns' width if no subitem is available for a column + if (ret_size.IsEmpty) { + ret_size.Width = ThemeEngine.Current.ListViewEmptyColumnWidth; + if (col.Text.Length > 0) + ret_size.Height = Size.Ceiling (TextRenderer.MeasureString + (col.Text, this.Font)).Height; + else + ret_size.Height = this.Font.Height; + } + } + + ret_size.Height += text_padding; + + // adjust the size for icon and checkbox for 0th column + if (index == 0) { + ret_size.Width += (this.CheckBoxSize.Width + 4); + if (this.small_image_list != null) + ret_size.Width += this.small_image_list.ImageSize.Width; + } + return ret_size; + } + + // Returns the size of biggest item text in a column + // or the sum of the text and indent count if we are on 2.0 + private Size BiggestItem (int col) + { + Size temp = Size.Empty; + Size ret_size = Size.Empty; + bool use_indent_count = small_image_list != null; + + // VirtualMode uses the first item text size + if (virtual_mode && items.Count > 0) { + ListViewItem item = items [0]; + ret_size = Size.Ceiling (TextRenderer.MeasureString (item.SubItems[col].Text, + Font)); + + if (use_indent_count) + ret_size.Width += item.IndentCount * small_image_list.ImageSize.Width; + } else { + // 0th column holds the item text, we check the size of + // the various subitems falling in that column and get + // the biggest one's size. + foreach (ListViewItem item in items) { + if (col >= item.SubItems.Count) + continue; + + temp = Size.Ceiling (TextRenderer.MeasureString + (item.SubItems [col].Text, Font)); + + if (use_indent_count) + temp.Width += item.IndentCount * small_image_list.ImageSize.Width; + + if (temp.Width > ret_size.Width) + ret_size = temp; + } + } + + // adjustment for space in Details view + if (!ret_size.IsEmpty && view == View.Details) + ret_size.Width += ThemeEngine.Current.ListViewItemPaddingWidth; + + return ret_size; + } + + const int max_wrap_padding = 30; + + // Sets the size of the biggest item text as per the view + private void CalcTextSize () + { + // clear the old value + text_size = Size.Empty; + + if (items.Count == 0) + return; + + text_size = BiggestItem (0); + + if (view == View.LargeIcon && this.label_wrap) { + Size temp = Size.Empty; + if (this.check_boxes) + temp.Width += 2 * this.CheckBoxSize.Width; + int icon_w = LargeImageList == null ? 12 : LargeImageList.ImageSize.Width; + temp.Width += icon_w + max_wrap_padding; + // wrapping is done for two lines only + if (text_size.Width > temp.Width) { + text_size.Width = temp.Width; + text_size.Height *= 2; + } + } + else if (view == View.List) { + // in list view max text shown in determined by the + // control width, even if scolling is enabled. + int max_wd = this.Width - (this.CheckBoxSize.Width - 2); + if (this.small_image_list != null) + max_wd -= this.small_image_list.ImageSize.Width; + + if (text_size.Width > max_wd) + text_size.Width = max_wd; + } + + // we do the default settings, if we have got 0's + if (text_size.Height <= 0) + text_size.Height = this.Font.Height; + if (text_size.Width <= 0) + text_size.Width = this.Width; + + // little adjustment + text_size.Width += 2; + text_size.Height += 2; + } + + private void SetScrollValue (ScrollBar scrollbar, int val) + { + int max; + if (scrollbar == h_scroll) + max = h_scroll.Maximum - h_scroll.LargeChange + 1; + else + max = v_scroll.Maximum - v_scroll.LargeChange + 1; + + if (val > max) + val = max; + else if (val < scrollbar.Minimum) + val = scrollbar.Minimum; + + scrollbar.Value = val; + } + + private void Scroll (ScrollBar scrollbar, int delta) + { + if (delta == 0 || !scrollbar.Visible) + return; + + SetScrollValue (scrollbar, scrollbar.Value + delta); + } + + private void CalculateScrollBars () + { + Rectangle client_area = ClientRectangle; + int height = client_area.Height; + int width = client_area.Width; + Size item_size; + + if (!scrollable) { + h_scroll.Visible = false; + v_scroll.Visible = false; + item_control.Size = new Size (width, height); + header_control.Width = width; + return; + } + + // Don't calculate if the view is not displayable + if (client_area.Height < 0 || client_area.Width < 0) + return; + + // making a scroll bar visible might make + // other scroll bar visible + if (layout_wd > client_area.Right) { + h_scroll.Visible = true; + if ((layout_ht + h_scroll.Height) > client_area.Bottom) + v_scroll.Visible = true; + else + v_scroll.Visible = false; + } else if (layout_ht > client_area.Bottom) { + v_scroll.Visible = true; + if ((layout_wd + v_scroll.Width) > client_area.Right) + h_scroll.Visible = true; + else + h_scroll.Visible = false; + } else { + h_scroll.Visible = false; + v_scroll.Visible = false; + } + + + item_size = ItemSize; + + if (h_scroll.is_visible) { + h_scroll.Location = new Point (client_area.X, client_area.Bottom - h_scroll.Height); + h_scroll.Minimum = 0; + + // if v_scroll is visible, adjust the maximum of the + // h_scroll to account for the width of v_scroll + if (v_scroll.Visible) { + h_scroll.Maximum = layout_wd + v_scroll.Width; + h_scroll.Width = client_area.Width - v_scroll.Width; + } + else { + h_scroll.Maximum = layout_wd; + h_scroll.Width = client_area.Width; + } + + if (view == View.List) + h_scroll.SmallChange = item_size.Width + ThemeEngine.Current.ListViewHorizontalSpacing; + else + h_scroll.SmallChange = Font.Height; + + h_scroll.LargeChange = client_area.Width; + height -= h_scroll.Height; + } + + if (v_scroll.is_visible) { + v_scroll.Location = new Point (client_area.Right - v_scroll.Width, client_area.Y); + v_scroll.Minimum = 0; + + // if h_scroll is visible, adjust the height of + // v_scroll to account for the height of h_scroll + if (h_scroll.Visible) { + v_scroll.Maximum = layout_ht + h_scroll.Height; + v_scroll.Height = client_area.Height > h_scroll.Height ? client_area.Height - h_scroll.Height : 0; + } else { + v_scroll.Maximum = layout_ht; + v_scroll.Height = client_area.Height; + } + + if (view == View.Details) { + // Need to update Maximum if using LargeChange with value other than the visible area + int headerPlusOneItem = header_control.Height + item_size.Height; + v_scroll.LargeChange = v_scroll.Height > headerPlusOneItem ? v_scroll.Height - headerPlusOneItem : 0; + v_scroll.Maximum = v_scroll.Maximum > headerPlusOneItem ? v_scroll.Maximum - headerPlusOneItem : 0; + } else + v_scroll.LargeChange = v_scroll.Height; + + v_scroll.SmallChange = item_size.Height; + width -= v_scroll.Width; + } + + item_control.Size = new Size (width, height); + + if (header_control.is_visible) + header_control.Width = width; + } + + internal int GetReorderedColumnIndex (ColumnHeader column) + { + if (reordered_column_indices == null) + return column.Index; + + for (int i = 0; i < Columns.Count; i++) + if (reordered_column_indices [i] == column.Index) + return i; + + return -1; + } + + internal ColumnHeader GetReorderedColumn (int index) + { + if (reordered_column_indices == null) + return Columns [index]; + else + return Columns [reordered_column_indices [index]]; + } + + internal void ReorderColumn (ColumnHeader col, int index, bool fireEvent) + { + if (fireEvent) { + ColumnReorderedEventHandler eh = (ColumnReorderedEventHandler) (Events [ColumnReorderedEvent]); + if (eh != null){ + ColumnReorderedEventArgs args = new ColumnReorderedEventArgs (col.Index, index, col); + + eh (this, args); + if (args.Cancel) { + header_control.Invalidate (); + item_control.Invalidate (); + return; + } + } + } + int column_count = Columns.Count; + + if (reordered_column_indices == null) { + reordered_column_indices = new int [column_count]; + for (int i = 0; i < column_count; i++) + reordered_column_indices [i] = i; + } + + if (reordered_column_indices [index] == col.Index) + return; + + int[] curr = reordered_column_indices; + int [] result = new int [column_count]; + int curr_idx = 0; + for (int i = 0; i < column_count; i++) { + if (curr_idx < column_count && curr [curr_idx] == col.Index) + curr_idx++; + + if (i == index) + result [i] = col.Index; + else + result [i] = curr [curr_idx++]; + } + + ReorderColumns (result, true); + } + + internal void ReorderColumns (int [] display_indices, bool redraw) + { + reordered_column_indices = display_indices; + for (int i = 0; i < Columns.Count; i++) { + ColumnHeader col = Columns [i]; + col.InternalDisplayIndex = reordered_column_indices [i]; + } + if (redraw && view == View.Details && IsHandleCreated) { + LayoutDetails (); + header_control.Invalidate (); + item_control.Invalidate (); + } + } + + internal void AddColumn (ColumnHeader newCol, int index, bool redraw) + { + int column_count = Columns.Count; + newCol.SetListView (this); + + int [] display_indices = new int [column_count]; + for (int i = 0; i < column_count; i++) { + ColumnHeader col = Columns [i]; + if (i == index) { + display_indices [i] = index; + } else { + int display_index = col.InternalDisplayIndex; + if (display_index < index) { + display_indices [i] = display_index; + } else { + display_indices [i] = (display_index + 1); + } + } + } + + ReorderColumns (display_indices, redraw); + Invalidate (); + } + + Size LargeIconItemSize + { + get { + int image_w = LargeImageList == null ? 12 : LargeImageList.ImageSize.Width; + int image_h = LargeImageList == null ? 2 : LargeImageList.ImageSize.Height; + int h = text_size.Height + 2 + Math.Max (CheckBoxSize.Height, image_h); + int w = Math.Max (text_size.Width, image_w); + + if (check_boxes) + w += 2 + CheckBoxSize.Width; + + return new Size (w, h); + } + } + + Size SmallIconItemSize { + get { + int image_w = SmallImageList == null ? 0 : SmallImageList.ImageSize.Width; + int image_h = SmallImageList == null ? 0 : SmallImageList.ImageSize.Height; + int h = Math.Max (text_size.Height, Math.Max (CheckBoxSize.Height, image_h)); + int w = text_size.Width + image_w; + + if (check_boxes) + w += 2 + CheckBoxSize.Width; + + return new Size (w, h); + } + } + + Size TileItemSize { + get { + // Calculate tile size if needed + // It appears that using Font.Size instead of a SizeF value can give us + // a slightly better approach to the proportions defined in .Net + if (tile_size == Size.Empty) { + int image_w = LargeImageList == null ? 0 : LargeImageList.ImageSize.Width; + int image_h = LargeImageList == null ? 0 : LargeImageList.ImageSize.Height; + int w = (int)Font.Size * ThemeEngine.Current.ListViewTileWidthFactor + image_w + 4; + int h = Math.Max ((int)Font.Size * ThemeEngine.Current.ListViewTileHeightFactor, image_h); + + tile_size = new Size (w, h); + } + + return tile_size; + } + } + + int GetDetailsItemHeight () + { + int item_height; + int checkbox_height = CheckBoxes ? CheckBoxSize.Height : 0; + int small_image_height = SmallImageList == null ? 0 : SmallImageList.ImageSize.Height; + item_height = Math.Max (checkbox_height, text_size.Height); + item_height = Math.Max (item_height, small_image_height); + return item_height; + } + + void SetItemLocation (int index, int x, int y, int row, int col) + { + Point old_location = items_location [index]; + if (old_location.X == x && old_location.Y == y) + return; + + items_location [index] = new Point (x, y); + items_matrix_location [index] = new ItemMatrixLocation (row, col); + + // + // Initial position matches item's position in ListViewItemCollection + // + reordered_items_indices [index] = index; + } + + + void ShiftItemsPositions (int from, int to, bool forward) + { + if (forward) { + for (int i = to + 1; i > from; i--) { + reordered_items_indices [i] = reordered_items_indices [i - 1]; + + ListViewItem item = items [reordered_items_indices [i]]; + item.Invalidate (); + item.DisplayIndex = i; + item.Invalidate (); + } + } else { + for (int i = from - 1; i < to; i++) { + reordered_items_indices [i] = reordered_items_indices [i + 1]; + + ListViewItem item = items [reordered_items_indices [i]]; + item.Invalidate (); + item.DisplayIndex = i; + item.Invalidate (); + } + } + } + + internal void ChangeItemLocation (int display_index, Point new_pos) + { + int new_display_index = GetDisplayIndexFromLocation (new_pos); + if (new_display_index == display_index) + return; + + int item_index = reordered_items_indices [display_index]; + ListViewItem item = items [item_index]; + + bool forward = new_display_index < display_index; + int index_from, index_to; + if (forward) { + index_from = new_display_index; + index_to = display_index - 1; + } else { + index_from = display_index + 1; + index_to = new_display_index; + } + + ShiftItemsPositions (index_from, index_to, forward); + + reordered_items_indices [new_display_index] = item_index; + + item.Invalidate (); + item.DisplayIndex = new_display_index; + item.Invalidate (); + } + + int GetDisplayIndexFromLocation (Point loc) + { + int display_index = -1; + Rectangle item_area; + + // First item + if (loc.X < 0 || loc.Y < 0) + return 0; + + // Adjustment to put in the next position refered by 'loc' + loc.X -= item_size.Width / 2; + if (loc.X < 0) + loc.X = 0; + + for (int i = 0; i < items.Count; i++) { + item_area = new Rectangle (GetItemLocation (i), item_size); + item_area.Inflate (ThemeEngine.Current.ListViewHorizontalSpacing, + ThemeEngine.Current.ListViewVerticalSpacing); + + if (item_area.Contains (loc)) { + display_index = i; + break; + } + } + + // Put in in last position + if (display_index == -1) + display_index = items.Count - 1; + + return display_index; + } + + // When using groups, the items with no group assigned + // belong to the DefaultGroup + int GetDefaultGroupItems () + { + int count = 0; + foreach (ListViewItem item in items) + if (item.Group == null) + count++; + + return count; + } + + // cache the spacing to let virtualmode compute the positions on the fly + int x_spacing; + int y_spacing; + int rows; + int cols; + int[,] item_index_matrix; + + void CalculateRowsAndCols (Size item_size, bool left_aligned, int x_spacing, int y_spacing) + { + Rectangle area = ClientRectangle; + + if (UseCustomColumnWidth) + CalculateCustomColumnWidth (); + if (UsingGroups) { + // When groups are used the alignment is always top-aligned + rows = 0; + cols = 0; + int items = 0; + + groups.DefaultGroup.ItemCount = GetDefaultGroupItems (); + for (int i = 0; i < groups.InternalCount; i++) { + ListViewGroup group = groups.GetInternalGroup (i); + int items_in_group = group.GetActualItemCount (); + + if (items_in_group == 0) + continue; + + int group_cols = (int) Math.Floor ((double)(area.Width - v_scroll.Width + x_spacing) / (double)(item_size.Width + x_spacing)); + if (group_cols <= 0) + group_cols = 1; + int group_rows = (int) Math.Ceiling ((double)items_in_group / (double)group_cols); + + group.starting_row = rows; + group.rows = group_rows; + group.starting_item = items; + group.current_item = 0; // Reset layout + + cols = Math.Max (group_cols, cols); + rows += group_rows; + items += items_in_group; + } + } else + { + // Simple matrix if no groups are used + if (left_aligned) { + rows = (int) Math.Floor ((double)(area.Height - h_scroll.Height + y_spacing) / (double)(item_size.Height + y_spacing)); + if (rows <= 0) + rows = 1; + cols = (int) Math.Ceiling ((double)items.Count / (double)rows); + } else { + if (UseCustomColumnWidth) + cols = (int) Math.Floor ((double)(area.Width - v_scroll.Width) / (double)(custom_column_width)); + else + cols = (int) Math.Floor ((double)(area.Width - v_scroll.Width + x_spacing) / (double)(item_size.Width + x_spacing)); + + if (cols < 1) + cols = 1; + + rows = (int) Math.Ceiling ((double)items.Count / (double)cols); + } + } + + item_index_matrix = new int [rows, cols]; + } + + // When using custom column width, we look for the minimum one + void CalculateCustomColumnWidth () + { + int min_width = Int32.MaxValue; + for (int i = 0; i < columns.Count; i++) { + int col_width = columns [i].Width; + + if (col_width < min_width) + min_width = col_width; + } + + custom_column_width = min_width; + } + + void LayoutIcons (Size item_size, bool left_aligned, int x_spacing, int y_spacing) + { + header_control.Visible = false; + header_control.Size = Size.Empty; + item_control.Visible = true; + item_control.Location = Point.Empty; + ItemSize = item_size; // Cache item size + this.x_spacing = x_spacing; + this.y_spacing = y_spacing; + + if (items.Count == 0) + return; + + Size sz = item_size; + + CalculateRowsAndCols (sz, left_aligned, x_spacing, y_spacing); + + layout_wd = UseCustomColumnWidth ? cols * custom_column_width : cols * (sz.Width + x_spacing) - x_spacing; + layout_ht = rows * (sz.Height + y_spacing) - y_spacing; + + if (virtual_mode) { // no actual assignment is needed on items for virtual mode + item_control.Size = new Size (layout_wd, layout_ht); + return; + } + + bool using_groups = UsingGroups; + if (using_groups) // the groups layout will override layout_ht + CalculateGroupsLayout (sz, y_spacing, 0); + + int row = 0, col = 0; + int x = 0, y = 0; + int display_index = 0; + + for (int i = 0; i < items.Count; i++) { + ListViewItem item = items [i]; + if (using_groups) { + ListViewGroup group = item.Group; + if (group == null) + group = groups.DefaultGroup; + + Point group_items_loc = group.items_area_location; + int current_item = group.current_item++; + int starting_row = group.starting_row; + + display_index = group.starting_item + current_item; + row = (current_item / cols); + col = current_item % cols; + + x = UseCustomColumnWidth ? col * custom_column_width : col * (item_size.Width + x_spacing); + y = row * (item_size.Height + y_spacing) + group_items_loc.Y; + + SetItemLocation (display_index, x, y, row + starting_row, col); + SetItemAtDisplayIndex (display_index, i); + item_index_matrix [row + starting_row, col] = i; + + } else + { + x = UseCustomColumnWidth ? col * custom_column_width : col * (item_size.Width + x_spacing); + y = row * (item_size.Height + y_spacing); + display_index = i; // Same as item index in Items + + SetItemLocation (i, x, y, row, col); + item_index_matrix [row, col] = i; + + if (left_aligned) { + row++; + if (row == rows) { + row = 0; + col++; + } + } else { + if (++col == cols) { + col = 0; + row++; + } + } + } + + item.Layout (); + item.DisplayIndex = display_index; + item.SetPosition (new Point (x, y)); + } + + item_control.Size = new Size (layout_wd, layout_ht); + } + + void CalculateGroupsLayout (Size item_size, int y_spacing, int y_origin) + { + int y = y_origin; + bool details = view == View.Details; + + for (int i = 0; i < groups.InternalCount; i++) { + ListViewGroup group = groups.GetInternalGroup (i); + if (group.ItemCount == 0) + continue; + + y += LayoutGroupHeader (group, y, item_size.Height, y_spacing, details ? group.ItemCount : group.rows); + } + + layout_ht = y; // Update height taking into account Groups' headers heights + } + + int LayoutGroupHeader (ListViewGroup group, int y_origin, int item_height, int y_spacing, int rows) + { + Rectangle client_area = ClientRectangle; + int header_height = Font.Height + 15; // one line height + some padding + + group.HeaderBounds = new Rectangle (0, y_origin, client_area.Width - v_scroll.Width, header_height); + group.items_area_location = new Point (0, y_origin + header_height); + + int items_area_height = ((item_height + y_spacing) * rows); + return header_height + items_area_height + 10; // Add a small bottom margin + } + + void CalculateDetailsGroupItemsCount () + { + int items = 0; + + groups.DefaultGroup.ItemCount = GetDefaultGroupItems (); + for (int i = 0; i < groups.InternalCount; i++) { + ListViewGroup group = groups.GetInternalGroup (i); + int items_in_group = group.GetActualItemCount (); + + if (items_in_group == 0) + continue; + + group.starting_item = items; + group.current_item = 0; // Reset layout. + items += items_in_group; + } + } + + void LayoutHeader () + { + int x = 0; + for (int i = 0; i < Columns.Count; i++) { + ColumnHeader col = GetReorderedColumn (i); + col.X = x; + col.Y = 0; + col.CalcColumnHeader (); + x += col.Wd; + } + + layout_wd = x; + + if (x < ClientRectangle.Width) + x = ClientRectangle.Width; + + if (header_style == ColumnHeaderStyle.None) { + header_control.Visible = false; + header_control.Size = Size.Empty; + layout_wd = ClientRectangle.Width; + } else { + header_control.Width = x; + header_control.Height = columns.Count > 0 ? columns [0].Ht : ThemeEngine.Current.ListViewGetHeaderHeight (this, Font); + header_control.Visible = true; + } + } + + void LayoutDetails () + { + LayoutHeader (); + + if (columns.Count == 0) { + item_control.Visible = false; + layout_wd = ClientRectangle.Width; + layout_ht = ClientRectangle.Height; + return; + } + + item_control.Visible = true; + item_control.Location = Point.Empty; + item_control.Width = ClientRectangle.Width; + AdjustChildrenZOrder (); + + int item_height = GetDetailsItemHeight (); + ItemSize = new Size (0, item_height); // We only cache Height for details view + int y = header_control.Height; + layout_ht = y + (item_height * items.Count); + if (items.Count > 0 && grid_lines) // some space for bottom gridline + layout_ht += 2; + + bool using_groups = UsingGroups; + if (using_groups) { + // Observe that this routines will override our layout_ht value + CalculateDetailsGroupItemsCount (); + CalculateGroupsLayout (ItemSize, 2, y); + } + + if (virtual_mode) // no assgination on items is needed + return; + + for (int i = 0; i < items.Count; i++) { + ListViewItem item = items [i]; + + int display_index; + int item_y; + + if (using_groups) { + ListViewGroup group = item.Group; + if (group == null) + group = groups.DefaultGroup; + + int current_item = group.current_item++; + Point group_items_loc = group.items_area_location; + display_index = group.starting_item + current_item; + + y = item_y = current_item * (item_height + 2) + group_items_loc.Y; + SetItemLocation (display_index, 0, item_y, 0, 0); + SetItemAtDisplayIndex (display_index, i); + } else + { + display_index = i; + item_y = y; + SetItemLocation (i, 0, item_y, 0, 0); + y += item_height; + } + + item.Layout (); + item.DisplayIndex = display_index; + item.SetPosition (new Point (0, item_y)); + } + } + + // Need to make sure HeaderControl is on top, and we can't simply use BringToFront since + // these Widgets are implicit, so we need to re-populate our collection. + void AdjustChildrenZOrder () + { + SuspendLayout (); + Widgets.ClearImplicit (); + Widgets.AddImplicit (header_control); + Widgets.AddImplicit (item_control); + Widgets.AddImplicit (h_scroll); + Widgets.AddImplicit (v_scroll); + ResumeLayout (); + } + + private void AdjustItemsPositionArray (int count) + { + // In virtual mode we compute the positions on the fly. + if (virtual_mode) + return; + if (items_location.Length >= count) + return; + + // items_location, items_matrix_location and reordered_items_indices must keep the same length + count = Math.Max (count, items_location.Length * 2); + items_location = new Point [count]; + items_matrix_location = new ItemMatrixLocation [count]; + reordered_items_indices = new int [count]; + } + + private void CalculateListView (ListViewAlignment align) + { + CalcTextSize (); + + AdjustItemsPositionArray (items.Count); + + switch (view) { + case View.Details: + LayoutDetails (); + break; + + case View.SmallIcon: + LayoutIcons (SmallIconItemSize, alignment == ListViewAlignment.Left, + ThemeEngine.Current.ListViewHorizontalSpacing, 2); + break; + + case View.LargeIcon: + LayoutIcons (LargeIconItemSize, alignment == ListViewAlignment.Left, + ThemeEngine.Current.ListViewHorizontalSpacing, + ThemeEngine.Current.ListViewVerticalSpacing); + break; + + case View.List: + LayoutIcons (SmallIconItemSize, true, + ThemeEngine.Current.ListViewHorizontalSpacing, 2); + break; + case View.Tile: + if (!Application.VisualStylesEnabled) + goto case View.LargeIcon; + + LayoutIcons (TileItemSize, alignment == ListViewAlignment.Left, + ThemeEngine.Current.ListViewHorizontalSpacing, + ThemeEngine.Current.ListViewVerticalSpacing); + break; + } + + CalculateScrollBars (); + } + + internal Point GetItemLocation (int index) + { + Point loc = Point.Empty; + if (virtual_mode) + loc = GetFixedItemLocation (index); + else + loc = items_location [index]; + + loc.X -= h_marker; // Adjust to scroll + loc.Y -= v_marker; + + return loc; + } + + Point GetFixedItemLocation (int index) + { + Point loc = Point.Empty; + + switch (view) { + case View.LargeIcon: + case View.SmallIcon: + loc.X = index % cols * (item_size.Width + x_spacing); + loc.Y = index / cols * (item_size.Height + y_spacing); + break; + case View.List: + loc.X = index / rows * (item_size.Width + x_spacing); + loc.Y = index % rows * (item_size.Height + y_spacing); + break; + case View.Details: + loc.Y = header_control.Height + (index * item_size.Height); + break; + } + + return loc; + } + + internal int GetItemIndex (int display_index) + { + if (virtual_mode) + return display_index; // no reordering in virtual mode. + return reordered_items_indices [display_index]; + } + + internal ListViewItem GetItemAtDisplayIndex (int display_index) + { + // in virtual mode there's no reordering at all. + if (virtual_mode) + return items [display_index]; + return items [reordered_items_indices [display_index]]; + } + + internal void SetItemAtDisplayIndex (int display_index, int index) + { + reordered_items_indices [display_index] = index; + } + + private bool KeySearchString (KeyEventArgs ke) + { + int current_tickcnt = Environment.TickCount; + if (keysearch_tickcnt > 0 && current_tickcnt - keysearch_tickcnt > keysearch_keydelay) { + keysearch_text = string.Empty; + } + + if (!Char.IsLetterOrDigit ((char)ke.KeyCode)) + return false; + + keysearch_text += (char)ke.KeyCode; + keysearch_tickcnt = current_tickcnt; + + int prev_focused = FocusedItem == null ? 0 : FocusedItem.DisplayIndex; + int start = prev_focused + 1 < Items.Count ? prev_focused + 1 : 0; + + ListViewItem item = FindItemWithText (keysearch_text, false, start, true, true); + if (item != null && prev_focused != item.DisplayIndex) { + selected_indices.Clear (); + + SetFocusedItem (item.DisplayIndex); + item.Selected = true; + EnsureVisible (GetItemIndex (item.DisplayIndex)); + } + + return true; + } + + private void OnItemsChanged () + { + ResetSearchString (); + } + + private void ResetSearchString () + { + keysearch_text = String.Empty; + } + + int GetAdjustedIndex (Keys key) + { + int result = -1; + + if (View == View.Details) { + switch (key) { + case Keys.Up: + result = FocusedItem.DisplayIndex - 1; + break; + case Keys.Down: + result = FocusedItem.DisplayIndex + 1; + if (result == items.Count) + result = -1; + break; + case Keys.PageDown: + int last_index = LastVisibleIndex; + Rectangle item_rect = new Rectangle (GetItemLocation (last_index), ItemSize); + if (item_rect.Bottom > item_control.ClientRectangle.Bottom) + last_index--; + if (FocusedItem.DisplayIndex == last_index) { + if (FocusedItem.DisplayIndex < Items.Count - 1) { + int page_size = item_control.Height / ItemSize.Height - 1; + result = FocusedItem.DisplayIndex + page_size - 1; + if (result >= Items.Count) + result = Items.Count - 1; + } + } else + result = last_index; + break; + case Keys.PageUp: + int first_index = FirstVisibleIndex; + if (GetItemLocation (first_index).Y < 0) + first_index++; + if (FocusedItem.DisplayIndex == first_index) { + if (first_index > 0) { + int page_size = item_control.Height / ItemSize.Height - 1; + result = first_index - page_size + 1; + if (result < 0) + result = 0; + } + } else + result = first_index; + break; + } + return result; + } + + if (virtual_mode) + return GetFixedAdjustedIndex (key); + + ItemMatrixLocation item_matrix_location = items_matrix_location [FocusedItem.DisplayIndex]; + int row = item_matrix_location.Row; + int col = item_matrix_location.Col; + + int adjusted_index = -1; + + switch (key) { + case Keys.Left: + if (col == 0) + return -1; + adjusted_index = item_index_matrix [row, col - 1]; + break; + + case Keys.Right: + if (col == (cols - 1)) + return -1; + while (item_index_matrix [row, col + 1] == 0) { + row--; + if (row < 0) + return -1; + } + adjusted_index = item_index_matrix [row, col + 1]; + break; + + case Keys.Up: + if (row == 0) + return -1; + while (item_index_matrix [row - 1, col] == 0 && row != 1) { + col--; + if (col < 0) + return -1; + } + adjusted_index = item_index_matrix [row - 1, col]; + break; + + case Keys.Down: + if (row == (rows - 1) || row == Items.Count - 1) + return -1; + while (item_index_matrix [row + 1, col] == 0) { + col--; + if (col < 0) + return -1; + } + adjusted_index = item_index_matrix [row + 1, col]; + break; + + default: + return -1; + } + + return items [adjusted_index].DisplayIndex; + } + + // Used for virtual mode, where items *cannot* be re-arranged + int GetFixedAdjustedIndex (Keys key) + { + int result; + + switch (key) { + case Keys.Left: + if (view == View.List) + result = focused_item_index - rows; + else + result = focused_item_index - 1; + break; + case Keys.Right: + if (view == View.List) + result = focused_item_index + rows; + else + result = focused_item_index + 1; + break; + case Keys.Up: + if (view != View.List) + result = focused_item_index - cols; + else + result = focused_item_index - 1; + break; + case Keys.Down: + if (view != View.List) + result = focused_item_index + cols; + else + result = focused_item_index + 1; + break; + default: + return -1; + + } + + if (result < 0 || result >= items.Count) + result = focused_item_index; + + return result; + } + + ListViewItem selection_start; + + private bool SelectItems (ArrayList sel_items) + { + bool changed = false; + foreach (ListViewItem item in SelectedItems) + if (!sel_items.Contains (item)) { + item.Selected = false; + changed = true; + } + foreach (ListViewItem item in sel_items) + if (!item.Selected) { + item.Selected = true; + changed = true; + } + return changed; + } + + private void UpdateMultiSelection (int index, bool reselect) + { + bool shift_pressed = (XplatUI.State.ModifierKeys & Keys.Shift) != 0; + bool ctrl_pressed = (XplatUI.State.ModifierKeys & Keys.Widget) != 0; + ListViewItem item = GetItemAtDisplayIndex (index); + + if (shift_pressed && selection_start != null) { + ArrayList list = new ArrayList (); + int start_index = selection_start.DisplayIndex; + int start = Math.Min (start_index, index); + int end = Math.Max (start_index, index); + if (View == View.Details) { + for (int i = start; i <= end; i++) + list.Add (GetItemAtDisplayIndex (i)); + } else { + ItemMatrixLocation start_item_matrix_location = items_matrix_location [start]; + ItemMatrixLocation end_item_matrix_location = items_matrix_location [end]; + int left = Math.Min (start_item_matrix_location.Col, end_item_matrix_location.Col); + int right = Math.Max (start_item_matrix_location.Col, end_item_matrix_location.Col); + int top = Math.Min (start_item_matrix_location.Row, end_item_matrix_location.Row); + int bottom = Math.Max (start_item_matrix_location.Row, end_item_matrix_location.Row); + + for (int i = 0; i < items.Count; i++) { + ItemMatrixLocation item_matrix_loc = items_matrix_location [i]; + + if (item_matrix_loc.Row >= top && item_matrix_loc.Row <= bottom && + item_matrix_loc.Col >= left && item_matrix_loc.Col <= right) + list.Add (GetItemAtDisplayIndex (i)); + } + } + SelectItems (list); + } else if (ctrl_pressed) { + item.Selected = !item.Selected; + selection_start = item; + } else { + if (!reselect) { + // do not unselect, and reselect the item + foreach (int itemIndex in SelectedIndices) { + if (index == itemIndex) + continue; + items [itemIndex].Selected = false; + } + } else { + SelectedItems.Clear (); + item.Selected = true; + } + selection_start = item; + } + } + + internal override bool InternalPreProcessMessage (ref Message msg) + { + if (msg.Msg == (int)Msg.WM_KEYDOWN) { + Keys key_data = (Keys)msg.WParam.ToInt32(); + + HandleNavKeys (key_data); + } + + return base.InternalPreProcessMessage (ref msg); + } + + bool HandleNavKeys (Keys key_data) + { + if (Items.Count == 0 || !item_control.Visible) + return false; + + if (FocusedItem == null) + SetFocusedItem (0); + + switch (key_data) { + case Keys.End: + SelectIndex (Items.Count - 1); + break; + + case Keys.Home: + SelectIndex (0); + break; + + case Keys.Left: + case Keys.Right: + case Keys.Up: + case Keys.Down: + case Keys.PageUp: + case Keys.PageDown: + SelectIndex (GetAdjustedIndex (key_data)); + break; + + case Keys.Space: + SelectIndex (focused_item_index); + ToggleItemsCheckState (); + break; + case Keys.Enter: + if (selected_indices.Count > 0) + OnItemActivate (EventArgs.Empty); + break; + + default: + return false; + } + + return true; + } + + void ToggleItemsCheckState () + { + if (!CheckBoxes) + return; + + // Don't modify check state if StateImageList has less than 2 elements + if (StateImageList != null && StateImageList.Images.Count < 2) + return; + + if (SelectedIndices.Count > 0) { + for (int i = 0; i < SelectedIndices.Count; i++) { + ListViewItem item = Items [SelectedIndices [i]]; + item.Checked = !item.Checked; + } + return; + } + + if (FocusedItem != null) { + FocusedItem.Checked = !FocusedItem.Checked; + SelectIndex (FocusedItem.Index); + } + } + + void SelectIndex (int display_index) + { + if (display_index == -1) + return; + + if (MultiSelect) + UpdateMultiSelection (display_index, true); + else if (!GetItemAtDisplayIndex (display_index).Selected) + GetItemAtDisplayIndex (display_index).Selected = true; + + SetFocusedItem (display_index); + EnsureVisible (GetItemIndex (display_index)); // Index in Items collection, not display index + } + + private void ListView_KeyDown (object sender, KeyEventArgs ke) + { + if (ke.Handled || Items.Count == 0 || !item_control.Visible) + return; + + if (ke.Alt || ke.Widget) + return; + + ke.Handled = KeySearchString (ke); + } + + private MouseEventArgs TranslateMouseEventArgs (MouseEventArgs args) + { + Point loc = PointToClient (Widget.MousePosition); + return new MouseEventArgs (args.Button, args.Clicks, loc.X, loc.Y, args.Delta); + } + + internal class ItemControl : Widget { + + ListView owner; + ListViewItem clicked_item; + ListViewItem last_clicked_item; + bool hover_processed = false; + bool checking = false; + ListViewItem prev_hovered_item; + ListViewItem prev_tooltip_item; + int clicks; + Point drag_begin = new Point (-1, -1); + internal int dragged_item_index = -1; + + ListViewLabelEditTextBox edit_text_box; + internal ListViewItem edit_item; + LabelEditEventArgs edit_args; + + public ItemControl (ListView owner) + { + this.owner = owner; + this.SetStyle (Widgetstyles.DoubleBuffer, true); + DoubleClick += new EventHandler(ItemsDoubleClick); + MouseDown += new MouseEventHandler(ItemsMouseDown); + MouseMove += new MouseEventHandler(ItemsMouseMove); + MouseHover += new EventHandler(ItemsMouseHover); + MouseUp += new MouseEventHandler(ItemsMouseUp); + } + + void ItemsDoubleClick (object sender, EventArgs e) + { + if (owner.activation == ItemActivation.Standard) + owner.OnItemActivate (EventArgs.Empty); + } + + enum BoxSelect { + None, + Normal, + Shift, + Widget + } + + BoxSelect box_select_mode = BoxSelect.None; + IList prev_selection; + Point box_select_start; + + Rectangle box_select_rect; + internal Rectangle BoxSelectRectangle { + get { return box_select_rect; } + set { + if (box_select_rect == value) + return; + + InvalidateBoxSelectRect (); + box_select_rect = value; + InvalidateBoxSelectRect (); + } + } + + void InvalidateBoxSelectRect () + { + if (BoxSelectRectangle.Size.IsEmpty) + return; + + Rectangle edge = BoxSelectRectangle; + edge.X -= 1; + edge.Y -= 1; + edge.Width += 2; + edge.Height = 2; + Invalidate (edge); + edge.Y = BoxSelectRectangle.Bottom - 1; + Invalidate (edge); + edge.Y = BoxSelectRectangle.Y - 1; + edge.Width = 2; + edge.Height = BoxSelectRectangle.Height + 2; + Invalidate (edge); + edge.X = BoxSelectRectangle.Right - 1; + Invalidate (edge); + } + + private Rectangle CalculateBoxSelectRectangle (Point pt) + { + int left = Math.Min (box_select_start.X, pt.X); + int right = Math.Max (box_select_start.X, pt.X); + int top = Math.Min (box_select_start.Y, pt.Y); + int bottom = Math.Max (box_select_start.Y, pt.Y); + return Rectangle.FromLTRB (left, top, right, bottom); + } + + bool BoxIntersectsItem (int index) + { + Rectangle r = new Rectangle (owner.GetItemLocation (index), owner.ItemSize); + if (owner.View != View.Details) { + r.X += r.Width / 4; + r.Y += r.Height / 4; + r.Width /= 2; + r.Height /= 2; + } + return BoxSelectRectangle.IntersectsWith (r); + } + + bool BoxIntersectsText (int index) + { + Rectangle r = owner.GetItemAtDisplayIndex (index).TextBounds; + return BoxSelectRectangle.IntersectsWith (r); + } + + ArrayList BoxSelectedItems { + get { + ArrayList result = new ArrayList (); + for (int i = 0; i < owner.Items.Count; i++) { + bool intersects; + // Can't iterate over specific items properties in virtualmode + if (owner.View == View.Details && !owner.FullRowSelect && !owner.VirtualMode) + intersects = BoxIntersectsText (i); + else + intersects = BoxIntersectsItem (i); + + if (intersects) + result.Add (owner.GetItemAtDisplayIndex (i)); + } + return result; + } + } + + private bool PerformBoxSelection (Point pt) + { + if (box_select_mode == BoxSelect.None) + return false; + + BoxSelectRectangle = CalculateBoxSelectRectangle (pt); + + ArrayList box_items = BoxSelectedItems; + + ArrayList items; + + switch (box_select_mode) { + + case BoxSelect.Normal: + items = box_items; + break; + + case BoxSelect.Widget: + items = new ArrayList (); + foreach (int index in prev_selection) + if (!box_items.Contains (owner.Items [index])) + items.Add (owner.Items [index]); + foreach (ListViewItem item in box_items) + if (!prev_selection.Contains (item.Index)) + items.Add (item); + break; + + case BoxSelect.Shift: + items = box_items; + foreach (ListViewItem item in box_items) + prev_selection.Remove (item.Index); + foreach (int index in prev_selection) + items.Add (owner.Items [index]); + break; + + default: + throw new Exception ("Unexpected Selection mode: " + box_select_mode); + } + + SuspendLayout (); + owner.SelectItems (items); + ResumeLayout (); + + return true; + } + + private void ItemsMouseDown (object sender, MouseEventArgs me) + { + owner.OnMouseDown (owner.TranslateMouseEventArgs (me)); + if (owner.items.Count == 0) + return; + + bool box_selecting = false; + Size item_size = owner.ItemSize; + Point pt = new Point (me.X, me.Y); + for (int i = 0; i < owner.items.Count; i++) { + Rectangle item_rect = new Rectangle (owner.GetItemLocation (i), item_size); + if (!item_rect.Contains (pt)) + continue; + + // Actual item in 'i' position + ListViewItem item = owner.GetItemAtDisplayIndex (i); + + if (item.CheckRectReal.Contains (pt)) { + // Don't modify check state if we have only one image + // and if we are in 1.1 profile only take into account + // double clicks + if (owner.StateImageList != null && owner.StateImageList.Images.Count < 2 + ) + return; + + // Generate an extra ItemCheck event when we got two clicks + // (Match weird .Net behaviour) + if (me.Clicks == 2) + item.Checked = !item.Checked; + + item.Checked = !item.Checked; + checking = true; + return; + } + + if (owner.View == View.Details) { + bool over_text = item.TextBounds.Contains (pt); + if (owner.FullRowSelect) { + clicked_item = item; + bool over_item_column = (me.X > owner.Columns[0].X && me.X < owner.Columns[0].X + owner.Columns[0].Width); + if (!over_text && over_item_column && owner.MultiSelect) + box_selecting = true; + } else if (over_text) + clicked_item = item; + else + owner.SetFocusedItem (i); + } else + clicked_item = item; + + break; + } + + + if (clicked_item != null) { + bool changed = !clicked_item.Selected; + if (me.Button == MouseButtons.Left || (XplatUI.State.ModifierKeys == Keys.None && changed)) + owner.SetFocusedItem (clicked_item.DisplayIndex); + + if (owner.MultiSelect) { + bool reselect = (!owner.LabelEdit || changed); + if (me.Button == MouseButtons.Left || (XplatUI.State.ModifierKeys == Keys.None && changed)) + owner.UpdateMultiSelection (clicked_item.DisplayIndex, reselect); + } else { + clicked_item.Selected = true; + } + + // Side-effects of changing the selection can possibly result in ItemsMouseUp() being called and + // and clicked_item being set to null. (See Xamarin bug 23591.) In such a case, assume + // that there's nothing more we can do here. + if (clicked_item == null) + return; + + if (owner.VirtualMode && changed) { + // Broken event - It's not fired from Item.Selected also + ListViewVirtualItemsSelectionRangeChangedEventArgs args = + new ListViewVirtualItemsSelectionRangeChangedEventArgs (0, owner.items.Count - 1, false); + + owner.OnVirtualItemsSelectionRangeChanged (args); + } + // Report clicks only if the item was clicked. On MS the + // clicks are only raised if you click an item + clicks = me.Clicks; + if (me.Clicks > 1) { + if (owner.CheckBoxes) + clicked_item.Checked = !clicked_item.Checked; + } else if (me.Clicks == 1) { + if (owner.LabelEdit && !changed) + BeginEdit (clicked_item); // this is probably not the correct place to execute BeginEdit + } + + drag_begin = me.Location; + dragged_item_index = clicked_item.Index; + } else { + if (owner.MultiSelect) + box_selecting = true; + else if (owner.SelectedItems.Count > 0) + owner.SelectedItems.Clear (); + } + + if (box_selecting) { + Keys mods = XplatUI.State.ModifierKeys; + if ((mods & Keys.Shift) != 0) + box_select_mode = BoxSelect.Shift; + else if ((mods & Keys.Widget) != 0) + box_select_mode = BoxSelect.Widget; + else + box_select_mode = BoxSelect.Normal; + box_select_start = pt; + prev_selection = owner.SelectedIndices.List.Clone () as IList; + } + } + + private void ItemsMouseMove (object sender, MouseEventArgs me) + { + bool done = PerformBoxSelection (new Point (me.X, me.Y)); + + owner.OnMouseMove (owner.TranslateMouseEventArgs (me)); + + if (done) + return; + if ((me.Button != MouseButtons.Left && me.Button != MouseButtons.Right) && + !hover_processed && owner.Activation != ItemActivation.OneClick + && !owner.ShowItemToolTips + ) + return; + + Point pt = PointToClient (Widget.MousePosition); + ListViewItem item = owner.GetItemAt (pt.X, pt.Y); + + if (hover_processed && item != null && item != prev_hovered_item) { + hover_processed = false; + XplatUI.ResetMouseHover (Handle); + } + + // Need to invalidate the item in HotTracking to show/hide the underline style + if (owner.Activation == ItemActivation.OneClick) { + if (item == null && owner.HotItemIndex != -1) { + if (owner.HotTracking) + Invalidate (owner.Items [owner.HotItemIndex].Bounds); // Previous one + + Cursor = Cursors.Default; + owner.HotItemIndex = -1; + } else if (item != null && owner.HotItemIndex == -1) { + if (owner.HotTracking) + Invalidate (item.Bounds); + + Cursor = Cursors.Hand; + owner.HotItemIndex = item.Index; + } + } + + if (me.Button == MouseButtons.Left || me.Button == MouseButtons.Right) { + if (drag_begin != new Point (-1, -1)) { + Rectangle r = new Rectangle (drag_begin, SystemInformation.DragSize); + if (!r.Contains (me.X, me.Y)) { + ListViewItem dragged_item = owner.items [dragged_item_index]; + owner.OnItemDrag (new ItemDragEventArgs (me.Button, dragged_item)); + + drag_begin = new Point (-1, -1); + dragged_item_index = -1; + } + } + } + + if (owner.ShowItemToolTips) { + if (item == null) { + owner.item_tooltip.Active = false; + prev_tooltip_item = null; + } else if (item != prev_tooltip_item && item.ToolTipText.Length > 0) { + owner.item_tooltip.Active = true; + owner.item_tooltip.SetToolTip (owner, item.ToolTipText); + prev_tooltip_item = item; + } + } + + } + + private void ItemsMouseHover (object sender, EventArgs e) + { + if (owner.hover_pending) { + owner.OnMouseHover (e); + owner.hover_pending = false; + } + + if (Capture) + return; + + hover_processed = true; + Point pt = PointToClient (Widget.MousePosition); + ListViewItem item = owner.GetItemAt (pt.X, pt.Y); + if (item == null) + return; + + prev_hovered_item = item; + + if (owner.HoverSelection) { + if (owner.MultiSelect) + owner.UpdateMultiSelection (item.Index, true); + else + item.Selected = true; + + owner.SetFocusedItem (item.DisplayIndex); + Select (); // Make sure we have the focus, since MouseHover doesn't give it to us + } + + owner.OnItemMouseHover (new ListViewItemMouseHoverEventArgs (item)); + } + + void HandleClicks (MouseEventArgs me) + { + // if the click is not on an item, + // clicks remains as 0 + if (clicks > 1) { + owner.OnDoubleClick (EventArgs.Empty); + owner.OnMouseDoubleClick (me); + } else if (clicks == 1) { + owner.OnClick (EventArgs.Empty); + owner.OnMouseClick (me); + } + + clicks = 0; + } + + private void ItemsMouseUp (object sender, MouseEventArgs me) + { + MouseEventArgs owner_me = owner.TranslateMouseEventArgs (me); + HandleClicks (owner_me); + + Capture = false; + if (owner.Items.Count == 0) { + ResetMouseState (); + owner.OnMouseUp (owner_me); + return; + } + + Point pt = new Point (me.X, me.Y); + + Rectangle rect = Rectangle.Empty; + if (clicked_item != null) { + if (owner.view == View.Details && !owner.full_row_select) + rect = clicked_item.GetBounds (ItemBoundsPortion.Label); + else + rect = clicked_item.Bounds; + + if (rect.Contains (pt)) { + switch (owner.activation) { + case ItemActivation.OneClick: + owner.OnItemActivate (EventArgs.Empty); + break; + + case ItemActivation.TwoClick: + if (last_clicked_item == clicked_item) { + owner.OnItemActivate (EventArgs.Empty); + last_clicked_item = null; + } else + last_clicked_item = clicked_item; + break; + default: + // DoubleClick activation is handled in another handler + break; + } + } + } else if (!checking && owner.SelectedItems.Count > 0 && BoxSelectRectangle.Size.IsEmpty) { + // Need this to clean up background clicks + owner.SelectedItems.Clear (); + } + + ResetMouseState (); + owner.OnMouseUp (owner_me); + } + + private void ResetMouseState () + { + clicked_item = null; + box_select_start = Point.Empty; + BoxSelectRectangle = Rectangle.Empty; + prev_selection = null; + box_select_mode = BoxSelect.None; + checking = false; + + // Clean these bits in case the mouse buttons were + // released before firing ItemDrag + dragged_item_index = -1; + drag_begin = new Point (-1, -1); + } + + private void LabelEditFinished (object sender, EventArgs e) + { + EndEdit (edit_item); + } + + private void LabelEditCancelled (object sender, EventArgs e) + { + edit_args.SetLabel (null); + EndEdit (edit_item); + } + + private void LabelTextChanged (object sender, EventArgs e) + { + if (edit_args != null) + edit_args.SetLabel (edit_text_box.Text); + } + + internal void BeginEdit (ListViewItem item) + { + if (edit_item != null) + EndEdit (edit_item); + + if (edit_text_box == null) { + edit_text_box = new ListViewLabelEditTextBox (); + edit_text_box.BorderStyle = BorderStyle.FixedSingle; + edit_text_box.EditingCancelled += new EventHandler (LabelEditCancelled); + edit_text_box.EditingFinished += new EventHandler (LabelEditFinished); + edit_text_box.TextChanged += new EventHandler (LabelTextChanged); + edit_text_box.Visible = false; + Widgets.Add (edit_text_box); + } + + item.EnsureVisible(); + + edit_text_box.Reset (); + + switch (owner.view) { + case View.List: + case View.SmallIcon: + case View.Details: + edit_text_box.TextAlign = HorizontalAlignment.Left; + edit_text_box.Bounds = item.GetBounds (ItemBoundsPortion.Label); + SizeF sizef = TextRenderer.MeasureString (item.Text, item.Font); + edit_text_box.Width = (int)sizef.Width + 4; + edit_text_box.MaxWidth = owner.ClientRectangle.Width - edit_text_box.Bounds.X; + edit_text_box.WordWrap = false; + edit_text_box.Multiline = false; + break; + case View.LargeIcon: + edit_text_box.TextAlign = HorizontalAlignment.Center; + edit_text_box.Bounds = item.GetBounds (ItemBoundsPortion.Label); + sizef = TextRenderer.MeasureString (item.Text, item.Font); + edit_text_box.Width = (int)sizef.Width + 4; + edit_text_box.MaxWidth = item.GetBounds(ItemBoundsPortion.Entire).Width; + edit_text_box.MaxHeight = owner.ClientRectangle.Height - edit_text_box.Bounds.Y; + edit_text_box.WordWrap = true; + edit_text_box.Multiline = true; + break; + } + + edit_item = item; + + edit_text_box.Text = item.Text; + edit_text_box.Font = item.Font; + edit_text_box.Visible = true; + edit_text_box.Focus (); + edit_text_box.SelectAll (); + + edit_args = new LabelEditEventArgs (owner.Items.IndexOf (edit_item)); + owner.OnBeforeLabelEdit (edit_args); + + if (edit_args.CancelEdit) + EndEdit (item); + } + + internal void CancelEdit (ListViewItem item) + { + // do nothing if there's no item being edited, or if the + // item being edited is not the one passed in + if (edit_item == null || edit_item != item) + return; + + edit_args.SetLabel (null); + EndEdit (item); + } + + internal void EndEdit (ListViewItem item) + { + // do nothing if there's no item being edited, or if the + // item being edited is not the one passed in + if (edit_item == null || edit_item != item) + return; + + if (edit_text_box != null) { + if (edit_text_box.Visible) + edit_text_box.Visible = false; + // ensure listview gets focus + owner.Focus (); + } + + // Same as TreeView.EndEdit: need to have focus in synch + Application.DoEvents (); + + // + // Create a new instance, since we could get a call to BeginEdit + // from the handler and have fields out of synch + // + LabelEditEventArgs args = new LabelEditEventArgs (item.Index, edit_args.Label); + edit_item = null; + + owner.OnAfterLabelEdit (args); + if (!args.CancelEdit && args.Label != null) + item.Text = args.Label; + } + + internal override void OnPaintInternal (PaintEventArgs pe) + { + ThemeEngine.Current.DrawListViewItems (pe.Graphics, pe.ClipRectangle, owner); + } + + protected override void WndProc (ref Message m) + { + switch ((Msg)m.Msg) { + case Msg.WM_KILLFOCUS: + owner.Select (false, true); + break; + case Msg.WM_SETFOCUS: + owner.Select (false, true); + break; + case Msg.WM_LBUTTONDOWN: + if (!Focused) + owner.Select (false, true); + break; + case Msg.WM_RBUTTONDOWN: + if (!Focused) + owner.Select (false, true); + break; + default: + break; + } + base.WndProc (ref m); + } + } + + internal class ListViewLabelEditTextBox : TextBox + { + int max_width = -1; + int min_width = -1; + + int max_height = -1; + int min_height = -1; + + int old_number_lines = 1; + + SizeF text_size_one_char; + + public ListViewLabelEditTextBox () + { + min_height = DefaultSize.Height; + text_size_one_char = TextRenderer.MeasureString ("B", Font); + } + + public int MaxWidth { + set { + if (value < min_width) + max_width = min_width; + else + max_width = value; + } + } + + public int MaxHeight { + set { + if (value < min_height) + max_height = min_height; + else + max_height = value; + } + } + + public new int Width { + get { + return base.Width; + } + set { + min_width = value; + base.Width = value; + } + } + + public override Font Font { + get { + return base.Font; + } + set { + base.Font = value; + text_size_one_char = TextRenderer.MeasureString ("B", Font); + } + } + + protected override void OnTextChanged (EventArgs e) + { + SizeF text_size = TextRenderer.MeasureString (Text, Font); + + int new_width = (int)text_size.Width + 8; + + if (!Multiline) + ResizeTextBoxWidth (new_width); + else { + if (Width != max_width) + ResizeTextBoxWidth (new_width); + + int number_lines = Lines.Length; + + if (number_lines != old_number_lines) { + int new_height = number_lines * (int)text_size_one_char.Height + 4; + old_number_lines = number_lines; + + ResizeTextBoxHeight (new_height); + } + } + + base.OnTextChanged (e); + } + + protected override bool IsInputKey (Keys key_data) + { + if ((key_data & Keys.Alt) == 0) { + switch (key_data & Keys.KeyCode) { + case Keys.Enter: + return true; + case Keys.Escape: + return true; + } + } + return base.IsInputKey (key_data); + } + + protected override void OnKeyDown (KeyEventArgs e) + { + if (!Visible) + return; + + switch (e.KeyCode) { + case Keys.Return: + Visible = false; + e.Handled = true; + OnEditingFinished (e); + break; + case Keys.Escape: + Visible = false; + e.Handled = true; + OnEditingCancelled (e); + break; + } + } + + protected override void OnLostFocus (EventArgs e) + { + if (Visible) { + OnEditingFinished (e); + } + } + + protected void OnEditingCancelled (EventArgs e) + { + EventHandler eh = (EventHandler)(Events [EditingCancelledEvent]); + if (eh != null) + eh (this, e); + } + + protected void OnEditingFinished (EventArgs e) + { + EventHandler eh = (EventHandler)(Events [EditingFinishedEvent]); + if (eh != null) + eh (this, e); + } + + private void ResizeTextBoxWidth (int new_width) + { + if (new_width > max_width) + base.Width = max_width; + else + if (new_width >= min_width) + base.Width = new_width; + else + base.Width = min_width; + } + + private void ResizeTextBoxHeight (int new_height) + { + if (new_height > max_height) + base.Height = max_height; + else + if (new_height >= min_height) + base.Height = new_height; + else + base.Height = min_height; + } + + public void Reset () + { + max_width = -1; + min_width = -1; + + max_height = -1; + + old_number_lines = 1; + + Text = String.Empty; + + Size = DefaultSize; + } + + static object EditingCancelledEvent = new object (); + public event EventHandler EditingCancelled { + add { Events.AddHandler (EditingCancelledEvent, value); } + remove { Events.RemoveHandler (EditingCancelledEvent, value); } + } + + static object EditingFinishedEvent = new object (); + public event EventHandler EditingFinished { + add { Events.AddHandler (EditingFinishedEvent, value); } + remove { Events.RemoveHandler (EditingFinishedEvent, value); } + } + } + + internal override void OnPaintInternal (PaintEventArgs pe) + { + if (updating) + return; + + CalculateScrollBars (); + } + + void FocusChanged (object o, EventArgs args) + { + if (Items.Count == 0) + return; + + if (FocusedItem == null) + SetFocusedItem (0); + + ListViewItem focused_item = FocusedItem; + + if (focused_item.ListView != null) { + focused_item.Invalidate (); + focused_item.Layout (); + focused_item.Invalidate (); + } + } + + private void ListView_Invalidated (object sender, InvalidateEventArgs e) + { + // When the ListView is invalidated, we need to invalidate + // the child Widgets. + header_control.Invalidate (); + item_control.Invalidate (); + } + + private void ListView_MouseEnter (object sender, EventArgs args) + { + hover_pending = true; // Need a hover event for every Enter/Leave cycle + } + + private void ListView_MouseWheel (object sender, MouseEventArgs me) + { + if (Items.Count == 0) + return; + + int lines = me.Delta / 120; + + if (lines == 0) + return; + + switch (View) { + case View.Details: + case View.SmallIcon: + Scroll (v_scroll, -ItemSize.Height * SystemInformation.MouseWheelScrollLines * lines); + break; + case View.LargeIcon: + Scroll (v_scroll, -(ItemSize.Height + ThemeEngine.Current.ListViewVerticalSpacing) * lines); + break; + case View.List: + Scroll (h_scroll, -ItemSize.Width * lines); + break; + case View.Tile: + if (!Application.VisualStylesEnabled) + goto case View.LargeIcon; + + Scroll (v_scroll, -(ItemSize.Height + ThemeEngine.Current.ListViewVerticalSpacing) * 2 * lines); + break; + } + } + + private void ListView_SizeChanged (object sender, EventArgs e) + { + Redraw (true); + } + + private void SetFocusedItem (int display_index) + { + if (display_index != -1) + GetItemAtDisplayIndex (display_index).Focused = true; + else if (focused_item_index != -1 && focused_item_index < items.Count) // Previous focused item + GetItemAtDisplayIndex (focused_item_index).Focused = false; + focused_item_index = display_index; + if (display_index == -1) + OnUIAFocusedItemChanged (); + // otherwise the event will have been fired + // when the ListViewItem's Focused was set + } + + private void HorizontalScroller (object sender, EventArgs e) + { + item_control.EndEdit (item_control.edit_item); + + // Avoid unnecessary flickering, when button is + // kept pressed at the end + if (h_marker != h_scroll.Value) { + + int pixels = h_marker - h_scroll.Value; + + h_marker = h_scroll.Value; + if (header_control.Visible) + XplatUI.ScrollWindow (header_control.Handle, pixels, 0, false); + + XplatUI.ScrollWindow (item_control.Handle, pixels, 0, false); + } + } + + private void VerticalScroller (object sender, EventArgs e) + { + item_control.EndEdit (item_control.edit_item); + + // Avoid unnecessary flickering, when button is + // kept pressed at the end + if (v_marker != v_scroll.Value) { + int pixels = v_marker - v_scroll.Value; + Rectangle area = item_control.ClientRectangle; + if (header_control.Visible) { + area.Y += header_control.Height; + area.Height -= header_control.Height; + } + + v_marker = v_scroll.Value; + XplatUI.ScrollWindow (item_control.Handle, area, 0, pixels, false); + } + } + + internal override bool IsInputCharInternal (char charCode) + { + return true; + } + #endregion // Internal Methods Properties + + #region Protected Methods + protected override void CreateHandle () + { + base.CreateHandle (); + is_selection_available = true; + for (int i = 0; i < SelectedItems.Count; i++) + OnSelectedIndexChanged (EventArgs.Empty); + } + + protected override void Dispose (bool disposing) + { + if (disposing) { + large_image_list = null; + small_image_list = null; + state_image_list = null; + + foreach (ColumnHeader col in columns) + col.SetListView (null); + + if (!virtual_mode) // In virtual mode we don't save the items + foreach (ListViewItem item in items) + item.Owner = null; + } + + base.Dispose (disposing); + } + + protected override bool IsInputKey (Keys keyData) + { + switch (keyData) { + case Keys.Up: + case Keys.Down: + case Keys.PageUp: + case Keys.PageDown: + case Keys.Right: + case Keys.Left: + case Keys.End: + case Keys.Home: + return true; + + default: + break; + } + + return base.IsInputKey (keyData); + } + + protected virtual void OnAfterLabelEdit (LabelEditEventArgs e) + { + LabelEditEventHandler eh = (LabelEditEventHandler)(Events [AfterLabelEditEvent]); + if (eh != null) + eh (this, e); + } + + protected override void OnBackgroundImageChanged (EventArgs e) + { + item_control.BackgroundImage = BackgroundImage; + base.OnBackgroundImageChanged (e); + } + + protected virtual void OnBeforeLabelEdit (LabelEditEventArgs e) + { + LabelEditEventHandler eh = (LabelEditEventHandler)(Events [BeforeLabelEditEvent]); + if (eh != null) + eh (this, e); + } + + protected internal virtual void OnColumnClick (ColumnClickEventArgs e) + { + ColumnClickEventHandler eh = (ColumnClickEventHandler)(Events [ColumnClickEvent]); + if (eh != null) + eh (this, e); + } + + protected internal virtual void OnDrawColumnHeader(DrawListViewColumnHeaderEventArgs e) + { + DrawListViewColumnHeaderEventHandler eh = (DrawListViewColumnHeaderEventHandler)(Events[DrawColumnHeaderEvent]); + if (eh != null) + eh(this, e); + } + + protected internal virtual void OnDrawItem(DrawListViewItemEventArgs e) + { + DrawListViewItemEventHandler eh = (DrawListViewItemEventHandler)(Events[DrawItemEvent]); + if (eh != null) + eh(this, e); + } + + protected internal virtual void OnDrawSubItem(DrawListViewSubItemEventArgs e) + { + DrawListViewSubItemEventHandler eh = (DrawListViewSubItemEventHandler)(Events[DrawSubItemEvent]); + if (eh != null) + eh(this, e); + } + + protected override void OnFontChanged (EventArgs e) + { + base.OnFontChanged (e); + Redraw (true); + } + + protected override void OnHandleCreated (EventArgs e) + { + base.OnHandleCreated (e); + CalculateListView (alignment); + if (!virtual_mode) // Sorting is not allowed in virtual mode + Sort (); + } + + protected override void OnHandleDestroyed (EventArgs e) + { + base.OnHandleDestroyed (e); + } + + protected virtual void OnItemActivate (EventArgs e) + { + EventHandler eh = (EventHandler)(Events [ItemActivateEvent]); + if (eh != null) + eh (this, e); + } + + protected internal virtual void OnItemCheck (ItemCheckEventArgs ice) + { + ItemCheckEventHandler eh = (ItemCheckEventHandler)(Events [ItemCheckEvent]); + if (eh != null) + eh (this, ice); + } + + protected internal virtual void OnItemChecked (ItemCheckedEventArgs e) + { + ItemCheckedEventHandler eh = (ItemCheckedEventHandler)(Events [ItemCheckedEvent]); + if (eh != null) + eh (this, e); + } + + protected virtual void OnItemDrag (ItemDragEventArgs e) + { + ItemDragEventHandler eh = (ItemDragEventHandler)(Events [ItemDragEvent]); + if (eh != null) + eh (this, e); + } + + protected virtual void OnItemMouseHover (ListViewItemMouseHoverEventArgs e) + { + ListViewItemMouseHoverEventHandler eh = (ListViewItemMouseHoverEventHandler)(Events [ItemMouseHoverEvent]); + if (eh != null) + eh (this, e); + } + + protected internal virtual void OnItemSelectionChanged (ListViewItemSelectionChangedEventArgs e) + { + ListViewItemSelectionChangedEventHandler eh = + (ListViewItemSelectionChangedEventHandler) Events [ItemSelectionChangedEvent]; + if (eh != null) + eh (this, e); + } + + protected override void OnMouseHover (EventArgs e) + { + base.OnMouseHover (e); + } + + protected override void OnParentChanged (EventArgs e) + { + base.OnParentChanged (e); + } + + protected virtual void OnSelectedIndexChanged (EventArgs e) + { + EventHandler eh = (EventHandler)(Events [SelectedIndexChangedEvent]); + if (eh != null) + eh (this, e); + } + + protected override void OnSystemColorsChanged (EventArgs e) + { + base.OnSystemColorsChanged (e); + } + + protected internal virtual void OnCacheVirtualItems (CacheVirtualItemsEventArgs e) + { + CacheVirtualItemsEventHandler eh = (CacheVirtualItemsEventHandler)Events [CacheVirtualItemsEvent]; + if (eh != null) + eh (this, e); + } + + protected virtual void OnRetrieveVirtualItem (RetrieveVirtualItemEventArgs e) + { + RetrieveVirtualItemEventHandler eh = (RetrieveVirtualItemEventHandler)Events [RetrieveVirtualItemEvent]; + if (eh != null) + eh (this, e); + } + + //[EditorBrowsable (EditorBrowsableState.Advanced)] + protected virtual void OnRightToLeftLayoutChanged (EventArgs e) + { + EventHandler eh = (EventHandler)Events[RightToLeftLayoutChangedEvent]; + if (eh != null) + eh (this, e); + } + + protected virtual void OnSearchForVirtualItem (SearchForVirtualItemEventArgs e) + { + SearchForVirtualItemEventHandler eh = (SearchForVirtualItemEventHandler) Events [SearchForVirtualItemEvent]; + if (eh != null) + eh (this, e); + } + + protected virtual void OnVirtualItemsSelectionRangeChanged (ListViewVirtualItemsSelectionRangeChangedEventArgs e) + { + ListViewVirtualItemsSelectionRangeChangedEventHandler eh = + (ListViewVirtualItemsSelectionRangeChangedEventHandler) Events [VirtualItemsSelectionRangeChangedEvent]; + if (eh != null) + eh (this, e); + } + + protected void RealizeProperties () + { + // FIXME: TODO + } + + protected void UpdateExtendedStyles () + { + // FIXME: TODO + } + + bool refocusing = false; + + protected override void WndProc (ref Message m) + { + switch ((Msg)m.Msg) { + case Msg.WM_KILLFOCUS: + Widget receiver = Widget.FromHandle (m.WParam); + if (receiver == item_control) { + has_focus = false; + refocusing = true; + return; + } + break; + case Msg.WM_SETFOCUS: + if (refocusing) { + has_focus = true; + refocusing = false; + return; + } + break; + default: + break; + } + base.WndProc (ref m); + } + #endregion // Protected Methods + + #region Public Instance Methods + public void ArrangeIcons () + { + ArrangeIcons (this.alignment); + } + + public void ArrangeIcons (ListViewAlignment value) + { + // Icons are arranged only if view is set to LargeIcon or SmallIcon + if (view == View.LargeIcon || view == View.SmallIcon) + Redraw (true); + } + + public void AutoResizeColumn (int columnIndex, ColumnHeaderAutoResizeStyle headerAutoResize) + { + if (columnIndex < 0 || columnIndex >= columns.Count) + throw new ArgumentOutOfRangeException ("columnIndex"); + + columns [columnIndex].AutoResize (headerAutoResize); + } + + public void AutoResizeColumns (ColumnHeaderAutoResizeStyle headerAutoResize) + { + BeginUpdate (); + foreach (ColumnHeader col in columns) + col.AutoResize (headerAutoResize); + EndUpdate (); + } + + public void BeginUpdate () + { + // flag to avoid painting + updating = true; + } + + public void Clear () + { + columns.Clear (); + items.Clear (); // Redraw (true) called here + } + + public void EndUpdate () + { + // flag to avoid painting + updating = false; + + // probably, now we need a redraw with recalculations + this.Redraw (true); + } + + public void EnsureVisible (int index) + { + if (index < 0 || index >= items.Count || scrollable == false || updating) + return; + + Rectangle view_rect = item_control.ClientRectangle; + // Avoid direct access to items in virtual mode, and use item bounds otherwise, since we could have reordered items + Rectangle bounds = virtual_mode ? new Rectangle (GetItemLocation (index), ItemSize) : items [index].Bounds; + + if (view == View.Details && header_style != ColumnHeaderStyle.None) { + view_rect.Y += header_control.Height; + view_rect.Height -= header_control.Height; + } + + if (view_rect.Contains (bounds)) + return; + + if (View != View.Details) { + if (bounds.Left < 0) + h_scroll.Value += bounds.Left; + // Don't shift right unless right-to-left layout is active. (Xamarin bug 22483) + else if (this.RightToLeftLayout && bounds.Right > view_rect.Right) + h_scroll.Value += (bounds.Right - view_rect.Right); + } + + if (bounds.Top < view_rect.Y) + v_scroll.Value += bounds.Top - view_rect.Y; + else if (bounds.Bottom > view_rect.Bottom) + v_scroll.Value += (bounds.Bottom - view_rect.Bottom); + } + + public ListViewItem FindItemWithText (string text) + { + if (items.Count == 0) + return null; + + return FindItemWithText (text, true, 0, true); + } + + public ListViewItem FindItemWithText (string text, bool includeSubItemsInSearch, int startIndex) + { + return FindItemWithText (text, includeSubItemsInSearch, startIndex, true, false); + } + + public ListViewItem FindItemWithText (string text, bool includeSubItemsInSearch, int startIndex, bool isPrefixSearch) + { + return FindItemWithText (text, includeSubItemsInSearch, startIndex, isPrefixSearch, false); + } + + internal ListViewItem FindItemWithText (string text, bool includeSubItemsInSearch, int startIndex, bool isPrefixSearch, bool roundtrip) + { + if (startIndex < 0 || startIndex >= items.Count) + throw new ArgumentOutOfRangeException ("startIndex"); + + if (text == null) + throw new ArgumentNullException ("text"); + + if (virtual_mode) { + SearchForVirtualItemEventArgs args = new SearchForVirtualItemEventArgs (true, + isPrefixSearch, includeSubItemsInSearch, text, Point.Empty, + SearchDirectionHint.Down, startIndex); + + OnSearchForVirtualItem (args); + int idx = args.Index; + if (idx >= 0 && idx < virtual_list_size) + return items [idx]; + + return null; + } + + int i = startIndex; + while (true) { + ListViewItem lvi = items [i]; + + if (isPrefixSearch) { // prefix search + if (CultureInfo.CurrentCulture.CompareInfo.IsPrefix (lvi.Text, text, CompareOptions.IgnoreCase)) + return lvi; + } else if (String.Compare (lvi.Text, text, true) == 0) // match + return lvi; + + if (i + 1 >= items.Count) { + if (!roundtrip) + break; + + i = 0; + } else + i++; + + if (i == startIndex) + break; + } + + // Subitems have a minor priority, so we have to do a second linear search + // Also, we don't need to to a roundtrip search for them by now + if (includeSubItemsInSearch) { + for (i = startIndex; i < items.Count; i++) { + ListViewItem lvi = items [i]; + foreach (ListViewItem.ListViewSubItem sub_item in lvi.SubItems) + if (isPrefixSearch) { + if (CultureInfo.CurrentCulture.CompareInfo.IsPrefix (sub_item.Text, + text, CompareOptions.IgnoreCase)) + return lvi; + } else if (String.Compare (sub_item.Text, text, true) == 0) + return lvi; + } + } + + return null; + } + + public ListViewItem FindNearestItem (SearchDirectionHint searchDirection, int x, int y) + { + return FindNearestItem (searchDirection, new Point (x, y)); + } + + public ListViewItem FindNearestItem (SearchDirectionHint dir, Point point) + { + if (dir < SearchDirectionHint.Left || dir > SearchDirectionHint.Down) + throw new ArgumentOutOfRangeException ("searchDirection"); + + if (view != View.LargeIcon && view != View.SmallIcon) + throw new InvalidOperationException (); + + if (virtual_mode) { + SearchForVirtualItemEventArgs args = new SearchForVirtualItemEventArgs (false, + false, false, String.Empty, point, + dir, 0); + + OnSearchForVirtualItem (args); + int idx = args.Index; + if (idx >= 0 && idx < virtual_list_size) + return items [idx]; + + return null; + } + + ListViewItem item = null; + int min_dist = Int32.MaxValue; + + // + // It looks like .Net does a previous adjustment + // + switch (dir) { + case SearchDirectionHint.Up: + point.Y -= item_size.Height; + break; + case SearchDirectionHint.Down: + point.Y += item_size.Height; + break; + case SearchDirectionHint.Left: + point.X -= item_size.Width; + break; + case SearchDirectionHint.Right: + point.X += item_size.Width; + break; + } + + for (int i = 0; i < items.Count; i++) { + Point item_loc = GetItemLocation (i); + + if (dir == SearchDirectionHint.Up) { + if (point.Y < item_loc.Y) + continue; + } else if (dir == SearchDirectionHint.Down) { + if (point.Y > item_loc.Y) + continue; + } else if (dir == SearchDirectionHint.Left) { + if (point.X < item_loc.X) + continue; + } else if (dir == SearchDirectionHint.Right) { + if (point.X > item_loc.X) + continue; + } + + int x_dist = point.X - item_loc.X; + int y_dist = point.Y - item_loc.Y; + + int dist = x_dist * x_dist + y_dist * y_dist; + if (dist < min_dist) { + item = items [i]; + min_dist = dist; + } + } + + return item; + } + + public ListViewItem GetItemAt (int x, int y) + { + Size item_size = ItemSize; + for (int i = 0; i < items.Count; i++) { + Rectangle item_rect = items [i].Bounds; + if (item_rect.Contains (x, y)) + return items [i]; + } + + return null; + } + + public Rectangle GetItemRect (int index) + { + return GetItemRect (index, ItemBoundsPortion.Entire); + } + + public Rectangle GetItemRect (int index, ItemBoundsPortion portion) + { + if (index < 0 || index >= items.Count) + throw new IndexOutOfRangeException ("index"); + + return items [index].GetBounds (portion); + } + + public ListViewHitTestInfo HitTest (Point point) + { + return HitTest (point.X, point.Y); + } + + public ListViewHitTestInfo HitTest (int x, int y) + { + if (x < 0) + throw new ArgumentOutOfRangeException ("x"); + if (y < 0) + throw new ArgumentOutOfRangeException ("y"); + + ListViewItem item = GetItemAt (x, y); + if (item == null) + return new ListViewHitTestInfo (null, null, ListViewHitTestLocations.None); + + ListViewHitTestLocations locations = 0; + if (item.GetBounds (ItemBoundsPortion.Label).Contains (x, y)) + locations |= ListViewHitTestLocations.Label; + else if (item.GetBounds (ItemBoundsPortion.Icon).Contains (x, y)) + locations |= ListViewHitTestLocations.Image; + else if (item.CheckRectReal.Contains (x, y)) + locations |= ListViewHitTestLocations.StateImage; + + ListViewItem.ListViewSubItem subitem = null; + if (view == View.Details) + foreach (ListViewItem.ListViewSubItem si in item.SubItems) + if (si.Bounds.Contains (x, y)) { + subitem = si; + break; + } + + return new ListViewHitTestInfo (item, subitem, locations); + } + + //[EditorBrowsable (EditorBrowsableState.Advanced)] + public void RedrawItems (int startIndex, int endIndex, bool invalidateOnly) + { + if (startIndex < 0 || startIndex >= items.Count) + throw new ArgumentOutOfRangeException ("startIndex"); + if (endIndex < 0 || endIndex >= items.Count) + throw new ArgumentOutOfRangeException ("endIndex"); + if (startIndex > endIndex) + throw new ArgumentException ("startIndex"); + + if (updating) + return; + + for (int i = startIndex; i <= endIndex; i++) + items [i].Invalidate (); + + if (!invalidateOnly) + Update (); + } + + public void Sort () + { + if (virtual_mode) + throw new InvalidOperationException (); + + Sort (true); + } + + // we need this overload to reuse the logic for sorting, while allowing + // redrawing to be done by caller or have it done by this method when + // sorting is really performed + // + // ListViewItemCollection's Add and AddRange methods call this overload + // with redraw set to false, as they take care of redrawing themselves + // (they even want to redraw the listview if no sort is performed, as + // an item was added), while ListView.Sort () only wants to redraw if + // sorting was actually performed + private void Sort (bool redraw) + { + if (!IsHandleCreated || item_sorter == null) { + return; + } + + items.Sort (item_sorter); + if (redraw) + this.Redraw (true); + } + + public override string ToString () + { + int count = this.Items.Count; + + if (count == 0) + return string.Format ("ShiftUI.ListView, Items.Count: 0"); + else + return string.Format ("ShiftUI.ListView, Items.Count: {0}, Items[0]: {1}", count, this.Items [0].ToString ()); + } + #endregion // Public Instance Methods + + + #region Subclasses + + internal class HeaderControl : Widget { + + ListView owner; + bool column_resize_active = false; + ColumnHeader resize_column; + ColumnHeader clicked_column; + ColumnHeader drag_column; + int drag_x; + int drag_to_index = -1; + ColumnHeader entered_column_header; + + public HeaderControl (ListView owner) + { + this.owner = owner; + this.SetStyle (Widgetstyles.DoubleBuffer, true); + MouseDown += new MouseEventHandler (HeaderMouseDown); + MouseMove += new MouseEventHandler (HeaderMouseMove); + MouseUp += new MouseEventHandler (HeaderMouseUp); + MouseLeave += new EventHandler (OnMouseLeave); + } + + internal ColumnHeader EnteredColumnHeader { + get { return entered_column_header; } + private set { + if (entered_column_header == value) + return; + if (ThemeEngine.Current.ListViewHasHotHeaderStyle) { + Region region_to_invalidate = new Region (); + region_to_invalidate.MakeEmpty (); + if (entered_column_header != null) + region_to_invalidate.Union (GetColumnHeaderInvalidateArea (entered_column_header)); + entered_column_header = value; + if (entered_column_header != null) + region_to_invalidate.Union (GetColumnHeaderInvalidateArea (entered_column_header)); + Invalidate (region_to_invalidate); + region_to_invalidate.Dispose (); + } else + entered_column_header = value; + } + } + + void OnMouseLeave (object sender, EventArgs e) + { + EnteredColumnHeader = null; + } + + private ColumnHeader ColumnAtX (int x) + { + Point pt = new Point (x, 0); + ColumnHeader result = null; + foreach (ColumnHeader col in owner.Columns) { + if (col.Rect.Contains (pt)) { + result = col; + break; + } + } + return result; + } + + private int GetReorderedIndex (ColumnHeader col) + { + if (owner.reordered_column_indices == null) + return col.Index; + else + for (int i = 0; i < owner.Columns.Count; i++) + if (owner.reordered_column_indices [i] == col.Index) + return i; + throw new Exception ("Column index missing from reordered array"); + } + + private void HeaderMouseDown (object sender, MouseEventArgs me) + { + if (resize_column != null) { + column_resize_active = true; + Capture = true; + return; + } + + clicked_column = ColumnAtX (me.X + owner.h_marker); + + if (clicked_column != null) { + Capture = true; + if (owner.AllowColumnReorder) { + drag_x = me.X; + drag_column = (ColumnHeader) (clicked_column as ICloneable).Clone (); + drag_column.Rect = clicked_column.Rect; + drag_to_index = GetReorderedIndex (clicked_column); + } + clicked_column.Pressed = true; + Invalidate (clicked_column); + return; + } + } + + void Invalidate (ColumnHeader columnHeader) + { + Invalidate (GetColumnHeaderInvalidateArea (columnHeader)); + } + + Rectangle GetColumnHeaderInvalidateArea (ColumnHeader columnHeader) + { + Rectangle bounds = columnHeader.Rect; + bounds.X -= owner.h_marker; + return bounds; + } + + void StopResize () + { + column_resize_active = false; + resize_column = null; + Capture = false; + Cursor = Cursors.Default; + } + + private void HeaderMouseMove (object sender, MouseEventArgs me) + { + Point pt = new Point (me.X + owner.h_marker, me.Y); + + if (column_resize_active) { + int width = pt.X - resize_column.X; + if (width < 0) + width = 0; + + if (!owner.CanProceedWithResize (resize_column, width)){ + StopResize (); + return; + } + resize_column.Width = width; + return; + } + + resize_column = null; + + if (clicked_column != null) { + if (owner.AllowColumnReorder) { + Rectangle r; + + r = drag_column.Rect; + r.X = clicked_column.Rect.X + me.X - drag_x; + drag_column.Rect = r; + + int x = me.X + owner.h_marker; + ColumnHeader over = ColumnAtX (x); + if (over == null) + drag_to_index = owner.Columns.Count; + else if (x < over.X + over.Width / 2) + drag_to_index = GetReorderedIndex (over); + else + drag_to_index = GetReorderedIndex (over) + 1; + Invalidate (); + } else { + ColumnHeader over = ColumnAtX (me.X + owner.h_marker); + bool pressed = clicked_column.Pressed; + clicked_column.Pressed = over == clicked_column; + if (clicked_column.Pressed ^ pressed) + Invalidate (clicked_column); + } + return; + } + + for (int i = 0; i < owner.Columns.Count; i++) { + Rectangle zone = owner.Columns [i].Rect; + if (zone.Contains (pt)) + EnteredColumnHeader = owner.Columns [i]; + zone.X = zone.Right - 5; + zone.Width = 10; + if (zone.Contains (pt)) { + if (i < owner.Columns.Count - 1 && owner.Columns [i + 1].Width == 0) + i++; + resize_column = owner.Columns [i]; + break; + } + } + + if (resize_column == null) + Cursor = Cursors.Default; + else + Cursor = Cursors.VSplit; + } + + void HeaderMouseUp (object sender, MouseEventArgs me) + { + Capture = false; + + if (column_resize_active) { + int column_idx = resize_column.Index; + StopResize (); + owner.RaiseColumnWidthChanged (column_idx); + return; + } + + if (clicked_column != null && clicked_column.Pressed) { + clicked_column.Pressed = false; + Invalidate (clicked_column); + owner.OnColumnClick (new ColumnClickEventArgs (clicked_column.Index)); + } + + if (drag_column != null && owner.AllowColumnReorder) { + drag_column = null; + if (drag_to_index > GetReorderedIndex (clicked_column)) + drag_to_index--; + if (owner.GetReorderedColumn (drag_to_index) != clicked_column) + owner.ReorderColumn (clicked_column, drag_to_index, true); + drag_to_index = -1; + Invalidate (); + } + + clicked_column = null; + } + + internal override void OnPaintInternal (PaintEventArgs pe) + { + if (owner.updating) + return; + + Theme theme = ThemeEngine.Current; + theme.DrawListViewHeader (pe.Graphics, pe.ClipRectangle, this.owner); + + if (drag_column == null) + return; + + int target_x; + if (drag_to_index == owner.Columns.Count) + target_x = owner.GetReorderedColumn (drag_to_index - 1).Rect.Right - owner.h_marker; + else + target_x = owner.GetReorderedColumn (drag_to_index).Rect.X - owner.h_marker; + theme.DrawListViewHeaderDragDetails (pe.Graphics, owner, drag_column, target_x); + } + + protected override void WndProc (ref Message m) + { + switch ((Msg)m.Msg) { + case Msg.WM_SETFOCUS: + owner.Focus (); + break; + default: + base.WndProc (ref m); + break; + } + } + } + + private class ItemComparer : IComparer { + readonly SortOrder sort_order; + + public ItemComparer (SortOrder sortOrder) + { + sort_order = sortOrder; + } + + public int Compare (object x, object y) + { + ListViewItem item_x = x as ListViewItem; + ListViewItem item_y = y as ListViewItem; + if (sort_order == SortOrder.Ascending) + return String.Compare (item_x.Text, item_y.Text); + else + return String.Compare (item_y.Text, item_x.Text); + } + } + + [ListBindable (false)] + public class CheckedIndexCollection : IList, ICollection, IEnumerable + { + private readonly ListView owner; + + #region Public Constructor + public CheckedIndexCollection (ListView owner) + { + this.owner = owner; + } + #endregion // Public Constructor + + #region Public Properties + [Browsable (false)] + public int Count { + get { return owner.CheckedItems.Count; } + } + + public bool IsReadOnly { + get { return true; } + } + + public int this [int index] { + get { + int [] indices = GetIndices (); + if (index < 0 || index >= indices.Length) + throw new ArgumentOutOfRangeException ("index"); + return indices [index]; + } + } + + bool ICollection.IsSynchronized { + get { return false; } + } + + object ICollection.SyncRoot { + get { return this; } + } + + bool IList.IsFixedSize { + get { return true; } + } + + object IList.this [int index] { + get { return this [index]; } + set { throw new NotSupportedException ("SetItem operation is not supported."); } + } + #endregion // Public Properties + + #region Public Methods + public bool Contains (int checkedIndex) + { + int [] indices = GetIndices (); + for (int i = 0; i < indices.Length; i++) { + if (indices [i] == checkedIndex) + return true; + } + return false; + } + + public IEnumerator GetEnumerator () + { + int [] indices = GetIndices (); + return indices.GetEnumerator (); + } + + void ICollection.CopyTo (Array dest, int index) + { + int [] indices = GetIndices (); + Array.Copy (indices, 0, dest, index, indices.Length); + } + + int IList.Add (object value) + { + throw new NotSupportedException ("Add operation is not supported."); + } + + void IList.Clear () + { + throw new NotSupportedException ("Clear operation is not supported."); + } + + bool IList.Contains (object checkedIndex) + { + if (!(checkedIndex is int)) + return false; + return Contains ((int) checkedIndex); + } + + int IList.IndexOf (object checkedIndex) + { + if (!(checkedIndex is int)) + return -1; + return IndexOf ((int) checkedIndex); + } + + void IList.Insert (int index, object value) + { + throw new NotSupportedException ("Insert operation is not supported."); + } + + void IList.Remove (object value) + { + throw new NotSupportedException ("Remove operation is not supported."); + } + + void IList.RemoveAt (int index) + { + throw new NotSupportedException ("RemoveAt operation is not supported."); + } + + public int IndexOf (int checkedIndex) + { + int [] indices = GetIndices (); + for (int i = 0; i < indices.Length; i++) { + if (indices [i] == checkedIndex) + return i; + } + return -1; + } + #endregion // Public Methods + + private int [] GetIndices () + { + ArrayList checked_items = owner.CheckedItems.List; + int [] indices = new int [checked_items.Count]; + for (int i = 0; i < checked_items.Count; i++) { + ListViewItem item = (ListViewItem) checked_items [i]; + indices [i] = item.Index; + } + return indices; + } + } // CheckedIndexCollection + + [ListBindable (false)] + public class CheckedListViewItemCollection : IList, ICollection, IEnumerable + { + private readonly ListView owner; + private ArrayList list; + + #region Public Constructor + public CheckedListViewItemCollection (ListView owner) + { + this.owner = owner; + this.owner.Items.Changed += new CollectionChangedHandler ( + ItemsCollection_Changed); + } + #endregion // Public Constructor + + #region Public Properties + [Browsable (false)] + public int Count { + get { + if (!owner.CheckBoxes) + return 0; + return List.Count; + } + } + + public bool IsReadOnly { + get { return true; } + } + + public ListViewItem this [int index] { + get { + if (owner.VirtualMode) + throw new InvalidOperationException (); + ArrayList checked_items = List; + if (index < 0 || index >= checked_items.Count) + throw new ArgumentOutOfRangeException ("index"); + return (ListViewItem) checked_items [index]; + } + } + + public virtual ListViewItem this [string key] { + get { + int idx = IndexOfKey (key); + return idx == -1 ? null : (ListViewItem) List [idx]; + } + } + + bool ICollection.IsSynchronized { + get { return false; } + } + + object ICollection.SyncRoot { + get { return this; } + } + + bool IList.IsFixedSize { + get { return true; } + } + + object IList.this [int index] { + get { return this [index]; } + set { throw new NotSupportedException ("SetItem operation is not supported."); } + } + #endregion // Public Properties + + #region Public Methods + public bool Contains (ListViewItem item) + { + if (!owner.CheckBoxes) + return false; + return List.Contains (item); + } + + public virtual bool ContainsKey (string key) + { + return IndexOfKey (key) != -1; + } + + public void CopyTo (Array dest, int index) + { + if (owner.VirtualMode) + throw new InvalidOperationException (); + if (!owner.CheckBoxes) + return; + List.CopyTo (dest, index); + } + + public IEnumerator GetEnumerator () + { + if (owner.VirtualMode) + throw new InvalidOperationException (); + if (!owner.CheckBoxes) + return (new ListViewItem [0]).GetEnumerator (); + return List.GetEnumerator (); + } + + int IList.Add (object value) + { + throw new NotSupportedException ("Add operation is not supported."); + } + + void IList.Clear () + { + throw new NotSupportedException ("Clear operation is not supported."); + } + + bool IList.Contains (object item) + { + if (!(item is ListViewItem)) + return false; + return Contains ((ListViewItem) item); + } + + int IList.IndexOf (object item) + { + if (!(item is ListViewItem)) + return -1; + return IndexOf ((ListViewItem) item); + } + + void IList.Insert (int index, object value) + { + throw new NotSupportedException ("Insert operation is not supported."); + } + + void IList.Remove (object value) + { + throw new NotSupportedException ("Remove operation is not supported."); + } + + void IList.RemoveAt (int index) + { + throw new NotSupportedException ("RemoveAt operation is not supported."); + } + + public int IndexOf (ListViewItem item) + { + if (owner.VirtualMode) + throw new InvalidOperationException (); + if (!owner.CheckBoxes) + return -1; + return List.IndexOf (item); + } + + public virtual int IndexOfKey (string key) + { + if (owner.VirtualMode) + throw new InvalidOperationException (); + if (key == null || key.Length == 0) + return -1; + + ArrayList checked_items = List; + for (int i = 0; i < checked_items.Count; i++) { + ListViewItem item = (ListViewItem) checked_items [i]; + if (String.Compare (key, item.Name, true) == 0) + return i; + } + + return -1; + } + #endregion // Public Methods + + internal ArrayList List { + get { + if (list == null) { + list = new ArrayList (); + foreach (ListViewItem item in owner.Items) { + if (item.Checked) + list.Add (item); + } + } + return list; + } + } + + internal void Reset () + { + // force re-population of list + list = null; + } + + private void ItemsCollection_Changed () + { + Reset (); + } + } // CheckedListViewItemCollection + + [ListBindable (false)] + public class ColumnHeaderCollection : IList, ICollection, IEnumerable + { + internal ArrayList list; + private ListView owner; + + #region UIA Framework Events + //NOTE: + // We are using Reflection to add/remove internal events. + // Class ListViewProvider uses the events when View is Details. + // + //Event used to generate UIA StructureChangedEvent + static object UIACollectionChangedEvent = new object (); + + internal event CollectionChangeEventHandler UIACollectionChanged { + add { + if (owner != null) + owner.Events.AddHandler (UIACollectionChangedEvent, value); + } + remove { + if (owner != null) + owner.Events.RemoveHandler (UIACollectionChangedEvent, value); + } + } + + internal void OnUIACollectionChangedEvent (CollectionChangeEventArgs args) + { + if (owner == null) + return; + + CollectionChangeEventHandler eh + = (CollectionChangeEventHandler) owner.Events [UIACollectionChangedEvent]; + if (eh != null) + eh (owner, args); + } + + #endregion UIA Framework Events + + #region Public Constructor + public ColumnHeaderCollection (ListView owner) + { + list = new ArrayList (); + this.owner = owner; + } + #endregion // Public Constructor + + #region Public Properties + [Browsable (false)] + public int Count { + get { return list.Count; } + } + + public bool IsReadOnly { + get { return false; } + } + + public virtual ColumnHeader this [int index] { + get { + if (index < 0 || index >= list.Count) + throw new ArgumentOutOfRangeException ("index"); + return (ColumnHeader) list [index]; + } + } + + public virtual ColumnHeader this [string key] { + get { + int idx = IndexOfKey (key); + if (idx == -1) + return null; + + return (ColumnHeader) list [idx]; + } + } + + bool ICollection.IsSynchronized { + get { return true; } + } + + object ICollection.SyncRoot { + get { return this; } + } + + bool IList.IsFixedSize { + get { return list.IsFixedSize; } + } + + object IList.this [int index] { + get { return this [index]; } + set { throw new NotSupportedException ("SetItem operation is not supported."); } + } + #endregion // Public Properties + + #region Public Methods + public virtual int Add (ColumnHeader value) + { + int idx = list.Add (value); + owner.AddColumn (value, idx, true); + + //UIA Framework event: Item Added + OnUIACollectionChangedEvent (new CollectionChangeEventArgs (CollectionChangeAction.Add, value)); + + return idx; + } + + public virtual ColumnHeader Add (string text, int width, HorizontalAlignment textAlign) + { + string str = text; + ColumnHeader colHeader = new ColumnHeader (this.owner, str, textAlign, width); + this.Add (colHeader); + return colHeader; + } + + public virtual ColumnHeader Add (string text) + { + return Add (String.Empty, text); + } + + public virtual ColumnHeader Add (string text, int width) + { + return Add (String.Empty, text, width); + } + + public virtual ColumnHeader Add (string key, string text) + { + ColumnHeader colHeader = new ColumnHeader (); + colHeader.Name = key; + colHeader.Text = text; + Add (colHeader); + return colHeader; + } + + public virtual ColumnHeader Add (string key, string text, int width) + { + return Add (key, text, width, HorizontalAlignment.Left, -1); + } + + public virtual ColumnHeader Add (string key, string text, int width, HorizontalAlignment textAlign, int imageIndex) + { + ColumnHeader colHeader = new ColumnHeader (key, text, width, textAlign); + colHeader.ImageIndex = imageIndex; + Add (colHeader); + return colHeader; + } + + public virtual ColumnHeader Add (string key, string text, int width, HorizontalAlignment textAlign, string imageKey) + { + ColumnHeader colHeader = new ColumnHeader (key, text, width, textAlign); + colHeader.ImageKey = imageKey; + Add (colHeader); + return colHeader; + } + + public virtual void AddRange (ColumnHeader [] values) + { + foreach (ColumnHeader colHeader in values) { + int idx = list.Add (colHeader); + owner.AddColumn (colHeader, idx, false); + } + + owner.Redraw (true); + } + + public virtual void Clear () + { + foreach (ColumnHeader col in list) + col.SetListView (null); + list.Clear (); + owner.ReorderColumns (new int [0], true); + + //UIA Framework event: Items cleared + OnUIACollectionChangedEvent (new CollectionChangeEventArgs (CollectionChangeAction.Refresh, null)); + + } + + public bool Contains (ColumnHeader value) + { + return list.Contains (value); + } + + public virtual bool ContainsKey (string key) + { + return IndexOfKey (key) != -1; + } + + public IEnumerator GetEnumerator () + { + return list.GetEnumerator (); + } + + void ICollection.CopyTo (Array dest, int index) + { + list.CopyTo (dest, index); + } + + int IList.Add (object value) + { + if (! (value is ColumnHeader)) { + throw new ArgumentException ("Not of type ColumnHeader", "value"); + } + + return this.Add ((ColumnHeader) value); + } + + bool IList.Contains (object value) + { + if (! (value is ColumnHeader)) { + throw new ArgumentException ("Not of type ColumnHeader", "value"); + } + + return this.Contains ((ColumnHeader) value); + } + + int IList.IndexOf (object value) + { + if (! (value is ColumnHeader)) { + throw new ArgumentException ("Not of type ColumnHeader", "value"); + } + + return this.IndexOf ((ColumnHeader) value); + } + + void IList.Insert (int index, object value) + { + if (! (value is ColumnHeader)) { + throw new ArgumentException ("Not of type ColumnHeader", "value"); + } + + this.Insert (index, (ColumnHeader) value); + } + + void IList.Remove (object value) + { + if (! (value is ColumnHeader)) { + throw new ArgumentException ("Not of type ColumnHeader", "value"); + } + + this.Remove ((ColumnHeader) value); + } + + public int IndexOf (ColumnHeader value) + { + return list.IndexOf (value); + } + + public virtual int IndexOfKey (string key) + { + if (key == null || key.Length == 0) + return -1; + + for (int i = 0; i < list.Count; i++) { + ColumnHeader col = (ColumnHeader) list [i]; + if (String.Compare (key, col.Name, true) == 0) + return i; + } + + return -1; + } + + public void Insert (int index, ColumnHeader value) + { + // LAMESPEC: MSDOCS say greater than or equal to the value of the Count property + // but it's really only greater. + if (index < 0 || index > list.Count) + throw new ArgumentOutOfRangeException ("index"); + + list.Insert (index, value); + owner.AddColumn (value, index, true); + + //UIA Framework event: Item added + OnUIACollectionChangedEvent (new CollectionChangeEventArgs (CollectionChangeAction.Add, value)); + } + + public void Insert (int index, string text) + { + Insert (index, String.Empty, text); + } + + public void Insert (int index, string text, int width) + { + Insert (index, String.Empty, text, width); + } + + public void Insert (int index, string key, string text) + { + ColumnHeader colHeader = new ColumnHeader (); + colHeader.Name = key; + colHeader.Text = text; + Insert (index, colHeader); + } + + public void Insert (int index, string key, string text, int width) + { + ColumnHeader colHeader = new ColumnHeader (key, text, width, HorizontalAlignment.Left); + Insert (index, colHeader); + } + + public void Insert (int index, string key, string text, int width, HorizontalAlignment textAlign, int imageIndex) + { + ColumnHeader colHeader = new ColumnHeader (key, text, width, textAlign); + colHeader.ImageIndex = imageIndex; + Insert (index, colHeader); + } + + public void Insert (int index, string key, string text, int width, HorizontalAlignment textAlign, string imageKey) + { + ColumnHeader colHeader = new ColumnHeader (key, text, width, textAlign); + colHeader.ImageKey = imageKey; + Insert (index, colHeader); + } + + public void Insert (int index, string text, int width, HorizontalAlignment textAlign) + { + string str = text; + ColumnHeader colHeader = new ColumnHeader (this.owner, str, textAlign, width); + this.Insert (index, colHeader); + } + + public virtual void Remove (ColumnHeader column) + { + if (!Contains (column)) + return; + + list.Remove (column); + column.SetListView (null); + + int rem_display_index = column.InternalDisplayIndex; + int [] display_indices = new int [list.Count]; + for (int i = 0; i < display_indices.Length; i++) { + ColumnHeader col = (ColumnHeader) list [i]; + int display_index = col.InternalDisplayIndex; + if (display_index < rem_display_index) { + display_indices [i] = display_index; + } else { + display_indices [i] = (display_index - 1); + } + } + + column.InternalDisplayIndex = -1; + owner.ReorderColumns (display_indices, true); + + //UIA Framework event: Item Removed + OnUIACollectionChangedEvent (new CollectionChangeEventArgs (CollectionChangeAction.Remove, column)); + } + + public virtual void RemoveByKey (string key) + { + int idx = IndexOfKey (key); + if (idx != -1) + RemoveAt (idx); + } + + public virtual void RemoveAt (int index) + { + if (index < 0 || index >= list.Count) + throw new ArgumentOutOfRangeException ("index"); + + ColumnHeader col = (ColumnHeader) list [index]; + Remove (col); + } + #endregion // Public Methods + + + } // ColumnHeaderCollection + + [ListBindable (false)] + public class ListViewItemCollection : IList, ICollection, IEnumerable + { + private readonly ArrayList list; + private ListView owner; + private ListViewGroup group; + + #region UIA Framework Events + //NOTE: + // We are using Reflection to add/remove internal events. + // Class ListViewProvider uses the events. + // + //Event used to generate UIA StructureChangedEvent + static object UIACollectionChangedEvent = new object (); + + internal event CollectionChangeEventHandler UIACollectionChanged { + add { + if (owner != null) + owner.Events.AddHandler (UIACollectionChangedEvent, value); + } + remove { + if (owner != null) + owner.Events.RemoveHandler (UIACollectionChangedEvent, value); + } + } + + internal void OnUIACollectionChangedEvent (CollectionChangeEventArgs args) + { + if (owner == null) + return; + + CollectionChangeEventHandler eh + = (CollectionChangeEventHandler) owner.Events [UIACollectionChangedEvent]; + if (eh != null) + eh (owner, args); + } + + #endregion UIA Framework Events + + // The collection can belong to a ListView (main) or to a ListViewGroup (sub-collection) + // In the later case ListViewItem.ListView never gets modified + private bool is_main_collection = true; + + #region Public Constructor + public ListViewItemCollection (ListView owner) + { + list = new ArrayList (0); + this.owner = owner; + } + #endregion // Public Constructor + + internal ListViewItemCollection (ListView owner, ListViewGroup group) : this (owner) + { + this.group = group; + is_main_collection = false; + } + + #region Public Properties + [Browsable (false)] + public int Count { + get { + if (owner != null && owner.VirtualMode) + return owner.VirtualListSize; + + return list.Count; + } + } + + public bool IsReadOnly { + get { return false; } + } + + public virtual ListViewItem this [int index] { + get { + if (index < 0 || index >= Count) + throw new ArgumentOutOfRangeException ("index"); + + if (owner != null && owner.VirtualMode) + return RetrieveVirtualItemFromOwner (index); + return (ListViewItem) list [index]; + } + + set { + if (index < 0 || index >= Count) + throw new ArgumentOutOfRangeException ("index"); + + if (owner != null && owner.VirtualMode) + throw new InvalidOperationException (); + + if (list.Contains (value)) + throw new ArgumentException ("An item cannot be added more than once. To add an item again, you need to clone it.", "value"); + + if (value.ListView != null && value.ListView != owner) + throw new ArgumentException ("Cannot add or insert the item '" + value.Text + "' in more than one place. You must first remove it from its current location or clone it.", "value"); + + if (is_main_collection) + value.Owner = owner; + else { + if (value.Group != null) + value.Group.Items.Remove (value); + + value.SetGroup (group); + } + + //UIA Framework event: Item Replaced + OnUIACollectionChangedEvent (new CollectionChangeEventArgs (CollectionChangeAction.Remove, list [index])); + + list [index] = value; + + CollectionChanged (true); + + //UIA Framework event: Item Replaced + OnUIACollectionChangedEvent (new CollectionChangeEventArgs (CollectionChangeAction.Add, value)); + + } + } + + public virtual ListViewItem this [string key] { + get { + int idx = IndexOfKey (key); + if (idx == -1) + return null; + + return this [idx]; + } + } + + bool ICollection.IsSynchronized { + get { return true; } + } + + object ICollection.SyncRoot { + get { return this; } + } + + bool IList.IsFixedSize { + get { return list.IsFixedSize; } + } + + object IList.this [int index] { + get { return this [index]; } + set { + //UIA Framework event: Item Replaced + OnUIACollectionChangedEvent (new CollectionChangeEventArgs (CollectionChangeAction.Remove, this [index])); + + if (value is ListViewItem) + this [index] = (ListViewItem) value; + else + this [index] = new ListViewItem (value.ToString ()); + + OnChange (); + //UIA Framework event: Item Replaced + OnUIACollectionChangedEvent (new CollectionChangeEventArgs (CollectionChangeAction.Add, value)); + } + } + #endregion // Public Properties + + #region Public Methods + public virtual ListViewItem Add (ListViewItem value) + { + if (owner != null && owner.VirtualMode) + throw new InvalidOperationException (); + + AddItem (value); + + // Item is ignored until it has been added to the ListView + if (is_main_collection || value.ListView != null) + CollectionChanged (true); + + //UIA Framework event: Item Added + OnUIACollectionChangedEvent (new CollectionChangeEventArgs (CollectionChangeAction.Add, value)); + + return value; + } + + public virtual ListViewItem Add (string text) + { + ListViewItem item = new ListViewItem (text); + return this.Add (item); + } + + public virtual ListViewItem Add (string text, int imageIndex) + { + ListViewItem item = new ListViewItem (text, imageIndex); + return this.Add (item); + } + + public virtual ListViewItem Add (string text, string imageKey) + { + ListViewItem item = new ListViewItem (text, imageKey); + return this.Add (item); + } + + public virtual ListViewItem Add (string key, string text, int imageIndex) + { + ListViewItem item = new ListViewItem (text, imageIndex); + item.Name = key; + return this.Add (item); + } + + public virtual ListViewItem Add (string key, string text, string imageKey) + { + ListViewItem item = new ListViewItem (text, imageKey); + item.Name = key; + return this.Add (item); + } + + public void AddRange (ListViewItem [] items) + { + if (items == null) + throw new ArgumentNullException ("Argument cannot be null!", "items"); + if (owner != null && owner.VirtualMode) + throw new InvalidOperationException (); + + owner.BeginUpdate (); + + foreach (ListViewItem item in items) { + AddItem (item); + + //UIA Framework event: Item Added + OnUIACollectionChangedEvent (new CollectionChangeEventArgs (CollectionChangeAction.Add, item)); + } + + owner.EndUpdate (); + + CollectionChanged (true); + } + + public void AddRange (ListViewItemCollection items) + { + if (items == null) + throw new ArgumentNullException ("Argument cannot be null!", "items"); + + ListViewItem[] itemArray = new ListViewItem[items.Count]; + items.CopyTo (itemArray,0); + this.AddRange (itemArray); + } + + public virtual void Clear () + { + if (owner != null && owner.VirtualMode) + throw new InvalidOperationException (); + if (is_main_collection && owner != null) { + owner.SetFocusedItem (-1); + owner.h_scroll.Value = owner.v_scroll.Value = 0; + + // first remove any item in the groups that *are* part of this LV too + foreach (ListViewGroup group in owner.groups) + group.Items.ClearItemsWithSameListView (); + + foreach (ListViewItem item in list) { + owner.item_control.CancelEdit (item); + item.Owner = null; + } + } + else + foreach (ListViewItem item in list) + item.SetGroup (null); + + list.Clear (); + CollectionChanged (false); + + //UIA Framework event: Items Removed + OnUIACollectionChangedEvent (new CollectionChangeEventArgs (CollectionChangeAction.Refresh, null)); + + } + + // This method is intended to be used from ListViewGroup.Items, not from ListView.Items, + // added for performance reasons (avoid calling manually Remove for every item on ListViewGroup.Items) + void ClearItemsWithSameListView () + { + if (is_main_collection) + return; + + int counter = list.Count - 1; + while (counter >= 0) { + ListViewItem item = list [counter] as ListViewItem; + + // remove only if the items in group have being added to the ListView too + if (item.ListView == group.ListView) { + list.RemoveAt (counter); + item.SetGroup (null); + } + + counter--; + } + } + + public bool Contains (ListViewItem item) + { + return IndexOf (item) != -1; + } + + public virtual bool ContainsKey (string key) + { + return IndexOfKey (key) != -1; + } + + public void CopyTo (Array dest, int index) + { + list.CopyTo (dest, index); + } + + public ListViewItem [] Find (string key, bool searchAllSubItems) + { + if (key == null) + return new ListViewItem [0]; + + List<ListViewItem> temp_list = new List<ListViewItem> (); + + for (int i = 0; i < list.Count; i++) { + ListViewItem lvi = (ListViewItem) list [i]; + if (String.Compare (key, lvi.Name, true) == 0) + temp_list.Add (lvi); + } + + ListViewItem [] retval = new ListViewItem [temp_list.Count]; + temp_list.CopyTo (retval); + + return retval; + } + + public IEnumerator GetEnumerator () + { + if (owner != null && owner.VirtualMode) + throw new InvalidOperationException (); + + // This enumerator makes a copy of the collection so + // it can be deleted from in a foreach + return new Widget.WidgetCollection.WidgetCollectionEnumerator (list); + } + + int IList.Add (object item) + { + int result; + ListViewItem li; + + if (owner != null && owner.VirtualMode) + throw new InvalidOperationException (); + + if (item is ListViewItem) { + li = (ListViewItem) item; + if (list.Contains (li)) + throw new ArgumentException ("An item cannot be added more than once. To add an item again, you need to clone it.", "item"); + + if (li.ListView != null && li.ListView != owner) + throw new ArgumentException ("Cannot add or insert the item '" + li.Text + "' in more than one place. You must first remove it from its current location or clone it.", "item"); + } + else + li = new ListViewItem (item.ToString ()); + + li.Owner = owner; + + + result = list.Add (li); + CollectionChanged (true); + + //UIA Framework event: Item Added + OnUIACollectionChangedEvent (new CollectionChangeEventArgs (CollectionChangeAction.Add, li)); + + return result; + } + + bool IList.Contains (object item) + { + return Contains ((ListViewItem) item); + } + + int IList.IndexOf (object item) + { + return IndexOf ((ListViewItem) item); + } + + void IList.Insert (int index, object item) + { + if (item is ListViewItem) + this.Insert (index, (ListViewItem) item); + else + this.Insert (index, item.ToString ()); + + //UIA Framework event: Item Added + OnUIACollectionChangedEvent (new CollectionChangeEventArgs (CollectionChangeAction.Add, this [index])); + } + + void IList.Remove (object item) + { + Remove ((ListViewItem) item); + } + + public int IndexOf (ListViewItem item) + { + if (owner != null && owner.VirtualMode) { + for (int i = 0; i < Count; i++) + if (RetrieveVirtualItemFromOwner (i) == item) + return i; + + return -1; + } + + return list.IndexOf (item); + } + + public virtual int IndexOfKey (string key) + { + if (key == null || key.Length == 0) + return -1; + + for (int i = 0; i < Count; i++) { + ListViewItem lvi = this [i]; + if (String.Compare (key, lvi.Name, true) == 0) + return i; + } + + return -1; + } + + public ListViewItem Insert (int index, ListViewItem item) + { + if (index < 0 || index > list.Count) + throw new ArgumentOutOfRangeException ("index"); + + if (owner != null && owner.VirtualMode) + throw new InvalidOperationException (); + + if (list.Contains (item)) + throw new ArgumentException ("An item cannot be added more than once. To add an item again, you need to clone it.", "item"); + + if (item.ListView != null && item.ListView != owner) + throw new ArgumentException ("Cannot add or insert the item '" + item.Text + "' in more than one place. You must first remove it from its current location or clone it.", "item"); + + if (is_main_collection) + item.Owner = owner; + else { + if (item.Group != null) + item.Group.Items.Remove (item); + + item.SetGroup (group); + } + + list.Insert (index, item); + + if (is_main_collection || item.ListView != null) + CollectionChanged (true); + + // force an update of the selected info if the new item is selected. + if (item.Selected) + item.SetSelectedCore (true); + //UIA Framework event: Item Added + OnUIACollectionChangedEvent (new CollectionChangeEventArgs (CollectionChangeAction.Add, item)); + + return item; + } + + public ListViewItem Insert (int index, string text) + { + return this.Insert (index, new ListViewItem (text)); + } + + public ListViewItem Insert (int index, string text, int imageIndex) + { + return this.Insert (index, new ListViewItem (text, imageIndex)); + } + + public ListViewItem Insert (int index, string text, string imageKey) + { + ListViewItem lvi = new ListViewItem (text, imageKey); + return Insert (index, lvi); + } + + public virtual ListViewItem Insert (int index, string key, string text, int imageIndex) + { + ListViewItem lvi = new ListViewItem (text, imageIndex); + lvi.Name = key; + return Insert (index, lvi); + } + + public virtual ListViewItem Insert (int index, string key, string text, string imageKey) + { + ListViewItem lvi = new ListViewItem (text, imageKey); + lvi.Name = key; + return Insert (index, lvi); + } + + public virtual void Remove (ListViewItem item) + { + if (owner != null && owner.VirtualMode) + throw new InvalidOperationException (); + + int idx = list.IndexOf (item); + if (idx != -1) + RemoveAt (idx); + } + + public virtual void RemoveAt (int index) + { + if (index < 0 || index >= Count) + throw new ArgumentOutOfRangeException ("index"); + + if (owner != null && owner.VirtualMode) + throw new InvalidOperationException (); + + ListViewItem item = (ListViewItem) list [index]; + + bool selection_changed = false; + if (is_main_collection && owner != null) { + + int display_index = item.DisplayIndex; + if (item.Focused && display_index + 1 == Count) // Last item + owner.SetFocusedItem (display_index == 0 ? -1 : display_index - 1); + + selection_changed = owner.SelectedIndices.Contains (index); + owner.item_control.CancelEdit (item); + } + + list.RemoveAt (index); + + if (is_main_collection) { + item.Owner = null; + if (item.Group != null) + item.Group.Items.Remove (item); + } else + item.SetGroup (null); + + CollectionChanged (false); + if (selection_changed && owner != null) + owner.OnSelectedIndexChanged (EventArgs.Empty); + + + //UIA Framework event: Item Removed + OnUIACollectionChangedEvent (new CollectionChangeEventArgs (CollectionChangeAction.Remove, item)); + } + + public virtual void RemoveByKey (string key) + { + int idx = IndexOfKey (key); + if (idx != -1) + RemoveAt (idx); + } + + #endregion // Public Methods + + internal ListView Owner { + get { + return owner; + } + set { + owner = value; + } + } + + internal ListViewGroup Group { + get { + return group; + } + set { + group = value; + } + } + + void AddItem (ListViewItem value) + { + if (list.Contains (value)) + throw new ArgumentException ("An item cannot be added more than once. To add an item again, you need to clone it.", "value"); + + if (value.ListView != null && value.ListView != owner) + throw new ArgumentException ("Cannot add or insert the item '" + value.Text + "' in more than one place. You must first remove it from its current location or clone it.", "value"); + if (is_main_collection) + value.Owner = owner; + else { + if (value.Group != null) + value.Group.Items.Remove (value); + + value.SetGroup (group); + } + + list.Add (value); + + // force an update of the selected info if the new item is selected. + if (value.Selected) + value.SetSelectedCore (true); + } + + void CollectionChanged (bool sort) + { + if (owner != null) { + if (sort) + owner.Sort (false); + + OnChange (); + owner.Redraw (true); + } + } + + ListViewItem RetrieveVirtualItemFromOwner (int displayIndex) + { + RetrieveVirtualItemEventArgs args = new RetrieveVirtualItemEventArgs (displayIndex); + + owner.OnRetrieveVirtualItem (args); + ListViewItem retval = args.Item; + retval.Owner = owner; + retval.DisplayIndex = displayIndex; + retval.Layout (); + + return retval; + } + + internal event CollectionChangedHandler Changed; + + internal void Sort (IComparer comparer) + { + list.Sort (comparer); + OnChange (); + } + + internal void OnChange () + { + if (Changed != null) + Changed (); + } + } // ListViewItemCollection + + + // In normal mode, the selection information resides in the Items, + // making SelectedIndexCollection.List read-only + // + // In virtual mode, SelectedIndexCollection directly saves the selection + // information, instead of getting it from Items, making List read-and-write + [ListBindable (false)] + public class SelectedIndexCollection : IList, ICollection, IEnumerable + { + private readonly ListView owner; + private ArrayList list; + + #region Public Constructor + public SelectedIndexCollection (ListView owner) + { + this.owner = owner; + owner.Items.Changed += new CollectionChangedHandler (ItemsCollection_Changed); + } + #endregion // Public Constructor + + #region Public Properties + [Browsable (false)] + public int Count { + get { + if (!owner.is_selection_available) + return 0; + + return List.Count; + } + } + + public bool IsReadOnly { + get { + return false; + } + } + + public int this [int index] { + get { + if (!owner.is_selection_available || index < 0 || index >= List.Count) + throw new ArgumentOutOfRangeException ("index"); + + return (int) List [index]; + } + } + + bool ICollection.IsSynchronized { + get { return false; } + } + + object ICollection.SyncRoot { + get { return this; } + } + + bool IList.IsFixedSize { + get { + return false; + } + } + + object IList.this [int index] { + get { return this [index]; } + set { throw new NotSupportedException ("SetItem operation is not supported."); } + } + #endregion // Public Properties + + #region Public Methods + public int Add (int itemIndex) + { + if (itemIndex < 0 || itemIndex >= owner.Items.Count) + throw new ArgumentOutOfRangeException ("index"); + + if (owner.virtual_mode && !owner.is_selection_available) + return -1; + + owner.Items [itemIndex].Selected = true; + + if (!owner.is_selection_available) + return 0; + + return List.Count; + } + + public void Clear () + { + if (!owner.is_selection_available) + return; + + int [] indexes = (int []) List.ToArray (typeof (int)); + foreach (int index in indexes) + owner.Items [index].Selected = false; + } + + public bool Contains (int selectedIndex) + { + return IndexOf (selectedIndex) != -1; + } + + public void CopyTo (Array dest, int index) + { + List.CopyTo (dest, index); + } + + public IEnumerator GetEnumerator () + { + return List.GetEnumerator (); + } + + int IList.Add (object value) + { + throw new NotSupportedException ("Add operation is not supported."); + } + + void IList.Clear () + { + Clear (); + } + + bool IList.Contains (object selectedIndex) + { + if (!(selectedIndex is int)) + return false; + return Contains ((int) selectedIndex); + } + + int IList.IndexOf (object selectedIndex) + { + if (!(selectedIndex is int)) + return -1; + return IndexOf ((int) selectedIndex); + } + + void IList.Insert (int index, object value) + { + throw new NotSupportedException ("Insert operation is not supported."); + } + + void IList.Remove (object value) + { + throw new NotSupportedException ("Remove operation is not supported."); + } + + void IList.RemoveAt (int index) + { + throw new NotSupportedException ("RemoveAt operation is not supported."); + } + + public int IndexOf (int selectedIndex) + { + if (!owner.is_selection_available) + return -1; + + return List.IndexOf (selectedIndex); + } + + public void Remove (int itemIndex) + { + if (itemIndex < 0 || itemIndex >= owner.Items.Count) + throw new ArgumentOutOfRangeException ("itemIndex"); + + owner.Items [itemIndex].Selected = false; + } + #endregion // Public Methods + + internal ArrayList List { + get { + if (list == null) { + list = new ArrayList (); + if (!owner.VirtualMode) + for (int i = 0; i < owner.Items.Count; i++) { + if (owner.Items [i].Selected) + list.Add (i); + } + } + return list; + } + } + + internal void Reset () + { + // force re-population of list + list = null; + } + + private void ItemsCollection_Changed () + { + Reset (); + } + + internal void RemoveIndex (int index) + { + int idx = List.BinarySearch (index); + if (idx != -1) + List.RemoveAt (idx); + } + + // actually store index in the collection + // also, keep the collection sorted, as .Net does + internal void InsertIndex (int index) + { + int iMin = 0; + int iMax = List.Count - 1; + while (iMin <= iMax) { + int iMid = (iMin + iMax) / 2; + int current_index = (int) List [iMid]; + + if (current_index == index) + return; // Already added + if (current_index > index) + iMax = iMid - 1; + else + iMin = iMid + 1; + } + + List.Insert (iMin, index); + } + + } // SelectedIndexCollection + + [ListBindable (false)] + public class SelectedListViewItemCollection : IList, ICollection, IEnumerable + { + private readonly ListView owner; + + #region Public Constructor + public SelectedListViewItemCollection (ListView owner) + { + this.owner = owner; + } + #endregion // Public Constructor + + #region Public Properties + [Browsable (false)] + public int Count { + get { + return owner.SelectedIndices.Count; + } + } + + public bool IsReadOnly { + get { return true; } + } + + public ListViewItem this [int index] { + get { + if (!owner.is_selection_available || index < 0 || index >= Count) + throw new ArgumentOutOfRangeException ("index"); + + int item_index = owner.SelectedIndices [index]; + return owner.Items [item_index]; + } + } + + public virtual ListViewItem this [string key] { + get { + int idx = IndexOfKey (key); + if (idx == -1) + return null; + + return this [idx]; + } + } + + bool ICollection.IsSynchronized { + get { return false; } + } + + object ICollection.SyncRoot { + get { return this; } + } + + bool IList.IsFixedSize { + get { return true; } + } + + object IList.this [int index] { + get { return this [index]; } + set { throw new NotSupportedException ("SetItem operation is not supported."); } + } + #endregion // Public Properties + + #region Public Methods + public void Clear () + { + owner.SelectedIndices.Clear (); + } + + public bool Contains (ListViewItem item) + { + return IndexOf (item) != -1; + } + + public virtual bool ContainsKey (string key) + { + return IndexOfKey (key) != -1; + } + + public void CopyTo (Array dest, int index) + { + if (!owner.is_selection_available) + return; + if (index > Count) // Throws ArgumentException instead of IOOR exception + throw new ArgumentException ("index"); + + for (int i = 0; i < Count; i++) + dest.SetValue (this [i], index++); + } + + public IEnumerator GetEnumerator () + { + if (!owner.is_selection_available) + return (new ListViewItem [0]).GetEnumerator (); + + ListViewItem [] items = new ListViewItem [Count]; + for (int i = 0; i < Count; i++) + items [i] = this [i]; + + return items.GetEnumerator (); + } + + int IList.Add (object value) + { + throw new NotSupportedException ("Add operation is not supported."); + } + + bool IList.Contains (object item) + { + if (!(item is ListViewItem)) + return false; + return Contains ((ListViewItem) item); + } + + int IList.IndexOf (object item) + { + if (!(item is ListViewItem)) + return -1; + return IndexOf ((ListViewItem) item); + } + + void IList.Insert (int index, object value) + { + throw new NotSupportedException ("Insert operation is not supported."); + } + + void IList.Remove (object value) + { + throw new NotSupportedException ("Remove operation is not supported."); + } + + void IList.RemoveAt (int index) + { + throw new NotSupportedException ("RemoveAt operation is not supported."); + } + + public int IndexOf (ListViewItem item) + { + if (!owner.is_selection_available) + return -1; + + for (int i = 0; i < Count; i++) + if (this [i] == item) + return i; + + return -1; + } + + public virtual int IndexOfKey (string key) + { + if (!owner.is_selection_available || key == null || key.Length == 0) + return -1; + + for (int i = 0; i < Count; i++) { + ListViewItem item = this [i]; + if (String.Compare (item.Name, key, true) == 0) + return i; + } + + return -1; + } + #endregion // Public Methods + + } // SelectedListViewItemCollection + + internal delegate void CollectionChangedHandler (); + + struct ItemMatrixLocation + { + int row; + int col; + + public ItemMatrixLocation (int row, int col) + { + this.row = row; + this.col = col; + + } + + public int Col { + get { + return col; + } + set { + col = value; + } + } + + public int Row { + get { + return row; + } + set { + row = value; + } + } + + } + + #endregion // Subclasses + protected override void OnResize (EventArgs e) + { + base.OnResize (e); + } + + protected override void OnMouseLeave (EventArgs e) + { + base.OnMouseLeave (e); + } + + // + // ColumnReorder event + // + static object ColumnReorderedEvent = new object (); + public event ColumnReorderedEventHandler ColumnReordered { + add { Events.AddHandler (ColumnReorderedEvent, value); } + remove { Events.RemoveHandler (ColumnReorderedEvent, value); } + } + + protected virtual void OnColumnReordered (ColumnReorderedEventArgs e) + { + ColumnReorderedEventHandler creh = (ColumnReorderedEventHandler) (Events [ColumnReorderedEvent]); + + if (creh != null) + creh (this, e); + } + + // + // ColumnWidthChanged + // + static object ColumnWidthChangedEvent = new object (); + public event ColumnWidthChangedEventHandler ColumnWidthChanged { + add { Events.AddHandler (ColumnWidthChangedEvent, value); } + remove { Events.RemoveHandler (ColumnWidthChangedEvent, value); } + } + + protected virtual void OnColumnWidthChanged (ColumnWidthChangedEventArgs e) + { + ColumnWidthChangedEventHandler eh = (ColumnWidthChangedEventHandler) (Events[ColumnWidthChangedEvent]); + if (eh != null) + eh (this, e); + } + + void RaiseColumnWidthChanged (int resize_column) + { + ColumnWidthChangedEventArgs n = new ColumnWidthChangedEventArgs (resize_column); + + OnColumnWidthChanged (n); + } + + // + // ColumnWidthChanging + // + static object ColumnWidthChangingEvent = new object (); + public event ColumnWidthChangingEventHandler ColumnWidthChanging { + add { Events.AddHandler (ColumnWidthChangingEvent, value); } + remove { Events.RemoveHandler (ColumnWidthChangingEvent, value); } + } + + protected virtual void OnColumnWidthChanging (ColumnWidthChangingEventArgs e) + { + ColumnWidthChangingEventHandler cwceh = (ColumnWidthChangingEventHandler) (Events[ColumnWidthChangingEvent]); + if (cwceh != null) + cwceh (this, e); + } + + // + // 2.0 profile based implementation + // + bool CanProceedWithResize (ColumnHeader col, int width) + { + ColumnWidthChangingEventHandler cwceh = (ColumnWidthChangingEventHandler) (Events[ColumnWidthChangingEvent]); + if (cwceh == null) + return true; + + ColumnWidthChangingEventArgs changing = new ColumnWidthChangingEventArgs (col.Index, width); + cwceh (this, changing); + return !changing.Cancel; + } + + internal void RaiseColumnWidthChanged (ColumnHeader column) + { + int index = Columns.IndexOf (column); + RaiseColumnWidthChanged (index); + } + + + #region UIA Framework: Methods, Properties and Events + + static object UIALabelEditChangedEvent = new object (); + static object UIAShowGroupsChangedEvent = new object (); + static object UIAMultiSelectChangedEvent = new object (); + static object UIAViewChangedEvent = new object (); + static object UIACheckBoxesChangedEvent = new object (); + static object UIAFocusedItemChangedEvent = new object (); + + internal Rectangle UIAHeaderControl { + get { return header_control.Bounds; } + } + + internal int UIAColumns { + get { return cols; } + } + + internal int UIARows { + get { return rows; } + } + + internal ListViewGroup UIADefaultListViewGroup + { + get { return groups.DefaultGroup; } + } + + internal ScrollBar UIAHScrollBar { + get { return h_scroll; } + } + + internal ScrollBar UIAVScrollBar { + get { return v_scroll; } + } + + internal event EventHandler UIAShowGroupsChanged { + add { Events.AddHandler (UIAShowGroupsChangedEvent, value); } + remove { Events.RemoveHandler (UIAShowGroupsChangedEvent, value); } + } + + internal event EventHandler UIACheckBoxesChanged { + add { Events.AddHandler (UIACheckBoxesChangedEvent, value); } + remove { Events.RemoveHandler (UIACheckBoxesChangedEvent, value); } + } + + internal event EventHandler UIAMultiSelectChanged { + add { Events.AddHandler (UIAMultiSelectChangedEvent, value); } + remove { Events.RemoveHandler (UIAMultiSelectChangedEvent, value); } + } + + internal event EventHandler UIALabelEditChanged { + add { Events.AddHandler (UIALabelEditChangedEvent, value); } + remove { Events.RemoveHandler (UIALabelEditChangedEvent, value); } + } + + internal event EventHandler UIAViewChanged { + add { Events.AddHandler (UIAViewChangedEvent, value); } + remove { Events.RemoveHandler (UIAViewChangedEvent, value); } + } + + internal event EventHandler UIAFocusedItemChanged { + add { Events.AddHandler (UIAFocusedItemChangedEvent, value); } + remove { Events.RemoveHandler (UIAFocusedItemChangedEvent, value); } + } + + internal Rectangle UIAGetHeaderBounds (ListViewGroup group) + { + return group.HeaderBounds; + } + + internal int UIAItemsLocationLength + { + get { return items_location.Length; } + } + + private void OnUIACheckBoxesChanged () + { + EventHandler eh = (EventHandler) Events [UIACheckBoxesChangedEvent]; + if (eh != null) + eh (this, EventArgs.Empty); + } + + private void OnUIAShowGroupsChanged () + { + EventHandler eh = (EventHandler) Events [UIAShowGroupsChangedEvent]; + if (eh != null) + eh (this, EventArgs.Empty); + } + + private void OnUIAMultiSelectChanged () + { + EventHandler eh = (EventHandler) Events [UIAMultiSelectChangedEvent]; + if (eh != null) + eh (this, EventArgs.Empty); + } + + private void OnUIALabelEditChanged () + { + EventHandler eh = (EventHandler) Events [UIALabelEditChangedEvent]; + if (eh != null) + eh (this, EventArgs.Empty); + } + + private void OnUIAViewChanged () + { + EventHandler eh = (EventHandler) Events [UIAViewChangedEvent]; + if (eh != null) + eh (this, EventArgs.Empty); + } + + internal void OnUIAFocusedItemChanged () + { + EventHandler eh = (EventHandler) Events [UIAFocusedItemChangedEvent]; + if (eh != null) + eh (this, EventArgs.Empty); + } + + #endregion // UIA Framework: Methods, Properties and Events + + } +} diff --git a/source/ShiftUI/Widgets/ListViewItem.cs b/source/ShiftUI/Widgets/ListViewItem.cs new file mode 100644 index 0000000..b9473e1 --- /dev/null +++ b/source/ShiftUI/Widgets/ListViewItem.cs @@ -0,0 +1,1638 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2004 Novell, Inc. (http://www.novell.com) +// +// Author: +// Ravindra ([email protected]) +// Mike Kestner <[email protected]> +// Daniel Nauck (dna(at)mono-project(dot)de) + +using System.Collections; +using System.ComponentModel; +using System.Drawing; +using System.Runtime.Serialization; +using System; + +namespace ShiftUI +{ + [DefaultProperty ("Text")] + [DesignTimeVisible (false)] + [Serializable] + [ToolboxItem (false)] + [TypeConverter (typeof (ListViewItemConverter))] + public class ListViewItem : ICloneable, ISerializable + { + #region Instance Variables + private int image_index = -1; + private bool is_checked = false; + private int state_image_index = -1; + private ListViewSubItemCollection sub_items; + private object tag; + private bool use_item_style = true; + int display_index = -1; // actual position in ListView + private ListViewGroup group = null; + private string name = String.Empty; + private string image_key = String.Empty; + string tooltip_text = String.Empty; + int indent_count; + Point position = new Point (-1, -1); // cached to mimic .Net behaviour + Rectangle bounds = Rectangle.Empty; + Rectangle checkbox_rect; // calculated by CalcListViewItem method + Rectangle icon_rect; + Rectangle item_rect; + Rectangle label_rect; + ListView owner; + Font font; + Font hot_font; // cached font for hot tracking + bool selected; + + internal int row; + internal int col; + + + #region UIA Framework: Methods, Properties and Events + + internal event EventHandler UIATextChanged; + + internal event LabelEditEventHandler UIASubItemTextChanged; + + internal void OnUIATextChanged () + { + if (UIATextChanged != null) + UIATextChanged (this, EventArgs.Empty); + } + + internal void OnUIASubItemTextChanged (LabelEditEventArgs args) + { + //If our index is 0 we also generate TextChanged for the ListViewItem + //because ListViewItem.Text is the same as ListViewItem.SubItems [0].Text + if (args.Item == 0) + OnUIATextChanged (); + + if (UIASubItemTextChanged != null) + UIASubItemTextChanged (this, args); + } + + #endregion // UIA Framework: Methods, Properties and Events + + + #endregion Instance Variables + + #region Public Constructors + public ListViewItem () : this (string.Empty) + { + } + + public ListViewItem (string text) : this (text, -1) + { + } + + public ListViewItem (string [] items) : this (items, -1) + { + } + + public ListViewItem (ListViewItem.ListViewSubItem [] subItems, int imageIndex) + { + this.sub_items = new ListViewSubItemCollection (this, null); + for (int i = 0; i < subItems.Length; i++) + sub_items.Add (subItems [i]); + this.image_index = imageIndex; + } + + public ListViewItem (string text, int imageIndex) + { + this.image_index = imageIndex; + this.sub_items = new ListViewSubItemCollection (this, text); + } + + public ListViewItem (string [] items, int imageIndex) + { + this.sub_items = new ListViewSubItemCollection (this, null); + if (items != null) { + for (int i = 0; i < items.Length; i++) + sub_items.Add (new ListViewSubItem (this, items [i])); + } + this.image_index = imageIndex; + } + + public ListViewItem (string [] items, int imageIndex, Color foreColor, + Color backColor, Font font) : this (items, imageIndex) + { + ForeColor = foreColor; + BackColor = backColor; + this.font = font; + } + + public ListViewItem(string[] items, string imageKey) : this(items) + { + this.ImageKey = imageKey; + } + + public ListViewItem(string text, string imageKey) : this(text) + { + this.ImageKey = imageKey; + } + + public ListViewItem(ListViewSubItem[] subItems, string imageKey) + { + this.sub_items = new ListViewSubItemCollection (this, null); + for (int i = 0; i < subItems.Length; i++) + this.sub_items.Add (subItems [i]); + this.ImageKey = imageKey; + } + + public ListViewItem(string[] items, string imageKey, Color foreColor, + Color backColor, Font font) : this(items, imageKey) + { + ForeColor = foreColor; + BackColor = backColor; + this.font = font; + } + + public ListViewItem(ListViewGroup group) : this() + { + Group = group; + } + + public ListViewItem(string text, ListViewGroup group) : this(text) + { + Group = group; + } + + public ListViewItem(string[] items, ListViewGroup group) : this(items) + { + Group = group; + } + + public ListViewItem(ListViewSubItem[] subItems, int imageIndex, ListViewGroup group) + : this(subItems, imageIndex) + { + Group = group; + } + + public ListViewItem(ListViewSubItem[] subItems, string imageKey, ListViewGroup group) + : this(subItems, imageKey) + { + Group = group; + } + + public ListViewItem(string text, int imageIndex, ListViewGroup group) + : this(text, imageIndex) + { + Group = group; + } + + public ListViewItem(string text, string imageKey, ListViewGroup group) + : this(text, imageKey) + { + Group = group; + } + + public ListViewItem(string[] items, int imageIndex, ListViewGroup group) + : this(items, imageIndex) + { + Group = group; + } + + public ListViewItem(string[] items, string imageKey, ListViewGroup group) + : this(items, imageKey) + { + Group = group; + } + + public ListViewItem(string[] items, int imageIndex, Color foreColor, Color backColor, + Font font, ListViewGroup group) + : this(items, imageIndex, foreColor, backColor, font) + { + Group = group; + } + + public ListViewItem(string[] items, string imageKey, Color foreColor, Color backColor, + Font font, ListViewGroup group) + : this(items, imageKey, foreColor, backColor, font) + { + Group = group; + } + #endregion // Public Constructors + + protected ListViewItem (SerializationInfo info, StreamingContext context) + { + Deserialize (info, context); + } + + #region Public Instance Properties + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public Color BackColor { + get { + if (sub_items.Count > 0) + return sub_items[0].BackColor; + + if (owner != null) + return owner.BackColor; + + return ThemeEngine.Current.ColorWindow; + } + set { SubItems [0].BackColor = value; } + } + + [Browsable (false)] + public Rectangle Bounds { + get { + return GetBounds (ItemBoundsPortion.Entire); + } + } + + [DefaultValue (false)] + [RefreshProperties (RefreshProperties.Repaint)] + public bool Checked { + get { return is_checked; } + set { + if (is_checked == value) + return; + + if (owner != null) { + CheckState current_value = is_checked ? CheckState.Checked : CheckState.Unchecked; + CheckState new_value = value ? CheckState.Checked : CheckState.Unchecked; + + ItemCheckEventArgs icea = new ItemCheckEventArgs (Index, + new_value, current_value); + owner.OnItemCheck (icea); + + if (new_value != current_value) { + // force re-population of list + owner.CheckedItems.Reset (); + is_checked = new_value == CheckState.Checked; + Invalidate (); + + ItemCheckedEventArgs args = new ItemCheckedEventArgs (this); + owner.OnItemChecked (args); + } + } else + is_checked = value; + } + } + + [Browsable (false)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public bool Focused { + get { + if (owner == null) + return false; + + // In virtual mode the checks are always done using indexes + if (owner.VirtualMode) + return Index == owner.focused_item_index; + + // Light check + return owner.FocusedItem == this; + + } + set { + if (owner == null) + return; + + if (Focused == value) + return; + + ListViewItem prev_focused_item = owner.FocusedItem; + if (prev_focused_item != null) + prev_focused_item.UpdateFocusedState (); + + owner.focused_item_index = value ? Index : -1; + if (value) + owner.OnUIAFocusedItemChanged (); + + UpdateFocusedState (); + } + } + + [Localizable (true)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public Font Font { + get { + if (font != null) + return font; + else if (owner != null) + return owner.Font; + + return ThemeEngine.Current.DefaultFont; + } + set { + if (font == value) + return; + + font = value; + hot_font = null; + + if (owner != null) + Layout (); + Invalidate (); + } + } + + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public Color ForeColor { + get { + if (sub_items.Count > 0) + return sub_items[0].ForeColor; + + if (owner != null) + return owner.ForeColor; + + return ThemeEngine.Current.ColorWindowText; + } + set { SubItems [0].ForeColor = value; } + } + + [DefaultValue (-1)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + //[Editor ("ShiftUI.Design.ImageIndexEditor, " + Consts.AssemblySystem_Design, + //typeof (System.Drawing.Design.UITypeEditor))] + [Localizable (true)] + [RefreshProperties (RefreshProperties.Repaint)] + [TypeConverter (typeof (NoneExcludedImageIndexConverter))] + public int ImageIndex { + get { return image_index; } + set { + if (value < -1) + throw new ArgumentException ("Invalid ImageIndex. It must be greater than or equal to -1."); + + image_index = value; + image_key = String.Empty; + + if (owner != null) + Layout (); + Invalidate (); + } + } + + [DefaultValue ("")] + [LocalizableAttribute (true)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + //[Editor ("ShiftUI.Design.ImageIndexEditor, " + Consts.AssemblySystem_Design, + //typeof (System.Drawing.Design.UITypeEditor))] + [RefreshProperties (RefreshProperties.Repaint)] + [TypeConverter (typeof (ImageKeyConverter))] + public string ImageKey { + get { + return image_key; + } + set { + image_key = value == null ? String.Empty : value; + image_index = -1; + + if (owner != null) + Layout (); + Invalidate (); + } + } + + [Browsable (false)] + public ImageList ImageList { + get { + if (owner == null) + return null; + else if (owner.View == View.LargeIcon) + return owner.large_image_list; + else + return owner.small_image_list; + } + } + + [DefaultValue (0)] + public int IndentCount { + get { + return indent_count; + } + set { + if (value < 0) + throw new ArgumentOutOfRangeException ("value"); + + if (value == indent_count) + return; + + indent_count = value; + Invalidate (); + } + } + + [Browsable (false)] + public int Index { + get { + if (owner == null) + return -1; + if (owner.VirtualMode) + return display_index; + + if (display_index == -1) + return owner.Items.IndexOf (this); + + return owner.GetItemIndex (display_index); + } + } + + [Browsable (false)] + public ListView ListView { + get { return owner; } + } + + [Browsable (false)] + [Localizable (true)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public string Name { + get { + return name; + } + set { + name = value == null ? String.Empty : value; + } + } + + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + [Browsable (false)] + public Point Position { + get { + if (owner != null && owner.VirtualMode) + return owner.GetItemLocation (display_index); + + if (owner != null && !owner.IsHandleCreated) + return new Point (-1, -1); + + return position; + } + set { + if (owner == null || owner.View == View.Details || owner.View == View.List) + return; + + if (owner.VirtualMode) + throw new InvalidOperationException (); + + owner.ChangeItemLocation (display_index, value); + } + } + + // When ListView uses VirtualMode, selection state info + // lives in the ListView, not in the item + // Also, in VirtualMode we can't Reset() the selection + [Browsable (false)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public bool Selected { + get { + if (owner != null && owner.VirtualMode) + return owner.SelectedIndices.Contains (Index); + + return selected; + } + set { + if (selected == value && owner != null && !owner.VirtualMode) + return; + + SetSelectedCore (value); + } + } + + // Expose this method as internal so we can force an update in the selection. + internal void SetSelectedCore (bool value) + { + if (owner != null) { + if (value && !owner.MultiSelect) + owner.SelectedIndices.Clear (); + if (owner.VirtualMode) { + if (value) + owner.SelectedIndices.InsertIndex (Index); + else + owner.SelectedIndices.RemoveIndex (Index); + } else { + selected = value; + owner.SelectedIndices.Reset (); // force re-population of list + } + + owner.OnItemSelectionChanged (new ListViewItemSelectionChangedEventArgs (this, Index, value)); + owner.OnSelectedIndexChanged (); + Invalidate (); + } else + selected = value; + } + + [DefaultValue (-1)] + //[Editor ("ShiftUI.Design.ImageIndexEditor, " + Consts.AssemblySystem_Design, + //typeof (System.Drawing.Design.UITypeEditor))] + [Localizable (true)] + [RefreshProperties (RefreshProperties.Repaint)] + [RelatedImageListAttribute ("ListView.StateImageList")] + [TypeConverter (typeof (NoneExcludedImageIndexConverter))] + public int StateImageIndex { + get { return state_image_index; } + set { + if (value < -1 || value > 14) + throw new ArgumentOutOfRangeException ("Invalid StateImageIndex. It must be in the range of [-1, 14]."); + + state_image_index = value; + } + } + + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + //[Editor ("ShiftUI.Design.ListViewSubItemCollectionEditor, " + Consts.AssemblySystem_Design, + //typeof (System.Drawing.Design.UITypeEditor))] + public ListViewSubItemCollection SubItems { + get { + if (sub_items.Count == 0) + this.sub_items.Add (string.Empty); + return sub_items; + } + } + + [Bindable (true)] + [DefaultValue (null)] + [Localizable (false)] + [TypeConverter (typeof (StringConverter))] + public object Tag { + get { return tag; } + set { tag = value; } + } + + [Localizable (true)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public string Text { + get { + if (this.sub_items.Count > 0) + return this.sub_items [0].Text; + else + return string.Empty; + } + set { + if (SubItems [0].Text == value) + return; + + sub_items [0].Text = value; + + if (owner != null) + Layout (); + Invalidate (); + + //UIA Framework: Generates Text changed + OnUIATextChanged (); + + } + } + + [DefaultValue (true)] + public bool UseItemStyleForSubItems { + get { return use_item_style; } + set { use_item_style = value; } + } + + [LocalizableAttribute(true)] + [DefaultValue (null)] + public ListViewGroup Group { + get { return this.group; } + set { + if (group != value) { + if (value == null) + group.Items.Remove (this); + else + value.Items.Add (this); + + group = value; + } + } + } + + [DefaultValue ("")] + public string ToolTipText { + get { + return tooltip_text; + } + set { + if (value == null) + value = String.Empty; + + tooltip_text = value; + } + } + + #endregion // Public Instance Properties + + #region Public Instance Methods + public void BeginEdit () + { + if (owner != null && owner.LabelEdit) { + owner.item_control.BeginEdit (this); + } + // FIXME: TODO + // if (owner != null && owner.LabelEdit + // && owner.Activation == ItemActivation.Standard) + // allow editing + // else + // throw new InvalidOperationException (); + } + + public virtual object Clone () + { + ListViewItem clone = new ListViewItem (); + clone.image_index = this.image_index; + clone.is_checked = this.is_checked; + clone.selected = this.selected; + clone.font = this.font; + clone.state_image_index = this.state_image_index; + clone.sub_items = new ListViewSubItemCollection (this, null); + + foreach (ListViewSubItem subItem in this.sub_items) + clone.sub_items.Add (subItem.Text, subItem.ForeColor, + subItem.BackColor, subItem.Font); + clone.tag = this.tag; + clone.use_item_style = this.use_item_style; + clone.owner = null; + clone.name = name; + clone.tooltip_text = tooltip_text; + + return clone; + } + + public virtual void EnsureVisible () + { + if (this.owner != null) { + owner.EnsureVisible (owner.Items.IndexOf (this)); + } + } + + public ListViewItem FindNearestItem (SearchDirectionHint searchDirection) + { + if (owner == null) + return null; + + Point loc = owner.GetItemLocation (display_index); + return owner.FindNearestItem (searchDirection, loc); + } + + public Rectangle GetBounds (ItemBoundsPortion portion) + { + if (owner == null) + return Rectangle.Empty; + + Rectangle rect; + + switch (portion) { + case ItemBoundsPortion.Icon: + rect = icon_rect; + break; + + case ItemBoundsPortion.Label: + rect = label_rect; + break; + + case ItemBoundsPortion.ItemOnly: + rect = item_rect; + break; + + case ItemBoundsPortion.Entire: + rect = bounds; + break; + + default: + throw new ArgumentException ("Invalid value for portion."); + } + + Point item_loc = owner.GetItemLocation (DisplayIndex); + rect.X += item_loc.X; + rect.Y += item_loc.Y; + return rect; + } + + void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context) + { + Serialize (info, context); + } + + public ListViewSubItem GetSubItemAt (int x, int y) + { + if (owner != null && owner.View != View.Details) + return null; + + foreach (ListViewSubItem sub_item in sub_items) + if (sub_item.Bounds.Contains (x, y)) + return sub_item; + + return null; + } + + public virtual void Remove () + { + if (owner == null) + return; + + owner.item_control.CancelEdit (this); + owner.Items.Remove (this); + owner = null; + } + + public override string ToString () + { + return string.Format ("ListViewItem: {0}", this.Text); + } + #endregion // Public Instance Methods + + #region Protected Methods + protected virtual void Deserialize (SerializationInfo info, StreamingContext context) + { + sub_items = new ListViewSubItemCollection (this, null); + int sub_items_count = 0; + + foreach (SerializationEntry entry in info) { + switch (entry.Name) { + case "Text": + sub_items.Add ((string)entry.Value); + break; + case "Font": + font = (Font)entry.Value; + break; + case "Checked": + is_checked = (bool)entry.Value; + break; + case "ImageIndex": + image_index = (int)entry.Value; + break; + case "StateImageIndex": + state_image_index = (int)entry.Value; + break; + case "UseItemStyleForSubItems": + use_item_style = (bool)entry.Value; + break; + case "SubItemCount": + sub_items_count = (int)entry.Value; + break; + case "Group": + group = (ListViewGroup)entry.Value; + break; + case "ImageKey": + if (image_index == -1) + image_key = (string)entry.Value; + break; + } + } + + Type subitem_type = typeof (ListViewSubItem); + if (sub_items_count > 0) { + sub_items.Clear (); // .net fixup + Text = info.GetString ("Text"); + for (int i = 0; i < sub_items_count - 1; i++) + sub_items.Add ((ListViewSubItem)info.GetValue ("SubItem" + (i + 1), subitem_type)); + } + + // After any sub item has been added. + ForeColor = (Color)info.GetValue ("ForeColor", typeof (Color)); + BackColor = (Color)info.GetValue ("BackColor", typeof (Color)); + } + + protected virtual void Serialize (SerializationInfo info, StreamingContext context) + { + info.AddValue ("Text", Text); + info.AddValue ("Font", Font); + info.AddValue ("ImageIndex", image_index); + info.AddValue ("Checked", is_checked); + info.AddValue ("StateImageIndex", state_image_index); + info.AddValue ("UseItemStyleForSubItems", use_item_style); + info.AddValue ("BackColor", BackColor); + info.AddValue ("ForeColor", ForeColor); + info.AddValue ("ImageKey", image_key); + if (group != null) + info.AddValue ("Group", group); + if (sub_items.Count > 1) { + info.AddValue ("SubItemCount", sub_items.Count); + for (int i = 1; i < sub_items.Count; i++) { + info.AddValue ("SubItem" + i, sub_items [i]); + } + } + } + #endregion // Protected Methods + + #region Private Internal Methods + internal Rectangle CheckRectReal { + get { + Rectangle rect = checkbox_rect; + Point item_loc = owner.GetItemLocation (DisplayIndex); + rect.X += item_loc.X; + rect.Y += item_loc.Y; + return rect; + } + } + + Rectangle text_bounds; + internal Rectangle TextBounds { + get { + // Call Layout() if it hasn't been called before. + if (owner.VirtualMode && bounds == new Rectangle (-1, -1, -1, -1)) + Layout (); + Rectangle result = text_bounds; + Point loc = owner.GetItemLocation (DisplayIndex); + result.X += loc.X; + result.Y += loc.Y; + return result; + } + } + + internal int DisplayIndex { + get { + // Special case for Details view + // and no columns (which means no Layout at all) + if (display_index == -1) + return owner.Items.IndexOf (this); + + return display_index; + } + set { + display_index = value; + } + } + + internal bool Hot { + get { + return Index == owner.HotItemIndex; + } + } + + internal Font HotFont { + get { + if (hot_font == null) + hot_font = new Font (Font, Font.Style | FontStyle.Underline); + + return hot_font; + } + } + + internal ListView Owner { + set { + if (owner == value) + return; + + owner = value; + } + } + + internal void SetGroup (ListViewGroup group) + { + this.group = group; + } + + internal void SetPosition (Point position) + { + this.position = position; + } + + // When focus changed, we need to invalidate area + // with previous layout and with the new one + void UpdateFocusedState () + { + if (owner != null) { + Invalidate (); + Layout (); + Invalidate (); + } + } + + internal void Invalidate () + { + if (owner == null || owner.item_control == null || owner.updating) + return; + + // Add some padding to bounds (focused extra space, selection) + Rectangle rect = Bounds; + rect.Inflate (1, 1); + owner.item_control.Invalidate (rect); + } + + internal void Layout () + { + if (owner == null) + return; + int item_ht; + Rectangle total; + Size text_size = owner.text_size; + + checkbox_rect = Rectangle.Empty; + if (owner.CheckBoxes) + checkbox_rect.Size = owner.CheckBoxSize; + switch (owner.View) { + case View.Details: + // LAMESPEC: MSDN says, "In all views except the details + // view of the ListView, this value specifies the same + // bounding rectangle as the Entire value." Actually, it + // returns same bounding rectangles for Item and Entire + // values in the case of Details view. + + int x_offset = 0; + if (owner.SmallImageList != null) + x_offset = indent_count * owner.SmallImageList.ImageSize.Width; + + // Handle reordered column + if (owner.Columns.Count > 0) + checkbox_rect.X = owner.Columns[0].Rect.X + x_offset; + + icon_rect = label_rect = Rectangle.Empty; + icon_rect.X = checkbox_rect.Right + 2; + item_ht = owner.ItemSize.Height; + + if (owner.SmallImageList != null) + icon_rect.Width = owner.SmallImageList.ImageSize.Width; + + label_rect.Height = icon_rect.Height = item_ht; + checkbox_rect.Y = item_ht - checkbox_rect.Height; + + label_rect.X = icon_rect.Width > 0 ? icon_rect.Right + 1 : icon_rect.Right; + + if (owner.Columns.Count > 0) + label_rect.Width = owner.Columns[0].Wd - label_rect.X + checkbox_rect.X; + else + label_rect.Width = text_size.Width; + + SizeF text_sz = TextRenderer.MeasureString (Text, Font); + text_bounds = label_rect; + text_bounds.Width = (int) text_sz.Width; + + item_rect = total = Rectangle.Union + (Rectangle.Union (checkbox_rect, icon_rect), label_rect); + bounds.Size = total.Size; + + item_rect.Width = 0; + bounds.Width = 0; + for (int i = 0; i < owner.Columns.Count; i++) { + item_rect.Width += owner.Columns [i].Wd; + bounds.Width += owner.Columns [i].Wd; + } + + // Bounds for sub items + int n = Math.Min (owner.Columns.Count, sub_items.Count); + for (int i = 0; i < n; i++) { + Rectangle col_rect = owner.Columns [i].Rect; + sub_items [i].SetBounds (col_rect.X, 0, col_rect.Width, item_ht); + } + break; + + case View.LargeIcon: + label_rect = icon_rect = Rectangle.Empty; + + SizeF sz = TextRenderer.MeasureString (Text, Font); + if ((int) sz.Width > text_size.Width) { + if (Focused && owner.InternalContainsFocus) { + int text_width = text_size.Width; + StringFormat format = new StringFormat (); + format.Alignment = StringAlignment.Center; + sz = TextRenderer.MeasureString (Text, Font, text_width, format); + text_size.Height = (int) sz.Height; + } else + text_size.Height = 2 * (int) sz.Height; + } + + if (owner.LargeImageList != null) { + icon_rect.Width = owner.LargeImageList.ImageSize.Width; + icon_rect.Height = owner.LargeImageList.ImageSize.Height; + } + + if (checkbox_rect.Height > icon_rect.Height) + icon_rect.Y = checkbox_rect.Height - icon_rect.Height; + else + checkbox_rect.Y = icon_rect.Height - checkbox_rect.Height; + + if (text_size.Width <= icon_rect.Width) { + icon_rect.X = checkbox_rect.Width + 1; + label_rect.X = icon_rect.X + (icon_rect.Width - text_size.Width) / 2; + label_rect.Y = icon_rect.Bottom + 2; + label_rect.Size = text_size; + } else { + int centerX = text_size.Width / 2; + icon_rect.X = checkbox_rect.Width + 1 + centerX - icon_rect.Width / 2; + label_rect.X = checkbox_rect.Width + 1; + label_rect.Y = icon_rect.Bottom + 2; + label_rect.Size = text_size; + } + + item_rect = Rectangle.Union (icon_rect, label_rect); + total = Rectangle.Union (item_rect, checkbox_rect); + bounds.Size = total.Size; + break; + + case View.List: + case View.SmallIcon: + label_rect = icon_rect = Rectangle.Empty; + icon_rect.X = checkbox_rect.Width + 1; + item_ht = Math.Max (owner.CheckBoxSize.Height, text_size.Height); + + if (owner.SmallImageList != null) { + item_ht = Math.Max (item_ht, owner.SmallImageList.ImageSize.Height); + icon_rect.Width = owner.SmallImageList.ImageSize.Width; + icon_rect.Height = owner.SmallImageList.ImageSize.Height; + } + + checkbox_rect.Y = item_ht - checkbox_rect.Height; + label_rect.X = icon_rect.Right + 1; + label_rect.Width = text_size.Width; + label_rect.Height = icon_rect.Height = item_ht; + + item_rect = Rectangle.Union (icon_rect, label_rect); + total = Rectangle.Union (item_rect, checkbox_rect); + bounds.Size = total.Size; + break; + case View.Tile: + if (!Application.VisualStylesEnabled) + goto case View.LargeIcon; + + label_rect = icon_rect = Rectangle.Empty; + + if (owner.LargeImageList != null) { + icon_rect.Width = owner.LargeImageList.ImageSize.Width; + icon_rect.Height = owner.LargeImageList.ImageSize.Height; + } + + int separation = 2; + SizeF tsize = TextRenderer.MeasureString (Text, Font); + int main_item_height = (int) Math.Ceiling (tsize.Height); + int main_item_width = (int) Math.Ceiling (tsize.Width); + sub_items [0].bounds.Height = main_item_height; + + // Set initial values for subitem's layout + int total_height = main_item_height; + int max_subitem_width = main_item_width; + + int count = Math.Min (owner.Columns.Count, sub_items.Count); + for (int i = 1; i < count; i++) { // Ignore first column and first subitem + ListViewSubItem sub_item = sub_items [i]; + if (sub_item.Text == null || sub_item.Text.Length == 0) + continue; + + tsize = TextRenderer.MeasureString (sub_item.Text, sub_item.Font); + + int width = (int)Math.Ceiling (tsize.Width); + if (width > max_subitem_width) + max_subitem_width = width; + + int height = (int)Math.Ceiling (tsize.Height); + total_height += height + separation; + + sub_item.bounds.Height = height; + } + + max_subitem_width = Math.Min (max_subitem_width, owner.TileSize.Width - (icon_rect.Width + 4)); + label_rect.X = icon_rect.Right + 4; + label_rect.Y = owner.TileSize.Height / 2 - total_height / 2; + label_rect.Width = max_subitem_width; + label_rect.Height = total_height; + + // Main item - always set bounds for it + sub_items [0].SetBounds (label_rect.X, label_rect.Y, max_subitem_width, sub_items [0].bounds.Height); + + // Second pass to assign bounds for every sub item + int current_y = sub_items [0].bounds.Bottom + separation; + for (int j = 1; j < count; j++) { + ListViewSubItem sub_item = sub_items [j]; + if (sub_item.Text == null || sub_item.Text.Length == 0) + continue; + + sub_item.SetBounds (label_rect.X, current_y, max_subitem_width, sub_item.bounds.Height); + current_y += sub_item.Bounds.Height + separation; + } + + item_rect = Rectangle.Union (icon_rect, label_rect); + bounds.Size = item_rect.Size; + break; + } + + } + #endregion // Private Internal Methods + + #region Subclasses + + [DefaultProperty ("Text")] + [DesignTimeVisible (false)] + [Serializable] + [ToolboxItem (false)] + [TypeConverter (typeof(ListViewSubItemConverter))] + public class ListViewSubItem + { + [NonSerialized] + internal ListViewItem owner; + private string text = string.Empty; + private string name; + private object userData; + private SubItemStyle style; + [NonSerialized] + internal Rectangle bounds; + + + #region UIA Framework: Methods, Properties and Events + + [field:NonSerialized] + internal event EventHandler UIATextChanged; + + private void OnUIATextChanged () + { + if (UIATextChanged != null) + UIATextChanged (this, EventArgs.Empty); + } + + #endregion // UIA Framework: Methods, Properties and Events + + + #region Public Constructors + public ListViewSubItem () + : this (null, string.Empty, Color.Empty, + Color.Empty, null) + { + } + + public ListViewSubItem (ListViewItem owner, string text) + : this (owner, text, Color.Empty, + Color.Empty, null) + { + } + + public ListViewSubItem (ListViewItem owner, string text, Color foreColor, + Color backColor, Font font) + { + this.owner = owner; + Text = text; + this.style = new SubItemStyle (foreColor, + backColor, font); + } + #endregion // Public Constructors + + #region Public Instance Properties + public Color BackColor { + get { + if (style.backColor != Color.Empty) + return style.backColor; + if (this.owner != null && this.owner.ListView != null) + return this.owner.ListView.BackColor; + return ThemeEngine.Current.ColorWindow; + } + set { + style.backColor = value; + Invalidate (); + } + } + + [Browsable (false)] + public Rectangle Bounds { + get { + Rectangle retval = bounds; + if (owner != null) { + retval.X += owner.Bounds.X; + retval.Y += owner.Bounds.Y; + } + + return retval; + } + } + + [Localizable (true)] + public Font Font { + get { + if (style.font != null) + return style.font; + else if (owner != null) + return owner.Font; + return ThemeEngine.Current.DefaultFont; + } + set { + if (style.font == value) + return; + style.font = value; + Invalidate (); + } + } + + public Color ForeColor { + get { + if (style.foreColor != Color.Empty) + return style.foreColor; + if (this.owner != null && this.owner.ListView != null) + return this.owner.ListView.ForeColor; + return ThemeEngine.Current.ColorWindowText; + } + set { + style.foreColor = value; + Invalidate (); + } + } + + [Localizable (true)] + public string Name { + get { + if (name == null) + return string.Empty; + return name; + } + set { + name = value; + } + } + + [TypeConverter (typeof (StringConverter))] + [BindableAttribute (true)] + [DefaultValue (null)] + [Localizable (false)] + public object Tag { + get { + return userData; + } + set { + userData = value; + } + } + + [Localizable (true)] + public string Text { + get { return text; } + set { + if(text == value) + return; + + if(value == null) + text = string.Empty; + else + text = value; + + Invalidate (); + + // UIA Framework: Generates SubItem TextChanged + OnUIATextChanged (); + } + } + #endregion // Public Instance Properties + + #region Public Methods + public void ResetStyle () + { + style.Reset (); + Invalidate (); + } + + public override string ToString () + { + return string.Format ("ListViewSubItem {{0}}", text); + } + #endregion // Public Methods + + #region Private Methods + private void Invalidate () + { + if (owner == null || owner.owner == null) + return; + + owner.Invalidate (); + } + + [OnDeserialized] + void OnDeserialized (StreamingContext context) + { + name = null; + userData = null; + } + + internal int Height { + get { + return bounds.Height; + } + } + + internal void SetBounds (int x, int y, int width, int height) + { + bounds = new Rectangle (x, y, width, height); + } + + #endregion // Private Methods + + [Serializable] + class SubItemStyle + { + public SubItemStyle () + { + } + + public SubItemStyle (Color foreColor, Color backColor, Font font) + { + this.foreColor = foreColor; + this.backColor = backColor; + this.font = font; + } + + public void Reset () + { + foreColor = Color.Empty; + backColor = Color.Empty; + font = null; + } + + public Color backColor; + public Color foreColor; + public Font font; + } + } + + public class ListViewSubItemCollection : IList, ICollection, IEnumerable + { + private ArrayList list; + internal ListViewItem owner; + + #region Public Constructors + public ListViewSubItemCollection (ListViewItem owner) : this (owner, owner.Text) + { + } + #endregion // Public Constructors + + internal ListViewSubItemCollection (ListViewItem owner, string text) + { + this.owner = owner; + this.list = new ArrayList (); + if (text != null) + Add (text); + } + #region Public Properties + [Browsable (false)] + public int Count { + get { return list.Count; } + } + + public bool IsReadOnly { + get { return false; } + } + + public ListViewSubItem this [int index] { + get { return (ListViewSubItem) list [index]; } + set { + value.owner = owner; + list [index] = value; + owner.Layout (); + owner.Invalidate (); + } + } + + public virtual ListViewSubItem this [string key] { + get { + int idx = IndexOfKey (key); + if (idx == -1) + return null; + + return (ListViewSubItem) list [idx]; + } + } + + bool ICollection.IsSynchronized { + get { return list.IsSynchronized; } + } + + object ICollection.SyncRoot { + get { return list.SyncRoot; } + } + + bool IList.IsFixedSize { + get { return list.IsFixedSize; } + } + + object IList.this [int index] { + get { return this [index]; } + set { + if (! (value is ListViewSubItem)) + throw new ArgumentException ("Not of type ListViewSubItem", "value"); + this [index] = (ListViewSubItem) value; + } + } + #endregion // Public Properties + + #region Public Methods + public ListViewSubItem Add (ListViewSubItem item) + { + AddSubItem (item); + owner.Layout (); + owner.Invalidate (); + return item; + } + + public ListViewSubItem Add (string text) + { + ListViewSubItem item = new ListViewSubItem (owner, text); + return Add (item); + } + + public ListViewSubItem Add (string text, Color foreColor, + Color backColor, Font font) + { + ListViewSubItem item = new ListViewSubItem (owner, text, + foreColor, backColor, font); + return Add (item); + } + + public void AddRange (ListViewSubItem [] items) + { + if (items == null) + throw new ArgumentNullException ("items"); + + foreach (ListViewSubItem item in items) { + if (item == null) + continue; + AddSubItem (item); + } + owner.Layout (); + owner.Invalidate (); + } + + public void AddRange (string [] items) + { + if (items == null) + throw new ArgumentNullException ("items"); + + foreach (string item in items) { + if (item == null) + continue; + AddSubItem (new ListViewSubItem (owner, item)); + } + owner.Layout (); + owner.Invalidate (); + } + + public void AddRange (string [] items, Color foreColor, + Color backColor, Font font) + { + if (items == null) + throw new ArgumentNullException ("items"); + + foreach (string item in items) { + if (item == null) + continue; + + AddSubItem (new ListViewSubItem (owner, item, foreColor, backColor, font)); + } + owner.Layout (); + owner.Invalidate (); + } + + void AddSubItem (ListViewSubItem subItem) + { + subItem.owner = owner; + list.Add (subItem); + + //UIA Framework + subItem.UIATextChanged += OnUIASubItemTextChanged; + } + + public void Clear () + { + list.Clear (); + } + + public bool Contains (ListViewSubItem subItem) + { + return list.Contains (subItem); + } + + public virtual bool ContainsKey (string key) + { + return IndexOfKey (key) != -1; + } + + public IEnumerator GetEnumerator () + { + return list.GetEnumerator (); + } + + void ICollection.CopyTo (Array dest, int index) + { + list.CopyTo (dest, index); + } + + int IList.Add (object item) + { + if (! (item is ListViewSubItem)) { + throw new ArgumentException ("Not of type ListViewSubItem", "item"); + } + + ListViewSubItem sub_item = (ListViewSubItem) item; + sub_item.owner = this.owner; + //UIA Framework + sub_item.UIATextChanged += OnUIASubItemTextChanged; + return list.Add (sub_item); + } + + bool IList.Contains (object subItem) + { + if (! (subItem is ListViewSubItem)) { + throw new ArgumentException ("Not of type ListViewSubItem", "subItem"); + } + + return this.Contains ((ListViewSubItem) subItem); + } + + int IList.IndexOf (object subItem) + { + if (! (subItem is ListViewSubItem)) { + throw new ArgumentException ("Not of type ListViewSubItem", "subItem"); + } + + return this.IndexOf ((ListViewSubItem) subItem); + } + + void IList.Insert (int index, object item) + { + if (! (item is ListViewSubItem)) { + throw new ArgumentException ("Not of type ListViewSubItem", "item"); + } + + this.Insert (index, (ListViewSubItem) item); + } + + void IList.Remove (object item) + { + if (! (item is ListViewSubItem)) { + throw new ArgumentException ("Not of type ListViewSubItem", "item"); + } + + this.Remove ((ListViewSubItem) item); + } + + public int IndexOf (ListViewSubItem subItem) + { + return list.IndexOf (subItem); + } + + public virtual int IndexOfKey (string key) + { + if (key == null || key.Length == 0) + return -1; + + for (int i = 0; i < list.Count; i++) { + ListViewSubItem l = (ListViewSubItem) list [i]; + if (String.Compare (l.Name, key, true) == 0) + return i; + } + + return -1; + } + + public void Insert (int index, ListViewSubItem item) + { + item.owner = this.owner; + list.Insert (index, item); + owner.Layout (); + owner.Invalidate (); + + //UIA Framework + item.UIATextChanged += OnUIASubItemTextChanged; + } + + public void Remove (ListViewSubItem item) + { + list.Remove (item); + owner.Layout (); + owner.Invalidate (); + + //UIA Framework + item.UIATextChanged -= OnUIASubItemTextChanged; + } + + public virtual void RemoveByKey (string key) + { + int idx = IndexOfKey (key); + if (idx != -1) + RemoveAt (idx); + } + + public void RemoveAt (int index) + { + //UIA Framework + if (index >= 0 && index < list.Count) + ((ListViewSubItem) list [index]).UIATextChanged -= OnUIASubItemTextChanged; + + list.RemoveAt (index); + + } + #endregion // Public Methods + #region UIA Event Handler + + private void OnUIASubItemTextChanged (object sender, EventArgs args) + { + owner.OnUIASubItemTextChanged (new LabelEditEventArgs (list.IndexOf (sender))); + } + + #endregion + + + } + #endregion // Subclasses + } +} diff --git a/source/ShiftUI/Widgets/MonthCalendar.cs b/source/ShiftUI/Widgets/MonthCalendar.cs new file mode 100644 index 0000000..24d7c8f --- /dev/null +++ b/source/ShiftUI/Widgets/MonthCalendar.cs @@ -0,0 +1,2430 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2004-2006 Novell, Inc. +// +// Authors: +// John BouAntoun [email protected] +// +// REMAINING TODO: +// - get the date_cell_size and title_size to be pixel perfect match of SWF + +using System; +using System.Collections; +using System.ComponentModel; +using System.ComponentModel.Design; +using System.Drawing; +using System.Globalization; +using System.Runtime.InteropServices; +using System.Threading; + +namespace ShiftUI { + [DefaultBindingProperty("SelectionRange")] + [ClassInterface(ClassInterfaceType.AutoDispatch)] + [ComVisible(true)] + [DefaultProperty("SelectionRange")] + [DefaultEvent("DateChanged")] + //[Designer ("ShiftUI.Design.MonthCalendarDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")] + public class MonthCalendar : Widget { + #region Local variables + ArrayList annually_bolded_dates; + ArrayList monthly_bolded_dates; + ArrayList bolded_dates; + Size calendar_dimensions; + Day first_day_of_week; + DateTime max_date; + int max_selection_count; + DateTime min_date; + int scroll_change; + SelectionRange selection_range; + bool show_today; + bool show_today_circle; + bool show_week_numbers; + Color title_back_color; + Color title_fore_color; + DateTime today_date; + bool today_date_set; + Color trailing_fore_color; + Timer timer; + Timer updown_timer; + const int initial_delay = 500; + const int subsequent_delay = 100; + private bool is_year_going_up; + private bool is_year_going_down; + private bool is_mouse_moving_year; + private int year_moving_count; + private bool date_selected_event_pending; + bool right_to_left_layout; + + // internal variables used + internal bool show_year_updown; + internal DateTime current_month; // the month that is being displayed in top left corner of the grid + internal DateTimePicker owner; // used if this control is popped up + internal int button_x_offset; + internal Size button_size; + internal Size title_size; + internal Size date_cell_size; + internal Size calendar_spacing; + internal int divider_line_offset; + internal DateTime clicked_date; + internal Rectangle clicked_rect; + internal bool is_date_clicked; + internal bool is_previous_clicked; + internal bool is_next_clicked; + internal bool is_shift_pressed; + internal DateTime first_select_start_date; + internal int last_clicked_calendar_index; + internal Rectangle last_clicked_calendar_rect; + internal Font bold_font; // Cache the font in FontStyle.Bold + internal StringFormat centered_format; // Cache centered string format + private Point month_title_click_location; + // this is used to see which item was actually clicked on in the beginning + // so that we know which item to fire on timer + // 0: date clicked + // 1: previous clicked + // 2: next clicked + private bool[] click_state; + + + + #endregion // Local variables + + #region Public Constructors + + public MonthCalendar () { + // set up the control painting + SetStyle (Widgetstyles.UserPaint | Widgetstyles.StandardClick, false); + + // mouse down timer + timer = new Timer (); + timer.Interval = 500; + timer.Enabled = false; + + // initialise default values + DateTime now = DateTime.Now.Date; + selection_range = new SelectionRange (now, now); + today_date = now; + current_month = new DateTime (now.Year , now.Month, 1); + + // iniatialise local members + annually_bolded_dates = null; + bolded_dates = null; + calendar_dimensions = new Size (1,1); + first_day_of_week = Day.Default; + max_date = new DateTime (9998, 12, 31); + max_selection_count = 7; + min_date = new DateTime (1753, 1, 1); + monthly_bolded_dates = null; + scroll_change = 0; + show_today = true; + show_today_circle = true; + show_week_numbers = false; + title_back_color = ThemeEngine.Current.ColorActiveCaption; + title_fore_color = ThemeEngine.Current.ColorActiveCaptionText; + today_date_set = false; + trailing_fore_color = SystemColors.GrayText; + bold_font = new Font (Font, Font.Style | FontStyle.Bold); + centered_format = new StringFormat (StringFormat.GenericTypographic); + centered_format.FormatFlags = centered_format.FormatFlags | StringFormatFlags.MeasureTrailingSpaces | StringFormatFlags.NoWrap | StringFormatFlags.FitBlackBox; + centered_format.FormatFlags &= ~StringFormatFlags.NoClip; + centered_format.LineAlignment = StringAlignment.Center; + centered_format.Alignment = StringAlignment.Center; + + // Set default values + ForeColor = SystemColors.WindowText; + BackColor = ThemeEngine.Current.ColorWindow; + + // intiailise internal variables used + button_x_offset = 5; + button_size = new Size (22, 17); + // default settings based on 8.25 pt San Serif Font + // Not sure of algorithm used to establish this + date_cell_size = new Size (24, 16); // default size at san-serif 8.25 + divider_line_offset = 4; + calendar_spacing = new Size (4, 5); // horiz and vert spacing between months in a calendar grid + + // set some state info + clicked_date = now; + is_date_clicked = false; + is_previous_clicked = false; + is_next_clicked = false; + is_shift_pressed = false; + click_state = new bool [] {false, false, false}; + first_select_start_date = now; + month_title_click_location = Point.Empty; + + // set up context menus + SetUpTodayMenu (); + SetUpMonthMenu (); + + // event handlers + timer.Tick += new EventHandler (TimerHandler); + MouseMove += new MouseEventHandler (MouseMoveHandler); + MouseDown += new MouseEventHandler (MouseDownHandler); + KeyDown += new KeyEventHandler (KeyDownHandler); + MouseUp += new MouseEventHandler (MouseUpHandler); + KeyUp += new KeyEventHandler (KeyUpHandler); + + // this replaces paint so call the control version + base.Paint += new PaintEventHandler (PaintHandler); + + Size = DefaultSize; + } + + // called when this control is added to date time picker + internal MonthCalendar (DateTimePicker owner) : this () { + this.owner = owner; + this.is_visible = false; + this.Size = this.DefaultSize; + } + + #endregion // Public Constructors + + #region Public Instance Properties + + // dates to make bold on calendar annually (recurring) + [Localizable (true)] + public DateTime [] AnnuallyBoldedDates { + set { + if (annually_bolded_dates == null) + annually_bolded_dates = new ArrayList (value); + else { + annually_bolded_dates.Clear (); + annually_bolded_dates.AddRange (value); + } + + UpdateBoldedDates (); + } + get { + if (annually_bolded_dates == null || annually_bolded_dates.Count == 0) { + return new DateTime [0]; + } + DateTime [] result = new DateTime [annually_bolded_dates.Count]; + annually_bolded_dates.CopyTo (result); + return result; + } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public override Image BackgroundImage { + get { + return base.BackgroundImage; + } + set { + base.BackgroundImage = value; + } + } + + //[EditorBrowsable (EditorBrowsableState.Never)] + [Browsable (false)] + public override ImageLayout BackgroundImageLayout { + get { + return base.BackgroundImageLayout; + } + set { + base.BackgroundImageLayout = value; + } + } + + // the back color for the main part of the calendar + public override Color BackColor { + set { + base.BackColor = value; + } + get { + return base.BackColor; + } + } + + // specific dates to make bold on calendar (non-recurring) + [Localizable (true)] + public DateTime[] BoldedDates { + set { + if (bolded_dates == null) { + bolded_dates = new ArrayList (value); + } else { + bolded_dates.Clear (); + bolded_dates.AddRange (value); + } + + UpdateBoldedDates (); + } + get { + if (bolded_dates == null || bolded_dates.Count == 0) + return new DateTime [0]; + + DateTime [] result = new DateTime [bolded_dates.Count]; + bolded_dates.CopyTo (result); + return result; + } + } + + // the configuration of the monthly grid display - only allowed to display at most, + // 1 calendar year at a time, will be trimmed to fit it properly + [Localizable (true)] + public Size CalendarDimensions { + set { + if (value.Width < 0 || value.Height < 0) { + throw new ArgumentException (); + } + if (calendar_dimensions != value) { + // squeeze the grid into 1 calendar year + if (value.Width * value.Height > 12) { + // iteratively reduce the largest dimension till our + // product is less than 12 + if (value.Width > 12 && value.Height > 12) { + calendar_dimensions = new Size (4, 3); + } else if (value.Width > 12) { + for (int i = 12; i > 0; i--) { + if (i * value.Height <= 12) { + calendar_dimensions = new Size (i, value.Height); + break; + } + } + } else if (value.Height > 12) { + for (int i = 12; i > 0; i--) { + if (i * value.Width <= 12) { + calendar_dimensions = new Size (value.Width, i); + break; + } + } + } + } else { + calendar_dimensions = value; + } + this.Invalidate (); + } + } + get { + return calendar_dimensions; + } + } + + //[EditorBrowsable (EditorBrowsableState.Never)] + protected override bool DoubleBuffered { + get { + return base.DoubleBuffered; + } + set { + base.DoubleBuffered = value; + } + } + + // the first day of the week to display + [Localizable (true)] + [DefaultValue (Day.Default)] + public Day FirstDayOfWeek { + set { + if (first_day_of_week != value) { + first_day_of_week = value; + this.Invalidate (); + } + } + get { + return first_day_of_week; + } + } + + // the fore color for the main part of the calendar + public override Color ForeColor { + set { + base.ForeColor = value; + } + get { + return base.ForeColor; + } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new ImeMode ImeMode { + get { return base.ImeMode; } + set { base.ImeMode = value; } + } + + // the maximum date allowed to be selected on this month calendar + public DateTime MaxDate { + set { + if (value < MinDate) { + string msg = string.Format (CultureInfo.CurrentCulture, + "Value of '{0}' is not valid for 'MaxDate'. 'MaxDate' " + + "must be greater than or equal to MinDate.", + value.ToString ("d", CultureInfo.CurrentCulture)); + throw new ArgumentOutOfRangeException ("MaxDate", + msg); + } + + if (max_date == value) + return; + + max_date = value; + + if (max_date < selection_range.Start || max_date < selection_range.End) { + DateTime start = max_date < selection_range.Start ? max_date : selection_range.Start; + DateTime end = max_date < selection_range.End ? max_date : selection_range.End; + SelectionRange = new SelectionRange (start, end); + } + } + get { + return max_date; + } + } + + // the maximum number of selectable days + [DefaultValue (7)] + public int MaxSelectionCount { + set { + if (value < 1) { + string msg = string.Format (CultureInfo.CurrentCulture, + "Value of '{0}' is not valid for 'MaxSelectionCount'. " + + "'MaxSelectionCount' must be greater than or equal to {1}.", + value, 1); + throw new ArgumentOutOfRangeException ("MaxSelectionCount", + msg); + } + + // can't set selectioncount less than already selected dates + if ((SelectionEnd - SelectionStart).Days > value) { + throw new ArgumentException(); + } + + if (max_selection_count != value) { + max_selection_count = value; + this.OnUIAMaxSelectionCountChanged (); + } + } + get { + return max_selection_count; + } + } + + // the minimum date allowed to be selected on this month calendar + public DateTime MinDate { + set { + DateTime absoluteMinDate = new DateTime (1753, 1, 1); + + if (value < absoluteMinDate) { + string msg = string.Format (CultureInfo.CurrentCulture, + "Value of '{0}' is not valid for 'MinDate'. 'MinDate' " + + "must be greater than or equal to {1}.", + value.ToString ("d", CultureInfo.CurrentCulture), + absoluteMinDate.ToString ("d", CultureInfo.CurrentCulture)); + throw new ArgumentOutOfRangeException ("MinDate", + msg); + } + + if (value > MaxDate) { + string msg = string.Format (CultureInfo.CurrentCulture, + "Value of '{0}' is not valid for 'MinDate'. 'MinDate' " + + "must be less than MaxDate.", + value.ToString ("d", CultureInfo.CurrentCulture)); + throw new ArgumentOutOfRangeException ("MinDate", + msg); + } + + if (min_date == value) + return; + + min_date = value; + + if (min_date > selection_range.Start || min_date > selection_range.End) { + DateTime start = min_date > selection_range.Start ? min_date : selection_range.Start; + DateTime end = min_date > selection_range.End ? min_date : selection_range.End; + SelectionRange = new SelectionRange (start, end); + } + } + get { + return min_date; + } + } + + // dates to make bold on calendar monthly (recurring) + [Localizable (true)] + public DateTime[] MonthlyBoldedDates { + set { + if (monthly_bolded_dates == null) { + monthly_bolded_dates = new ArrayList (value); + } else { + monthly_bolded_dates.Clear (); + monthly_bolded_dates.AddRange (value); + } + + UpdateBoldedDates (); + } + get { + if (monthly_bolded_dates == null || monthly_bolded_dates.Count == 0) + return new DateTime [0]; + + DateTime [] result = new DateTime [monthly_bolded_dates.Count]; + monthly_bolded_dates.CopyTo (result); + return result; + } + } + + //[EditorBrowsable (EditorBrowsableState.Never)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + [Browsable (false)] + // Padding should not have any effect on the appearance of MonthCalendar. + public new Padding Padding { + get { + return base.Padding; + } + set { + base.Padding = value; + } + } + + [DefaultValue (false)] + [Localizable (true)] + public virtual bool RightToLeftLayout { + get { + return right_to_left_layout; + } + set { + right_to_left_layout = value; + } + } + + // the ammount by which to scroll this calendar by + [DefaultValue (0)] + public int ScrollChange { + set { + if (value < 0 || value > 20000) { + throw new ArgumentException(); + } + + if (scroll_change != value) { + scroll_change = value; + } + } + get { + return scroll_change; + } + } + + + // the last selected date + [Browsable (false)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public DateTime SelectionEnd { + set { + if (value < MinDate || value > MaxDate) { + throw new ArgumentException(); + } + + if (SelectionRange.End != value) { + DateTime old_end = SelectionRange.End; + // make sure the end obeys the max selection range count + if (value < SelectionRange.Start) { + SelectionRange.Start = value; + } + if (value.AddDays((MaxSelectionCount-1)*-1) > SelectionRange.Start) { + SelectionRange.Start = value.AddDays((MaxSelectionCount-1)*-1); + } + SelectionRange.End = value; + this.InvalidateDateRange (new SelectionRange (old_end, SelectionRange.End)); + this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd)); + this.OnUIASelectionChanged (); + } + } + get { + return SelectionRange.End; + } + } + + [Bindable(true)] + // the range of selected dates + public SelectionRange SelectionRange { + set { + if (selection_range != value) { + if (value.Start < MinDate) + throw new ArgumentException ("SelectionStart cannot be less than MinDate"); + else if (value.End > MaxDate) + throw new ArgumentException ("SelectionEnd cannot be greated than MaxDate"); + + SelectionRange old_range = selection_range; + + // make sure the end obeys the max selection range count + if (value.End.AddDays((MaxSelectionCount-1)*-1) > value.Start) { + selection_range = new SelectionRange (value.End.AddDays((MaxSelectionCount-1)*-1), value.End); + } else { + selection_range = value; + } + SelectionRange visible_range = this.GetDisplayRange(true); + if(visible_range.Start > selection_range.End) { + this.current_month = new DateTime (selection_range.Start.Year, selection_range.Start.Month, 1); + this.Invalidate (); + } else if (visible_range.End < selection_range.Start) { + int year_diff = selection_range.End.Year - visible_range.End.Year; + int month_diff = selection_range.End.Month - visible_range.End.Month; + this.current_month = current_month.AddMonths(year_diff * 12 + month_diff); + this.Invalidate (); + } + // invalidate the selected range changes + DateTime diff_start = old_range.Start; + DateTime diff_end = old_range.End; + // now decide which region is greated + if (old_range.Start > SelectionRange.Start) { + diff_start = SelectionRange.Start; + } else if (old_range.Start == SelectionRange.Start) { + if (old_range.End < SelectionRange.End) { + diff_start = old_range.End; + } else { + diff_start = SelectionRange.End; + } + } + if (old_range.End < SelectionRange.End) { + diff_end = SelectionRange.End; + } else if (old_range.End == SelectionRange.End) { + if (old_range.Start < SelectionRange.Start) { + diff_end = SelectionRange.Start; + } else { + diff_end = old_range.Start; + } + } + + + // invalidate the region required + SelectionRange new_range = new SelectionRange (diff_start, diff_end); + if (new_range.End != old_range.End || new_range.Start != old_range.Start) + this.InvalidateDateRange (new_range); + // raise date changed event + this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd)); + this.OnUIASelectionChanged (); + } + } + get { + return selection_range; + } + } + + // the first selected date + [Browsable (false)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public DateTime SelectionStart { + set { + if (value < MinDate || value > MaxDate) { + throw new ArgumentException(); + } + + if (SelectionRange.Start != value) { + // make sure the end obeys the max selection range count + if (value > SelectionRange.End) { + SelectionRange.End = value; + } else if (value.AddDays(MaxSelectionCount-1) < SelectionRange.End) { + SelectionRange.End = value.AddDays(MaxSelectionCount-1); + } + SelectionRange.Start = value; + DateTime new_month = new DateTime(value.Year, value.Month, 1); + if (current_month != new_month) + current_month = new_month; + + this.Invalidate (); + this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd)); + this.OnUIASelectionChanged (); + } + } + get { + return selection_range.Start; + } + } + + // whether or not to show todays date + [DefaultValue (true)] + public bool ShowToday { + set { + if (show_today != value) { + show_today = value; + this.Invalidate (); + } + } + get { + return show_today; + } + } + + // whether or not to show a circle around todays date + [DefaultValue (true)] + public bool ShowTodayCircle { + set { + if (show_today_circle != value) { + show_today_circle = value; + this.Invalidate (); + } + } + get { + return show_today_circle; + } + } + + // whether or not to show numbers beside each row of weeks + [Localizable (true)] + [DefaultValue (false)] + public bool ShowWeekNumbers { + set { + if (show_week_numbers != value) { + show_week_numbers = value; + // The values here don't matter, SetBoundsCore will calculate its own + SetBoundsCore (Left, Top, Width, Height, BoundsSpecified.Width); + this.Invalidate (); + } + } + get { + return show_week_numbers; + } + } + + // the rectangle size required to render one month based on current font + [Browsable (false)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public Size SingleMonthSize { + get { + if (this.Font == null) { + throw new InvalidOperationException(); + } + + // multiplier is sucked out from the font size + int multiplier = this.Font.Height; + + // establis how many columns and rows we have + int column_count = (ShowWeekNumbers) ? 8 : 7; + int row_count = 7; // not including the today date + + // set the date_cell_size and the title_size + date_cell_size = new Size ((int) Math.Ceiling (1.8 * multiplier), multiplier); + title_size = new Size ((date_cell_size.Width * column_count), 2 * multiplier); + + return new Size (column_count * date_cell_size.Width, row_count * date_cell_size.Height + title_size.Height); + } + } + + [Localizable(false)] + //[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public new Size Size { + get { + return base.Size; + } + set { + base.Size = value; + } + } + + [Bindable(false)] + [Browsable(false)] + //[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public override string Text { + get { + return base.Text; + } + set { + base.Text = value; + } + } + + // the back color for the title of the calendar and the + // forecolor for the day of the week text + public Color TitleBackColor { + set { + if (title_back_color != value) { + title_back_color = value; + this.Invalidate (); + } + } + get { + return title_back_color; + } + } + + // the fore color for the title of the calendar + public Color TitleForeColor { + set { + if (title_fore_color != value) { + title_fore_color = value; + this.Invalidate (); + } + } + get { + return title_fore_color; + } + } + + // the date this calendar is using to refer to today's date + public DateTime TodayDate { + set { + today_date_set = true; + if (today_date != value) { + today_date = value; + this.Invalidate (); + } + } + get { + return today_date; + } + } + + // tells if user specifically set today_date for this control + [Browsable (false)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public bool TodayDateSet { + get { + return today_date_set; + } + } + + // the color used for trailing dates in the calendar + public Color TrailingForeColor { + set { + if (trailing_fore_color != value) { + trailing_fore_color = value; + SelectionRange bounds = this.GetDisplayRange (false); + SelectionRange visible_bounds = this.GetDisplayRange (true); + this.InvalidateDateRange (new SelectionRange (bounds.Start, visible_bounds.Start)); + this.InvalidateDateRange (new SelectionRange (bounds.End, visible_bounds.End)); + } + } + get { + return trailing_fore_color; + } + } + + #endregion // Public Instance Properties + + #region Protected Instance Properties + + // overloaded to allow controll to be windowed for drop down + protected override CreateParams CreateParams { + get { + if (this.owner == null) { + return base.CreateParams; + } else { + CreateParams cp = base.CreateParams; + cp.Style ^= (int) WindowStyles.WS_CHILD; + cp.Style |= (int) WindowStyles.WS_POPUP; + cp.ExStyle |= (int)(WindowExStyles.WS_EX_TOOLWINDOW | WindowExStyles.WS_EX_TOPMOST); + + return cp; + } + } + } + + // not sure what to put in here - just doing a base() call - jba + protected override ImeMode DefaultImeMode { + get { + return base.DefaultImeMode; + } + } + + protected override Padding DefaultMargin { + get { + return new Padding (9); + } + } + + protected override Size DefaultSize { + get { + Size single_month = SingleMonthSize; + // get the width + int width = calendar_dimensions.Width * single_month.Width; + if (calendar_dimensions.Width > 1) { + width += (calendar_dimensions.Width - 1) * calendar_spacing.Width; + } + + // get the height + int height = calendar_dimensions.Height * single_month.Height; + if (this.ShowToday) { + height += date_cell_size.Height + 2; // add the height of the "Today: " ... + } + if (calendar_dimensions.Height > 1) { + height += (calendar_dimensions.Height - 1) * calendar_spacing.Height; + } + + // add the 1 pixel boundary + if (width > 0) { + width += 2; + } + if (height > 0) { + height +=2; + } + + return new Size (width, height); + } + } + + #endregion // Protected Instance Properties + + #region Public Instance Methods + + // add a date to the anually bolded date arraylist + public void AddAnnuallyBoldedDate (DateTime date) { + if (annually_bolded_dates == null) + annually_bolded_dates = new ArrayList (); + if (!annually_bolded_dates.Contains (date)) + annually_bolded_dates.Add (date); + } + + // add a date to the normal bolded date arraylist + public void AddBoldedDate (DateTime date) { + if (bolded_dates == null) + bolded_dates = new ArrayList (); + if (!bolded_dates.Contains (date)) + bolded_dates.Add (date); + } + + // add a date to the anually monthly date arraylist + public void AddMonthlyBoldedDate (DateTime date) { + if (monthly_bolded_dates == null) + monthly_bolded_dates = new ArrayList (); + if (!monthly_bolded_dates.Contains (date)) + monthly_bolded_dates.Add (date); + } + + // if visible = true, return only the dates of full months, else return all dates visible + public SelectionRange GetDisplayRange (bool visible) { + DateTime start; + DateTime end; + start = new DateTime (current_month.Year, current_month.Month, 1); + end = start.AddMonths (calendar_dimensions.Width * calendar_dimensions.Height); + end = end.AddDays(-1); + + // process all visible dates if needed (including the grayed out dates + if (!visible) { + start = GetFirstDateInMonthGrid (start); + end = GetLastDateInMonthGrid (end); + } + + return new SelectionRange (start, end); + } + + // HitTest overload that recieve's x and y co-ordinates as separate ints + public HitTestInfo HitTest (int x, int y) { + return HitTest (new Point (x, y)); + } + + // returns a HitTestInfo for MonthCalendar element's under the specified point + public HitTestInfo HitTest (Point point) { + return HitTest (point, out last_clicked_calendar_index, out last_clicked_calendar_rect); + } + + // clears all the annually bolded dates + public void RemoveAllAnnuallyBoldedDates () { + if (annually_bolded_dates != null) + annually_bolded_dates.Clear (); + } + + // clears all the normal bolded dates + public void RemoveAllBoldedDates () { + if (bolded_dates != null) + bolded_dates.Clear (); + } + + // clears all the monthly bolded dates + public void RemoveAllMonthlyBoldedDates () { + if (monthly_bolded_dates != null) + monthly_bolded_dates.Clear (); + } + + // clears the specified annually bolded date (only compares day and month) + // only removes the first instance of the match + public void RemoveAnnuallyBoldedDate (DateTime date) { + if (annually_bolded_dates == null) + return; + + for (int i = 0; i < annually_bolded_dates.Count; i++) { + DateTime dt = (DateTime) annually_bolded_dates [i]; + if (dt.Day == date.Day && dt.Month == date.Month) { + annually_bolded_dates.RemoveAt (i); + return; + } + } + } + + // clears all the normal bolded date + // only removes the first instance of the match + public void RemoveBoldedDate (DateTime date) { + if (bolded_dates == null) + return; + + for (int i = 0; i < bolded_dates.Count; i++) { + DateTime dt = (DateTime) bolded_dates [i]; + if (dt.Year == date.Year && dt.Month == date.Month && dt.Day == date.Day) { + bolded_dates.RemoveAt (i); + return; + } + } + } + + // clears the specified monthly bolded date (only compares day and month) + // only removes the first instance of the match + public void RemoveMonthlyBoldedDate (DateTime date) { + if (monthly_bolded_dates == null) + return; + + for (int i = 0; i < monthly_bolded_dates.Count; i++) { + DateTime dt = (DateTime) monthly_bolded_dates [i]; + if (dt.Day == date.Day && dt.Month == date.Month) { + monthly_bolded_dates.RemoveAt (i); + return; + } + } + } + + // sets the calendar_dimensions. If product is > 12, the larger dimension is reduced to make product < 12 + public void SetCalendarDimensions(int x, int y) { + this.CalendarDimensions = new Size(x, y); + } + + // sets the currently selected date as date + public void SetDate (DateTime date) { + this.SetSelectionRange (date.Date, date.Date); + } + + // utility method set the SelectionRange property using individual dates + public void SetSelectionRange (DateTime date1, DateTime date2) { + this.SelectionRange = new SelectionRange(date1, date2); + } + + public override string ToString () { + return this.GetType().Name + ", " + this.SelectionRange.ToString (); + } + + // usually called after an AddBoldedDate method is called + // formats monthly and daily bolded dates according to the current calendar year + public void UpdateBoldedDates () { + Invalidate (); + } + + #endregion // Public Instance Methods + + #region Protected Instance Methods + + // not sure why this needs to be overriden + protected override void CreateHandle () { + base.CreateHandle (); + } + + // not sure why this needs to be overriden + protected override void Dispose (bool disposing) { + base.Dispose (disposing); + } + + // Handle arrow keys + protected override bool IsInputKey (Keys keyData) { + switch (keyData) { + case Keys.Up: + case Keys.Down: + case Keys.Right: + case Keys.Left: + return true; + default: + break; + } + + return base.IsInputKey (keyData); + } + + // not sure why this needs to be overriden + protected override void OnBackColorChanged (EventArgs e) { + base.OnBackColorChanged (e); + this.Invalidate (); + } + + // raises the date changed event + protected virtual void OnDateChanged (DateRangeEventArgs drevent) { + DateRangeEventHandler eh = (DateRangeEventHandler) (Events [DateChangedEvent]); + if (eh != null) + eh (this, drevent); + } + + // raises the DateSelected event + protected virtual void OnDateSelected (DateRangeEventArgs drevent) { + DateRangeEventHandler eh = (DateRangeEventHandler) (Events [DateSelectedEvent]); + if (eh != null) + eh (this, drevent); + } + + protected override void OnFontChanged (EventArgs e) { + // Update size based on new font's space requirements + Size = new Size (CalendarDimensions.Width * SingleMonthSize.Width, + CalendarDimensions.Height * SingleMonthSize.Height); + bold_font = new Font (Font, Font.Style | FontStyle.Bold); + base.OnFontChanged (e); + } + + protected override void OnForeColorChanged (EventArgs e) { + base.OnForeColorChanged (e); + } + + protected override void OnHandleCreated (EventArgs e) { + base.OnHandleCreated (e); + } + + protected override void OnHandleDestroyed (EventArgs e) + { + base.OnHandleDestroyed (e); + } + + //[EditorBrowsable (EditorBrowsableState.Advanced)] + protected virtual void OnRightToLeftLayoutChanged (EventArgs e) { + EventHandler eh = (EventHandler) (Events [RightToLeftLayoutChangedEvent]); + if (eh != null) + eh (this, e); + } + + // i think this is overriden to not allow the control to be changed to an arbitrary size + protected override void SetBoundsCore (int x, int y, int width, int height, BoundsSpecified specified) + { + // only allow sizes = default size to be set + Size default_size = DefaultSize; + Size min_size = default_size; + Size max_size = new Size (default_size.Width + SingleMonthSize.Width + calendar_spacing.Width, + default_size.Height + SingleMonthSize.Height + calendar_spacing.Height); + int x_mid_point = (max_size.Width + min_size.Width)/2; + int y_mid_point = (max_size.Height + min_size.Height)/2; + + if (width < x_mid_point) { + width = min_size.Width; + } else { + width = max_size.Width; + } + if (height < y_mid_point) { + height = min_size.Height; + } else { + height = max_size.Height; + } + base.SetBoundsCore (x, y, width, height, specified); + } + + protected override void WndProc (ref Message m) { + base.WndProc (ref m); + } + + #endregion // Protected Instance Methods + + #region public events + static object DateChangedEvent = new object (); + static object DateSelectedEvent = new object (); + static object RightToLeftLayoutChangedEvent = new object (); + + // fired when the date is changed (either explicitely or implicitely) + // when navigating the month selector + public event DateRangeEventHandler DateChanged { + add { Events.AddHandler (DateChangedEvent, value); } + remove { Events.RemoveHandler (DateChangedEvent, value); } + } + + // fired when the user explicitely clicks on date to select it + public event DateRangeEventHandler DateSelected { + add { Events.AddHandler (DateSelectedEvent, value); } + remove { Events.RemoveHandler (DateSelectedEvent, value); } + } + + [Browsable(false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler BackgroundImageChanged { + add { base.BackgroundImageChanged += value; } + remove { base.BackgroundImageChanged -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler BackgroundImageLayoutChanged + { + add { base.BackgroundImageLayoutChanged += value;} + remove { base.BackgroundImageLayoutChanged += value;} + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler Click { + add {base.Click += value; } + remove {base.Click -= value;} + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler DoubleClick { + add {base.DoubleClick += value; } + remove {base.DoubleClick -= value; } + } + + [Browsable(false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler ImeModeChanged { + add { base.ImeModeChanged += value; } + remove { base.ImeModeChanged -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event MouseEventHandler MouseClick { + add { base.MouseClick += value;} + remove { base.MouseClick -= value;} + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event MouseEventHandler MouseDoubleClick { + add { base.MouseDoubleClick += value; } + remove { base.MouseDoubleClick -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler PaddingChanged { + add {base.PaddingChanged += value;} + remove {base.PaddingChanged -= value;} + } + + // XXX check this out + [Browsable(false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event PaintEventHandler Paint; + + public event EventHandler RightToLeftLayoutChanged { + add {Events.AddHandler (RightToLeftLayoutChangedEvent, value);} + remove {Events.RemoveHandler (RightToLeftLayoutChangedEvent, value);} + } + + [Browsable(false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler TextChanged { + add { base.TextChanged += value; } + remove { base.TextChanged -= value; } + } + #endregion // public events + + #region internal properties + + private void AddYears (int years, bool fast) + { + DateTime newDate; + if (fast) { + if (!(CurrentMonth.Year + years * 5 > MaxDate.Year)) { + newDate = CurrentMonth.AddYears (years * 5); + if (MaxDate >= newDate && MinDate <= newDate) { + CurrentMonth = newDate; + return; + } + } + } + if (!(CurrentMonth.Year + years > MaxDate.Year)) { + newDate = CurrentMonth.AddYears (years); + if (MaxDate >= newDate && MinDate <= newDate) { + CurrentMonth = newDate; + } + } + } + + internal bool IsYearGoingUp { + get { + return is_year_going_up; + } + set { + if (value) { + is_year_going_down = false; + year_moving_count = (is_year_going_up ? year_moving_count + 1 : 1); + if (is_year_going_up) + year_moving_count++; + else { + year_moving_count = 1; + } + AddYears (1, year_moving_count > 10); + if (is_mouse_moving_year) + StartHideTimer (); + } else { + year_moving_count = 0; + } + is_year_going_up = value; + Invalidate (); + } + } + + internal bool IsYearGoingDown { + get { + return is_year_going_down; + } + set + { + if (value) { + is_year_going_up = false; + year_moving_count = (is_year_going_down ? year_moving_count + 1 : 1); + if (is_year_going_down) + year_moving_count++; + else { + year_moving_count = 1; + } + AddYears (-1, year_moving_count > 10); + if (is_mouse_moving_year) + StartHideTimer (); + } else { + year_moving_count = 0; + } + is_year_going_down = value; + Invalidate (); + } + } + + internal bool ShowYearUpDown { + get { + return show_year_updown; + } + set { + if (show_year_updown != value) { + show_year_updown = value; + Invalidate (); + } + } + } + + internal DateTime CurrentMonth { + set { + // only interested in if the month (not actual date) has change + if (value < MinDate || value > MaxDate) { + return; + } + + if (value.Month != current_month.Month || + value.Year != current_month.Year) { + this.SelectionRange = new SelectionRange( + this.SelectionStart.Add(value.Subtract(current_month)), + this.SelectionEnd.Add(value.Subtract(current_month))); + current_month = value; + UpdateBoldedDates(); + this.Invalidate(); + } + } + get { + return current_month; + } + } + + #endregion // internal properties + + #region internal/private methods + internal HitTestInfo HitTest ( + Point point, + out int calendar_index, + out Rectangle calendar_rect) { + // start by initialising the ref parameters + calendar_index = -1; + calendar_rect = Rectangle.Empty; + + // before doing all the hard work, see if the today's date wasn't clicked + Rectangle today_rect = new Rectangle ( + ClientRectangle.X, + ClientRectangle.Bottom - date_cell_size.Height, + 7 * date_cell_size.Width, + date_cell_size.Height); + if (today_rect.Contains (point) && this.ShowToday) { + return new HitTestInfo(HitArea.TodayLink, point, DateTime.Now); + } + + Size month_size = SingleMonthSize; + // define calendar rect's that this thing can land in + Rectangle[] calendars = new Rectangle [CalendarDimensions.Width * CalendarDimensions.Height]; + for (int i=0; i < CalendarDimensions.Width * CalendarDimensions.Height; i ++) { + if (i == 0) { + calendars[i] = new Rectangle ( + new Point (ClientRectangle.X + 1, ClientRectangle.Y + 1), + month_size); + } else { + // calendar on the next row + if (i % CalendarDimensions.Width == 0) { + calendars[i] = new Rectangle ( + new Point (calendars[i-CalendarDimensions.Width].X, calendars[i-CalendarDimensions.Width].Bottom + calendar_spacing.Height), + month_size); + } else { + // calendar on the next column + calendars[i] = new Rectangle ( + new Point (calendars[i-1].Right + calendar_spacing.Width, calendars[i-1].Y), + month_size); + } + } + } + + // through each trying to find a match + for (int i = 0; i < calendars.Length ; i++) { + if (calendars[i].Contains (point)) { + // check the title section + Rectangle title_rect = new Rectangle ( + calendars[i].Location, + title_size); + if (title_rect.Contains (point) ) { + // make sure it's not a previous button + if (i == 0) { + Rectangle button_rect = new Rectangle( + new Point (calendars[i].X + button_x_offset, (title_size.Height - button_size.Height)/2), + button_size); + if (button_rect.Contains (point)) { + return new HitTestInfo (HitArea.PrevMonthButton, point, new DateTime (1, 1, 1)); + } + } + // make sure it's not the next button + if (i % CalendarDimensions.Height == 0 && i % CalendarDimensions.Width == calendar_dimensions.Width - 1) { + Rectangle button_rect = new Rectangle( + new Point (calendars[i].Right - button_x_offset - button_size.Width, (title_size.Height - button_size.Height)/2), + button_size); + if (button_rect.Contains (point)) { + return new HitTestInfo (HitArea.NextMonthButton, point, new DateTime (1, 1, 1)); + } + } + + // indicate which calendar and month it was + calendar_index = i; + calendar_rect = calendars[i]; + + // make sure it's not the month or the year of the calendar + if (GetMonthNameRectangle (title_rect, i).Contains (point)) { + return new HitTestInfo (HitArea.TitleMonth, point, new DateTime (1, 1, 1)); + } + Rectangle year, up, down; + GetYearNameRectangles (title_rect, i, out year, out up, out down); + if (year.Contains (point)) { + return new HitTestInfo (HitArea.TitleYear, point, new DateTime (1, 1, 1), HitAreaExtra.YearRectangle); + } else if (up.Contains (point)) { + return new HitTestInfo (HitArea.TitleYear, point, new DateTime (1, 1, 1), HitAreaExtra.UpButton); + } else if (down.Contains (point)) { + return new HitTestInfo (HitArea.TitleYear, point, new DateTime (1, 1, 1), HitAreaExtra.DownButton); + } + + // return the hit test in the title background + return new HitTestInfo (HitArea.TitleBackground, point, new DateTime (1, 1, 1)); + } + + Point date_grid_location = new Point (calendars[i].X, title_rect.Bottom); + + // see if it's in the Week numbers + if (ShowWeekNumbers) { + Rectangle weeks_rect = new Rectangle ( + date_grid_location, + new Size (date_cell_size.Width,Math.Max (calendars[i].Height - title_rect.Height, 0))); + if (weeks_rect.Contains (point)) { + return new HitTestInfo(HitArea.WeekNumbers, point, DateTime.Now); + } + + // move the location of the grid over + date_grid_location.X += date_cell_size.Width; + } + + // see if it's in the week names + Rectangle day_rect = new Rectangle ( + date_grid_location, + new Size (Math.Max (calendars[i].Right - date_grid_location.X, 0), date_cell_size.Height)); + if (day_rect.Contains (point)) { + return new HitTestInfo (HitArea.DayOfWeek, point, new DateTime (1, 1, 1)); + } + + // finally see if it was a date that was clicked + Rectangle date_grid = new Rectangle ( + new Point (day_rect.X, day_rect.Bottom), + new Size (day_rect.Width, Math.Max(calendars[i].Bottom - day_rect.Bottom, 0))); + if (date_grid.Contains (point)) { + clicked_rect = date_grid; + // okay so it's inside the grid, get the offset + Point offset = new Point (point.X - date_grid.X, point.Y - date_grid.Y); + int row = offset.Y / date_cell_size.Height; + int col = offset.X / date_cell_size.Width; + // establish our first day of the month + DateTime calendar_month = this.CurrentMonth.AddMonths(i); + DateTime first_day = GetFirstDateInMonthGrid (calendar_month); + DateTime time = first_day.AddDays ((row * 7) + col); + // establish which date was clicked + if (time.Year != calendar_month.Year || time.Month != calendar_month.Month) { + if (time < calendar_month && i == 0) { + return new HitTestInfo (HitArea.PrevMonthDate, point, new DateTime (1, 1, 1), time); + } else if (time > calendar_month && i == CalendarDimensions.Width*CalendarDimensions.Height - 1) { + return new HitTestInfo (HitArea.NextMonthDate, point, new DateTime (1, 1, 1), time); + } + return new HitTestInfo (HitArea.Nowhere, point, new DateTime (1, 1, 1)); + } + return new HitTestInfo(HitArea.Date, point, time); + } + } + } + + return new HitTestInfo (); + } + + // returns the date of the first cell of the specified month grid + internal DateTime GetFirstDateInMonthGrid (DateTime month) { + // convert the first_day_of_week into a DayOfWeekEnum + DayOfWeek first_day = GetDayOfWeek (first_day_of_week); + // find the first day of the month + DateTime first_date_of_month = new DateTime (month.Year, month.Month, 1); + DayOfWeek first_day_of_month = first_date_of_month.DayOfWeek; + // adjust for the starting day of the week + int offset = first_day_of_month - first_day; + if (offset < 0) { + offset += 7; + } + return first_date_of_month.AddDays (-1*offset); + } + + // returns the date of the last cell of the specified month grid + internal DateTime GetLastDateInMonthGrid (DateTime month) + { + DateTime start = GetFirstDateInMonthGrid(month); + return start.AddDays ((7 * 6)-1); + } + + internal bool IsBoldedDate (DateTime date) { + // check bolded dates + if (bolded_dates != null && bolded_dates.Count > 0) { + foreach (DateTime bolded_date in bolded_dates) { + if (bolded_date.Date == date.Date) { + return true; + } + } + } + // check monthly dates + if (monthly_bolded_dates != null && monthly_bolded_dates.Count > 0) { + foreach (DateTime bolded_date in monthly_bolded_dates) { + if (bolded_date.Day == date.Day) { + return true; + } + } + } + // check yearly dates + if (annually_bolded_dates != null && annually_bolded_dates.Count > 0) { + foreach (DateTime bolded_date in annually_bolded_dates) { + if (bolded_date.Month == date.Month && bolded_date.Day == date.Day) { + return true; + } + } + } + + return false; // no match + } + + // initialise the 'go to today' context menu + private void SetUpTodayMenu () { + } + + // initialise the month context menu + private void SetUpMonthMenu () { + + } + + // returns the first date of the month + private DateTime GetFirstDateInMonth (DateTime date) { + return new DateTime (date.Year, date.Month, 1); + } + + // returns the last date of the month + private DateTime GetLastDateInMonth (DateTime date) { + return new DateTime (date.Year, date.Month, 1).AddMonths(1).AddDays(-1); + } + + // called in response to users seletion with shift key + private void AddTimeToSelection (int delta, bool isDays) + { + DateTime cursor_point; + DateTime end_point; + // okay we add the period to the date that is not the same as the + // start date when shift was first clicked. + if (SelectionStart != first_select_start_date) { + cursor_point = SelectionStart; + } else { + cursor_point = SelectionEnd; + } + // add the days + if (isDays) { + end_point = cursor_point.AddDays (delta); + } else { + // delta must be months + end_point = cursor_point.AddMonths (delta); + } + // set the new selection range + SelectionRange range = new SelectionRange (first_select_start_date, end_point); + if (range.Start.AddDays (MaxSelectionCount-1) < range.End) { + // okay the date is beyond what is allowed, lets set the maximum we can + if (range.Start != first_select_start_date) { + range.Start = range.End.AddDays ((MaxSelectionCount-1)*-1); + } else { + range.End = range.Start.AddDays (MaxSelectionCount-1); + } + } + + // Avoid re-setting SelectionRange to the same value and fire an extra DateChanged event + if (range.Start != selection_range.Start || range.End != selection_range.End) + SelectionRange = range; + } + + // attempts to add the date to the selection without throwing exception + private void SelectDate (DateTime date) { + // try and add the new date to the selction range + SelectionRange range = null; + if (is_shift_pressed || (click_state [0])) { + range = new SelectionRange (first_select_start_date, date); + if (range.Start.AddDays (MaxSelectionCount-1) < range.End) { + // okay the date is beyond what is allowed, lets set the maximum we can + if (range.Start != first_select_start_date) { + range.Start = range.End.AddDays ((MaxSelectionCount-1)*-1); + } else { + range.End = range.Start.AddDays (MaxSelectionCount-1); + } + } + } else { + if (date >= MinDate && date <= MaxDate) { + range = new SelectionRange (date, date); + first_select_start_date = date; + } + } + + // Only set if we re actually getting a different range (avoid an extra DateChanged event) + if (range != null && range.Start != selection_range.Start || range.End != selection_range.End) + SelectionRange = range; + } + + // gets the week of the year + internal int GetWeekOfYear (DateTime date) { + // convert the first_day_of_week into a DayOfWeekEnum + DayOfWeek first_day = GetDayOfWeek (first_day_of_week); + // find the first day of the year + DayOfWeek first_day_of_year = new DateTime (date.Year, 1, 1).DayOfWeek; + // adjust for the starting day of the week + int offset = first_day_of_year - first_day; + int week = ((date.DayOfYear + offset) / 7) + 1; + return week; + } + + // convert a Day enum into a DayOfWeek enum + internal DayOfWeek GetDayOfWeek (Day day) { + if (day == Day.Default) { + return Thread.CurrentThread.CurrentCulture.DateTimeFormat.FirstDayOfWeek; + } else { + return (DayOfWeek) DayOfWeek.Parse (typeof (DayOfWeek), day.ToString ()); + } + } + + // returns the rectangle for themonth name + internal Rectangle GetMonthNameRectangle (Rectangle title_rect, int calendar_index) { + DateTime this_month = this.current_month.AddMonths (calendar_index); + Size title_text_size = TextRenderer.MeasureString (this_month.ToString ("MMMM yyyy"), this.Font).ToSize (); + Size month_size = TextRenderer.MeasureString (this_month.ToString ("MMMM"), this.Font).ToSize (); + // return only the month name part of that + return new Rectangle ( + new Point ( + title_rect.X + ((title_rect.Width - title_text_size.Width)/2), + title_rect.Y + ((title_rect.Height - title_text_size.Height)/2)), + month_size); + } + + internal void GetYearNameRectangles (Rectangle title_rect, int calendar_index, out Rectangle year_rect, out Rectangle up_rect, out Rectangle down_rect) + { + DateTime this_month = this.current_month.AddMonths (calendar_index); + SizeF title_text_size = TextRenderer.MeasureString (this_month.ToString ("MMMM yyyy"), this.bold_font, int.MaxValue, centered_format); + SizeF year_size = TextRenderer.MeasureString (this_month.ToString ("yyyy"), this.bold_font, int.MaxValue, centered_format); + // find out how much space the title took + RectangleF text_rect = new RectangleF ( + new PointF ( + title_rect.X + ((title_rect.Width - title_text_size.Width) / 2), + title_rect.Y + ((title_rect.Height - title_text_size.Height) / 2)), + title_text_size); + // return only the rect of the year + year_rect = new Rectangle ( + new Point ( + ((int)(text_rect.Right - year_size.Width + 1)), + (int)text_rect.Y), + new Size ((int)(year_size.Width + 1), (int)(year_size.Height + 1))); + + year_rect.Inflate (0, 1); + up_rect = new Rectangle (); + up_rect.Location = new Point (year_rect.X + year_rect.Width + 2, year_rect.Y); + up_rect.Size = new Size (16, year_rect.Height / 2); + down_rect = new Rectangle (); + down_rect.Location = new Point (up_rect.X, up_rect.Y + up_rect.Height + 1); + down_rect.Size = up_rect.Size; + } + + // returns the rectangle for the year in the title + internal Rectangle GetYearNameRectangle (Rectangle title_rect, int calendar_index) { + Rectangle result, discard; + GetYearNameRectangles (title_rect, calendar_index, out result, out discard, out discard); + return result; + } + + // determine if date is allowed to be drawn in month + internal bool IsValidWeekToDraw (DateTime month, DateTime date, int row, int col) { + DateTime tocheck = month.AddMonths (-1); + if ((month.Year == date.Year && month.Month == date.Month) || + (tocheck.Year == date.Year && tocheck.Month == date.Month)) { + return true; + } + + // check the railing dates (days in the month after the last month in grid) + if (row == CalendarDimensions.Height - 1 && col == CalendarDimensions.Width - 1) { + tocheck = month.AddMonths (1); + return (tocheck.Year == date.Year && tocheck.Month == date.Month) ; + } + + return false; + } + + // set one item clicked and all others off + private void SetItemClick(HitTestInfo hti) + { + switch(hti.HitArea) { + case HitArea.NextMonthButton: + this.is_previous_clicked = false; + this.is_next_clicked = true; + this.is_date_clicked = false; + break; + case HitArea.PrevMonthButton: + this.is_previous_clicked = true; + this.is_next_clicked = false; + this.is_date_clicked = false; + break; + case HitArea.PrevMonthDate: + case HitArea.NextMonthDate: + case HitArea.Date: + this.clicked_date = hti.hit_time; + this.is_previous_clicked = false; + this.is_next_clicked = false; + this.is_date_clicked = true; + break; + default : + this.is_previous_clicked = false; + this.is_next_clicked = false; + this.is_date_clicked = false; + break; + } + } + + // called when today context menu is clicked + private void TodayMenuItemClickHandler (object sender, EventArgs e) + { + this.SetSelectionRange (DateTime.Now.Date, DateTime.Now.Date); + this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd)); + } + + // called when month context menu is clicked + private void MonthMenuItemClickHandler (object sender, EventArgs e) { + MenuItem item = sender as MenuItem; + if (item != null && month_title_click_location != Point.Empty) { + // establish which month we want to move to + if (item.Parent == null) { + return; + } + int new_month = item.Parent.MenuItems.IndexOf (item) + 1; + if (new_month == 0) { + return; + } + // okay let's establish which calendar was hit + Size month_size = this.SingleMonthSize; + for (int i=0; i < CalendarDimensions.Height; i++) { + for (int j=0; j < CalendarDimensions.Width; j++) { + int month_index = (i * CalendarDimensions.Width) + j; + Rectangle month_rect = new Rectangle ( new Point (0, 0), month_size); + if (j == 0) { + month_rect.X = this.ClientRectangle.X + 1; + } else { + month_rect.X = this.ClientRectangle.X + 1 + ((j)*(month_size.Width+calendar_spacing.Width)); + } + if (i == 0) { + month_rect.Y = this.ClientRectangle.Y + 1; + } else { + month_rect.Y = this.ClientRectangle.Y + 1 + ((i)*(month_size.Height+calendar_spacing.Height)); + } + // see if the point is inside + if (month_rect.Contains (month_title_click_location)) { + DateTime clicked_month = CurrentMonth.AddMonths (month_index); + // get the month that we want to move to + int month_offset = new_month - clicked_month.Month; + + // move forward however more months we need to + this.CurrentMonth = this.CurrentMonth.AddMonths (month_offset); + break; + } + } + } + + // clear the point + month_title_click_location = Point.Empty; + } + } + + // raised on the timer, for mouse hold clicks + private void TimerHandler (object sender, EventArgs e) { + // now find out which area was click + if (this.Capture) { + HitTestInfo hti = this.HitTest (this.PointToClient (MousePosition)); + // see if it was clicked on the prev or next mouse + if (click_state [1] || click_state [2]) { + // invalidate the area where the mouse was last held + DoMouseUp (); + // register the click + if (hti.HitArea == HitArea.PrevMonthButton || + hti.HitArea == HitArea.NextMonthButton) { + DoButtonMouseDown (hti); + click_state [1] = (hti.HitArea == HitArea.PrevMonthButton); + click_state [2] = !click_state [1]; + } + if (timer.Interval != 300) { + timer.Interval = 300; + } + } + } else { + timer.Enabled = false; + } + } + + // selects one of the buttons + private void DoButtonMouseDown (HitTestInfo hti) { + // show the click then move on + SetItemClick(hti); + if (hti.HitArea == HitArea.PrevMonthButton) { + // invalidate the prev monthbutton + this.Invalidate( + new Rectangle ( + this.ClientRectangle.X + 1 + button_x_offset, + this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2, + button_size.Width, + button_size.Height)); + int scroll = (scroll_change == 0 ? CalendarDimensions.Width * CalendarDimensions.Height : scroll_change); + this.CurrentMonth = this.CurrentMonth.AddMonths (-scroll); + } else { + // invalidate the next monthbutton + this.Invalidate( + new Rectangle ( + this.ClientRectangle.Right - 1 - button_x_offset - button_size.Width, + this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2, + button_size.Width, + button_size.Height)); + int scroll = (scroll_change == 0 ? CalendarDimensions.Width * CalendarDimensions.Height : scroll_change); + this.CurrentMonth = this.CurrentMonth.AddMonths (scroll); + } + } + + // selects the clicked date + private void DoDateMouseDown (HitTestInfo hti) { + SetItemClick(hti); + } + + // event run on the mouse up event + private void DoMouseUp () { + + IsYearGoingDown = false; + IsYearGoingUp = false; + is_mouse_moving_year = false; + + // invalidate the next monthbutton + if (this.is_next_clicked) { + this.Invalidate( + new Rectangle ( + this.ClientRectangle.Right - 1 - button_x_offset - button_size.Width, + this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2, + button_size.Width, + button_size.Height)); + } + // invalidate the prev monthbutton + if (this.is_previous_clicked) { + this.Invalidate( + new Rectangle ( + this.ClientRectangle.X + 1 + button_x_offset, + this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2, + button_size.Width, + button_size.Height)); + } + if (this.is_date_clicked) { + // invalidate the area under the cursor, to remove focus rect + this.InvalidateDateRange (new SelectionRange (clicked_date, clicked_date)); + } + this.is_previous_clicked = false; + this.is_next_clicked = false; + this.is_date_clicked = false; + } + + // needed when in windowed mode to close the calendar if no + // part of it has focus. + private void UpDownTimerTick(object sender, EventArgs e) + { + if (IsYearGoingUp) { + IsYearGoingUp = true; + } + if (IsYearGoingDown) { + IsYearGoingDown = true; + } + + if (!IsYearGoingDown && !IsYearGoingUp) { + updown_timer.Enabled = false; + } else if (IsYearGoingDown || IsYearGoingUp) { + updown_timer.Interval = subsequent_delay; + } + } + + // Needed when in windowed mode. + private void StartHideTimer () + { + if (updown_timer == null) { + updown_timer = new Timer (); + updown_timer.Tick += new EventHandler (UpDownTimerTick); + } + updown_timer.Interval = initial_delay; + updown_timer.Enabled = true; + } + + // occurs when mouse moves around control, used for selection + private void MouseMoveHandler (object sender, MouseEventArgs e) { + HitTestInfo hti = this.HitTest (e.X, e.Y); + // clear the last clicked item + if (click_state [0]) { + // register the click + if (hti.HitArea == HitArea.PrevMonthDate || + hti.HitArea == HitArea.NextMonthDate || + hti.HitArea == HitArea.Date) + { + Rectangle prev_rect = clicked_rect; + DateTime prev_clicked = clicked_date; + DoDateMouseDown (hti); + if (owner == null) { + click_state [0] = true; + } else { + click_state [0] = false; + click_state [1] = false; + click_state [2] = false; + } + + if (prev_clicked != clicked_date) { + // select date after updating click_state and clicked_date + SelectDate (clicked_date); + date_selected_event_pending = true; + + Rectangle invalid = Rectangle.Union (prev_rect, clicked_rect); + Invalidate (invalid); + } + } + + } + } + + // to check if the mouse has come down on this control + private void MouseDownHandler (object sender, MouseEventArgs e) + { + if ((e.Button & MouseButtons.Left) == 0) + return; + + // clear the click_state variables + click_state [0] = false; + click_state [1] = false; + click_state [2] = false; + + // disable the timer if it was enabled + if (timer.Enabled) { + timer.Stop (); + timer.Enabled = false; + } + + Point point = new Point (e.X, e.Y); + // figure out if we are in drop down mode and a click happened outside us + if (this.owner != null) { + if (!this.ClientRectangle.Contains (point)) { + this.owner.HideMonthCalendar (); + return; + } + } + + //establish where was hit + HitTestInfo hti = this.HitTest(point); + // hide the year numeric up down if it was clicked + if (ShowYearUpDown && hti.HitArea != HitArea.TitleYear) { + ShowYearUpDown = false; + } + switch (hti.HitArea) { + case HitArea.PrevMonthButton: + case HitArea.NextMonthButton: + DoButtonMouseDown (hti); + click_state [1] = (hti.HitArea == HitArea.PrevMonthDate); + click_state [2] = !click_state [1]; + timer.Interval = 750; + timer.Start (); + break; + case HitArea.Date: + case HitArea.PrevMonthDate: + case HitArea.NextMonthDate: + DoDateMouseDown (hti); + + // select date before updating click_state + SelectDate (clicked_date); + date_selected_event_pending = true; + + // leave clicked state blank if drop down window + if (owner == null) { + click_state [0] = true; + } else { + click_state [0] = false; + click_state [1] = false; + click_state [2] = false; + } + + break; + case HitArea.TitleMonth: + month_title_click_location = hti.Point; + if (this.Capture && owner != null) { + Capture = false; + Capture = true; + } + break; + case HitArea.TitleYear: + // place the numeric up down + if (ShowYearUpDown) { + if (hti.hit_area_extra == HitAreaExtra.UpButton) { + is_mouse_moving_year = true; + IsYearGoingUp = true; + } else if (hti.hit_area_extra == HitAreaExtra.DownButton) { + is_mouse_moving_year = true; + IsYearGoingDown = true; + } + return; + } else { + ShowYearUpDown = true; + } + break; + case HitArea.TodayLink: + this.SetSelectionRange (DateTime.Now.Date, DateTime.Now.Date); + this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd)); + break; + default: + this.is_previous_clicked = false; + this.is_next_clicked = false; + this.is_date_clicked = false; + break; + } + } + + // raised by any key down events + private void KeyDownHandler (object sender, KeyEventArgs e) { + // send keys to the year_updown control, let it handle it + if(ShowYearUpDown) { + switch (e.KeyCode) { + case Keys.Enter: + ShowYearUpDown = false; + IsYearGoingDown = false; + IsYearGoingUp = false; + break; + case Keys.Up: { + IsYearGoingUp = true; + break; + } + case Keys.Down: { + IsYearGoingDown = true; + break; + } + } + } else { + if (!is_shift_pressed && e.Shift) { + first_select_start_date = SelectionStart; + is_shift_pressed = e.Shift; + e.Handled = true; + } + switch (e.KeyCode) { + case Keys.Home: + // set the date to the start of the month + if (is_shift_pressed) { + DateTime date = GetFirstDateInMonth (first_select_start_date); + if (date < first_select_start_date.AddDays ((MaxSelectionCount-1)*-1)) { + date = first_select_start_date.AddDays ((MaxSelectionCount-1)*-1); + } + this.SetSelectionRange (date, first_select_start_date); + } else { + DateTime date = GetFirstDateInMonth (this.SelectionStart); + this.SetSelectionRange (date, date); + } + e.Handled = true; + break; + case Keys.End: + // set the date to the last of the month + if (is_shift_pressed) { + DateTime date = GetLastDateInMonth (first_select_start_date); + if (date > first_select_start_date.AddDays (MaxSelectionCount-1)) { + date = first_select_start_date.AddDays (MaxSelectionCount-1); + } + this.SetSelectionRange (date, first_select_start_date); + } else { + DateTime date = GetLastDateInMonth (this.SelectionStart); + this.SetSelectionRange (date, date); + } + e.Handled = true; + break; + case Keys.PageUp: + // set the date to the last of the month + if (is_shift_pressed) { + this.AddTimeToSelection (-1, false); + } else { + DateTime date = this.SelectionStart.AddMonths (-1); + this.SetSelectionRange (date, date); + } + e.Handled = true; + break; + case Keys.PageDown: + // set the date to the last of the month + if (is_shift_pressed) { + this.AddTimeToSelection (1, false); + } else { + DateTime date = this.SelectionStart.AddMonths (1); + this.SetSelectionRange (date, date); + } + e.Handled = true; + break; + case Keys.Up: + // set the back 1 week + if (is_shift_pressed) { + this.AddTimeToSelection (-7, true); + } else { + DateTime date = this.SelectionStart.AddDays (-7); + this.SetSelectionRange (date, date); + } + e.Handled = true; + break; + case Keys.Down: + // set the date forward 1 week + if (is_shift_pressed) { + this.AddTimeToSelection (7, true); + } else { + DateTime date = this.SelectionStart.AddDays (7); + this.SetSelectionRange (date, date); + } + e.Handled = true; + break; + case Keys.Left: + // move one left + if (is_shift_pressed) { + this.AddTimeToSelection (-1, true); + } else { + DateTime date = this.SelectionStart.AddDays (-1); + this.SetSelectionRange (date, date); + } + e.Handled = true; + break; + case Keys.Right: + // move one left + if (is_shift_pressed) { + this.AddTimeToSelection (1, true); + } else { + DateTime date = this.SelectionStart.AddDays (1); + this.SetSelectionRange (date, date); + } + e.Handled = true; + break; + case Keys.F4: + // Close ourselves on Alt-F4 if we are a popup + if (e.Alt && owner != null) { + this.Hide (); + e.Handled = true; + } + break; + default: + break; + } + } + } + + // to check if the mouse has come up on this control + private void MouseUpHandler (object sender, MouseEventArgs e) + { + if ((e.Button & MouseButtons.Left) == 0) { + return; + } + + if (timer.Enabled) { + timer.Stop (); + } + // clear the click state array + click_state [0] = false; + click_state [1] = false; + click_state [2] = false; + // do the regulare mouseup stuff + this.DoMouseUp (); + + if (date_selected_event_pending) { + OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd)); + date_selected_event_pending = false; + } + } + + // raised by any key up events + private void KeyUpHandler (object sender, KeyEventArgs e) { + is_shift_pressed = e.Shift ; + e.Handled = true; + IsYearGoingUp = false; + IsYearGoingDown = false; + } + + // paint this control now + private void PaintHandler (object sender, PaintEventArgs pe) { + if (Width <= 0 || Height <= 0 || Visible == false) + return; + + Draw (pe.ClipRectangle, pe.Graphics); + + // fire the new paint handler + if (this.Paint != null) + { + this.Paint (sender, pe); + } + } + + // returns the region of the control that needs to be redrawn + private void InvalidateDateRange (SelectionRange range) { + SelectionRange bounds = this.GetDisplayRange (false); + + if (range.End < bounds.Start || range.Start > bounds.End) { + // don't invalidate anything, as the modified date range + // is outside the visible bounds of this control + return; + } + // adjust the start and end to be inside the visible range + if (range.Start < bounds.Start) { + range = new SelectionRange (bounds.Start, range.End); + } + if (range.End > bounds.End) { + range = new SelectionRange (range.Start, bounds.End); + } + // now invalidate the date rectangles as series of rows + DateTime last_month = this.current_month.AddMonths ((CalendarDimensions.Width * CalendarDimensions.Height)).AddDays (-1); + DateTime current = range.Start; + while (current <= range.End) { + DateTime month_end = new DateTime (current.Year, current.Month, 1).AddMonths (1).AddDays (-1);; + Rectangle start_rect; + Rectangle end_rect; + // see if entire selection is in this current month + if (range.End <= month_end && current < last_month) { + // the end is the last date + if (current < this.current_month) { + start_rect = GetDateRowRect (current_month, current_month); + } else { + start_rect = GetDateRowRect (current, current); + } + end_rect = GetDateRowRect (current, range.End); + } else if (current < last_month) { + // otherwise it simply means we have a selection spaning + // multiple months simply set rectangle inside the current month + start_rect = GetDateRowRect (current, current); + end_rect = GetDateRowRect (month_end, month_end); + } else { + // it's outside the visible range + start_rect = GetDateRowRect (last_month, last_month.AddDays (1)); + end_rect = GetDateRowRect (last_month, range.End); + } + // push to the next month + current = month_end.AddDays (1); + // invalidate from the start row to the end row for this month + this.Invalidate ( + new Rectangle ( + start_rect.X, + start_rect.Y, + start_rect.Width, + Math.Max (end_rect.Bottom - start_rect.Y, 0))); + } + } + + // gets the rect of the row where the specified date appears on the specified month + private Rectangle GetDateRowRect (DateTime month, DateTime date) { + // first get the general rect of the supplied month + Size month_size = SingleMonthSize; + Rectangle month_rect = Rectangle.Empty; + for (int i=0; i < CalendarDimensions.Width*CalendarDimensions.Height; i++) { + DateTime this_month = this.current_month.AddMonths (i); + if (month.Year == this_month.Year && month.Month == this_month.Month) { + month_rect = new Rectangle ( + this.ClientRectangle.X + 1 + (month_size.Width * (i%CalendarDimensions.Width)) + (this.calendar_spacing.Width * (i%CalendarDimensions.Width)), + this.ClientRectangle.Y + 1 + (month_size.Height * (i/CalendarDimensions.Width)) + (this.calendar_spacing.Height * (i/CalendarDimensions.Width)), + month_size.Width, + month_size.Height); + break; + } + } + // now find out where in the month the supplied date is + if (month_rect == Rectangle.Empty) { + return Rectangle.Empty; + } + // find out which row this date is in + int row = -1; + DateTime first_date = GetFirstDateInMonthGrid (month); + DateTime end_date = first_date.AddDays (7); + for (int i=0; i < 6; i++) { + if (date >= first_date && date < end_date) { + row = i; + break; + } + first_date = end_date; + end_date = end_date.AddDays (7); + } + // ensure it's a valid row + if (row < 0) { + return Rectangle.Empty; + } + int x_offset = (this.ShowWeekNumbers) ? date_cell_size.Width : 0; + int y_offset = title_size.Height + (date_cell_size.Height * (row + 1)); + return new Rectangle ( + month_rect.X + x_offset, + month_rect.Y + y_offset, + date_cell_size.Width * 7, + date_cell_size.Height); + } + + internal void Draw (Rectangle clip_rect, Graphics dc) + { + ThemeEngine.Current.DrawMonthCalendar (dc, clip_rect, this); + } + + internal override bool InternalCapture { + get { + return base.InternalCapture; + } + set { + // Don't allow internal capture when DateTimePicker is using us + // Widget sets this on MouseDown + if (owner == null) + base.InternalCapture = value; + } + } + + #endregion //internal methods + + #region internal drawing methods + + + #endregion // internal drawing methods + + #region inner classes and enumerations + + // enumeration about what type of area on the calendar was hit + public enum HitArea { + Nowhere, + TitleBackground, + TitleMonth, + TitleYear, + NextMonthButton, + PrevMonthButton, + CalendarBackground, + Date, + NextMonthDate, + PrevMonthDate, + DayOfWeek, + WeekNumbers, + TodayLink + } + + internal enum HitAreaExtra { + YearRectangle, + UpButton, + DownButton + } + + // info regarding to a hit test on this calendar + public sealed class HitTestInfo { + + private HitArea hit_area; + private Point point; + private DateTime time; + + internal HitAreaExtra hit_area_extra; + internal DateTime hit_time; + + // default constructor + internal HitTestInfo () { + hit_area = HitArea.Nowhere; + point = new Point (0, 0); + time = DateTime.Now; + } + + // overload receives all properties + internal HitTestInfo (HitArea hit_area, Point point, DateTime time) { + this.hit_area = hit_area; + this.point = point; + this.time = time; + this.hit_time = time; + } + + // overload receives all properties + internal HitTestInfo (HitArea hit_area, Point point, DateTime time, DateTime hit_time) + { + this.hit_area = hit_area; + this.point = point; + this.time = time; + this.hit_time = hit_time; + } + + internal HitTestInfo (HitArea hit_area, Point point, DateTime time, HitAreaExtra hit_area_extra) + { + this.hit_area = hit_area; + this.hit_area_extra = hit_area_extra; + this.point = point; + this.time = time; + } + + // the type of area that was hit + public HitArea HitArea { + get { + return hit_area; + } + } + + // the point that is being test + public Point Point { + get { + return point; + } + } + + // the date under the hit test point, only valid if HitArea is Date + public DateTime Time { + get { + return time; + } + } + } + + #endregion // inner classes + + #region UIA Framework: Methods, Properties and Events + + static object UIAMaxSelectionCountChangedEvent = new object (); + static object UIASelectionChangedEvent = new object (); + + internal event EventHandler UIAMaxSelectionCountChanged { + add { Events.AddHandler (UIAMaxSelectionCountChangedEvent, value); } + remove { Events.RemoveHandler (UIAMaxSelectionCountChangedEvent, value); } + } + + internal event EventHandler UIASelectionChanged { + add { Events.AddHandler (UIASelectionChangedEvent, value); } + remove { Events.RemoveHandler (UIASelectionChangedEvent, value); } + } + + private void OnUIAMaxSelectionCountChanged () + { + EventHandler eh = (EventHandler) Events [UIAMaxSelectionCountChangedEvent]; + if (eh != null) + eh (this, EventArgs.Empty); + } + + private void OnUIASelectionChanged () + { + EventHandler eh = (EventHandler) Events [UIASelectionChangedEvent]; + if (eh != null) + eh (this, EventArgs.Empty); + } + + #endregion + } +} diff --git a/source/ShiftUI/Widgets/NotifyIcon.cs b/source/ShiftUI/Widgets/NotifyIcon.cs new file mode 100644 index 0000000..c30006a --- /dev/null +++ b/source/ShiftUI/Widgets/NotifyIcon.cs @@ -0,0 +1,753 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2005 Novell, Inc. (http://www.novell.com) +// +// Authors: +// Peter Bartok [email protected] +// +// + +using System; +using System.ComponentModel; +using System.ComponentModel.Design; +using System.Drawing; +using System.Drawing.Text; + +namespace ShiftUI { + [DefaultProperty("Text")] + [DefaultEvent("MouseDoubleClick")] + //[Designer ("ShiftUI.Design.NotifyIconDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")] + [ToolboxItemFilter("ShiftUI", ToolboxItemFilterType.Allow)] + public sealed class NotifyIcon : Component { + #region Local Variables + private Icon icon; + private Bitmap icon_bitmap; + private string text; + private bool visible; + private NotifyIconWindow window; + private bool systray_active; + private ToolTip tooltip; + private bool double_click; + private string balloon_text; + private string balloon_title; + private ToolTipIcon balloon_icon; + private ContextMenuStrip context_menu_strip; + private object tag; + #endregion // Local Variables + + #region NotifyIconWindow Class + internal class NotifyIconWindow : Form { + NotifyIcon owner; + Rectangle rect; + + public NotifyIconWindow(NotifyIcon owner) { + this.owner = owner; + is_visible = false; + rect = new Rectangle(0, 0, 1, 1); + + FormBorderStyle = FormBorderStyle.None; + + //CreateControl(); + + SizeChanged += new EventHandler(HandleSizeChanged); + + // Events that need to be sent to our parent + DoubleClick += new EventHandler(HandleDoubleClick); + MouseDown +=new MouseEventHandler(HandleMouseDown); + MouseUp +=new MouseEventHandler(HandleMouseUp); + MouseMove +=new MouseEventHandler(HandleMouseMove); + ContextMenuStrip = owner.context_menu_strip; + } + + protected override CreateParams CreateParams { + get { + CreateParams cp; + + cp = base.CreateParams; + + cp.Parent = IntPtr.Zero; + cp.Style = (int)WindowStyles.WS_POPUP; + cp.Style |= (int)WindowStyles.WS_CLIPSIBLINGS; + + cp.ExStyle = (int)(WindowExStyles.WS_EX_TOOLWINDOW); + + return cp; + } + } + + protected override void WndProc(ref Message m) { + switch((Msg)m.Msg) { + // + // NotifyIcon does CONTEXTMENU on mouse up, not down + // so we swallow the message here, and handle it on our own + // + case Msg.WM_CONTEXTMENU: + return; + + case Msg.WM_USER: { + switch ((Msg)m.LParam.ToInt32()) { + case Msg.WM_LBUTTONDOWN: { + owner.OnMouseDown (new MouseEventArgs(MouseButtons.Left, 1, Widget.MousePosition.X, Widget.MousePosition.Y, 0)); + return; + } + + case Msg.WM_LBUTTONUP: { + owner.OnMouseUp (new MouseEventArgs(MouseButtons.Left, 1, Widget.MousePosition.X, Widget.MousePosition.Y, 0)); + return; + } + + case Msg.WM_LBUTTONDBLCLK: { + owner.OnDoubleClick (EventArgs.Empty); + owner.OnMouseDoubleClick (new MouseEventArgs (MouseButtons.Left, 2, Widget.MousePosition.X, Widget.MousePosition.Y, 0)); + return; + } + + case Msg.WM_MOUSEMOVE: { + owner.OnMouseMove (new MouseEventArgs(MouseButtons.None, 1, Widget.MousePosition.X, Widget.MousePosition.Y, 0)); + return; + } + + case Msg.WM_RBUTTONDOWN: { + owner.OnMouseDown (new MouseEventArgs(MouseButtons.Right, 1, Widget.MousePosition.X, Widget.MousePosition.Y, 0)); + return; + } + + case Msg.WM_RBUTTONUP: { + owner.OnMouseUp (new MouseEventArgs(MouseButtons.Right, 1, Widget.MousePosition.X, Widget.MousePosition.Y, 0)); + return; + } + + case Msg.WM_RBUTTONDBLCLK: { + owner.OnDoubleClick (EventArgs.Empty); + owner.OnMouseDoubleClick (new MouseEventArgs (MouseButtons.Left, 2, Widget.MousePosition.X, Widget.MousePosition.Y, 0)); + return; + } + + case Msg.NIN_BALLOONUSERCLICK: { + owner.OnBalloonTipClicked (EventArgs.Empty); + return; + } + + case Msg.NIN_BALLOONSHOW: { + owner.OnBalloonTipShown (EventArgs.Empty); + return; + } + + case Msg.NIN_BALLOONHIDE: + case Msg.NIN_BALLOONTIMEOUT: { + owner.OnBalloonTipClosed (EventArgs.Empty); + return; + } + } + return; + } + } + base.WndProc (ref m); + } + + internal void CalculateIconRect() { + int x; + int y; + int size; + + // Icons are always square. Try to center them in the window + if (ClientRectangle.Width < ClientRectangle.Height) { + size = ClientRectangle.Width; + } else { + size = ClientRectangle.Height; + } + x = this.ClientRectangle.Width / 2 - size / 2; + y = this.ClientRectangle.Height / 2 - size / 2; + rect = new Rectangle(x, y, size, size); + + Bounds = new Rectangle (0, 0, size, size); + } + + internal override void OnPaintInternal (PaintEventArgs e) { + if (owner.icon != null) { + // At least in Gnome, the background of the panel is the same as the Menu, so we go for it + // instead of (most of the time) plain white. + e.Graphics.FillRectangle(ThemeEngine.Current.ResPool.GetSolidBrush(SystemColors.Menu), rect); + e.Graphics.DrawImage(owner.icon_bitmap, + rect, + new Rectangle (0, 0, owner.icon_bitmap.Width, owner.icon_bitmap.Height), + GraphicsUnit.Pixel); + + } + } + + internal void InternalRecreateHandle () { + base.RecreateHandle (); + } + + private void HandleSizeChanged(object sender, EventArgs e) { + owner.Recalculate (); + } + + private void HandleDoubleClick (object sender, EventArgs e) + { + owner.OnDoubleClick (e); + owner.OnMouseDoubleClick (new MouseEventArgs (MouseButtons.Left, 2, Widget.MousePosition.X, Widget.MousePosition.Y, 0)); + } + + private void HandleMouseDown (object sender, MouseEventArgs e) + { + owner.OnMouseDown (e); + } + + private void HandleMouseUp (object sender, MouseEventArgs e) + { + owner.OnMouseUp (e); + } + + private void HandleMouseMove (object sender, MouseEventArgs e) + { + owner.OnMouseMove (e); + } + } + #endregion // NotifyIconWindow Class + + #region NotifyIconBalloonWindow Class + internal class BalloonWindow : Form + { + private IntPtr owner; + private Timer timer; + + private string title; + private string text; + private ToolTipIcon icon; + + public BalloonWindow (IntPtr owner) + { + this.owner = owner; + + StartPosition = FormStartPosition.Manual; + FormBorderStyle = FormBorderStyle.None; + + MouseDown += new MouseEventHandler (HandleMouseDown); + + timer = new Timer (); + timer.Enabled = false; + timer.Tick += new EventHandler (HandleTimer); + } + + public IntPtr OwnerHandle { + get { + return owner; + } + } + + protected override void Dispose (bool disposing) + { + if (disposing) { + timer.Stop(); + timer.Dispose(); + } + base.Dispose (disposing); + } + + protected override CreateParams CreateParams { + get { + CreateParams cp; + + cp = base.CreateParams; + + cp.Style = (int)WindowStyles.WS_POPUP; + cp.Style |= (int)WindowStyles.WS_CLIPSIBLINGS; + + cp.ExStyle = (int)(WindowExStyles.WS_EX_TOOLWINDOW | WindowExStyles.WS_EX_TOPMOST); + + return cp; + } + } + + public new void Close () { + base.Close (); + XplatUI.SendMessage (owner, Msg.WM_USER, IntPtr.Zero, (IntPtr) Msg.NIN_BALLOONHIDE); + } + + protected override void OnShown (EventArgs e) + { + base.OnShown (e); + timer.Start (); + } + + protected override void OnPaint (PaintEventArgs e) + { + ThemeEngine.Current.DrawBalloonWindow (e.Graphics, ClientRectangle, this); + base.OnPaint (e); + } + + private void Recalculate () + { + Rectangle rect = ThemeEngine.Current.BalloonWindowRect (this); + + Left = rect.Left; + Top = rect.Top; + Width = rect.Width; + Height = rect.Height; + } + + // To be used when we have a "close button" inside balloon. + //private void HandleClick (object sender, EventArgs e) + //{ + // Close (); + //} + + private void HandleMouseDown (object sender, MouseEventArgs e) + { + XplatUI.SendMessage (owner, Msg.WM_USER, IntPtr.Zero, (IntPtr) Msg.NIN_BALLOONUSERCLICK); + base.Close (); + } + + private void HandleTimer (object sender, EventArgs e) + { + timer.Stop (); + XplatUI.SendMessage (owner, Msg.WM_USER, IntPtr.Zero, (IntPtr) Msg.NIN_BALLOONTIMEOUT); + base.Close (); + } + + internal StringFormat Format { + get { + StringFormat format = new StringFormat (); + format.Alignment = StringAlignment.Near; + format.HotkeyPrefix = HotkeyPrefix.Hide; + + return format; + } + } + + public new ToolTipIcon Icon { + get { return this.icon; } + set { + if (value == this.icon) + return; + + this.icon = value; + Recalculate (); + } + } + + public string Title { + get { return this.title; } + set { + if (value == this.title) + return; + + this.title = value; + Recalculate (); + } + } + + public override string Text { + get { return this.text; } + set { + if (value == this.text) + return; + + this.text = value; + Recalculate (); + } + } + + public int Timeout { + get { return timer.Interval; } + set { + // Some systems theres a limitiation in timeout, WinXP is between 10k and 30k. + if (value < 10000) + timer.Interval = 10000; + else if (value > 30000) + timer.Interval = 30000; + else + timer.Interval = value; + } + } + } + #endregion // NotifyIconBalloonWindow Class + + #region Public Constructors + public NotifyIcon() { + window = new NotifyIconWindow(this); + systray_active = false; + + balloon_title = ""; + balloon_text = ""; + } + + public NotifyIcon(System.ComponentModel.IContainer container) : this() { + } + #endregion // Public Constructors + + #region Public Methods + public void ShowBalloonTip (int timeout) + { + ShowBalloonTip(timeout, balloon_title, balloon_text, balloon_icon); + } + + public void ShowBalloonTip(int timeout, string tipTitle, string tipText, ToolTipIcon tipIcon) + { + XplatUI.SystrayBalloon(window.Handle, timeout, tipTitle, tipText, tipIcon); + } + #endregion Public Methods + + #region Private Methods + private void OnBalloonTipClicked (EventArgs e) + { + EventHandler eh = (EventHandler)(Events [BalloonTipClickedEvent]); + if (eh != null) + eh (this, e); + } + + private void OnBalloonTipClosed (EventArgs e) + { + EventHandler eh = (EventHandler)(Events [BalloonTipClosedEvent]); + if (eh != null) + eh (this, e); + } + + private void OnBalloonTipShown (EventArgs e) + { + EventHandler eh = (EventHandler)(Events [BalloonTipShownEvent]); + if (eh != null) + eh (this, e); + } + + private void OnClick (EventArgs e) + { + EventHandler eh = (EventHandler)(Events [ClickEvent]); + if (eh != null) + eh (this, e); + } + + private void OnDoubleClick (EventArgs e) + { + double_click = true; + EventHandler eh = (EventHandler)(Events [DoubleClickEvent]); + if (eh != null) + eh (this, e); + } + + private void OnMouseClick (MouseEventArgs e) + { + MouseEventHandler eh = (MouseEventHandler)(Events[MouseClickEvent]); + if (eh != null) + eh (this, e); + } + + private void OnMouseDoubleClick (MouseEventArgs e) + { + MouseEventHandler eh = (MouseEventHandler)(Events[MouseDoubleClickEvent]); + if (eh != null) + eh (this, e); + } + + private void OnMouseDown (MouseEventArgs e) + { + MouseEventHandler eh = (MouseEventHandler)(Events [MouseDownEvent]); + if (eh != null) + eh (this, e); + } + + private void OnMouseUp (MouseEventArgs e) + { + if ((e.Button & MouseButtons.Right) == MouseButtons.Right) { + if (context_menu_strip != null) { + XplatUI.SetForegroundWindow (window.Handle); + context_menu_strip.Show (window, new Point (e.X, e.Y), ToolStripDropDownDirection.AboveLeft); + } + } + + MouseEventHandler eh = (MouseEventHandler)(Events [MouseUpEvent]); + if (eh != null) + eh (this, e); + + if (!double_click) { + OnClick (EventArgs.Empty); + OnMouseClick (e); + double_click = false; + } + } + + private void OnMouseMove (MouseEventArgs e) + { + MouseEventHandler eh = (MouseEventHandler)(Events [MouseMoveEvent]); + if (eh != null) + eh (this, e); + } + + private void Recalculate () + { + window.CalculateIconRect (); + + if (!Visible || (text == string.Empty && icon == null)) { + HideSystray (); + } else { + + if (systray_active) + UpdateSystray (); + else + ShowSystray (); + } + } + + private void ShowSystray() + { + if (icon == null) + return; + + icon_bitmap = icon.ToBitmap(); + + systray_active = true; + XplatUI.SystrayAdd(window.Handle, text, icon, out tooltip); + } + + private void HideSystray() + { + if (!systray_active) { + return; + } + + systray_active = false; + XplatUI.SystrayRemove(window.Handle, ref tooltip); + } + + private void UpdateSystray() + { + if (icon_bitmap != null) { + icon_bitmap.Dispose(); + } + + if (icon != null) { + icon_bitmap = icon.ToBitmap(); + } + + window.Invalidate(); + XplatUI.SystrayChange(window.Handle, text, icon, ref tooltip); + } + #endregion // Private Methods + + #region Public Instance Properties + [DefaultValue ("None")] + public ToolTipIcon BalloonTipIcon { + get { return this.balloon_icon; } + set { + if (value == this.balloon_icon) + return; + + this.balloon_icon = value; + } + } + + [Localizable(true)] + [DefaultValue ("")] + //[Editor ("System.ComponentModel.Design.MultilineStringEditor, " + Consts.AssemblySystem_Design, + //"System.Drawing.Design.UITypeEditor, " + Consts.AssemblySystem_Drawing)] + public string BalloonTipText { + get { return this.balloon_text; } + set { + if (value == this.balloon_text) + return; + + this.balloon_text = value; + } + } + + [Localizable(true)] + [DefaultValue ("")] + public string BalloonTipTitle { + get { return this.balloon_title; } + set { + if (value == this.balloon_title) + return; + + this.balloon_title = value; + } + } + + + + [DefaultValue (null)] + public ContextMenuStrip ContextMenuStrip { + get { return this.context_menu_strip; } + set { + if (this.context_menu_strip != value) { + this.context_menu_strip = value; + window.ContextMenuStrip = value; + } + } + } + + [Localizable(true)] + [DefaultValue(null)] + public Icon Icon { + get { + return icon; + } + + set { + if (icon != value) { + icon = value; + Recalculate (); + } + } + } + + [Localizable (false)] + [Bindable (true)] + [TypeConverter (typeof (StringConverter))] + [DefaultValue (null)] + public object Tag { + get { return this.tag; } + set { this.tag = value; } + } + + [DefaultValue ("")] + //[Editor ("System.ComponentModel.Design.MultilineStringEditor, " + Consts.AssemblySystem_Design, + // typeof (System.Drawing.Design.UITypeEditor))] + [Localizable (true)] + public string Text { + get { + return text; + } + + set { + if (text != value) { + if (value.Length >= 64) { + throw new ArgumentException("ToolTip length must be less than 64 characters long", "Text"); + } + text = value; + Recalculate (); + } + } + } + + [Localizable(true)] + [DefaultValue(false)] + public bool Visible { + get { + return visible; + } + + set { + if (visible != value) { + visible = value; + + // Let our control know, too + window.is_visible = value; + + if (visible) { + ShowSystray (); + } else { + HideSystray(); + } + } + } + } + #endregion // Public Instance Properties + + #region Protected Instance Methods + protected override void Dispose(bool disposing) { + if (visible) + HideSystray(); + + if (icon_bitmap != null) { + icon_bitmap.Dispose(); + } + + if (disposing) + icon = null; + + base.Dispose (disposing); + } + + #endregion // Protected Instance Methods + + #region Events + static object ClickEvent = new object (); + static object DoubleClickEvent = new object (); + static object MouseDownEvent = new object (); + static object MouseMoveEvent = new object (); + static object MouseUpEvent = new object (); + static object BalloonTipClickedEvent = new object (); + static object BalloonTipClosedEvent = new object (); + static object BalloonTipShownEvent = new object (); + static object MouseClickEvent = new object (); + static object MouseDoubleClickEvent = new object (); + + [MWFCategory("Action")] + public event EventHandler BalloonTipClicked { + add { Events.AddHandler (BalloonTipClickedEvent, value); } + remove { Events.RemoveHandler (BalloonTipClickedEvent, value); } + } + + [MWFCategory("Action")] + public event EventHandler BalloonTipClosed { + add { Events.AddHandler (BalloonTipClosedEvent, value); } + remove { Events.RemoveHandler (BalloonTipClosedEvent, value); } + } + + [MWFCategory("Action")] + public event EventHandler BalloonTipShown { + add { Events.AddHandler (BalloonTipShownEvent, value); } + remove { Events.RemoveHandler (BalloonTipShownEvent, value); } + } + + [MWFCategory("Action")] + public event MouseEventHandler MouseClick { + add { Events.AddHandler (MouseClickEvent, value); } + remove { Events.RemoveHandler (MouseClickEvent, value); } + } + + [MWFCategory ("Action")] + public event MouseEventHandler MouseDoubleClick { + add { Events.AddHandler (MouseDoubleClickEvent, value); } + remove { Events.RemoveHandler (MouseDoubleClickEvent, value); } + } + + [MWFCategory("Action")] + public event EventHandler Click { + add { Events.AddHandler (ClickEvent, value); } + remove { Events.RemoveHandler (ClickEvent, value); } + } + + [MWFCategory("Action")] + public event EventHandler DoubleClick { + add { Events.AddHandler (DoubleClickEvent, value); } + remove { Events.RemoveHandler (DoubleClickEvent, value); } + } + + public event MouseEventHandler MouseDown { + add { Events.AddHandler (MouseDownEvent, value); } + remove { Events.RemoveHandler (MouseDownEvent, value); } + } + + public event MouseEventHandler MouseMove { + add { Events.AddHandler (MouseMoveEvent, value); } + remove { Events.RemoveHandler (MouseMoveEvent, value); } + } + + public event MouseEventHandler MouseUp { + add { Events.AddHandler (MouseUpEvent, value); } + remove { Events.RemoveHandler (MouseUpEvent, value); } + } + + #endregion // Events + } +} diff --git a/source/ShiftUI/Widgets/Panel.cs b/source/ShiftUI/Widgets/Panel.cs new file mode 100644 index 0000000..3f0be8a --- /dev/null +++ b/source/ShiftUI/Widgets/Panel.cs @@ -0,0 +1,187 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2004-2005 Novell, Inc. +// +// Authors: +// Jackson Harper ([email protected]) +// + +// COMPLETE + +using System; +using System.ComponentModel; +using System.ComponentModel.Design; +using System.Drawing; +using System.Runtime.InteropServices; + +namespace ShiftUI { + [DefaultProperty("BorderStyle")] + [DefaultEvent("Paint")] + //[Designer ("ShiftUI.Design.PanelDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")] + [Docking (DockingBehavior.Ask)] + [ClassInterface (ClassInterfaceType.AutoDispatch)] + [ComVisible (true)] + [ToolboxWidget] + public class Panel : ScrollableWidget { + #region Constructors & Destructors + public Panel () { + base.TabStop = false; + SetStyle(Widgetstyles.Selectable, false); + SetStyle (Widgetstyles.SupportsTransparentBackColor, true); + } + #endregion // Constructors & Destructors + + #region Public Instance Properties + [Browsable (true)] + //[EditorBrowsable (EditorBrowsableState.Always)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Visible)] + public override bool AutoSize { + get { return base.AutoSize; } + set { base.AutoSize = value; } + } + + [Browsable (true)] + [DefaultValue (AutoSizeMode.GrowOnly)] + [Localizable (true)] + public virtual AutoSizeMode AutoSizeMode { + get { return base.GetAutoSizeMode (); } + set { base.SetAutoSizeMode (value); } + } + + [DefaultValue(BorderStyle.None)] + [DispId(-504)] + public BorderStyle BorderStyle { + get { return InternalBorderStyle; } + set { InternalBorderStyle = value; } + } + + [DefaultValue(false)] + public new bool TabStop { + get { return base.TabStop; } + set { + if (value == TabStop) + return; + base.TabStop = value; + } + } + + [Bindable(false)] + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public override string Text { + get { return base.Text; } + set { + if (value == Text) + return; + base.Text = value; + Refresh (); + } + } + #endregion // Public Instance Properties + + #region Protected Instance Properties + protected override CreateParams CreateParams { + get { + return base.CreateParams; + } + } + + protected override Size DefaultSize { + get { return ThemeEngine.Current.PanelDefaultSize; } + } + #endregion // Proteced Instance Properties + + #region Public Instance Methods + public override string ToString () + { + return base.ToString () + ", BorderStyle: " + BorderStyle; + } + #endregion // Public Instance Methods + + #region Protected Instance Methods + protected override void OnResize(EventArgs eventargs) { + base.OnResize (eventargs); + Invalidate(true); + } + + #endregion // Protected Instance Methods + + #region Events + [Browsable (true)] + //[EditorBrowsable (EditorBrowsableState.Always)] + public new event EventHandler AutoSizeChanged { + add { base.AutoSizeChanged += value; } + remove { base.AutoSizeChanged -= value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event KeyEventHandler KeyDown { + add { base.KeyDown += value; } + remove { base.KeyDown -= value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event KeyPressEventHandler KeyPress { + add { base.KeyPress += value; } + remove { base.KeyPress -= value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event KeyEventHandler KeyUp { + add { base.KeyUp += value; } + remove { base.KeyUp -= value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler TextChanged { + add { base.TextChanged += value; } + remove { base.TextChanged -= value; } + } + #endregion + + #region Internal Methods + internal override Size GetPreferredSizeCore (Size proposedSize) + { + Size retsize = Size.Empty; + + foreach (Widget child in Widgets) { + if (child.Dock == DockStyle.Fill) { + if (child.Bounds.Right > retsize.Width) + retsize.Width = child.Bounds.Right; + } else if (child.Dock != DockStyle.Top && child.Dock != DockStyle.Bottom && (child.Anchor & AnchorStyles.Right) == 0 && (child.Bounds.Right + child.Margin.Right) > retsize.Width) + retsize.Width = child.Bounds.Right + child.Margin.Right; + + if (child.Dock == DockStyle.Fill) { + if (child.Bounds.Bottom > retsize.Height) + retsize.Height = child.Bounds.Bottom; + } else if (child.Dock != DockStyle.Left && child.Dock != DockStyle.Right && (child.Anchor & AnchorStyles.Bottom) == 0 && (child.Bounds.Bottom + child.Margin.Bottom) > retsize.Height) + retsize.Height = child.Bounds.Bottom + child.Margin.Bottom; + } + + return retsize; + } + #endregion + } +} + diff --git a/source/ShiftUI/Widgets/PictureBox.cs b/source/ShiftUI/Widgets/PictureBox.cs new file mode 100644 index 0000000..1574907 --- /dev/null +++ b/source/ShiftUI/Widgets/PictureBox.cs @@ -0,0 +1,618 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2004-2005 Novell, Inc. +// +// Authors: +// Jackson Harper ([email protected]) +// + +// COMPLETE + +using System; +using System.ComponentModel; +using System.ComponentModel.Design; +using System.Drawing; +using System.Drawing.Imaging; +using System.Runtime.InteropServices; +using System.IO; +using System.Net; + +namespace ShiftUI { + [DefaultProperty("Image")] + //[Designer("ShiftUI.Design.PictureBoxDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")] + [Docking (DockingBehavior.Ask)] + [ClassInterface (ClassInterfaceType.AutoDispatch)] + [ComVisible (true)] + [DefaultBindingProperty ("Image")] + [ToolboxWidget] + public class PictureBox : Widget, ISupportInitialize + { + #region Fields + private Image image; + private PictureBoxSizeMode size_mode; + private Image error_image; + private string image_location; + private Image initial_image; + private bool wait_on_load; + private WebClient image_download; + private bool image_from_url; + private int no_update; + #endregion // Fields + + private EventHandler frame_handler; + + #region Public Constructor + public PictureBox () + { + //recalc = true; + no_update = 0; + + SetStyle (Widgetstyles.OptimizedDoubleBuffer, true); + SetStyle (Widgetstyles.Opaque, false); + SetStyle (Widgetstyles.Selectable, false); + SetStyle (Widgetstyles.SupportsTransparentBackColor, true); + HandleCreated += new EventHandler(PictureBox_HandleCreated); + initial_image = null; + error_image = null; + } + #endregion // Public Constructor + + #region Public Properties + [DefaultValue(PictureBoxSizeMode.Normal)] + [Localizable(true)] + [RefreshProperties(RefreshProperties.Repaint)] + public PictureBoxSizeMode SizeMode { + get { return size_mode; } + set { + if (size_mode == value) + return; + size_mode = value; + + if (size_mode == PictureBoxSizeMode.AutoSize) { + AutoSize = true; + SetAutoSizeMode (AutoSizeMode.GrowAndShrink); + } else { + AutoSize = false; + SetAutoSizeMode (AutoSizeMode.GrowOnly); + } + + UpdateSize (); + if (no_update == 0) { + Invalidate (); + } + + OnSizeModeChanged (EventArgs.Empty); + } + } + + [Bindable (true)] + [Localizable(true)] + public Image Image { + get { return image; } + set { ChangeImage (value, false); } + } + + [DefaultValue(BorderStyle.None)] + [DispId(-504)] + public BorderStyle BorderStyle { + get { return InternalBorderStyle; } + set { InternalBorderStyle = value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new bool CausesValidation { + get { return base.CausesValidation; } + set { base.CausesValidation = value; } + } + + [Localizable (true)] + [RefreshProperties (RefreshProperties.All)] + public Image ErrorImage { + get { return error_image; } + set { error_image = value; } + } + + [RefreshProperties (RefreshProperties.All)] + [Localizable(true)] + public Image InitialImage { + get { return initial_image; } + set { initial_image = value; } + } + + [Localizable (true)] + [DefaultValue (null)] + [RefreshProperties (RefreshProperties.All)] + public string ImageLocation { + get { return image_location; } + set { + image_location = value; + + if (!string.IsNullOrEmpty (value)) { + if (WaitOnLoad) + Load (value); + else + LoadAsync (value); + } else if (image_from_url) + ChangeImage (null, true); + } + } + + [Localizable (true)] + [DefaultValue (false)] + public bool WaitOnLoad { + get { return wait_on_load; } + set { wait_on_load = value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new ImeMode ImeMode { + get { return base.ImeMode; } + set { base.ImeMode = value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public override RightToLeft RightToLeft { + get { return base.RightToLeft; } + set { base.RightToLeft = value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new int TabIndex { + get { return base.TabIndex; } + set { base.TabIndex = value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new bool TabStop { + get { return base.TabStop; } + set { base.TabStop = value; } + } + + [Bindable(false)] + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public override string Text { + get { return base.Text; } + set { base.Text = value; } + } + + protected override CreateParams CreateParams { + get { + return base.CreateParams; + } + } + + protected override ImeMode DefaultImeMode { + get { return base.DefaultImeMode; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public override Font Font { + get { return base.Font; } + set { base.Font = value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public override Color ForeColor { + get { return base.ForeColor; } + set { base.ForeColor = value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public override bool AllowDrop { + get { return base.AllowDrop; } + set { base.AllowDrop = value; } + } + #endregion // Public Properties + + #region Protected Instance Methods + protected override Size DefaultSize { + get { return ThemeEngine.Current.PictureBoxDefaultSize; } + } + + protected override void Dispose (bool disposing) + { + if (image != null) { + StopAnimation (); + image = null; + } + initial_image = null; + + base.Dispose (disposing); + } + + protected override void OnPaint (PaintEventArgs pe) + { + ThemeEngine.Current.DrawPictureBox (pe.Graphics, pe.ClipRectangle, this); + base.OnPaint (pe); + } + + protected override void OnVisibleChanged (EventArgs e) + { + base.OnVisibleChanged (e); + } + + protected virtual void OnSizeModeChanged (EventArgs e) + { + EventHandler eh = (EventHandler)(Events [SizeModeChangedEvent]); + if (eh != null) + eh (this, e); + } + + protected override void OnEnabledChanged (EventArgs e) + { + base.OnEnabledChanged (e); + } + + //[EditorBrowsable (EditorBrowsableState.Advanced)] + protected override void OnHandleCreated (EventArgs e) + { + base.OnHandleCreated (e); + } + + //[EditorBrowsable (EditorBrowsableState.Advanced)] + protected override void OnHandleDestroyed (EventArgs e) + { + base.OnHandleDestroyed (e); + } + + protected virtual void OnLoadCompleted (AsyncCompletedEventArgs e) + { + AsyncCompletedEventHandler eh = (AsyncCompletedEventHandler)(Events[LoadCompletedEvent]); + if (eh != null) + eh (this, e); + } + + protected virtual void OnLoadProgressChanged (ProgressChangedEventArgs e) + { + ProgressChangedEventHandler eh = (ProgressChangedEventHandler)(Events[LoadProgressChangedEvent]); + if (eh != null) + eh (this, e); + } + + protected override void OnParentChanged (EventArgs e) + { + base.OnParentChanged (e); + } + + protected override void OnResize (EventArgs e) + { + base.OnResize (e); + + Invalidate (); + } + + internal override Size GetPreferredSizeCore (Size proposedSize) + { + if (image == null) + return base.GetPreferredSizeCore (proposedSize); + else + return image.Size; + } + #endregion // Protected Instance Methods + + #region ISupportInitialize Interface + void System.ComponentModel.ISupportInitialize.BeginInit() { + no_update++; + } + + void System.ComponentModel.ISupportInitialize.EndInit() { + if (no_update > 0) { + no_update--; + } + if (no_update == 0) { + Invalidate (); + } + } + #endregion // ISupportInitialize Interface + + #region Private Properties + private WebClient ImageDownload { + get { + if (image_download == null) + image_download = new WebClient (); + + return image_download; + } + } + #endregion + + #region Private Methods + + private void ChangeImage (Image value, bool from_url) + { + StopAnimation (); + + image_from_url = from_url; + image = value; + + if (IsHandleCreated) { + UpdateSize (); + if (image != null && ImageAnimator.CanAnimate (image)) { + frame_handler = new EventHandler (OnAnimateImage); + ImageAnimator.Animate (image, frame_handler); + } + if (no_update == 0) { + Invalidate (); + } + } + } + + private void StopAnimation () + { + if (frame_handler == null) + return; + ImageAnimator.StopAnimate (image, frame_handler); + frame_handler = null; + } + + private void UpdateSize () + { + if (image == null) + return; + + if (Parent != null) + Parent.PerformLayout (this, "AutoSize"); + } + + private void OnAnimateImage (object sender, EventArgs e) + { + // This is called from a worker thread,BeginInvoke is used + // so the control is updated from the correct thread + + // Check if we have a handle again, since it may have gotten + // destroyed since the last time we checked. + if (!IsHandleCreated) + return; + + BeginInvoke (new EventHandler (UpdateAnimatedImage), new object [] { this, e }); + } + + private void UpdateAnimatedImage (object sender, EventArgs e) + { + // Check if we have a handle again, since it may have gotten + // destroyed since the last time we checked. + if (!IsHandleCreated) + return; + + ImageAnimator.UpdateFrames (image); + Refresh (); + } + + private void PictureBox_HandleCreated(object sender, EventArgs e) { + UpdateSize (); + if (image != null && ImageAnimator.CanAnimate (image)) { + frame_handler = new EventHandler (OnAnimateImage); + ImageAnimator.Animate (image, frame_handler); + } + if (no_update == 0) { + Invalidate (); + } + } + + void ImageDownload_DownloadDataCompleted (object sender, DownloadDataCompletedEventArgs e) + { + if (e.Error != null && !e.Cancelled) + Image = error_image; + else if (e.Error == null && !e.Cancelled) + using (MemoryStream ms = new MemoryStream (e.Result)) + Image = Image.FromStream (ms); + + ImageDownload.DownloadProgressChanged -= new DownloadProgressChangedEventHandler (ImageDownload_DownloadProgressChanged); + ImageDownload.DownloadDataCompleted -= new DownloadDataCompletedEventHandler (ImageDownload_DownloadDataCompleted); + image_download = null; + + OnLoadCompleted (e); + } + + private void ImageDownload_DownloadProgressChanged (object sender, DownloadProgressChangedEventArgs e) + { + OnLoadProgressChanged (new ProgressChangedEventArgs (e.ProgressPercentage, e.UserState)); + } + #endregion // Private Methods + + #region Public Instance Methods + public void CancelAsync () + { + if (image_download != null) + image_download.CancelAsync (); + } + + public void Load () + { + Load (image_location); + } + + public void Load (string url) + { + if (string.IsNullOrEmpty (url)) + throw new InvalidOperationException ("ImageLocation not specified."); + + image_location = url; + + if (url.Contains ("://")) + using (Stream s = ImageDownload.OpenRead (url)) + ChangeImage (Image.FromStream (s), true); + else + ChangeImage (Image.FromFile (url), true); + } + + public void LoadAsync () + { + LoadAsync (image_location); + } + + public void LoadAsync (string url) + { + // If WaitOnLoad is true, do not do async + if (wait_on_load) { + Load (url); + return; + } + + if (string.IsNullOrEmpty (url)) + throw new InvalidOperationException ("ImageLocation not specified."); + + image_location = url; + ChangeImage (InitialImage, true); + + if (ImageDownload.IsBusy) + ImageDownload.CancelAsync (); + + Uri uri = null; + try { + uri = new Uri (url); + } catch (UriFormatException) { + uri = new Uri (Path.GetFullPath (url)); + } + + ImageDownload.DownloadProgressChanged += new DownloadProgressChangedEventHandler (ImageDownload_DownloadProgressChanged); + ImageDownload.DownloadDataCompleted += new DownloadDataCompletedEventHandler (ImageDownload_DownloadDataCompleted); + ImageDownload.DownloadDataAsync (uri); + } + + public override string ToString() { + return String.Format("{0}, SizeMode: {1}", base.ToString (), SizeMode); + } + #endregion + + #region Events + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler CausesValidationChanged { + add { base.CausesValidationChanged += value; } + remove { base.CausesValidationChanged -= value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler Enter { + add { base.Enter += value; } + remove { base.Enter -= value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler FontChanged { + add { base.FontChanged += value; } + remove { base.FontChanged -= value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler ForeColorChanged { + add { base.ForeColorChanged += value; } + remove { base.ForeColorChanged -= value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler ImeModeChanged { + add { base.ImeModeChanged += value; } + remove { base.ImeModeChanged -= value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event KeyEventHandler KeyDown { + add { base.KeyDown += value; } + remove { base.KeyDown -= value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event KeyPressEventHandler KeyPress { + add { base.KeyPress += value; } + remove { base.KeyPress -= value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event KeyEventHandler KeyUp { + add { base.KeyUp += value; } + remove { base.KeyUp -= value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler Leave { + add { base.Leave += value; } + remove { base.Leave -= value; } + } + + static object LoadCompletedEvent = new object (); + static object LoadProgressChangedEvent = new object (); + + public event AsyncCompletedEventHandler LoadCompleted { + add { Events.AddHandler (LoadCompletedEvent, value); } + remove { Events.RemoveHandler (LoadCompletedEvent, value); } + } + + public event ProgressChangedEventHandler LoadProgressChanged { + add { Events.AddHandler (LoadProgressChangedEvent, value); } + remove { Events.RemoveHandler (LoadProgressChangedEvent, value); } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler RightToLeftChanged { + add { base.RightToLeftChanged += value; } + remove { base.RightToLeftChanged -= value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler TabIndexChanged { + add { base.TabIndexChanged += value; } + remove { base.TabIndexChanged -= value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler TabStopChanged { + add { base.TabStopChanged += value; } + remove { base.TabStopChanged -= value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler TextChanged { + add { base.TextChanged += value; } + remove { base.TextChanged -= value; } + } + + static object SizeModeChangedEvent = new object (); + public event EventHandler SizeModeChanged { + add { Events.AddHandler (SizeModeChangedEvent, value); } + remove { Events.RemoveHandler (SizeModeChangedEvent, value); } + } + + #endregion // Events + } +} + diff --git a/source/ShiftUI/Widgets/PictureBoxSizeMode.cs b/source/ShiftUI/Widgets/PictureBoxSizeMode.cs new file mode 100644 index 0000000..80319a4 --- /dev/null +++ b/source/ShiftUI/Widgets/PictureBoxSizeMode.cs @@ -0,0 +1,38 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2004 Novell, Inc. +// +// Authors: +// Jackson Harper ([email protected]) +// + + +namespace ShiftUI { + + public enum PictureBoxSizeMode { + Normal = 0, + StretchImage = 1, + AutoSize = 2, + CenterImage = 3, + Zoom = 4 + } +} + + diff --git a/source/ShiftUI/Widgets/ProgressBar.cs b/source/ShiftUI/Widgets/ProgressBar.cs new file mode 100644 index 0000000..89d06d0 --- /dev/null +++ b/source/ShiftUI/Widgets/ProgressBar.cs @@ -0,0 +1,533 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (C) 2004-2006 Novell, Inc. +// +// Authors: +// Jordi Mas i Hernandez [email protected] +// Peter Dennis Bartok [email protected] +// +// + +using System.Drawing; +using System.ComponentModel; +using System.Drawing.Imaging; +using System.Drawing.Drawing2D; +using System.Runtime.InteropServices; +using System; + +namespace ShiftUI +{ + [DefaultProperty ("Value")] + [DefaultBindingProperty ("Value")] + [ClassInterface (ClassInterfaceType.AutoDispatch)] + [ComVisible (true)] + [ToolboxWidget] + public class ProgressBar : Widget + { + #region Local Variables + private int maximum; + private int minimum; + internal int step; + internal int val; + internal DateTime start = DateTime.Now; + internal Rectangle client_area = new Rectangle (); + internal ProgressBarStyle style; + Timer marquee_timer; + bool right_to_left_layout; + private static readonly Color defaultForeColor = SystemColors.Highlight; + #endregion // Local Variables + + #region events + static object RightToLeftLayoutChangedEvent = new object (); + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler BackgroundImageChanged { + add { base.BackgroundImageChanged += value; } + remove { base.BackgroundImageChanged -= value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler BackgroundImageLayoutChanged { + add { base.BackgroundImageLayoutChanged += value; } + remove { base.BackgroundImageLayoutChanged -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler CausesValidationChanged { + add { base.CausesValidationChanged += value; } + remove { base.CausesValidationChanged -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler DoubleClick { + add { base.DoubleClick += value; } + remove { base.DoubleClick -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler Enter { + add { base.Enter += value; } + remove { base.Enter -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler FontChanged { + add { base.FontChanged += value; } + remove { base.FontChanged -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler ImeModeChanged { + add { base.ImeModeChanged += value; } + remove { base.ImeModeChanged -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event KeyEventHandler KeyDown { + add { base.KeyDown += value; } + remove { base.KeyDown -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event KeyPressEventHandler KeyPress { + add { base.KeyPress += value; } + remove { base.KeyPress -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event KeyEventHandler KeyUp { + add { base.KeyUp += value; } + remove { base.KeyUp -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler Leave { + add { base.Leave += value; } + remove { base.Leave -= value; } + } + + //[EditorBrowsable(EditorBrowsableState.Never)] + [Browsable(false)] + public new event MouseEventHandler MouseDoubleClick { + add { base.MouseDoubleClick += value; } + remove { base.MouseDoubleClick -= value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler PaddingChanged { + add { base.PaddingChanged += value; } + remove { base.PaddingChanged -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event PaintEventHandler Paint { + add { base.Paint += value; } + remove { base.Paint -= value; } + } + + public event EventHandler RightToLeftLayoutChanged { + add { Events.AddHandler (RightToLeftLayoutChangedEvent, value); } + remove { Events.RemoveHandler (RightToLeftLayoutChangedEvent, value); } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler TabStopChanged { + add { base.TabStopChanged += value; } + remove { base.TabStopChanged -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler TextChanged { + add { base.TextChanged += value; } + remove { base.TextChanged -= value; } + } + #endregion Events + + #region Public Constructors + public ProgressBar() + { + maximum = 100; + minimum = 0; + step = 10; + val = 0; + + base.Resize += new EventHandler (OnResizeTB); + + SetStyle (Widgetstyles.UserPaint | + Widgetstyles.Selectable | + Widgetstyles.ResizeRedraw | + Widgetstyles.Opaque | + Widgetstyles.UseTextForAccessibility + , false); + + force_double_buffer = true; + + ForeColor = defaultForeColor; + } + #endregion // Public Constructors + + #region Public Instance Properties + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public override bool AllowDrop + { + get { return base.AllowDrop; } + set { + base.AllowDrop = value; + } + } + + // Setting this property in MS .Net 1.1 does not have any visual effect and it + // does not fire a BackgroundImageChanged event + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public override Image BackgroundImage + { + get { return base.BackgroundImage; } + set { base.BackgroundImage = value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public override ImageLayout BackgroundImageLayout { + get { return base.BackgroundImageLayout; } + set { base.BackgroundImageLayout = value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new bool CausesValidation + { + get { return base.CausesValidation; } + set { base.CausesValidation = value; } + } + + protected override CreateParams CreateParams + { + get { return base.CreateParams; } + } + + protected override ImeMode DefaultImeMode + { + get { return base.DefaultImeMode; } + } + + protected override Size DefaultSize + { + get { return ThemeEngine.Current.ProgressBarDefaultSize; } + } + + //[EditorBrowsable(EditorBrowsableState.Never)] + protected override bool DoubleBuffered { + get { return base.DoubleBuffered; } + set { base.DoubleBuffered = value; } + } + + // Setting this property in MS .Net 1.1 does not have any visual effect and it + // does not fire a FontChanged event + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public override Font Font + { + get { return base.Font; } + set { base.Font = value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new ImeMode ImeMode + { + get { return base.ImeMode; } + set { base.ImeMode = value; } + } + + [RefreshProperties(RefreshProperties.Repaint)] + [DefaultValue (100)] + public int Maximum + { + get { + return maximum; + } + set { + if (value < 0) + throw new ArgumentOutOfRangeException ("Maximum", + string.Format("Value '{0}' must be greater than or equal to 0.", value )); + + maximum = value; + minimum = Math.Min (minimum, maximum); + val = Math.Min (val, maximum); + Refresh (); + } + } + + [RefreshProperties(RefreshProperties.Repaint)] + [DefaultValue (0)] + public int Minimum { + get { + return minimum; + } + set { + if (value < 0) + throw new ArgumentOutOfRangeException ("Minimum", + string.Format("Value '{0}' must be greater than or equal to 0.", value )); + + minimum = value; + maximum = Math.Max (maximum, minimum); + val = Math.Max (val, minimum); + Refresh (); + } + } + + //[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new Padding Padding { + get { return base.Padding; } + set { base.Padding = value; } + } + + [Localizable(true)] + [DefaultValue(false)] + [MonoTODO ("RTL is not supported")] + public virtual bool RightToLeftLayout { + get { return right_to_left_layout;} + set { + if (right_to_left_layout != value) { + right_to_left_layout = value; + OnRightToLeftLayoutChanged (EventArgs.Empty); + } + } + } + + [DefaultValue (10)] + public int Step + { + get { return step; } + set { + step = value; + Refresh (); + } + } + + [Browsable (true)] + [DefaultValue (ProgressBarStyle.Blocks)] + //[EditorBrowsable (EditorBrowsableState.Always)] + public ProgressBarStyle Style { + get { + return style; + } + + set { + if (value != ProgressBarStyle.Blocks && value != ProgressBarStyle.Continuous + && value != ProgressBarStyle.Marquee) + throw new InvalidEnumArgumentException ("value", unchecked((int)value), typeof (ProgressBarStyle)); + if (style != value) { + style = value; + + if (style == ProgressBarStyle.Marquee) { + if (marquee_timer == null) { + marquee_timer = new Timer (); + marquee_timer.Interval = 10; + marquee_timer.Tick += new EventHandler (marquee_timer_Tick); + } + marquee_timer.Start (); + } else { + if (marquee_timer != null) { + marquee_timer.Stop (); + } + Refresh (); + } + } + } + } + + void marquee_timer_Tick (object sender, EventArgs e) + { + Invalidate (); + } + + int marquee_animation_speed = 100; + [DefaultValue (100)] + public int MarqueeAnimationSpeed { + get { + return marquee_animation_speed; + } + + set { + marquee_animation_speed = value; + } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new bool TabStop + { + get { return base.TabStop; } + set { base.TabStop = value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + [Bindable(false)] + public override string Text + { + get { return base.Text; } + set { base.Text = value; } + } + + [Bindable(true)] + [DefaultValue (0)] + public int Value + { + get { + return val; + } + set { + if (value < Minimum || value > Maximum) + throw new ArgumentOutOfRangeException ("Value", string.Format("'{0}' is not a valid value for 'Value'. 'Value' should be between 'Minimum' and 'Maximum'", value)); + val = value; + Refresh (); + } + } + + + #endregion // Protected Instance Properties + + #region Public Instance Methods + + protected override void CreateHandle () + { + base.CreateHandle (); + } + + public void Increment (int value) + { + if (Style == ProgressBarStyle.Marquee) + throw new InvalidOperationException ("Increment should not be called if the style is Marquee."); + + int newValue = Value + value; + + if (newValue < Minimum) + newValue = Minimum; + + if (newValue > Maximum) + newValue = Maximum; + + Value = newValue; + Refresh (); + } + + protected override void OnHandleCreated (EventArgs e) + { + base.OnHandleCreated (e); + + UpdateAreas (); + } + + protected override void OnBackColorChanged (EventArgs e) + { + base.OnBackColorChanged (e); + } + + protected override void OnForeColorChanged (EventArgs e) + { + base.OnForeColorChanged (e); + } + + protected override void OnHandleDestroyed (EventArgs e) + { + base.OnHandleDestroyed (e); + } + + //[EditorBrowsable(EditorBrowsableState.Advanced)] + protected virtual void OnRightToLeftLayoutChanged(EventArgs e) + { + EventHandler eh = (EventHandler) Events [RightToLeftLayoutChangedEvent]; + if (eh != null) + eh (this, e); + } + + public void PerformStep () + { + if (Style == ProgressBarStyle.Marquee) + throw new InvalidOperationException ("PerformStep should not be called if the style is Marquee."); + + Increment (Step); + } + + //[EditorBrowsable (EditorBrowsableState.Never)] + public override void ResetForeColor () + { + ForeColor = defaultForeColor; + } + + public override string ToString() + { + return string.Format ("{0}, Minimum: {1}, Maximum: {2}, Value: {3}", + GetType().FullName, + Minimum.ToString (), + Maximum.ToString (), + Value.ToString () ); + } + + #endregion // Public Instance Methods + + #region Private Instance Methods + + private void UpdateAreas () + { + client_area.X = client_area.Y = 2; + client_area.Width = Width - 4; + client_area.Height = Height - 4; + } + + private void OnResizeTB (Object o, EventArgs e) + { + if (Width <= 0 || Height <= 0) + return; + + UpdateAreas (); + Invalidate(); // Invalidate the full surface, blocks will not match + } + + internal override void OnPaintInternal (PaintEventArgs pevent) + { + ThemeEngine.Current.DrawProgressBar (pevent.Graphics, pevent.ClipRectangle, this); + } + + #endregion + } +} diff --git a/source/ShiftUI/Widgets/PropertiesTab.cs b/source/ShiftUI/Widgets/PropertiesTab.cs new file mode 100644 index 0000000..a92fd33 --- /dev/null +++ b/source/ShiftUI/Widgets/PropertiesTab.cs @@ -0,0 +1,76 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2005-2008 Novell, Inc. +// +// Authors: +// Jonathan Chambers [email protected] +// Ivan N. Zlatev [email protected] +// + +using System; +using System.ComponentModel; +using ShiftUI.Design; + +namespace ShiftUI.PropertyGridInternal +{ + public class PropertiesTab : PropertyTab + { + public PropertiesTab () + { + } + + public override PropertyDescriptorCollection GetProperties (object component, Attribute[] attributes) + { + return GetProperties (null, component, attributes); + } + + public override PropertyDescriptorCollection GetProperties (ITypeDescriptorContext context, object component, Attribute[] attributes) + { + if (component == null) + return new PropertyDescriptorCollection (null); + if (attributes == null) + attributes = new Attribute[] { BrowsableAttribute.Yes }; + + PropertyDescriptorCollection properties = null; + TypeConverter converter = TypeDescriptor.GetConverter (component); + if (converter != null && converter.GetPropertiesSupported ()) + properties = converter.GetProperties (context, component, attributes); + if (properties == null) // try 3: TypeDescriptor + properties = TypeDescriptor.GetProperties (component, attributes); + return properties; + } + + public override PropertyDescriptor GetDefaultProperty (object obj) + { + if (obj == null) + return null; + + return TypeDescriptor.GetDefaultProperty (obj); + } + + public override string HelpKeyword { + get { return "vs.properties"; } + } + + public override string TabName { + get { return "Properties"; } + } + } +} diff --git a/source/ShiftUI/Widgets/PropertyGrid.cs b/source/ShiftUI/Widgets/PropertyGrid.cs new file mode 100644 index 0000000..36af17c --- /dev/null +++ b/source/ShiftUI/Widgets/PropertyGrid.cs @@ -0,0 +1,1626 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2004-2008 Novell, Inc. +// +// Authors: +// Jonathan Chambers ([email protected]) +// Ivan N. Zlatev ([email protected]) +// + +// NOT COMPLETE + +using System; +using System.IO; +using System.Drawing; +using System.Drawing.Design; +using System.ComponentModel; +using System.Collections; +using System.ComponentModel.Design; +using System.Reflection; +using System.Runtime.InteropServices; +using ShiftUI.Design; +using ShiftUI.PropertyGridInternal; + +namespace ShiftUI +{ + [Designer("ShiftUI.Design.PropertyGridDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")] + [ClassInterface (ClassInterfaceType.AutoDispatch)] + [ComVisible (true)] + [ToolboxWidget] + public class PropertyGrid : ShiftUI.ContainerWidget + { + #region Private Members + + + private const string UNCATEGORIZED_CATEGORY_LABEL = "Misc"; + private AttributeCollection browsable_attributes = null; + private bool can_show_commands = false; + private Color commands_back_color; + private Color commands_fore_color; + private bool commands_visible; + private bool commands_visible_if_available; + private Point context_menu_default_location; + private bool large_buttons; + private Color line_color; + private PropertySort property_sort; + private PropertyTabCollection property_tabs; + private GridEntry selected_grid_item; + private GridEntry root_grid_item; + private object[] selected_objects; + private PropertyTab properties_tab; + private PropertyTab selected_tab; + + private ImageList toolbar_imagelist; + private Image categorized_image; + private Image alphabetical_image; + private Image propertypages_image; + private PropertyToolBarButton categorized_toolbarbutton; + private PropertyToolBarButton alphabetic_toolbarbutton; + private PropertyToolBarButton propertypages_toolbarbutton; + private PropertyToolBarSeparator separator_toolbarbutton; + private bool events_tab_visible; + + private PropertyToolBar toolbar; + + private PropertyGridView property_grid_view; + private Splitter splitter; + private Panel help_panel; + private Label help_title_label; + private Label help_description_label; + private MenuItem reset_menuitem; + private MenuItem description_menuitem; + + private Color category_fore_color; + private Color commands_active_link_color; + private Color commands_disabled_link_color; + private Color commands_link_color; + #endregion // Private Members + + #region Contructors + public PropertyGrid () + { + selected_objects = new object[0]; + property_tabs = new PropertyTabCollection(this); + + line_color = SystemColors.ScrollBar; + category_fore_color = line_color; + commands_visible = false; + commands_visible_if_available = false; + property_sort = PropertySort.CategorizedAlphabetical; + property_grid_view = new PropertyGridView(this); + + splitter = new Splitter(); + splitter.Dock = DockStyle.Bottom; + + help_panel = new Panel(); + help_panel.Dock = DockStyle.Bottom; + //help_panel.DockPadding.All = 3; + help_panel.Height = 50; + help_panel.BackColor = SystemColors.Control; + + + help_title_label = new Label(); + help_title_label.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right; + help_title_label.Name = "help_title_label"; + help_title_label.Font = new Font(this.Font,FontStyle.Bold); + help_title_label.Location = new Point(2,2); + help_title_label.Height = 17; + help_title_label.Width = help_panel.Width - 4; + + + help_description_label = new Label(); + help_description_label.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom; + help_description_label.AutoEllipsis = true; + help_description_label.AutoSize = false; + help_description_label.Font = this.Font; + help_description_label.Location = new Point(2,help_title_label.Top+help_title_label.Height); + help_description_label.Width = help_panel.Width - 4; + help_description_label.Height = help_panel.Height - help_description_label.Top - 2; + + help_panel.Widgets.Add(help_description_label); + help_panel.Widgets.Add(help_title_label); + help_panel.Paint+=new PaintEventHandler(help_panel_Paint); + + toolbar = new PropertyToolBar(); + toolbar.Dock = DockStyle.Top; + categorized_toolbarbutton = new PropertyToolBarButton (); + categorized_toolbarbutton.Pushed = true; + alphabetic_toolbarbutton = new PropertyToolBarButton (); + propertypages_toolbarbutton = new PropertyToolBarButton (); + separator_toolbarbutton = new PropertyToolBarSeparator (); + context_menu_default_location = Point.Empty; + + toolbar.Appearance = ToolBarAppearance.Flat; + toolbar.AutoSize = false; + + toolbar.Location = new System.Drawing.Point(0, 0); + toolbar.ShowToolTips = true; + toolbar.Size = new System.Drawing.Size(256, 27); + toolbar.TabIndex = 0; + + toolbar.Items.AddRange (new ToolStripItem [] {categorized_toolbarbutton, + alphabetic_toolbarbutton, + new PropertyToolBarSeparator (), + propertypages_toolbarbutton}); + //toolbar.ButtonSize = new System.Drawing.Size (20, 20); + categorized_toolbarbutton.Click += new EventHandler (toolbarbutton_clicked); + alphabetic_toolbarbutton.Click += new EventHandler (toolbarbutton_clicked); + propertypages_toolbarbutton.Click += new EventHandler (toolbarbutton_clicked); + + categorized_toolbarbutton.Style = ToolBarButtonStyle.ToggleButton; + categorized_toolbarbutton.ToolTipText = "Categorized"; + + alphabetic_toolbarbutton.Style = ToolBarButtonStyle.ToggleButton; + alphabetic_toolbarbutton.ToolTipText = "Alphabetic"; + + propertypages_toolbarbutton.Enabled = false; + propertypages_toolbarbutton.Style = ToolBarButtonStyle.ToggleButton; + propertypages_toolbarbutton.ToolTipText = "Property Pages"; + + properties_tab = CreatePropertyTab (this.DefaultTabType); + selected_tab = properties_tab; + RefreshToolbar (property_tabs); + + BorderHelperControl helper = new BorderHelperControl (); + helper.Dock = DockStyle.Fill; + helper.Widgets.Add (property_grid_view); + + this.Widgets.Add(helper); + this.Widgets.Add(toolbar); + this.Widgets.Add(splitter); + this.Widgets.Add(help_panel); + this.Name = "PropertyGrid"; + this.Size = new System.Drawing.Size(256, 400); + } + #endregion // Constructors + + #region Public Instance Properties + + [BrowsableAttribute(false)] + [EditorBrowsableAttribute(EditorBrowsableState.Advanced)] + [DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden)] + public AttributeCollection BrowsableAttributes { + get { + if (browsable_attributes == null) { + browsable_attributes = new AttributeCollection (new Attribute[] { + BrowsableAttribute.Yes }); + } + return browsable_attributes; + } + set { + if (browsable_attributes == value) + return; + + if (browsable_attributes == null || browsable_attributes.Count == 0) + browsable_attributes = null; + else + browsable_attributes = value; + } + } + + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool AutoScroll { + get { + return base.AutoScroll; + } + set { + base.AutoScroll = value; + } + } + + public override Color BackColor { + get { + return base.BackColor; + } + + set { + base.BackColor = value; + toolbar.BackColor = value; + Refresh (); + } + } + + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + public override Image BackgroundImage { + get { + return base.BackgroundImage; + } + set { + base.BackgroundImage = value; + } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + [Browsable(false)] + public override ImageLayout BackgroundImageLayout { + get { return base.BackgroundImageLayout; } + set { base.BackgroundImageLayout = value; } + } + + [BrowsableAttribute(false)] + [EditorBrowsableAttribute(EditorBrowsableState.Advanced)] + public virtual bool CanShowCommands { + get { + return can_show_commands; + } + } + + [DefaultValue(typeof(Color), "ControlText")] + public Color CategoryForeColor { + get { + return category_fore_color; + } + set { + if (category_fore_color != value) { + category_fore_color = value; + Invalidate (); + } + } + } + + public Color CommandsBackColor { + get { + return commands_back_color; + } + + set { + if (commands_back_color == value) { + return; + } + commands_back_color = value; + } + } + + public Color CommandsForeColor { + get { + return commands_fore_color; + } + + set { + if (commands_fore_color == value) { + return; + } + commands_fore_color = value; + } + } + + public Color CommandsActiveLinkColor { + get { + return commands_active_link_color; + } + set { + commands_active_link_color = value; + } + } + + public Color CommandsDisabledLinkColor { + get { + return commands_disabled_link_color; + } + set { + commands_disabled_link_color = value; + } + } + + public Color CommandsLinkColor { + get { + return commands_link_color; + } + set { + commands_link_color = value; + } + } + + [BrowsableAttribute (false)] + [EditorBrowsableAttribute(EditorBrowsableState.Advanced)] + [MonoTODO ("Commands are not implemented yet.")] + public virtual bool CommandsVisible { + get { + return commands_visible; + } + } + + [DefaultValue (true)] + public virtual bool CommandsVisibleIfAvailable { + get { + return commands_visible_if_available; + } + + set { + if (commands_visible_if_available == value) { + return; + } + commands_visible_if_available = value; + } + } + + [BrowsableAttribute(false)] + [EditorBrowsableAttribute(EditorBrowsableState.Advanced)] + [DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden)] + public Point ContextMenuDefaultLocation { + get { + return context_menu_default_location; + } + } + + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public new Widget.WidgetCollection Widgets { + get { + return base.Widgets; + } + } + + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + public override Color ForeColor { + get { + return base.ForeColor; + } + set { + base.ForeColor = value; + } + } + + [DefaultValue ("Color [Widget]")] + public Color HelpBackColor { + get { + return help_panel.BackColor; + } + set { + help_panel.BackColor = value; + } + } + + [DefaultValue ("Color [ControlText]")] + public Color HelpForeColor { + get { + return help_panel.ForeColor; + } + + set { + help_panel.ForeColor = value; + } + } + + [DefaultValue(true)] + [Localizable(true)] + public virtual bool HelpVisible { + get { + return help_panel.Visible; + } + + set { + splitter.Visible = value; + help_panel.Visible = value; + } + } + + [DefaultValue (false)] + public bool LargeButtons { + get { + return large_buttons; + } + + set { + if (large_buttons == value) { + return; + } + + large_buttons = value; + } + } + + [DefaultValue ("Color [InactiveBorder]")] + public Color LineColor { + get { + return line_color; + } + + set { + if (line_color == value) { + return; + } + + line_color = value; + } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + [Browsable(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public new Padding Padding { + get { return base.Padding; } + set { base.Padding = value; } + } + + [DefaultValue(PropertySort.CategorizedAlphabetical)] + public PropertySort PropertySort { + get { + return property_sort; + } + + set { + if (!Enum.IsDefined (typeof (PropertySort), value)) + throw new InvalidEnumArgumentException ("value", (int) value, typeof (PropertySort)); + if (property_sort == value) + return; + + // we do not need to update the the grid items and fire + // a PropertySortChanged event when switching between + // Categorized and CateogizedAlphabetical + bool needUpdate = (property_sort & PropertySort.Categorized) == 0 || + (value & PropertySort.Categorized) == 0; + property_sort = value; + if (needUpdate) { + UpdateSortLayout (root_grid_item); + // update selection + if (selected_grid_item != null) { + if (selected_grid_item.GridItemType == GridItemType.Category && + (value == PropertySort.Alphabetical || value == PropertySort.NoSort)) + SelectItemCore (null, null); + else + SelectItemCore (null, selected_grid_item); + } + property_grid_view.UpdateView (); + + EventHandler eh = (EventHandler)(Events [PropertySortChangedEvent]); + if (eh != null) + eh (this, EventArgs.Empty); + } + UpdatePropertySortButtonsState (); + } + } + + [BrowsableAttribute(false)] + [EditorBrowsableAttribute(EditorBrowsableState.Advanced)] + [DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden)] + public PropertyTabCollection PropertyTabs { + get { return property_tabs; } + } + + [BrowsableAttribute(false)] + [EditorBrowsableAttribute(EditorBrowsableState.Advanced)] + [DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden)] + public GridItem SelectedGridItem { + get { return selected_grid_item; } + set { + if (value == null) + throw new ArgumentException ("GridItem specified to PropertyGrid.SelectedGridItem must be a valid GridItem."); + if (value != selected_grid_item) { + GridEntry oldItem = selected_grid_item; + SelectItemCore (oldItem, (GridEntry)value); + OnSelectedGridItemChanged (new SelectedGridItemChangedEventArgs (oldItem, value)); + } + } + } + + internal GridItem RootGridItem { + get { return root_grid_item; } + } + + private void UpdateHelp (GridItem item) + { + if (item == null) { + help_title_label.Text = string.Empty; + help_description_label.Text = string.Empty; + } else { + help_title_label.Text = item.Label; + if (item.PropertyDescriptor != null) + this.help_description_label.Text = item.PropertyDescriptor.Description; + } + } + + private void SelectItemCore (GridEntry oldItem, GridEntry item) + { + UpdateHelp (item); + selected_grid_item = item; + property_grid_view.SelectItem (oldItem, item); + } + + internal void OnPropertyValueChangedInternal (GridItem item, object property_value) + { + property_grid_view.UpdateView (); + OnPropertyValueChanged (new PropertyValueChangedEventArgs (item, property_value)); + } + + internal void OnExpandItem (GridEntry item) + { + property_grid_view.ExpandItem (item); + } + + internal void OnCollapseItem (GridEntry item) + { + property_grid_view.CollapseItem (item); + } + + internal DialogResult ShowError (string text) + { + return this.ShowError (text, MessageBoxButtons.OK); + } + + internal DialogResult ShowError (string text, MessageBoxButtons buttons) + { + if (text == null) + throw new ArgumentNullException ("text"); + MessageBox.Show (text, "Properties Window"); + return DialogResult.OK; + } + + [DefaultValue(null)] + [TypeConverter("ShiftUI.PropertyGrid+SelectedObjectConverter, " + Consts.AssemblySystem_Windows_Forms)] + public object SelectedObject { + get { + if (selected_objects.Length > 0) + return selected_objects[0]; + return null; + } + + set { + if (selected_objects != null && selected_objects.Length == 1 && selected_objects[0] == value) + return; + if (value == null) + SelectedObjects = new object[0]; + else + SelectedObjects = new object[] {value}; + + } + } + + [BrowsableAttribute(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public object[] SelectedObjects { + get { + return selected_objects; + } + + set { + root_grid_item = null; + SelectItemCore (null, null); // unselect current item in the view + if (value != null) { + for (int i = 0; i < value.Length; i++) { + if (value [i] == null) + throw new ArgumentException (String.Format ("Item {0} in the objs array is null.", i)); + } + selected_objects = value; + } else { + selected_objects = new object [0]; + } + + ShowEventsButton (false); + PopulateGrid (selected_objects); + RefreshTabs(PropertyTabScope.Component); + if (root_grid_item != null) + SelectItemCore (null, GetDefaultPropertyItem (root_grid_item, selected_tab)); + property_grid_view.UpdateView (); + OnSelectedObjectsChanged (EventArgs.Empty); + } + } + + [BrowsableAttribute(false)] + [EditorBrowsable(EditorBrowsableState.Advanced)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public PropertyTab SelectedTab { + get { return selected_tab; } + } + + public override ISite Site { + get { return base.Site; } + set { base.Site = value; } + } + + [Browsable (false)] + [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public override string Text { + get { return base.Text; } + set { base.Text = value; } + } + + [DefaultValue(true)] + public virtual bool ToolbarVisible { + get { return toolbar.Visible; } + set { + if (toolbar.Visible == value) { + return; + } + + toolbar.Visible = value; + } + } + + protected ToolStripRenderer ToolStripRenderer { + get { + if (toolbar != null) { + return toolbar.Renderer; + } + return null; + } + set { + if (toolbar != null) { + toolbar.Renderer = value; + } + } + } + + [DefaultValue ("Color [Window]")] + public Color ViewBackColor { + get { return property_grid_view.BackColor; } + set { + if (property_grid_view.BackColor == value) { + return; + } + + property_grid_view.BackColor = value; + } + } + + [DefaultValue ("Color [WindowText]")] + public Color ViewForeColor { + get { return property_grid_view.ForeColor; } + set { + if (property_grid_view.ForeColor == value) { + return; + } + + property_grid_view.ForeColor = value; + } + } + + [DefaultValue (false)] + public bool UseCompatibleTextRendering { + get { return use_compatible_text_rendering; } + set { + if (use_compatible_text_rendering != value) { + use_compatible_text_rendering = value; + if (Parent != null) + Parent.PerformLayout (this, "UseCompatibleTextRendering"); + Invalidate (); + } + } + } + + #endregion // Public Instance Properties + + #region Protected Instance Properties + + protected override Size DefaultSize { + get { return base.DefaultSize; } + } + + + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Advanced)] + [DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden)] + protected virtual Type DefaultTabType { + get { return typeof(PropertiesTab); } + } + + protected bool DrawFlatToolbar { + get { return (toolbar.Appearance == ToolBarAppearance.Flat); } + set { + if (value) + toolbar.Appearance = ToolBarAppearance.Flat; + else + toolbar.Appearance = ToolBarAppearance.Normal; + } + } + + protected internal override bool ShowFocusCues { + get { return base.ShowFocusCues; } + } + + #endregion // Protected Instance Properties + + #region Public Instance Methods + + protected override void Dispose(bool disposing) { + base.Dispose(disposing); + } + + public void CollapseAllGridItems () + { + GridEntry category = FindCategoryItem (selected_grid_item); + if (category != null) + SelectedGridItem = category; + CollapseItemRecursive (root_grid_item); + property_grid_view.UpdateView (); + } + + private void CollapseItemRecursive (GridItem item) + { + if (item == null) + return; + + foreach (GridItem child in item.GridItems) { + CollapseItemRecursive (child); + if (child.Expandable) + child.Expanded = false; + } + } + + private GridEntry FindCategoryItem (GridEntry entry) + { + if (entry == null || (property_sort != PropertySort.Categorized && + property_sort != PropertySort.CategorizedAlphabetical)) + return null; + + if (entry.GridItemType == GridItemType.Category) + return entry; + + GridEntry category = null; + GridItem current = (GridItem)entry; + while (category == null) { + if (current.Parent != null && current.Parent.GridItemType == GridItemType.Category) + category = (GridEntry) current.Parent; + current = current.Parent; + if (current == null) + break; + } + return (GridEntry) category; + } + + public void ExpandAllGridItems () + { + ExpandItemRecursive (root_grid_item); + property_grid_view.UpdateView (); + } + + private void ExpandItemRecursive (GridItem item) + { + if (item == null) + return; + + foreach (GridItem child in item.GridItems) { + ExpandItemRecursive (child); + if (child.Expandable) + child.Expanded = true; + } + } + + public override void Refresh () + { + base.Refresh (); + // force a full reload here + SelectedObjects = SelectedObjects; + } + + private void toolbar_Clicked (PropertyToolBarButton button) + { + if (button == null) + return; + + if (button == alphabetic_toolbarbutton) { + this.PropertySort = PropertySort.Alphabetical; + alphabetic_toolbarbutton.Pushed = true; + categorized_toolbarbutton.Pushed = false; + } else if (button == categorized_toolbarbutton) { + this.PropertySort = PropertySort.CategorizedAlphabetical; + categorized_toolbarbutton.Pushed = true; + alphabetic_toolbarbutton.Pushed = false; + } else { + if (button.Enabled) + SelectPropertyTab (button.PropertyTab); + } + } + + private void toolbarbutton_clicked (object o, EventArgs args) + { + toolbar_Clicked (o as PropertyToolBarButton); + } + + private void SelectPropertyTab (PropertyTab propertyTab) + { + if (propertyTab != null && selected_tab != propertyTab) { + foreach (object toolbarItem in toolbar.Items) { + PropertyToolBarButton button = toolbarItem as PropertyToolBarButton; + if (button != null && button.PropertyTab != null) { + if (button.PropertyTab == selected_tab) + button.Pushed = false; + else if (button.PropertyTab == propertyTab) + button.Pushed = true; + } + } + selected_tab = propertyTab; + PopulateGrid (selected_objects); + SelectItemCore (null, GetDefaultPropertyItem (root_grid_item, selected_tab)); + property_grid_view.UpdateView (); + } + } + + private void UpdatePropertySortButtonsState () + { + if (property_sort == PropertySort.NoSort) { + alphabetic_toolbarbutton.Pushed = false; + categorized_toolbarbutton.Pushed = false; + } else if (property_sort == PropertySort.Alphabetical) { + alphabetic_toolbarbutton.Pushed = true; + categorized_toolbarbutton.Pushed = false; + } else if (property_sort == PropertySort.Categorized || + property_sort == PropertySort.CategorizedAlphabetical) { + alphabetic_toolbarbutton.Pushed = false; + categorized_toolbarbutton.Pushed = true; + } + } + + protected void ShowEventsButton (bool value) + { + if (value && property_tabs.Contains (typeof (EventsTab))) + events_tab_visible = true; + else + events_tab_visible = false; + RefreshTabs (PropertyTabScope.Component); + } + + public void RefreshTabs (PropertyTabScope tabScope) + { + property_tabs.Clear (tabScope); + if (selected_objects != null) { + Type[] tabTypes = null; + PropertyTabScope[] tabScopes = null; + + if (events_tab_visible && property_tabs.Contains (typeof (EventsTab))) + property_tabs.InsertTab (0, properties_tab, PropertyTabScope.Component); + + GetMergedPropertyTabs (selected_objects, out tabTypes, out tabScopes); + if (tabTypes != null && tabScopes != null && tabTypes.Length > 0) { + bool selectedTabPreserved = false; + for (int i=0; i < tabTypes.Length; i++) { + property_tabs.AddTabType (tabTypes[i], tabScopes[i]); + if (tabTypes[i] == selected_tab.GetType ()) + selectedTabPreserved = true; + } + if (!selectedTabPreserved) + SelectPropertyTab (properties_tab); + } + } else { + SelectPropertyTab (properties_tab); + } + RefreshToolbar (property_tabs); + } + + private void RefreshToolbar (PropertyTabCollection tabs) + { + EnsurePropertiesTab (); + + toolbar.SuspendLayout (); + toolbar.Items.Clear (); + + int imageIndex = 0; + toolbar.Items.Add (categorized_toolbarbutton); + categorized_toolbarbutton.DisplayStyle = ToolStripItemDisplayStyle.Text; + categorized_toolbarbutton.Text = "Categorized"; + imageIndex++; + toolbar.Items.Add (alphabetic_toolbarbutton); + alphabetic_toolbarbutton.DisplayStyle = ToolStripItemDisplayStyle.Text; + alphabetic_toolbarbutton.Text = "Alphabetical"; + imageIndex++; + toolbar.Items.Add (separator_toolbarbutton); + if (tabs != null && tabs.Count > 0) { + foreach (PropertyTab tab in tabs) { + PropertyToolBarButton button = new PropertyToolBarButton (tab); + toolbar.Items.Add (button); + if (tab == selected_tab) + button.Pushed = true; + } + toolbar.Items.Add (new PropertyToolBarSeparator ()); + } + + toolbar.Items.Add (propertypages_toolbarbutton); + + toolbar.ResumeLayout (); + } + + private void EnsurePropertiesTab () + { + if (property_tabs == null) + return; + + if (property_tabs.Count > 0 && !property_tabs.Contains (this.DefaultTabType)) + property_tabs.InsertTab (0, properties_tab, PropertyTabScope.Component); + } + + private void GetMergedPropertyTabs (object[] objects, out Type[] tabTypes, out PropertyTabScope[] tabScopes) + { + tabTypes = null; + tabScopes = null; + if (objects == null || objects.Length == 0) + return; + + ArrayList intersection = null; + ArrayList scopes = new ArrayList (); + for (int i=0; i < objects.Length; i++) { + if (objects[i] == null) + continue; + PropertyTabAttribute tabAttribute = (PropertyTabAttribute)TypeDescriptor.GetAttributes (objects[i])[typeof (PropertyTabAttribute)]; + if (tabAttribute == null || tabAttribute.TabClasses == null || tabAttribute.TabClasses.Length == 0) + return; + + ArrayList new_intersection = new ArrayList (); + scopes.Clear (); + IList currentIntersection = (i == 0 ? (IList)tabAttribute.TabClasses : (IList)intersection); + for (int j=0; j < currentIntersection.Count; j++) { + if ((Type)intersection[j] == tabAttribute.TabClasses[j]) { + new_intersection.Add (tabAttribute.TabClasses[j]); + scopes.Add (tabAttribute.TabScopes[j]); + } + } + intersection = new_intersection; + } + + tabTypes = new Type[intersection.Count]; + intersection.CopyTo (tabTypes); + tabScopes = new PropertyTabScope[tabTypes.Length]; + scopes.CopyTo (tabScopes); + } + + public void ResetSelectedProperty() + { + if (selected_grid_item == null) + return; + selected_grid_item.ResetValue (); + } + #endregion // Public Instance Methods + + #region Protected Instance Methods + + protected virtual PropertyTab CreatePropertyTab (Type tabType) + { + if (!typeof(PropertyTab).IsAssignableFrom (tabType)) + return null; + + PropertyTab tab = null; + + ConstructorInfo ctor = tabType.GetConstructor (new Type[] { typeof (IServiceProvider) }); + if (ctor != null) + tab = (PropertyTab)ctor.Invoke (new object[] { this.Site }); + else + tab = (PropertyTab)Activator.CreateInstance (tabType); + return tab; + } + + protected override void OnEnabledChanged (EventArgs e) { + base.OnEnabledChanged (e); + } + + protected override void OnFontChanged(EventArgs e) { + base.OnFontChanged (e); + } + + protected override void OnGotFocus(EventArgs e) { + base.OnGotFocus(e); + } + + protected override void OnHandleCreated (EventArgs e) { + base.OnHandleCreated (e); + } + + protected override void OnHandleDestroyed (EventArgs e) { + base.OnHandleDestroyed (e); + } + + protected override void OnMouseDown (MouseEventArgs me) { + base.OnMouseDown (me); + } + + protected override void OnMouseMove (MouseEventArgs me) { + base.OnMouseMove (me); + } + + protected override void OnMouseUp (MouseEventArgs me) { + base.OnMouseUp (me); + } + + protected void OnNotifyPropertyValueUIItemsChanged(object sender, EventArgs e) + { + property_grid_view.UpdateView (); + } + + protected override void OnPaint (PaintEventArgs pevent) { + pevent.Graphics.FillRectangle(ThemeEngine.Current.ResPool.GetSolidBrush(BackColor), pevent.ClipRectangle); + base.OnPaint (pevent); + } + + protected virtual void OnPropertySortChanged(EventArgs e) { + EventHandler eh = (EventHandler) Events [PropertySortChangedEvent]; + if (eh != null) + eh (this, e); + } + + protected virtual void OnPropertyTabChanged (PropertyTabChangedEventArgs e) + { + PropertyTabChangedEventHandler eh = (PropertyTabChangedEventHandler)(Events [PropertyTabChangedEvent]); + if (eh != null) + eh (this, e); + } + + protected virtual void OnPropertyValueChanged (PropertyValueChangedEventArgs e) { + PropertyValueChangedEventHandler eh = (PropertyValueChangedEventHandler)(Events [PropertyValueChangedEvent]); + if (eh != null) + eh (this, e); + } + + protected override void OnResize (EventArgs e) { + base.OnResize (e); + } + + protected virtual void OnSelectedGridItemChanged (SelectedGridItemChangedEventArgs e) { + SelectedGridItemChangedEventHandler eh = (SelectedGridItemChangedEventHandler)(Events [SelectedGridItemChangedEvent]); + if (eh != null) + eh (this, e); + } + + protected virtual void OnSelectedObjectsChanged (EventArgs e) { + EventHandler eh = (EventHandler)(Events [SelectedObjectsChangedEvent]); + if (eh != null) + eh (this, e); + } + + protected override void OnSystemColorsChanged (EventArgs e) { + base.OnSystemColorsChanged (e); + } + + protected override void OnVisibleChanged (EventArgs e) { + base.OnVisibleChanged (e); + } + + protected override bool ProcessDialogKey (Keys keyData) { + return base.ProcessDialogKey (keyData); + } + + [EditorBrowsable (EditorBrowsableState.Never)] + protected override void ScaleCore (float dx, float dy) { + base.ScaleCore (dx, dy); + } + + protected override void WndProc (ref Message m) + { + base.WndProc (ref m); + } + #endregion + + #region Events + static object PropertySortChangedEvent = new object (); + static object PropertyTabChangedEvent = new object (); + static object PropertyValueChangedEvent = new object (); + static object SelectedGridItemChangedEvent = new object (); + static object SelectedObjectsChangedEvent = new object (); + + public event EventHandler PropertySortChanged { + add { Events.AddHandler (PropertySortChangedEvent, value); } + remove { Events.RemoveHandler (PropertySortChangedEvent, value); } + } + + public event PropertyTabChangedEventHandler PropertyTabChanged { + add { Events.AddHandler (PropertyTabChangedEvent, value); } + remove { Events.RemoveHandler (PropertyTabChangedEvent, value); } + } + + public event PropertyValueChangedEventHandler PropertyValueChanged { + add { Events.AddHandler (PropertyValueChangedEvent, value); } + remove { Events.RemoveHandler (PropertyValueChangedEvent, value); } + } + + public event SelectedGridItemChangedEventHandler SelectedGridItemChanged { + add { Events.AddHandler (SelectedGridItemChangedEvent, value); } + remove { Events.RemoveHandler (SelectedGridItemChangedEvent, value); } + } + + public event EventHandler SelectedObjectsChanged { + add { Events.AddHandler (SelectedObjectsChangedEvent, value); } + remove { Events.RemoveHandler (SelectedObjectsChangedEvent, value); } + } + + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler BackgroundImageChanged { + add { base.BackgroundImageChanged += value; } + remove { base.BackgroundImageChanged -= value; } + } + + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler BackgroundImageLayoutChanged { + add { base.BackgroundImageLayoutChanged += value; } + remove { base.BackgroundImageLayoutChanged -= value; } + } + + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler ForeColorChanged { + add { base.ForeColorChanged += value; } + remove { base.ForeColorChanged -= value; } + } + + [EditorBrowsable(EditorBrowsableState.Advanced)] + [Browsable(false)] + public new event KeyEventHandler KeyDown { + add { base.KeyDown += value; } + remove { base.KeyDown -= value; } + } + + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Advanced)] + public new event KeyPressEventHandler KeyPress { + add { base.KeyPress += value; } + remove { base.KeyPress -= value; } + } + + [EditorBrowsable(EditorBrowsableState.Advanced)] + [Browsable(false)] + public new event KeyEventHandler KeyUp { + add { base.KeyUp += value; } + remove { base.KeyUp -= value; } + } + + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Advanced)] + public new event MouseEventHandler MouseDown { + add { base.MouseDown += value; } + remove { base.MouseDown -= value; } + } + + [EditorBrowsable(EditorBrowsableState.Advanced)] + [Browsable(false)] + public new event EventHandler MouseEnter { + add { base.MouseEnter += value; } + remove { base.MouseEnter -= value; } + } + + [EditorBrowsable(EditorBrowsableState.Advanced)] + [Browsable(false)] + public new event EventHandler MouseLeave { + add { base.MouseLeave += value; } + remove { base.MouseLeave -= value; } + } + + [EditorBrowsable(EditorBrowsableState.Advanced)] + [Browsable(false)] + public new event MouseEventHandler MouseMove { + add { base.MouseMove += value; } + remove { base.MouseMove -= value; } + } + + [EditorBrowsable(EditorBrowsableState.Advanced)] + [Browsable(false)] + public new event MouseEventHandler MouseUp { + add { base.MouseUp += value; } + remove { base.MouseUp -= value; } + } + + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler PaddingChanged { + add { base.PaddingChanged += value; } + remove { base.PaddingChanged -= value; } + } + + [Browsable(false)] + public new event EventHandler TextChanged { + add { base.TextChanged += value; } + remove { base.TextChanged -= value; } + } + #endregion + + #region PropertyTabCollection Class + public class PropertyTabCollection : ICollection, IEnumerable + { + ArrayList property_tabs; + ArrayList property_tabs_scopes; + PropertyGrid property_grid; + + internal PropertyTabCollection (PropertyGrid propertyGrid) + { + property_grid = propertyGrid; + property_tabs = new ArrayList (); + property_tabs_scopes = new ArrayList (); + } + + public PropertyTab this[int index] { + get { return (PropertyTab)property_tabs[index]; } + } + + bool ICollection.IsSynchronized { + get { return property_tabs.IsSynchronized; } + } + + void ICollection.CopyTo (Array dest, int index) + { + property_tabs.CopyTo (dest, index); + } + + object ICollection.SyncRoot { + get { return property_tabs.SyncRoot; } + } + + public IEnumerator GetEnumerator () + { + return property_tabs.GetEnumerator (); + } + + public int Count { + get { return property_tabs.Count; } + } + + public void AddTabType (Type propertyTabType) + { + AddTabType (propertyTabType, PropertyTabScope.Global); + } + + public void AddTabType (Type propertyTabType, PropertyTabScope tabScope) + { + if (propertyTabType == null) + throw new ArgumentNullException ("propertyTabType"); + + // Avoid duplicates + if (this.Contains (propertyTabType)) + return; + PropertyTab tab = property_grid.CreatePropertyTab (propertyTabType); + if (tab != null) { + property_tabs.Add (tab); + property_tabs_scopes.Add (tabScope); + } + property_grid.RefreshToolbar (this); + } + + internal PropertyTabScope GetTabScope (PropertyTab tab) + { + if (tab == null) + throw new ArgumentNullException ("tab"); + + int index = property_tabs.IndexOf (tab); + if (index != -1) + return (PropertyTabScope)property_tabs_scopes[index]; + return PropertyTabScope.Global; + } + + internal void InsertTab (int index, PropertyTab propertyTab, PropertyTabScope tabScope) + { + if (propertyTab == null) + throw new ArgumentNullException ("propertyTab"); + + if (!this.Contains (propertyTab.GetType ())) { + property_tabs.Insert (index, propertyTab); + property_tabs_scopes.Insert (index, tabScope); + } + } + + internal bool Contains (Type propertyType) + { + if (propertyType == null) + throw new ArgumentNullException ("propertyType"); + + foreach (PropertyTab t in property_tabs) { + if (t.GetType () == propertyType) + return true; + } + return false; + } + + internal PropertyTab this[Type tabType] { + get { + foreach (PropertyTab tab in property_tabs) { + if (tabType == tab.GetType ()) + return tab; + } + return null; + } + } + + public void Clear (PropertyTabScope tabScope) + { + ArrayList toRemove = new ArrayList (); + for (int i=0; i < property_tabs_scopes.Count; i++) { + if ((PropertyTabScope)property_tabs_scopes[i] == tabScope) + toRemove.Add (i); + } + foreach (int indexToRemove in toRemove) { + property_tabs.RemoveAt (indexToRemove); + property_tabs_scopes.RemoveAt (indexToRemove); + } + property_grid.RefreshToolbar (this); + } + + public void RemoveTabType (Type propertyTabType) + { + if (propertyTabType == null) + throw new ArgumentNullException ("propertyTabType"); + + ArrayList toRemove = new ArrayList (); + for (int i=0; i < property_tabs.Count; i++) { + if (property_tabs[i].GetType () == propertyTabType) + toRemove.Add (i); + } + foreach (int indexToRemove in toRemove) { + property_tabs.RemoveAt (indexToRemove); + property_tabs_scopes.RemoveAt (indexToRemove); + } + property_grid.RefreshToolbar (this); + } + } + #endregion // PropertyTabCollection Class + + #region Private Helper Methods + + private GridItem FindFirstPropertyItem (GridItem root) + { + if (root.GridItemType == GridItemType.Property) + return root; + + foreach (GridItem item in root.GridItems) { + GridItem subitem = FindFirstPropertyItem (item); + if (subitem != null) + return subitem; + } + + return null; + } + + private GridEntry GetDefaultPropertyItem (GridEntry rootItem, PropertyTab propertyTab) + { + if (rootItem == null || rootItem.GridItems.Count == 0 || propertyTab == null) + return null; + object[] propertyOwners = rootItem.Values; + if (propertyOwners == null || propertyOwners.Length == 0 || propertyOwners[0] == null) + return null; + + GridItem defaultSelected = null; + if (propertyOwners.Length > 1) + defaultSelected = rootItem.GridItems[0]; + else { + PropertyDescriptor defaultProperty = propertyTab.GetDefaultProperty (propertyOwners[0]); + if (defaultProperty != null) + defaultSelected = FindItem (defaultProperty.Name, rootItem); + if (defaultSelected == null) + defaultSelected = FindFirstPropertyItem (rootItem); + } + + return defaultSelected as GridEntry; + } + + private GridEntry FindItem (string name, GridEntry rootItem) + { + if (rootItem == null || name == null) + return null; + + if (property_sort == PropertySort.Alphabetical || property_sort == PropertySort.NoSort) { + foreach (GridItem item in rootItem.GridItems) { + if (item.Label == name) { + return (GridEntry)item; + } + } + } else if (property_sort == PropertySort.Categorized || + property_sort == PropertySort.CategorizedAlphabetical) { + foreach (GridItem categoryItem in rootItem.GridItems) { + foreach (GridItem item in categoryItem.GridItems) { + if (item.Label == name) { + return (GridEntry)item; + } + } + } + } + + return null; + } + + private void OnResetPropertyClick (object sender, EventArgs e) + { + ResetSelectedProperty(); + } + + private void OnDescriptionClick (object sender, EventArgs e) + { + this.HelpVisible = !this.HelpVisible; + description_menuitem.Checked = this.HelpVisible; + } + + private void PopulateGrid (object[] objects) + { + if (objects.Length > 0) { + root_grid_item = new RootGridEntry (this, objects); + root_grid_item.Expanded = true; + UpdateSortLayout (root_grid_item); + } else { + root_grid_item = null; + } + } + + private void UpdateSortLayout (GridEntry rootItem) + { + if (rootItem == null) + return; + + GridItemCollection reordered = new GridItemCollection (); + + if (property_sort == PropertySort.Alphabetical || property_sort == PropertySort.NoSort) { + alphabetic_toolbarbutton.Pushed = true; + categorized_toolbarbutton.Pushed = false; + foreach (GridItem item in rootItem.GridItems) { + if (item.GridItemType == GridItemType.Category) { + foreach (GridItem categoryChild in item.GridItems) { + reordered.Add (categoryChild); + ((GridEntry)categoryChild).SetParent (rootItem); + } + } else { + reordered.Add (item); + } + } + } else if (property_sort == PropertySort.Categorized || + property_sort == PropertySort.CategorizedAlphabetical) { + alphabetic_toolbarbutton.Pushed = false; + categorized_toolbarbutton.Pushed = true; + GridItemCollection categories = new GridItemCollection (); + + foreach (GridItem item in rootItem.GridItems) { + if (item.GridItemType == GridItemType.Category) { + categories.Add (item); + continue; + } + + string categoryName = item.PropertyDescriptor.Category; + if (categoryName == null) + categoryName = UNCATEGORIZED_CATEGORY_LABEL; + GridItem category_item = rootItem.GridItems [categoryName]; + if (category_item == null) + category_item = categories [categoryName]; + + if (category_item == null) { + // Create category grid items if they already don't + category_item = new CategoryGridEntry (this, categoryName, rootItem); + category_item.Expanded = true; + categories.Add (category_item); + } + + category_item.GridItems.Add (item); + ((GridEntry)item).SetParent (category_item); + } + + reordered.AddRange (categories); + } + + rootItem.GridItems.Clear (); + rootItem.GridItems.AddRange (reordered); + } + + private void help_panel_Paint(object sender, PaintEventArgs e) { + e.Graphics.FillRectangle(ThemeEngine.Current.ResPool.GetSolidBrush(help_panel.BackColor), help_panel.ClientRectangle ); + e.Graphics.DrawRectangle(SystemPens.ControlDark, 0,0,help_panel.Width-1,help_panel.Height-1 ); + } + + #endregion // Private Helper Methods + +#region Internal helper classes + // as we can not change the color for BorderStyle.FixedSingle and we need the correct + // ClientRectangle so that the ScrollBar doesn't draw over the border we need this class + internal class BorderHelperControl : Widget { + + public BorderHelperControl () + { + BackColor = ThemeEngine.Current.ColorWindow; + } + + protected override void OnPaint (PaintEventArgs e) + { + e.Graphics.DrawRectangle (SystemPens.ControlDark, 0 , 0 , Width - 1, Height - 1); + base.OnPaint (e); + } + + protected override void OnSizeChanged (EventArgs e) + { + if (Widgets.Count == 1) { + Widget control = Widgets [0]; + + if (control.Location.X != 1 || control.Location.Y != 1) + control.Location = new Point (1, 1); + + control.Width = ClientRectangle.Width - 2; + control.Height = ClientRectangle.Height - 2; + } + base.OnSizeChanged (e); + } + } + + private class PropertyToolBarSeparator : ToolStripSeparator + { + public PropertyToolBarSeparator () + { + } + } + + private class PropertyToolBarButton : ToolStripButton + { + private PropertyTab property_tab; + + public PropertyToolBarButton () + { + } + + public PropertyToolBarButton (PropertyTab propertyTab) + { + if (propertyTab == null) + throw new ArgumentNullException ("propertyTab"); + property_tab = propertyTab; + } + + public PropertyTab PropertyTab { + get { return property_tab; } + } + + public bool Pushed { + get { return base.Checked; } + set { base.Checked = value; } + } + + public ToolBarButtonStyle Style { + get { return ToolBarButtonStyle.PushButton; } + set { } + } + } + + // needed! this little helper makes it possible to draw a different toolbar border + // and toolbar backcolor in ThemeWin32Classic + internal class PropertyToolBar : ToolStrip + { + ToolBarAppearance appearance; + + public PropertyToolBar () + { + SetStyle (Widgetstyles.ResizeRedraw, true); + GripStyle = ToolStripGripStyle.Hidden; + appearance = ToolBarAppearance.Normal; + } + + public bool ShowToolTips { + get { return base.ShowItemToolTips; } + set { base.ShowItemToolTips = value; } + } + + public ToolBarAppearance Appearance { + get { return appearance; } + set { + if (value == Appearance) + return; + + switch (value) { + case ToolBarAppearance.Flat: + Renderer = new ToolStripSystemRenderer (); + appearance = ToolBarAppearance.Flat; + break; + case ToolBarAppearance.Normal: + ProfessionalColorTable table = new ProfessionalColorTable (); + table.UseSystemColors = true; + Renderer = new ToolStripProfessionalRenderer (table); + appearance = ToolBarAppearance.Normal; + break; + } + } + } + } + + + [MonoInternalNote ("not sure what this class does, but it's listed as a type converter for a property in this class, and this causes problems if it's not present")] + private class SelectedObjectConverter : TypeConverter + { + } +#endregion + } +} diff --git a/source/ShiftUI/Widgets/PropertyGridCommands.cs b/source/ShiftUI/Widgets/PropertyGridCommands.cs new file mode 100644 index 0000000..d3d376e --- /dev/null +++ b/source/ShiftUI/Widgets/PropertyGridCommands.cs @@ -0,0 +1,47 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2005 Novell, Inc. +// +// Authors: +// Jonathan Chambers [email protected] +// + + +// COMPLETE + +using System; +using System.ComponentModel.Design; + +namespace ShiftUI.PropertyGridInternal +{ + public class PropertyGridCommands + { + public PropertyGridCommands() + { + } + + public static readonly CommandID Commands; + public static readonly CommandID Description; + public static readonly CommandID Hide; + public static readonly CommandID Reset; + protected static readonly Guid wfcMenuCommand; + protected static readonly Guid wfcMenuGroup; + } +} diff --git a/source/ShiftUI/Widgets/PropertyGridTextBox.cs b/source/ShiftUI/Widgets/PropertyGridTextBox.cs new file mode 100644 index 0000000..a815f28 --- /dev/null +++ b/source/ShiftUI/Widgets/PropertyGridTextBox.cs @@ -0,0 +1,320 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2004-2005 Novell, Inc. +// +// Authors: +// Jonathan Chambers ([email protected]) +// + +// COMPLETE + +using System; +using System.Drawing; +using System.ComponentModel; + +namespace ShiftUI.PropertyGridInternal +{ + internal class PGTextBox : TextBox + { + private bool _focusing = false; + + public void FocusAt (Point location) + { + _focusing = true; + Point pnt = PointToClient (location); + XplatUI.SendMessage (Handle, Msg.WM_LBUTTONDOWN, new IntPtr ((int)MsgButtons.MK_LBUTTON), Widget.MakeParam (pnt.X, pnt.Y)); + } + + protected override bool IsInputKey (Keys keyData) + { + // To be handled by the PropertyGridView + if ((keyData & Keys.Alt) != 0 && + (keyData & Keys.KeyCode) == Keys.Down) + return true; + return base.IsInputKey (keyData); + } + + protected override void WndProc (ref Message m) + { + // Swallow the first MOUSEMOVE after the focusing WM_LBUTTONDOWN + if (_focusing && m.Msg == (int)Msg.WM_MOUSEMOVE) { + _focusing = false; + return; + } + base.WndProc (ref m); + } + } + + internal class PropertyGridTextBox : ShiftUI.UserWidget, IMessageFilter + { + #region Private Members + + private PGTextBox textbox; + private Button dialog_button; + private Button dropdown_button; + private bool validating = false; + private bool filtering = false; + + #endregion Private Members + + #region Contructors + public PropertyGridTextBox() { + dialog_button = new Button(); + dropdown_button = new Button(); + textbox = new PGTextBox (); + + SuspendLayout(); + + dialog_button.Dock = DockStyle.Right; + dialog_button.BackColor = SystemColors.Control; + dialog_button.Size = new Size(16, 16); + dialog_button.TabIndex = 1; + dialog_button.Visible = false; + dialog_button.Click += new System.EventHandler(dialog_button_Click); + + dropdown_button.Dock = DockStyle.Right; + dropdown_button.BackColor = SystemColors.Control; + dropdown_button.Size = new Size(16, 16); + dropdown_button.TabIndex = 2; + dropdown_button.Visible = false; + dropdown_button.Click += new System.EventHandler(dropdown_button_Click); + + textbox.AutoSize = false; + textbox.BorderStyle = BorderStyle.None; + textbox.Dock = DockStyle.Fill; + textbox.TabIndex = 3; + + Widgets.Add(textbox); + Widgets.Add(dropdown_button); + Widgets.Add(dialog_button); + + SetStyle (Widgetstyles.Selectable, true); + + ResumeLayout(false); + + dropdown_button.Paint+=new PaintEventHandler(dropdown_button_Paint); + dialog_button.Paint+=new PaintEventHandler(dialog_button_Paint); + textbox.DoubleClick+=new EventHandler(textbox_DoubleClick); + textbox.KeyDown+=new KeyEventHandler(textbox_KeyDown); + textbox.GotFocus+=new EventHandler(textbox_GotFocus); + } + + + #endregion Contructors + + #region Protected Instance Properties + + protected override void OnGotFocus (EventArgs args) + { + base.OnGotFocus (args); + // force-disable selection + textbox.has_been_focused = true; + textbox.Focus (); + textbox.SelectionLength = 0; + } + + #endregion + + #region Public Instance Properties + + public bool DialogButtonVisible { + get{ + return dialog_button.Visible; + } + set { + dialog_button.Visible = value; + } + } + public bool DropDownButtonVisible { + get{ + return dropdown_button.Visible; + } + set { + dropdown_button.Visible = value; + } + } + + public new Color ForeColor { + get { + return base.ForeColor; + } + set { + textbox.ForeColor = value; + dropdown_button.ForeColor = value; + dialog_button.ForeColor = value; + base.ForeColor = value; + } + } + + public new Color BackColor { + get { + return base.BackColor; + } + set { + textbox.BackColor = value; + base.BackColor = value; + } + } + public bool ReadOnly { + get { + return textbox.ReadOnly; + } + set { + textbox.ReadOnly = value; + } + } + + public new string Text { + get { + return textbox.Text; + } + set { + textbox.Text = value; + } + } + + public char PasswordChar { + set { textbox.PasswordChar = value; } + } + + #endregion Public Instance Properties + + #region Events + static object DropDownButtonClickedEvent = new object (); + static object DialogButtonClickedEvent = new object (); + static object ToggleValueEvent = new object (); + static object KeyDownEvent = new object (); + static object ValidateEvent = new object (); + + public event EventHandler DropDownButtonClicked { + add { Events.AddHandler (DropDownButtonClickedEvent, value); } + remove { Events.RemoveHandler (DropDownButtonClickedEvent, value); } + } + + public event EventHandler DialogButtonClicked { + add { Events.AddHandler (DialogButtonClickedEvent, value); } + remove { Events.RemoveHandler (DialogButtonClickedEvent, value); } + } + + public event EventHandler ToggleValue { + add { Events.AddHandler (ToggleValueEvent, value); } + remove { Events.RemoveHandler (ToggleValueEvent, value); } + } + + public new event KeyEventHandler KeyDown { + add { Events.AddHandler (KeyDownEvent, value); } + remove { Events.RemoveHandler (KeyDownEvent, value); } + } + + public new event CancelEventHandler Validate { + add { Events.AddHandler (ValidateEvent, value); } + remove { Events.RemoveHandler (ValidateEvent, value); } + } + #endregion Events + + #region Private Helper Methods + + private void dropdown_button_Paint(object sender, PaintEventArgs e) + { + ThemeEngine.Current.CPDrawComboButton(e.Graphics, dropdown_button.ClientRectangle, dropdown_button.ButtonState); + } + + private void dialog_button_Paint(object sender, PaintEventArgs e) { + // best way to draw the ellipse? + e.Graphics.DrawString("...", new Font(Font,FontStyle.Bold), Brushes.Black, 0,0); + } + + private void dropdown_button_Click(object sender, EventArgs e) { + EventHandler eh = (EventHandler)(Events [DropDownButtonClickedEvent]); + if (eh != null) + eh (this, e); + } + + private void dialog_button_Click(object sender, EventArgs e) { + EventHandler eh = (EventHandler)(Events [DialogButtonClickedEvent]); + if (eh != null) + eh (this, e); + } + + #endregion Private Helper Methods + + internal void SendMouseDown (Point screenLocation) + { + Point clientLocation = PointToClient (screenLocation); + XplatUI.SendMessage (Handle, Msg.WM_LBUTTONDOWN, new IntPtr ((int) MsgButtons.MK_LBUTTON), Widget.MakeParam (clientLocation.X, clientLocation.Y)); + textbox.FocusAt (screenLocation); + } + + private void textbox_DoubleClick(object sender, EventArgs e) { + EventHandler eh = (EventHandler)(Events [ToggleValueEvent]); + if (eh != null) + eh (this, e); + } + + private void textbox_KeyDown(object sender, KeyEventArgs e) { + KeyEventHandler eh = (KeyEventHandler)(Events [KeyDownEvent]); + if (eh != null) + eh (this, e); + } + + private void textbox_GotFocus(object sender, EventArgs e) { + if (!filtering) { + filtering = true; + Application.AddMessageFilter ((IMessageFilter)this); + } + } + + + protected override void DestroyHandle () + { + Application.RemoveMessageFilter ((IMessageFilter)this); + filtering = false; + base.DestroyHandle (); + } + + bool IMessageFilter.PreFilterMessage(ref Message m) + { + // validating check is to allow whatever UI code to execute + // without filtering messages (i.e. error dialogs, etc) + // + if (!validating && m.HWnd != textbox.Handle && textbox.Focused && + (m.Msg == (int)Msg.WM_LBUTTONDOWN || + m.Msg == (int)Msg.WM_MBUTTONDOWN || + m.Msg == (int)Msg.WM_RBUTTONDOWN || + m.Msg == (int)Msg.WM_NCLBUTTONDOWN || + m.Msg == (int)Msg.WM_NCMBUTTONDOWN || + m.Msg == (int)Msg.WM_NCRBUTTONDOWN)) { + CancelEventHandler validateHandler = (CancelEventHandler)(Events [ValidateEvent]); + if (validateHandler != null) { + CancelEventArgs args = new CancelEventArgs (); + validating = true; + validateHandler (this, args); + validating = false; + if (!args.Cancel) { + Application.RemoveMessageFilter ((IMessageFilter)this); + filtering = false; + } + return args.Cancel; + } + } + return false; + } + } +} diff --git a/source/ShiftUI/Widgets/PropertyGridView.cs b/source/ShiftUI/Widgets/PropertyGridView.cs new file mode 100644 index 0000000..ea26d0e --- /dev/null +++ b/source/ShiftUI/Widgets/PropertyGridView.cs @@ -0,0 +1,1101 @@ + +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2005-2008 Novell, Inc. (http://www.novell.com) +// +// Authors: +// Jonathan Chambers ([email protected]) +// Ivan N. Zlatev ([email protected]) +// +// + +// NOT COMPLETE + +using System; +using System.Collections; +using System.ComponentModel.Design; +using System.Drawing; +using System.Drawing.Design; +using System.ComponentModel; +using System.Threading; +using ShiftUI.Design; +using System.Collections.Generic; + +namespace ShiftUI.PropertyGridInternal { + internal class PropertyGridView : ScrollableWidget, IWindowsFormsEditorService { + + #region Private Members + private const char PASSWORD_PAINT_CHAR = '\u25cf'; // the dot char + private const char PASSWORD_TEXT_CHAR = '*'; + private const int V_INDENT = 16; + private const int ENTRY_SPACING = 2; + private const int RESIZE_WIDTH = 3; + private const int BUTTON_WIDTH = 25; + private const int VALUE_PAINT_WIDTH = 19; + private const int VALUE_PAINT_INDENT = 27; + private double splitter_percent = .5; + private int row_height; + private int font_height_padding = 3; + private PropertyGridTextBox grid_textbox; + private PropertyGrid property_grid; + private bool resizing_grid; + private PropertyGridDropDown dropdown_form; + private Form dialog_form; + private ImplicitVScrollBar vbar; + private StringFormat string_format; + private Font bold_font; + private Brush inactive_text_brush; + private ListBox dropdown_list; + private Point last_click; + private Padding dropdown_form_padding; + #endregion + + #region Contructors + public PropertyGridView (PropertyGrid propertyGrid) { + property_grid = propertyGrid; + + string_format = new StringFormat (); + string_format.FormatFlags = StringFormatFlags.NoWrap; + string_format.Trimming = StringTrimming.None; + + grid_textbox = new PropertyGridTextBox (); + grid_textbox.DropDownButtonClicked +=new EventHandler (DropDownButtonClicked); + grid_textbox.DialogButtonClicked +=new EventHandler (DialogButtonClicked); + + dropdown_form = new PropertyGridDropDown (); + dropdown_form.FormBorderStyle = FormBorderStyle.None; + dropdown_form.StartPosition = FormStartPosition.Manual; + dropdown_form.ShowInTaskbar = false; + + dialog_form = new Form (); + dialog_form.StartPosition = FormStartPosition.Manual; + dialog_form.FormBorderStyle = FormBorderStyle.None; + dialog_form.ShowInTaskbar = false; + + dropdown_form_padding = new Padding (0, 0, 2, 2); + + row_height = Font.Height + font_height_padding; + + grid_textbox.Visible = false; + grid_textbox.Font = this.Font; + grid_textbox.BackColor = SystemColors.Window; + grid_textbox.Validate += new CancelEventHandler (grid_textbox_Validate); + grid_textbox.ToggleValue+=new EventHandler (grid_textbox_ToggleValue); + grid_textbox.KeyDown+=new KeyEventHandler (grid_textbox_KeyDown); + this.Widgets.Add (grid_textbox); + + vbar = new ImplicitVScrollBar (); + vbar.Visible = false; + vbar.Value = 0; + vbar.ValueChanged+=new EventHandler (VScrollBar_HandleValueChanged); + vbar.Dock = DockStyle.Right; + this.Widgets.AddImplicit (vbar); + + resizing_grid = false; + + bold_font = new Font (this.Font, FontStyle.Bold); + inactive_text_brush = new SolidBrush (ThemeEngine.Current.ColorGrayText); + + ForeColorChanged+=new EventHandler (RedrawEvent); + BackColorChanged+=new System.EventHandler (RedrawEvent); + FontChanged+=new EventHandler (RedrawEvent); + + SetStyle (Widgetstyles.Selectable, true); + SetStyle (Widgetstyles.DoubleBuffer, true); + SetStyle (Widgetstyles.UserPaint, true); + SetStyle (Widgetstyles.AllPaintingInWmPaint, true); + SetStyle (Widgetstyles.ResizeRedraw, true); + } + + #endregion + + private GridEntry RootGridItem { + get { return (GridEntry)property_grid.RootGridItem; } + } + + private GridEntry SelectedGridItem { + get { return (GridEntry)property_grid.SelectedGridItem; } + set { property_grid.SelectedGridItem = value; } + } + + #region Protected Instance Methods + + protected override void OnFontChanged (EventArgs e) { + base.OnFontChanged (e); + + bold_font = new Font (this.Font, FontStyle.Bold); + row_height = Font.Height + font_height_padding; + } + + private void InvalidateItemLabel (GridEntry item) + { + Invalidate (new Rectangle (0, ((GridEntry)item).Top, SplitterLocation, row_height)); + } + + private void InvalidateItem (GridEntry item) + { + if (item == null) + return; + + Rectangle rect = new Rectangle (0, item.Top, Width, row_height); + Invalidate (rect); + + if (item.Expanded) { + rect = new Rectangle (0, item.Top + row_height, Width, + Height - (item.Top + row_height)); + Invalidate (rect); + } + } + + // [+] expanding is handled in OnMouseDown, so in order to prevent + // duplicate expanding ignore it here. + // + protected override void OnDoubleClick (EventArgs e) + { + if (this.SelectedGridItem != null && this.SelectedGridItem.Expandable && + !this.SelectedGridItem.PlusMinusBounds.Contains (last_click)) + this.SelectedGridItem.Expanded = !this.SelectedGridItem.Expanded; + else + ToggleValue (this.SelectedGridItem); + } + + protected override void OnPaint (PaintEventArgs e) + { + // Background + e.Graphics.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (BackColor), ClientRectangle); + + int yLoc = -vbar.Value*row_height; + if (this.RootGridItem != null) + DrawGridItems (this.RootGridItem.GridItems, e, 1, ref yLoc); + + UpdateScrollBar (); + } + + protected override void OnMouseWheel (MouseEventArgs e) + { + if (vbar == null || !vbar.Visible) + return; + if (e.Delta < 0) + vbar.Value = Math.Min (vbar.Maximum - GetVisibleRowsCount () + 1, vbar.Value + SystemInformation.MouseWheelScrollLines); + else + vbar.Value = Math.Max (0, vbar.Value - SystemInformation.MouseWheelScrollLines); + base.OnMouseWheel (e); + } + + + protected override void OnMouseMove (MouseEventArgs e) { + if (this.RootGridItem == null) + return; + + if (resizing_grid) { + int loc = Math.Max (e.X,2*V_INDENT); + SplitterPercent = 1.0*loc/Width; + } + if (e.X > SplitterLocation - RESIZE_WIDTH && e.X < SplitterLocation + RESIZE_WIDTH) + this.Cursor = Cursors.SizeWE; + else + this.Cursor = Cursors.Default; + base.OnMouseMove (e); + } + + protected override void OnMouseDown (MouseEventArgs e) + { + base.OnMouseDown (e); + last_click = e.Location; + if (this.RootGridItem == null) + return; + + if (e.X > SplitterLocation - RESIZE_WIDTH && e.X < SplitterLocation + RESIZE_WIDTH) { + resizing_grid = true; + } + else { + int offset = -vbar.Value*row_height; + GridItem foundItem = GetSelectedGridItem (this.RootGridItem.GridItems, e.Y, ref offset); + + if (foundItem != null) { + if (foundItem.Expandable && ((GridEntry)foundItem).PlusMinusBounds.Contains (e.X, e.Y)) + foundItem.Expanded = !foundItem.Expanded; + + this.SelectedGridItem = (GridEntry)foundItem; + if (!GridLabelHitTest (e.X)) { + // send mouse down so we get the carret under cursor + grid_textbox.SendMouseDown (PointToScreen (e.Location)); + } + } + } + } + + protected override void OnMouseUp (MouseEventArgs e) { + resizing_grid = false; + base.OnMouseUp (e); + } + + protected override void OnResize (EventArgs e) { + base.OnResize (e); + if (this.SelectedGridItem != null) // initialized already + UpdateView (); + } + + private void UnfocusSelection () + { + Select (this); + } + + private void FocusSelection () + { + Select (grid_textbox); + } + + protected override bool ProcessDialogKey (Keys keyData) { + GridEntry selectedItem = this.SelectedGridItem; + if (selectedItem != null + && grid_textbox.Visible) { + switch (keyData) { + case Keys.Enter: + if (TrySetEntry (selectedItem, grid_textbox.Text)) + UnfocusSelection (); + return true; + case Keys.Escape: + if (selectedItem.IsEditable) + UpdateItem (selectedItem); // reset value + UnfocusSelection (); + return true; + case Keys.Tab: + FocusSelection (); + return true; + default: + return false; + } + } + return base.ProcessDialogKey (keyData); + } + + private bool TrySetEntry (GridEntry entry, object value) + { + if (entry == null || grid_textbox.Text.Equals (entry.ValueText)) + return true; + + if (entry.IsEditable || !entry.IsEditable && (entry.HasCustomEditor || entry.AcceptedValues != null) || + !entry.IsMerged || entry.HasMergedValue || + (!entry.HasMergedValue && grid_textbox.Text != String.Empty)) { + string error = null; + bool changed = entry.SetValue (value, out error); + if (!changed && error != null) { + if (property_grid.ShowError (error, MessageBoxButtons.OKCancel) == DialogResult.Cancel) { + UpdateItem (entry); // restore value, repaint, etc + UnfocusSelection (); + } + return false; + } + } + UpdateItem (entry); // restore value, repaint, etc + return true; + } + + protected override bool IsInputKey (Keys keyData) { + switch (keyData) { + case Keys.Left: + case Keys.Right: + case Keys.Enter: + case Keys.Escape: + case Keys.Up: + case Keys.Down: + case Keys.PageDown: + case Keys.PageUp: + case Keys.Home: + case Keys.End: + return true; + default: + return false; + } + } + + private GridEntry MoveUpFromItem (GridEntry item, int up_count) + { + GridItemCollection items; + int index; + + /* move back up the visible rows (and up the hierarchy as necessary) until + up_count == 0, or we reach the top of the display */ + while (up_count > 0) { + items = item.Parent != null ? item.Parent.GridItems : this.RootGridItem.GridItems; + index = items.IndexOf (item); + + if (index == 0) { + if (item.Parent.GridItemType == GridItemType.Root) // we're at the top row + return item; + item = (GridEntry)item.Parent; + up_count --; + } + else { + GridEntry prev_item = (GridEntry)items[index-1]; + if (prev_item.Expandable && prev_item.Expanded) { + item = (GridEntry)prev_item.GridItems[prev_item.GridItems.Count - 1]; + } + else { + item = prev_item; + } + up_count --; + } + } + return item; + } + + private GridEntry MoveDownFromItem (GridEntry item, int down_count) + { + while (down_count > 0) { + /* if we're a parent node and we're expanded, move to our first child */ + if (item.Expandable && item.Expanded) { + item = (GridEntry)item.GridItems[0]; + down_count--; + } + else { + GridItem searchItem = item; + GridItemCollection searchItems = searchItem.Parent.GridItems; + int searchIndex = searchItems.IndexOf (searchItem); + + while (searchIndex == searchItems.Count - 1) { + searchItem = searchItem.Parent; + + if (searchItem == null || searchItem.Parent == null) + break; + + searchItems = searchItem.Parent.GridItems; + searchIndex = searchItems.IndexOf (searchItem); + } + + if (searchIndex == searchItems.Count - 1) { + /* if we got all the way back to the root with no nodes after + us, the original item was the last one */ + return item; + } + else { + item = (GridEntry)searchItems[searchIndex+1]; + down_count--; + } + } + } + + return item; + } + + protected override void OnKeyDown (KeyEventArgs e) + { + GridEntry selectedItem = this.SelectedGridItem; + + if (selectedItem == null) { + /* XXX not sure what MS does, but at least we shouldn't crash */ + base.OnKeyDown (e); + return; + } + + switch (e.KeyData & Keys.KeyCode) { + case Keys.Left: + if (e.Widget) { + if (SplitterLocation > 2 * V_INDENT) + SplitterPercent -= 0.01; + + e.Handled = true; + break; + } + else { + /* if the node is expandable and is expanded, collapse it. + otherwise, act just like the user pressed up */ + if (selectedItem.Expandable && selectedItem.Expanded) { + selectedItem.Expanded = false; + e.Handled = true; + break; + } + else + goto case Keys.Up; + } + case Keys.Right: + if (e.Widget) { + if (SplitterLocation < Width) + SplitterPercent += 0.01; + + e.Handled = true; + break; + } + else { + /* if the node is expandable and not expanded, expand it. + otherwise, act just like the user pressed down */ + if (selectedItem.Expandable && !selectedItem.Expanded) { + selectedItem.Expanded = true; + e.Handled = true; + break; + } + else + goto case Keys.Down; + } + case Keys.Enter: + /* toggle the expanded state of the selected item */ + if (selectedItem.Expandable) { + selectedItem.Expanded = !selectedItem.Expanded; + } + e.Handled = true; + break; + case Keys.Up: + this.SelectedGridItem = MoveUpFromItem (selectedItem, 1); + e.Handled = true; + break; + case Keys.Down: + this.SelectedGridItem = MoveDownFromItem (selectedItem, 1); + e.Handled = true; + break; + case Keys.PageUp: + this.SelectedGridItem = MoveUpFromItem (selectedItem, vbar.LargeChange); + e.Handled = true; + break; + case Keys.PageDown: + this.SelectedGridItem = MoveDownFromItem (selectedItem, vbar.LargeChange); + e.Handled = true; + break; + case Keys.End: + /* find the last, most deeply nested visible item */ + GridEntry item = (GridEntry)this.RootGridItem.GridItems[this.RootGridItem.GridItems.Count - 1]; + while (item.Expandable && item.Expanded) + item = (GridEntry)item.GridItems[item.GridItems.Count - 1]; + this.SelectedGridItem = item; + e.Handled = true; + break; + case Keys.Home: + this.SelectedGridItem = (GridEntry)this.RootGridItem.GridItems[0]; + e.Handled = true; + break; + } + + base.OnKeyDown (e); + } + + #endregion + + #region Private Helper Methods + + private int SplitterLocation { + get { + return (int)(splitter_percent*Width); + } + } + + private double SplitterPercent { + set { + int old_splitter_location = SplitterLocation; + + splitter_percent = Math.Max (Math.Min (value, .9),.1); + + if (old_splitter_location != SplitterLocation) { + int x = old_splitter_location > SplitterLocation ? SplitterLocation : old_splitter_location; + Invalidate (new Rectangle (x, 0, Width - x - (vbar.Visible ? vbar.Width : 0), Height)); + UpdateItem (this.SelectedGridItem); + } + } + get { + return splitter_percent; + } + } + + private bool GridLabelHitTest (int x) + { + if (0 <= x && x <= splitter_percent * this.Width) + return true; + return false; + } + + private GridItem GetSelectedGridItem (GridItemCollection grid_items, int y, ref int current) { + foreach (GridItem child_grid_item in grid_items) { + if (y > current && y < current + row_height) { + return child_grid_item; + } + current += row_height; + if (child_grid_item.Expanded) { + GridItem foundItem = GetSelectedGridItem (child_grid_item.GridItems, y, ref current); + if (foundItem != null) + return foundItem; + } + } + return null; + } + + private int GetVisibleItemsCount (GridEntry entry) + { + if (entry == null) + return 0; + + int count = 0; + foreach (GridEntry e in entry.GridItems) { + count += 1; + if (e.Expandable && e.Expanded) + count += GetVisibleItemsCount (e); + } + return count; + } + + private int GetVisibleRowsCount () + { + return this.Height / row_height; + } + + private void UpdateScrollBar () + { + if (this.RootGridItem == null) + return; + + int visibleRows = GetVisibleRowsCount (); + int openedItems = GetVisibleItemsCount (this.RootGridItem); + if (openedItems > visibleRows) { + vbar.Visible = true; + vbar.SmallChange = 1; + vbar.LargeChange = visibleRows; + vbar.Maximum = Math.Max (0, openedItems - 1); + } else { + vbar.Value = 0; + vbar.Visible = false; + } + UpdateGridTextBoxBounds (this.SelectedGridItem); + } + + // private bool GetScrollBarVisible () + // { + // if (this.RootGridItem == null) + // return false; + // + // int visibleRows = GetVisibleRowsCount (); + // int openedItems = GetVisibleItemsCount (this.RootGridItem); + // if (openedItems > visibleRows) + // return true; + // return false; + // } + #region Drawing Code + + private void DrawGridItems (GridItemCollection grid_items, PaintEventArgs pevent, int depth, ref int yLoc) { + foreach (GridItem grid_item in grid_items) { + DrawGridItem ((GridEntry)grid_item, pevent, depth, ref yLoc); + if (grid_item.Expanded) + DrawGridItems (grid_item.GridItems, pevent, (grid_item.GridItemType == GridItemType.Category) ? depth : depth+1, ref yLoc); + } + } + + private void DrawGridItemLabel (GridEntry grid_item, PaintEventArgs pevent, int depth, Rectangle rect) { + Font font = this.Font; + Brush brush; + + if (grid_item.GridItemType == GridItemType.Category) { + font = bold_font; + brush = SystemBrushes.ControlText; + + pevent.Graphics.DrawString (grid_item.Label, font, brush, rect.X + 1, rect.Y + ENTRY_SPACING); + if (grid_item == this.SelectedGridItem) { + SizeF size = pevent.Graphics.MeasureString (grid_item.Label, font); + WidgetPaint.DrawFocusRectangle (pevent.Graphics, new Rectangle (rect.X + 1, rect.Y+ENTRY_SPACING, (int)size.Width, (int)size.Height)); + } + } + else { + if (grid_item == this.SelectedGridItem) { + Rectangle highlight = rect; + if (depth > 1) { + highlight.X -= V_INDENT; + highlight.Width += V_INDENT; + } + pevent.Graphics.FillRectangle (SystemBrushes.Highlight, highlight); + // Label + brush = SystemBrushes.HighlightText; + } + else { + brush = grid_item.IsReadOnly ? inactive_text_brush : SystemBrushes.ControlText; + } + } + pevent.Graphics.DrawString (grid_item.Label, font, brush, + new Rectangle (rect.X + 1, rect.Y + ENTRY_SPACING, rect.Width - ENTRY_SPACING, rect.Height - ENTRY_SPACING), + string_format); + } + + private void DrawGridItemValue (GridEntry grid_item, PaintEventArgs pevent, int depth, Rectangle rect) + { + if (grid_item.PropertyDescriptor == null) + return; + + int xLoc = SplitterLocation+ENTRY_SPACING; + if (grid_item.PaintValueSupported) { + pevent.Graphics.DrawRectangle (Pens.Black, SplitterLocation + ENTRY_SPACING, + rect.Y + 2, VALUE_PAINT_WIDTH + 1, row_height - ENTRY_SPACING*2); + grid_item.PaintValue (pevent.Graphics, + new Rectangle (SplitterLocation + ENTRY_SPACING + 1, + rect.Y + ENTRY_SPACING + 1, + VALUE_PAINT_WIDTH, row_height - (ENTRY_SPACING*2 +1))); + xLoc += VALUE_PAINT_INDENT; + } + + Font font = this.Font; + if (grid_item.IsResetable || !grid_item.HasDefaultValue) + font = bold_font; + Brush brush = grid_item.IsReadOnly ? inactive_text_brush : SystemBrushes.ControlText; + string valueText = String.Empty; + if (!grid_item.IsMerged || grid_item.IsMerged && grid_item.HasMergedValue) { + if (grid_item.IsPassword) + valueText = new String (PASSWORD_PAINT_CHAR, grid_item.ValueText.Length); + else + valueText = grid_item.ValueText; + } + pevent.Graphics.DrawString (valueText, font, + brush, + new RectangleF (xLoc + ENTRY_SPACING, rect.Y + ENTRY_SPACING, + ClientRectangle.Width-(xLoc), row_height - ENTRY_SPACING*2), + string_format); + } + + private void DrawGridItem (GridEntry grid_item, PaintEventArgs pevent, int depth, ref int yLoc) { + if (yLoc > -row_height && yLoc < ClientRectangle.Height) { + // Left column + pevent.Graphics.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (property_grid.LineColor), + 0, yLoc, V_INDENT, row_height); + + if (grid_item.GridItemType == GridItemType.Category) { + pevent.Graphics.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (property_grid.CategoryForeColor), depth*V_INDENT,yLoc,ClientRectangle.Width-(depth*V_INDENT), row_height); + } + + DrawGridItemLabel (grid_item, pevent, + depth, + new Rectangle (depth * V_INDENT, yLoc, SplitterLocation - depth * V_INDENT, row_height)); + DrawGridItemValue (grid_item, pevent, + depth, + new Rectangle (SplitterLocation + ENTRY_SPACING , yLoc, + ClientRectangle.Width - SplitterLocation - ENTRY_SPACING - (vbar.Visible ? vbar.Width : 0), + row_height)); + + if (grid_item.GridItemType != GridItemType.Category) { + Pen pen = ThemeEngine.Current.ResPool.GetPen (property_grid.LineColor); + // vertical divider line + pevent.Graphics.DrawLine (pen, SplitterLocation, yLoc, SplitterLocation, yLoc + row_height); + + // draw the horizontal line + pevent.Graphics.DrawLine (pen, 0, yLoc + row_height, ClientRectangle.Width, yLoc + row_height); + } + + if (grid_item.Expandable) { + int y = yLoc + row_height / 2 - ENTRY_SPACING + 1; + grid_item.PlusMinusBounds = DrawPlusMinus (pevent.Graphics, (depth - 1) * V_INDENT + ENTRY_SPACING + 1, + y, grid_item.Expanded, grid_item.GridItemType == GridItemType.Category); + } + + } + grid_item.Top = yLoc; + yLoc += row_height; + } + + private Rectangle DrawPlusMinus (Graphics g, int x, int y, bool expanded, bool category) { + Rectangle bounds = new Rectangle (x, y, 8, 8); + if (!category) g.FillRectangle (Brushes.White, bounds); + Pen pen = ThemeEngine.Current.ResPool.GetPen (property_grid.ViewForeColor); + g.DrawRectangle (pen, bounds); + g.DrawLine (pen, x+2, y+4, x + 6, y+4); + if (!expanded) + g.DrawLine (pen, x+4, y+2, x+4, y+6); + + return bounds; + } + + #endregion + + #region Event Handling + private void RedrawEvent (object sender, System.EventArgs e) + { + Refresh (); + } + + #endregion + + private void listBox_MouseUp (object sender, MouseEventArgs e) { + AcceptListBoxSelection (sender); + } + + private void listBox_KeyDown (object sender, KeyEventArgs e) + { + switch (e.KeyData & Keys.KeyCode) { + case Keys.Enter: + AcceptListBoxSelection (sender); + return; + case Keys.Escape: + CloseDropDown (); + return; + } + } + + void AcceptListBoxSelection (object sender) + { + GridEntry entry = this.SelectedGridItem as GridEntry; + if (entry != null) { + grid_textbox.Text = (string) ((ListBox) sender).SelectedItem; + CloseDropDown (); + if (TrySetEntry (entry, grid_textbox.Text)) + UnfocusSelection (); + } + } + + private void DropDownButtonClicked (object sender, EventArgs e) + { + DropDownEdit (); + } + + private void DropDownEdit () + { + GridEntry entry = SelectedGridItem as GridEntry; + if (entry == null) + return; + + if (entry.HasCustomEditor) { + entry.EditValue ((IWindowsFormsEditorService) this); + } else { + if (dropdown_form.Visible) { + CloseDropDown (); + } + else { + ICollection std_values = entry.AcceptedValues; + if (std_values != null) { + if (dropdown_list == null) { + dropdown_list = new ListBox (); + dropdown_list.KeyDown += new KeyEventHandler (listBox_KeyDown); + dropdown_list.MouseUp += new MouseEventHandler (listBox_MouseUp); + } + dropdown_list.Items.Clear (); + dropdown_list.BorderStyle = BorderStyle.FixedSingle; + int selected_index = 0; + int i = 0; + string valueText = entry.ValueText; + foreach (object obj in std_values) { + dropdown_list.Items.Add (obj); + if (valueText != null && valueText.Equals (obj)) + selected_index = i; + i++; + } + dropdown_list.Height = row_height * Math.Min (dropdown_list.Items.Count, 15); + dropdown_list.Width = ClientRectangle.Width - SplitterLocation - (vbar.Visible ? vbar.Width : 0); + if (std_values.Count > 0) + dropdown_list.SelectedIndex = selected_index; + DropDownWidget (dropdown_list); + } + } + } + } + + private void DialogButtonClicked (object sender, EventArgs e) + { + GridEntry entry = this.SelectedGridItem as GridEntry; + if (entry != null && entry.HasCustomEditor) + { + entry.Value = GetResultFromEditor(entry.Value); + } + } + + public object GetResultFromEditor(object val) + { + var EditorDialogs = new Dictionary<Type, Dialogs.IEditorDialog>(); + EditorDialogs.Add(typeof(string[]), new Dialogs.StringArrayDialog()); + EditorDialogs.Add(typeof(ComboBox.ObjectCollection), new Dialogs.ComboBoxEditorDialog((val as ComboBox.ObjectCollection).Owner)); + object retval = val; + try + { + var dialog = EditorDialogs[retval.GetType()]; + dialog.ShowEditor(); + retval = dialog.Value; + } + catch (Exception ex) + { + MessageBox.Show($"Object {retval} is not valid for the given property. {ex.Message}", "Property Grid"); + retval = val; + } + MessageBox.Show($"val: {val.GetType().Name}, retval: {retval.GetType().Name}", "Property Grid"); + return retval; + } + + private void VScrollBar_HandleValueChanged (object sender, EventArgs e) + { + UpdateView (); + } + + private void grid_textbox_ToggleValue (object sender, EventArgs args) + { + ToggleValue (this.SelectedGridItem); + } + + private void grid_textbox_KeyDown (object sender, KeyEventArgs e) + { + switch (e.KeyData & Keys.KeyCode) { + case Keys.Down: + if (e.Alt) { + DropDownEdit (); + e.Handled = true; + } + break; + } + } + + private void grid_textbox_Validate (object sender, CancelEventArgs args) + { + if (!TrySetEntry (this.SelectedGridItem, grid_textbox.Text)) + args.Cancel = true; + } + + private void ToggleValue (GridEntry entry) + { + if (entry != null && !entry.IsReadOnly && entry.GridItemType == GridItemType.Property) + entry.ToggleValue (); + } + + internal void UpdateItem (GridEntry entry) + { + if (entry == null || entry.GridItemType == GridItemType.Category || + entry.GridItemType == GridItemType.Root) { + grid_textbox.Visible = false; + InvalidateItem (entry); + return; + } + + if (this.SelectedGridItem == entry) { + SuspendLayout (); + grid_textbox.Visible = false; + if (entry.IsResetable || !entry.HasDefaultValue) + grid_textbox.Font = bold_font; + else + grid_textbox.Font = this.Font; + + if (entry.IsReadOnly) { + grid_textbox.DropDownButtonVisible = false; + grid_textbox.DialogButtonVisible = false; + grid_textbox.ReadOnly = true; + grid_textbox.ForeColor = SystemColors.GrayText; + } else { + grid_textbox.DropDownButtonVisible = entry.AcceptedValues != null || + entry.EditorStyle == UITypeEditorEditStyle.DropDown; + grid_textbox.DialogButtonVisible = entry.EditorStyle == UITypeEditorEditStyle.Modal; + grid_textbox.ForeColor = SystemColors.ControlText; + grid_textbox.ReadOnly = !entry.IsEditable; + } + UpdateGridTextBoxBounds (entry); + grid_textbox.PasswordChar = entry.IsPassword ? PASSWORD_TEXT_CHAR : '\0'; + grid_textbox.Text = entry.IsMerged && !entry.HasMergedValue ? String.Empty : entry.ValueText; + grid_textbox.Visible = true; + InvalidateItem (entry); + ResumeLayout (false); + } else { + grid_textbox.Visible = false; + } + } + + private void UpdateGridTextBoxBounds (GridEntry entry) + { + if (entry == null || this.RootGridItem == null) + return; + + int y = -vbar.Value*row_height; + CalculateItemY (entry, this.RootGridItem.GridItems, ref y); + int x = SplitterLocation + ENTRY_SPACING + (entry.PaintValueSupported ? VALUE_PAINT_INDENT : 0); + grid_textbox.SetBounds (x + ENTRY_SPACING, y + ENTRY_SPACING, + ClientRectangle.Width - ENTRY_SPACING - x - (vbar.Visible ? vbar.Width : 0), + row_height - ENTRY_SPACING); + } + + // Calculates the sum of the heights of all items before the one + // + private bool CalculateItemY (GridEntry entry, GridItemCollection items, ref int y) + { + foreach (GridItem item in items) { + if (item == entry) + return true; + y += row_height; + if (item.Expandable && item.Expanded) + if (CalculateItemY (entry, item.GridItems, ref y)) + return true; + } + return false; + } + + private void ScrollToItem (GridEntry item) + { + if (item == null || this.RootGridItem == null) + return; + + int itemY = -vbar.Value*row_height; + int value = vbar.Value;; + CalculateItemY (item, this.RootGridItem.GridItems, ref itemY); + if (itemY < 0) // the new item is above the viewable area + value += itemY / row_height; + else if (itemY + row_height > Height) // the new item is below the viewable area + value += ((itemY + row_height) - Height) / row_height + 1; + if (value >= vbar.Minimum && value <= vbar.Maximum) + vbar.Value = value; + } + + internal void SelectItem (GridEntry oldItem, GridEntry newItem) + { + if (oldItem != null) + InvalidateItemLabel (oldItem); + if (newItem != null) { + UpdateItem (newItem); + ScrollToItem (newItem); + } else { + grid_textbox.Visible = false; + vbar.Visible = false; + } + } + + internal void UpdateView () + { + UpdateScrollBar (); + Invalidate (); + Update (); + UpdateItem (this.SelectedGridItem); + } + + internal void ExpandItem (GridEntry item) + { + UpdateItem (this.SelectedGridItem); + Invalidate (new Rectangle (0, item.Top, Width, Height - item.Top)); + } + + internal void CollapseItem (GridEntry item) + { + UpdateItem (this.SelectedGridItem); + Invalidate (new Rectangle (0, item.Top, Width, Height - item.Top)); + } + + private void ShowDropDownControl (Widget control, bool resizeable) + { + dropdown_form.Size = control.Size; + control.Dock = DockStyle.Fill; + + if (resizeable) { + dropdown_form.Padding = dropdown_form_padding; + dropdown_form.Width += dropdown_form_padding.Right; + dropdown_form.Height += dropdown_form_padding.Bottom; + dropdown_form.FormBorderStyle = FormBorderStyle.Sizable; + dropdown_form.SizeGripStyle = SizeGripStyle.Show; + } else { + dropdown_form.FormBorderStyle = FormBorderStyle.None; + dropdown_form.SizeGripStyle = SizeGripStyle.Hide; + dropdown_form.Padding = Padding.Empty; + } + + dropdown_form.Widgets.Add (control); + dropdown_form.Width = Math.Max (ClientRectangle.Width - SplitterLocation - (vbar.Visible ? vbar.Width : 0), + control.Width); + dropdown_form.Location = PointToScreen (new Point (grid_textbox.Right - dropdown_form.Width, grid_textbox.Location.Y + row_height)); + RepositionInScreenWorkingArea (dropdown_form); + Point location = dropdown_form.Location; + + Form owner = FindForm (); + owner.AddOwnedForm (dropdown_form); + dropdown_form.Show (); + if (dropdown_form.Location != location) + dropdown_form.Location = location; + control.Show(); + ShiftUI.MSG msg = new MSG (); + object queue_id = XplatUI.StartLoop (Thread.CurrentThread); + control.Focus (); + while (dropdown_form.Visible && XplatUI.GetMessage (queue_id, ref msg, IntPtr.Zero, 0, 0)) { + switch (msg.message) { + case Msg.WM_NCLBUTTONDOWN: + case Msg.WM_NCMBUTTONDOWN: + case Msg.WM_NCRBUTTONDOWN: + case Msg.WM_LBUTTONDOWN: + case Msg.WM_MBUTTONDOWN: + case Msg.WM_RBUTTONDOWN: + if (!HwndInControl (dropdown_form, msg.hwnd)) + CloseDropDown (); + break; + case Msg.WM_ACTIVATE: + case Msg.WM_NCPAINT: + if (owner.window.Handle == msg.hwnd) + CloseDropDown (); + break; + } + XplatUI.TranslateMessage (ref msg); + XplatUI.DispatchMessage (ref msg); + } + XplatUI.EndLoop (Thread.CurrentThread); + } + + private void RepositionInScreenWorkingArea (Form form) + { + Rectangle workingArea = Screen.FromControl (form).WorkingArea; + if (!workingArea.Contains (form.Bounds)) { + int x, y; + x = form.Location.X; + y = form.Location.Y; + + if (form.Location.X < workingArea.X) + x = workingArea.X; + + if (form.Location.Y + form.Size.Height > workingArea.Height) { + Point aboveTextBox = PointToScreen (new Point (grid_textbox.Right - form.Width, grid_textbox.Location.Y)); + y = aboveTextBox.Y - form.Size.Height; + } + + form.Location = new Point (x, y); + } + } + + private bool HwndInControl (Widget c, IntPtr hwnd) + { + if (hwnd == c.window.Handle) + return true; + foreach (Widget cc in c.Widgets.GetAllWidgets ()) { + if (HwndInControl (cc, hwnd)) + return true; + } + return false; + } + #endregion + + #region IWindowsFormsEditorService Members + + + public void CloseDropDown () + { + dropdown_form.Hide (); + dropdown_form.Widgets.Clear (); + } + + public void DropDownWidget (Widget control) + { + bool resizeable = this.SelectedGridItem != null ? SelectedGridItem.EditorResizeable : false; + ShowDropDownControl (control, resizeable); + } + + public ShiftUI.DialogResult ShowDialog (Form dialog) { + return dialog.ShowDialog (this); + } + + #endregion + + internal class PropertyGridDropDown : Form + { + protected override CreateParams CreateParams { + get { + CreateParams cp = base.CreateParams; + cp.Style = unchecked ((int)(WindowStyles.WS_POPUP | WindowStyles.WS_CLIPSIBLINGS | WindowStyles.WS_CLIPCHILDREN)); + cp.ExStyle |= (int)(WindowExStyles.WS_EX_TOPMOST); + return cp; + } + } + + } + } +} diff --git a/source/ShiftUI/Widgets/RadioButton.cs b/source/ShiftUI/Widgets/RadioButton.cs new file mode 100644 index 0000000..83a4f7c --- /dev/null +++ b/source/ShiftUI/Widgets/RadioButton.cs @@ -0,0 +1,391 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2004-2005 Novell, Inc. +// +// Authors: +// Peter Bartok [email protected] +// + +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Text; +using System.Runtime.InteropServices; +using System; + +namespace ShiftUI { + [DefaultProperty("Checked")] + [DefaultEvent("CheckedChanged")] + [ClassInterface (ClassInterfaceType.AutoDispatch)] + [ComVisible (true)] + [DefaultBindingProperty ("Checked")] + [ToolboxItem ("ShiftUI.Design.AutoSizeToolboxItem," + Consts.AssemblySystem_Design)] + //[Designer ("ShiftUI.Design.RadioButtonDesigner, " + Consts.AssemblySystem_Design)] + [ToolboxWidget] + public class RadioButton : ButtonBase { + #region Local Variables + internal Appearance appearance; + internal bool auto_check; + internal ContentAlignment radiobutton_alignment; + internal CheckState check_state; + #endregion // Local Variables + + #region RadioButtonAccessibleObject Subclass + [ComVisible(true)] + public class RadioButtonAccessibleObject : ButtonBaseAccessibleObject { + #region RadioButtonAccessibleObject Local Variables + private new RadioButton owner; + #endregion // RadioButtonAccessibleObject Local Variables + + #region RadioButtonAccessibleObject Constructors + public RadioButtonAccessibleObject(RadioButton owner) : base(owner) { + this.owner = owner; + } + #endregion // RadioButtonAccessibleObject Constructors + + #region RadioButtonAccessibleObject Properties + public override string DefaultAction { + get { + return "Select"; + } + } + + public override AccessibleRole Role { + get { + return AccessibleRole.RadioButton; + } + } + + public override AccessibleStates State { + get { + AccessibleStates retval; + + retval = AccessibleStates.Default; + + if (owner.check_state == CheckState.Checked) { + retval |= AccessibleStates.Checked; + } + + if (owner.Focused) { + retval |= AccessibleStates.Focused; + } + + if (owner.CanFocus) { + retval |= AccessibleStates.Focusable; + } + + return retval; + } + } + #endregion // RadioButtonAccessibleObject Properties + + #region RadioButtonAccessibleObject Methods + public override void DoDefaultAction() { + owner.PerformClick(); + } + #endregion // RadioButtonAccessibleObject Methods + } + #endregion // RadioButtonAccessibleObject Sub-class + + #region Public Constructors + public RadioButton() { + appearance = Appearance.Normal; + auto_check = true; + radiobutton_alignment = ContentAlignment.MiddleLeft; + TextAlign = ContentAlignment.MiddleLeft; + TabStop = false; + } + #endregion // Public Constructors + + #region Private Methods + + // When getting OnEnter we need to set Checked as true in case none of the sibling radio + // buttons is checked. + private void PerformDefaultCheck () + { + // if we are already checked, no need to check the other Widgets + if (!auto_check || Checked) + return; + + bool is_any_selected = false; + Widget c = Parent; + if (c != null) { + for (int i = 0; i < c.Widgets.Count; i++) { + RadioButton rb = c.Widgets [i] as RadioButton; + if (rb == null || !rb.auto_check) + continue; + + if (rb.check_state == CheckState.Checked) { + is_any_selected = true; + break; + } + } + } + + if (!is_any_selected) + Checked = true; + } + + private void UpdateSiblings() { + Widget c; + + if (auto_check == false) { + return; + } + + // Remove tabstop property from and uncheck our radio-button siblings + c = this.Parent; + if (c != null) { + for (int i = 0; i < c.Widgets.Count; i++) { + if ((this != c.Widgets[i]) && (c.Widgets[i] is RadioButton)) { + if (((RadioButton)(c.Widgets[i])).auto_check) { + c.Widgets[i].TabStop = false; + ((RadioButton)(c.Widgets[i])).Checked = false; + } + } + } + } + + this.TabStop = true; + } + + internal override void Draw (PaintEventArgs pe) { + // FIXME: This should be called every time something that can affect it + // is changed, not every paint. Can only change so many things at a time. + + // Figure out where our text and image should go + Rectangle glyph_rectangle; + Rectangle text_rectangle; + Rectangle image_rectangle; + + ThemeEngine.Current.CalculateRadioButtonTextAndImageLayout (this, Point.Empty, out glyph_rectangle, out text_rectangle, out image_rectangle); + + // Draw our button + if (FlatStyle != FlatStyle.System && Appearance != Appearance.Button) + ThemeEngine.Current.DrawRadioButton (pe.Graphics, this, glyph_rectangle, text_rectangle, image_rectangle, pe.ClipRectangle); + else + ThemeEngine.Current.DrawRadioButton (pe.Graphics, this.ClientRectangle, this); + } + + internal override Size GetPreferredSizeCore (Size proposedSize) + { + if (this.AutoSize) + return ThemeEngine.Current.CalculateRadioButtonAutoSize (this); + + return base.GetPreferredSizeCore (proposedSize); + } + #endregion // Private Methods + + #region Public Instance Properties + [DefaultValue(Appearance.Normal)] + [Localizable(true)] + public Appearance Appearance { + get { + return appearance; + } + + set { + if (value != appearance) { + appearance = value; + EventHandler eh = (EventHandler)(Events [AppearanceChangedEvent]); + if (eh != null) + eh (this, EventArgs.Empty); + if (Parent != null) + Parent.PerformLayout (this, "Appearance"); + Invalidate(); + } + } + } + + [DefaultValue(true)] + public bool AutoCheck { + get { + return auto_check; + } + + set { + auto_check = value; + } + } + + [Localizable(true)] + [DefaultValue(ContentAlignment.MiddleLeft)] + public ContentAlignment CheckAlign { + get { + return radiobutton_alignment; + } + + set { + if (value != radiobutton_alignment) { + radiobutton_alignment = value; + + Invalidate(); + } + } + } + + [DefaultValue(false)] + [SettingsBindable (true)] + [Bindable (true, BindingDirection.OneWay)] + public bool Checked { + get { + if (check_state != CheckState.Unchecked) { + return true; + } + return false; + } + + set { + if (value && (check_state != CheckState.Checked)) { + check_state = CheckState.Checked; + Invalidate(); + UpdateSiblings(); + OnCheckedChanged(EventArgs.Empty); + } else if (!value && (check_state != CheckState.Unchecked)) { + TabStop = false; + check_state = CheckState.Unchecked; + Invalidate(); + OnCheckedChanged(EventArgs.Empty); + } + } + } + + [DefaultValue(false)] + public new bool TabStop { + get { return base.TabStop; } + set { base.TabStop = value; } + } + + [DefaultValue(ContentAlignment.MiddleLeft)] + [Localizable(true)] + public override ContentAlignment TextAlign { + get { return base.TextAlign; } + set { base.TextAlign = value; } + } + #endregion // Public Instance Properties + + #region Protected Instance Properties + protected override CreateParams CreateParams { + get { + SetStyle(Widgetstyles.AllPaintingInWmPaint, true); + SetStyle(Widgetstyles.UserPaint, true); + + return base.CreateParams; + } + } + + protected override Size DefaultSize { + get { + return ThemeEngine.Current.RadioButtonDefaultSize; + } + } + #endregion // Protected Instance Properties + + #region Public Instance Methods + public void PerformClick() { + OnClick(EventArgs.Empty); + } + + public override string ToString() { + return base.ToString() + ", Checked: " + this.Checked; + } + #endregion // Public Instance Methods + + #region Protected Instance Methods + protected override AccessibleObject CreateAccessibilityInstance() { + AccessibleObject ao; + + ao = base.CreateAccessibilityInstance (); + ao.role = AccessibleRole.RadioButton; + + return ao; + } + + protected virtual void OnCheckedChanged(EventArgs e) { + EventHandler eh = (EventHandler)(Events [CheckedChangedEvent]); + if (eh != null) + eh (this, e); + } + + protected override void OnClick(EventArgs e) { + if (auto_check) { + if (!Checked) { + Checked = true; + } + } else { + Checked = !Checked; + } + + base.OnClick (e); + } + + protected override void OnEnter(EventArgs e) { + PerformDefaultCheck (); + base.OnEnter(e); + } + + protected override void OnHandleCreated(EventArgs e) { + base.OnHandleCreated(e); + } + + protected override void OnMouseUp(MouseEventArgs mevent) { + base.OnMouseUp(mevent); + } + + protected override bool ProcessMnemonic(char charCode) { + if (IsMnemonic(charCode, Text) == true) { + Select(); + PerformClick(); + return true; + } + + return base.ProcessMnemonic(charCode); + } + #endregion // Protected Instance Methods + + #region Events + static object AppearanceChangedEvent = new object (); + static object CheckedChangedEvent = new object (); + + public event EventHandler AppearanceChanged { + add { Events.AddHandler (AppearanceChangedEvent, value); } + remove { Events.RemoveHandler (AppearanceChangedEvent, value); } + } + + public event EventHandler CheckedChanged { + add { Events.AddHandler (CheckedChangedEvent, value); } + remove { Events.RemoveHandler (CheckedChangedEvent, value); } + } + + [Browsable(false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler DoubleClick { + add { base.DoubleClick += value; } + remove { base.DoubleClick -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event MouseEventHandler MouseDoubleClick { + add { base.MouseDoubleClick += value; } + remove { base.MouseDoubleClick -= value; } + } + #endregion // Events + } +} diff --git a/source/ShiftUI/Widgets/RichTextBox.cs b/source/ShiftUI/Widgets/RichTextBox.cs new file mode 100644 index 0000000..c1d2325 --- /dev/null +++ b/source/ShiftUI/Widgets/RichTextBox.cs @@ -0,0 +1,2089 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2005-2006 Novell, Inc. (http://www.novell.com) +// +// Authors: +// Peter Bartok <[email protected]> +// +// + +// #define DEBUG + +using System; +using System.Collections; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Text; +using System.Runtime.InteropServices; +using RTF=ShiftUI.RTF; + +namespace ShiftUI { + [ClassInterface (ClassInterfaceType.AutoDispatch)] + [Docking (DockingBehavior.Ask)] + [ComVisible (true)] + [Designer ("ShiftUI.Design.RichTextBoxDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")] + [ToolboxWidget] + public class RichTextBox : TextBoxBase { + #region Local Variables + internal bool auto_word_select; + internal int bullet_indent; + internal bool detect_urls; + private bool reuse_line; // Sometimes we are loading text with already available lines + internal int margin_right; + internal float zoom; + private StringBuilder rtf_line; + + private RtfSectionStyle rtf_style; // Replaces individual style + // properties so we can revert + private Stack rtf_section_stack; + + private RTF.TextMap rtf_text_map; + private int rtf_skip_count; + private int rtf_cursor_x; + private int rtf_cursor_y; + private int rtf_chars; + + private bool enable_auto_drag_drop; + private RichTextBoxLanguageOptions language_option; + private bool rich_text_shortcuts_enabled; + private Color selection_back_color; + #endregion // Local Variables + + #region Public Constructors + public RichTextBox() { + accepts_return = true; + auto_size = false; + auto_word_select = false; + bullet_indent = 0; + base.MaxLength = Int32.MaxValue; + margin_right = 0; + zoom = 1; + base.Multiline = true; + document.CRLFSize = 1; + shortcuts_enabled = true; + base.EnableLinks = true; + richtext = true; + + rtf_style = new RtfSectionStyle (); + rtf_section_stack = null; + + scrollbars = RichTextBoxScrollBars.Both; + alignment = HorizontalAlignment.Left; + LostFocus += new EventHandler(RichTextBox_LostFocus); + GotFocus += new EventHandler(RichTextBox_GotFocus); + BackColor = ThemeEngine.Current.ColorWindow; + backcolor_set = false; + language_option = RichTextBoxLanguageOptions.AutoFontSizeAdjust; + rich_text_shortcuts_enabled = true; + selection_back_color = DefaultBackColor; + ForeColor = ThemeEngine.Current.ColorWindowText; + + base.HScrolled += new EventHandler(RichTextBox_HScrolled); + base.VScrolled += new EventHandler(RichTextBox_VScrolled); + + SetStyle (Widgetstyles.StandardDoubleClick, false); + } + #endregion // Public Constructors + + #region Private & Internal Methods + + internal override void HandleLinkClicked (LinkRectangle link) + { + OnLinkClicked (new LinkClickedEventArgs (link.LinkTag.LinkText)); + } + + internal override Color ChangeBackColor (Color backColor) + { + if (backColor == Color.Empty) { + backcolor_set = false; + if (!ReadOnly) { + backColor = SystemColors.Window; + } + } + return backColor; + } + + internal override void RaiseSelectionChanged() + { + OnSelectionChanged (EventArgs.Empty); + } + + private void RichTextBox_LostFocus(object sender, EventArgs e) { + Invalidate(); + } + + private void RichTextBox_GotFocus(object sender, EventArgs e) { + Invalidate(); + } + #endregion // Private & Internal Methods + + #region Public Instance Properties + [Browsable (false)] + public override bool AllowDrop { + get { + return base.AllowDrop; + } + + set { + base.AllowDrop = value; + } + } + + [DefaultValue(false)] + [DesignerSerializationVisibility (DesignerSerializationVisibility.Visible)] + [RefreshProperties (RefreshProperties.Repaint)] + [EditorBrowsable (EditorBrowsableState.Never)] + [Browsable (false)] + public override bool AutoSize { + get { + return auto_size; + } + + set { + base.AutoSize = value; + } + } + + [MonoTODO ("Value not respected, always true")] + [DefaultValue(false)] + public bool AutoWordSelection { + get { return auto_word_select; } + set { auto_word_select = value; } + } + + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + public override System.Drawing.Image BackgroundImage { + get { return base.BackgroundImage; } + set { base.BackgroundImage = value; } + } + + [Browsable (false)] + [EditorBrowsable (EditorBrowsableState.Never)] + public override ImageLayout BackgroundImageLayout { + get { return base.BackgroundImageLayout; } + set { base.BackgroundImageLayout = value; } + } + + [DefaultValue(0)] + [Localizable(true)] + public int BulletIndent { + get { + return bullet_indent; + } + + set { + bullet_indent = value; + } + } + + [Browsable(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool CanRedo { + get { + return document.undo.CanRedo; + } + } + + [DefaultValue(true)] + public bool DetectUrls { + get { return base.EnableLinks; } + set { base.EnableLinks = value; } + } + + [MonoTODO ("Stub, does nothing")] + [DefaultValue (false)] + public bool EnableAutoDragDrop { + get { return enable_auto_drag_drop; } + set { enable_auto_drag_drop = value; } + } + + public override Font Font { + get { + return base.Font; + } + + set { + if (font != value) { + Line start; + Line end; + + if (auto_size) { + if (PreferredHeight != Height) { + Height = PreferredHeight; + } + } + + base.Font = value; + + // Font changes always set the whole doc to that font + start = document.GetLine(1); + end = document.GetLine(document.Lines); + document.FormatText(start, 1, end, end.text.Length + 1, base.Font, Color.Empty, Color.Empty, FormatSpecified.Font); + } + } + } + + public override Color ForeColor { + get { + return base.ForeColor; + } + + set { + base.ForeColor = value; + } + } + + [MonoTODO ("Stub, does nothing")] + [Browsable (false)] + [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public RichTextBoxLanguageOptions LanguageOption { + get { return language_option; } + set { language_option = value; } + } + + [DefaultValue(Int32.MaxValue)] + public override int MaxLength { + get { return base.MaxLength; } + set { base.MaxLength = value; } + } + + [DefaultValue(true)] + public override bool Multiline { + get { + return base.Multiline; + } + + set { + base.Multiline = value; + } + } + + [Browsable(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public string RedoActionName { + get { + return document.undo.RedoActionName; + } + } + + [MonoTODO ("Stub, does nothing")] + [Browsable (false)] + [DefaultValue (true)] + [EditorBrowsable (EditorBrowsableState.Never)] + public bool RichTextShortcutsEnabled { + get { return rich_text_shortcuts_enabled; } + set { rich_text_shortcuts_enabled = value; } + } + + [DefaultValue(0)] + [Localizable(true)] + [MonoTODO ("Stub, does nothing")] + [MonoInternalNote ("Teach TextControl.RecalculateLine to consider the right margin as well")] + public int RightMargin { + get { + return margin_right; + } + + set { + margin_right = value; + } + } + + [Browsable(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + [RefreshProperties (RefreshProperties.All)] + public string Rtf { + get { + Line start_line; + Line end_line; + + start_line = document.GetLine(1); + end_line = document.GetLine(document.Lines); + return GenerateRTF(start_line, 0, end_line, end_line.text.Length).ToString(); + } + + set { + MemoryStream data; + + document.Empty(); + data = new MemoryStream(Encoding.ASCII.GetBytes(value), false); + + InsertRTFFromStream(data, 0, 1); + + data.Close(); + + Invalidate(); + } + } + + [DefaultValue(RichTextBoxScrollBars.Both)] + [Localizable(true)] + public RichTextBoxScrollBars ScrollBars { + get { + return scrollbars; + } + + set { + if (!Enum.IsDefined (typeof (RichTextBoxScrollBars), value)) + throw new InvalidEnumArgumentException ("value", (int) value, + typeof (RichTextBoxScrollBars)); + + if (value != scrollbars) { + scrollbars = value; + CalculateDocument (); + } + } + } + + [Browsable(false)] + [DefaultValue("")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public string SelectedRtf { + get { + return GenerateRTF(document.selection_start.line, document.selection_start.pos, document.selection_end.line, document.selection_end.pos).ToString(); + } + + set { + MemoryStream data; + int x; + int y; + int sel_start; + int chars; + Line line; + LineTag tag; + + if (document.selection_visible) { + document.ReplaceSelection("", false); + } + + sel_start = document.LineTagToCharIndex(document.selection_start.line, document.selection_start.pos); + + data = new MemoryStream(Encoding.ASCII.GetBytes(value), false); + int cursor_x = document.selection_start.pos; + int cursor_y = document.selection_start.line.line_no; + + // The RFT parser by default, when finds our x cursor in 0, it thinks if needs to + // add a new line; but in *this* scenario the line is already created, so force it to reuse it. + // Hackish, but works without touching the heart of the buggy parser. + if (cursor_x == 0) + reuse_line = true; + + InsertRTFFromStream(data, cursor_x, cursor_y, out x, out y, out chars); + data.Close(); + + int nl_length = document.LineEndingLength (XplatUI.RunningOnUnix ? LineEnding.Rich : LineEnding.Hard); + document.CharIndexToLineTag(sel_start + chars + (y - document.selection_start.line.line_no) * nl_length, + out line, out tag, out sel_start); + if (sel_start >= line.text.Length) + sel_start = line.text.Length -1; + + document.SetSelection(line, sel_start); + document.PositionCaret(line, sel_start); + document.DisplayCaret(); + ScrollToCaret(); + OnTextChanged(EventArgs.Empty); + } + } + + [Browsable(false)] + [DefaultValue("")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public override string SelectedText { + get { + return base.SelectedText; + } + + set { + // TextBox/TextBoxBase don't set Modified in this same property + Modified = true; + base.SelectedText = value; + } + } + + [Browsable(false)] + [DefaultValue(HorizontalAlignment.Left)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public HorizontalAlignment SelectionAlignment { + get { + HorizontalAlignment align; + Line start; + Line end; + Line line; + + start = document.ParagraphStart(document.selection_start.line); + align = start.alignment; + + end = document.ParagraphEnd(document.selection_end.line); + + line = start; + + while (true) { + if (line.alignment != align) { + return HorizontalAlignment.Left; + } + + if (line == end) { + break; + } + line = document.GetLine(line.line_no + 1); + } + + return align; + } + + set { + Line start; + Line end; + Line line; + + start = document.ParagraphStart(document.selection_start.line); + + end = document.ParagraphEnd(document.selection_end.line); + + line = start; + + while (true) { + line.alignment = value; + + if (line == end) { + break; + } + line = document.GetLine(line.line_no + 1); + } + this.CalculateDocument(); + } + } + + [MonoTODO ("Stub, does nothing")] + [Browsable (false)] + [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public Color SelectionBackColor { + get { return selection_back_color; } + set { selection_back_color = value; } + } + + [Browsable(false)] + [DefaultValue(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + [MonoTODO ("Stub, does nothing")] + public bool SelectionBullet { + get { + return false; + } + + set { + } + } + + [Browsable(false)] + [DefaultValue(0)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + [MonoTODO ("Stub, does nothing")] + public int SelectionCharOffset { + get { + return 0; + } + + set { + } + } + + [Browsable(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public Color SelectionColor { + get { + Color color; + LineTag start; + LineTag end; + LineTag tag; + + if (selection_length > 0) { + start = document.selection_start.line.FindTag (document.selection_start.pos + 1); + end = document.selection_start.line.FindTag (document.selection_end.pos); + } else { + start = document.selection_start.line.FindTag (document.selection_start.pos); + end = start; + } + + color = start.Color; + + tag = start; + while (tag != null) { + + if (!color.Equals (tag.Color)) + return Color.Empty; + + if (tag == end) + break; + + tag = document.NextTag (tag); + } + + return color; + } + + set { + if (value == Color.Empty) + value = DefaultForeColor; + + int sel_start; + int sel_end; + + sel_start = document.LineTagToCharIndex(document.selection_start.line, document.selection_start.pos); + sel_end = document.LineTagToCharIndex(document.selection_end.line, document.selection_end.pos); + + document.FormatText (document.selection_start.line, document.selection_start.pos + 1, + document.selection_end.line, document.selection_end.pos + 1, null, + value, Color.Empty, FormatSpecified.Color); + + document.CharIndexToLineTag(sel_start, out document.selection_start.line, out document.selection_start.tag, out document.selection_start.pos); + document.CharIndexToLineTag(sel_end, out document.selection_end.line, out document.selection_end.tag, out document.selection_end.pos); + + document.UpdateView(document.selection_start.line, 0); + + //Re-Align the caret in case its changed size or position + //probably not necessary here + document.AlignCaret(false); + } + } + + [Browsable(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public Font SelectionFont { + get { + Font font; + LineTag start; + LineTag end; + LineTag tag; + + if (selection_length > 0) { + start = document.selection_start.line.FindTag (document.selection_start.pos + 1); + end = document.selection_start.line.FindTag (document.selection_end.pos); + } else { + start = document.selection_start.line.FindTag (document.selection_start.pos); + end = start; + } + + font = start.Font; + + if (selection_length > 1) { + tag = start; + while (tag != null) { + + if (!font.Equals(tag.Font)) + return null; + + if (tag == end) + break; + + tag = document.NextTag (tag); + } + } + + return font; + } + + set { + int sel_start; + int sel_end; + + sel_start = document.LineTagToCharIndex(document.selection_start.line, document.selection_start.pos); + sel_end = document.LineTagToCharIndex(document.selection_end.line, document.selection_end.pos); + + document.FormatText (document.selection_start.line, document.selection_start.pos + 1, + document.selection_end.line, document.selection_end.pos + 1, value, + Color.Empty, Color.Empty, FormatSpecified.Font); + + document.CharIndexToLineTag(sel_start, out document.selection_start.line, out document.selection_start.tag, out document.selection_start.pos); + document.CharIndexToLineTag(sel_end, out document.selection_end.line, out document.selection_end.tag, out document.selection_end.pos); + + document.UpdateView(document.selection_start.line, 0); + //Re-Align the caret in case its changed size or position + Document.AlignCaret (false); + + } + } + + [Browsable(false)] + [DefaultValue(0)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + [MonoTODO ("Stub, does nothing")] + public int SelectionHangingIndent { + get { + return 0; + } + + set { + } + } + + [Browsable(false)] + [DefaultValue(0)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + [MonoTODO ("Stub, does nothing")] + public int SelectionIndent { + get { + return 0; + } + + set { + } + } + + [Browsable(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public override int SelectionLength { + get { + return base.SelectionLength; + } + + set { + base.SelectionLength = value; + } + } + + [Browsable(false)] + [DefaultValue(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + [MonoTODO ("Stub, does nothing")] + public bool SelectionProtected { + get { + return false; + } + + set { + } + } + + [Browsable(false)] + [DefaultValue(0)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + [MonoTODO ("Stub, does nothing")] + public int SelectionRightIndent { + get { + return 0; + } + + set { + } + } + + [Browsable(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + [MonoTODO ("Stub, does nothing")] + public int[] SelectionTabs { + get { + return new int[0]; + } + + set { + } + } + + [Browsable(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public RichTextBoxSelectionTypes SelectionType { + get { + if (document.selection_start == document.selection_end) { + return RichTextBoxSelectionTypes.Empty; + } + + // Lazy, but works + if (SelectedText.Length > 1) { + return RichTextBoxSelectionTypes.MultiChar | RichTextBoxSelectionTypes.Text; + } + + return RichTextBoxSelectionTypes.Text; + } + } + + [DefaultValue(false)] + [MonoTODO ("Stub, does nothing")] + public bool ShowSelectionMargin { + get { + return false; + } + + set { + } + } + + [Localizable(true)] + [RefreshProperties (RefreshProperties.All)] + public override string Text { + get { + return base.Text; + } + + set { + base.Text = value; + } + } + + [Browsable(false)] + public override int TextLength { + get { + return base.TextLength; + } + } + + [Browsable(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public string UndoActionName { + get { + return document.undo.UndoActionName; + } + } + + [Localizable(true)] + [DefaultValue(1)] + public float ZoomFactor { + get { + return zoom; + } + + set { + zoom = value; + } + } + #endregion // Public Instance Properties + + #region Protected Instance Properties + protected override CreateParams CreateParams { + get { + return base.CreateParams; + } + } + + protected override Size DefaultSize { + get { + return new Size(100, 96); + } + } + #endregion // Protected Instance Properties + + #region Public Instance Methods + public bool CanPaste(DataFormats.Format clipFormat) { + if ((clipFormat.Name == DataFormats.Rtf) || + (clipFormat.Name == DataFormats.Text) || + (clipFormat.Name == DataFormats.UnicodeText)) { + return true; + } + return false; + } + + public int Find(char[] characterSet) { + return Find(characterSet, -1, -1); + } + + public int Find(char[] characterSet, int start) { + return Find(characterSet, start, -1); + } + + public int Find(char[] characterSet, int start, int end) { + Document.Marker start_mark; + Document.Marker end_mark; + Document.Marker result; + + if (start == -1) { + document.GetMarker(out start_mark, true); + } else { + Line line; + LineTag tag; + int pos; + + start_mark = new Document.Marker(); + + document.CharIndexToLineTag(start, out line, out tag, out pos); + start_mark.line = line; + start_mark.tag = tag; + start_mark.pos = pos; + } + + if (end == -1) { + document.GetMarker(out end_mark, false); + } else { + Line line; + LineTag tag; + int pos; + + end_mark = new Document.Marker(); + + document.CharIndexToLineTag(end, out line, out tag, out pos); + end_mark.line = line; + end_mark.tag = tag; + end_mark.pos = pos; + } + + if (document.FindChars(characterSet, start_mark, end_mark, out result)) { + return document.LineTagToCharIndex(result.line, result.pos); + } + + return -1; + } + + public int Find(string str) { + return Find(str, -1, -1, RichTextBoxFinds.None); + } + + public int Find(string str, int start, int end, RichTextBoxFinds options) { + Document.Marker start_mark; + Document.Marker end_mark; + Document.Marker result; + + if (start == -1) { + document.GetMarker(out start_mark, true); + } else { + Line line; + LineTag tag; + int pos; + + start_mark = new Document.Marker(); + + document.CharIndexToLineTag(start, out line, out tag, out pos); + + start_mark.line = line; + start_mark.tag = tag; + start_mark.pos = pos; + } + + if (end == -1) { + document.GetMarker(out end_mark, false); + } else { + Line line; + LineTag tag; + int pos; + + end_mark = new Document.Marker(); + + document.CharIndexToLineTag(end, out line, out tag, out pos); + + end_mark.line = line; + end_mark.tag = tag; + end_mark.pos = pos; + } + + if (document.Find(str, start_mark, end_mark, out result, options)) { + return document.LineTagToCharIndex(result.line, result.pos); + } + + return -1; + } + + public int Find(string str, int start, RichTextBoxFinds options) { + return Find(str, start, -1, options); + } + + public int Find(string str, RichTextBoxFinds options) { + return Find(str, -1, -1, options); + } + + + internal override char GetCharFromPositionInternal (Point p) + { + LineTag tag; + int pos; + + PointToTagPos (p, out tag, out pos); + + if (pos >= tag.Line.text.Length) + return '\n'; + + return tag.Line.text[pos]; + } + + public override int GetCharIndexFromPosition(Point pt) { + LineTag tag; + int pos; + + PointToTagPos(pt, out tag, out pos); + + return document.LineTagToCharIndex(tag.Line, pos); + } + + public override int GetLineFromCharIndex(int index) { + Line line; + LineTag tag; + int pos; + + document.CharIndexToLineTag(index, out line, out tag, out pos); + + return line.LineNo - 1; + } + + public override Point GetPositionFromCharIndex(int index) { + Line line; + LineTag tag; + int pos; + + document.CharIndexToLineTag(index, out line, out tag, out pos); + return new Point(line.X + (int)line.widths[pos] + document.OffsetX - document.ViewPortX, + line.Y + document.OffsetY - document.ViewPortY); + } + + public void LoadFile(System.IO.Stream data, RichTextBoxStreamType fileType) { + document.Empty(); + + + // FIXME - ignoring unicode + if (fileType == RichTextBoxStreamType.PlainText) { + StringBuilder sb; + char[] buffer; + + try { + sb = new StringBuilder ((int) data.Length); + buffer = new char [1024]; + } catch { + throw new IOException("Not enough memory to load document"); + } + + StreamReader sr = new StreamReader (data, Encoding.Default, true); + int charsRead = sr.Read (buffer, 0, buffer.Length); + while (charsRead > 0) { + sb.Append (buffer, 0, charsRead); + charsRead = sr.Read (buffer, 0, buffer.Length); + } + + // Remove the EOF converted to an extra EOL by the StreamReader + if (sb.Length > 0 && sb [sb.Length - 1] == '\n') + sb.Remove (sb.Length - 1, 1); + + base.Text = sb.ToString(); + return; + } + + InsertRTFFromStream(data, 0, 1); + + document.PositionCaret (document.GetLine (1), 0); + document.SetSelectionToCaret (true); + ScrollToCaret (); + } + + public void LoadFile(string path) { + LoadFile (path, RichTextBoxStreamType.RichText); + } + + public void LoadFile(string path, RichTextBoxStreamType fileType) { + FileStream data; + + data = null; + + + try { + data = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 1024); + + LoadFile(data, fileType); + } +#if !DEBUG + catch (Exception ex) { + throw new IOException("Could not open file " + path, ex); + } +#endif + finally { + if (data != null) { + data.Close(); + } + } + } + + public void Paste(DataFormats.Format clipFormat) { + base.Paste(Clipboard.GetDataObject(), clipFormat, false); + } + + public void Redo() + { + if (document.undo.Redo ()) + OnTextChanged (EventArgs.Empty); + } + + public void SaveFile(Stream data, RichTextBoxStreamType fileType) { + Encoding encoding; + int i; + Byte[] bytes; + + + if (fileType == RichTextBoxStreamType.UnicodePlainText) { + encoding = Encoding.Unicode; + } else { + encoding = Encoding.ASCII; + } + + switch(fileType) { + case RichTextBoxStreamType.PlainText: + case RichTextBoxStreamType.TextTextOleObjs: + case RichTextBoxStreamType.UnicodePlainText: { + if (!Multiline) { + bytes = encoding.GetBytes(document.Root.text.ToString()); + data.Write(bytes, 0, bytes.Length); + return; + } + + for (i = 1; i < document.Lines; i++) { + // Normalize the new lines to the system ones + string line_text = document.GetLine (i).TextWithoutEnding () + Environment.NewLine; + bytes = encoding.GetBytes(line_text); + data.Write(bytes, 0, bytes.Length); + } + bytes = encoding.GetBytes(document.GetLine(document.Lines).text.ToString()); + data.Write(bytes, 0, bytes.Length); + return; + } + } + + // If we're here we're saving RTF + Line start_line; + Line end_line; + StringBuilder rtf; + int current; + int total; + + start_line = document.GetLine(1); + end_line = document.GetLine(document.Lines); + rtf = GenerateRTF(start_line, 0, end_line, end_line.text.Length); + total = rtf.Length; + bytes = new Byte[4096]; + + // Let's chunk it so we don't use up all memory... + for (i = 0; i < total; i += 1024) { + if ((i + 1024) < total) { + current = encoding.GetBytes(rtf.ToString(i, 1024), 0, 1024, bytes, 0); + } else { + current = total - i; + current = encoding.GetBytes(rtf.ToString(i, current), 0, current, bytes, 0); + } + data.Write(bytes, 0, current); + } + } + + public void SaveFile(string path) { + if (path.EndsWith(".rtf")) { + SaveFile(path, RichTextBoxStreamType.RichText); + } else { + SaveFile(path, RichTextBoxStreamType.PlainText); + } + } + + public void SaveFile(string path, RichTextBoxStreamType fileType) { + FileStream data; + + data = null; + +// try { + data = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, 1024, false); + SaveFile(data, fileType); +// } + +// catch { +// throw new IOException("Could not write document to file " + path); +// } + +// finally { + if (data != null) { + data.Close(); + } +// } + } + + [EditorBrowsable (EditorBrowsableState.Never)] + public new void DrawToBitmap (Bitmap bitmap, Rectangle targetBounds) + { + using (Graphics dc = Graphics.FromImage (bitmap)) + Draw (dc, targetBounds); + } + + #endregion // Public Instance Methods + + #region Protected Instance Methods + protected virtual object CreateRichEditOleCallback() + { + throw new NotImplementedException (); + } + + protected override void OnBackColorChanged (EventArgs e) + { + base.OnBackColorChanged (e); + } + + protected virtual void OnContentsResized (ContentsResizedEventArgs e) + { + ContentsResizedEventHandler eh = (ContentsResizedEventHandler)(Events [ContentsResizedEvent]); + if (eh != null) + eh (this, e); + } + + protected override void OnContextMenuChanged (EventArgs e) + { + base.OnContextMenuChanged (e); + } + + protected override void OnHandleCreated (EventArgs e) + { + base.OnHandleCreated (e); + } + + protected override void OnHandleDestroyed (EventArgs e) + { + base.OnHandleDestroyed (e); + } + + protected virtual void OnHScroll(EventArgs e) + { + EventHandler eh = (EventHandler)(Events [HScrollEvent]); + if (eh != null) + eh (this, e); + } + + [MonoTODO ("Stub, never called")] + protected virtual void OnImeChange(EventArgs e) { + EventHandler eh = (EventHandler)(Events [ImeChangeEvent]); + if (eh != null) + eh (this, e); + } + + protected virtual void OnLinkClicked(LinkClickedEventArgs e) { + LinkClickedEventHandler eh = (LinkClickedEventHandler)(Events [LinkClickedEvent]); + if (eh != null) + eh (this, e); + } + + protected virtual void OnProtected(EventArgs e) { + EventHandler eh = (EventHandler)(Events [ProtectedEvent]); + if (eh != null) + eh (this, e); + } + + protected override void OnRightToLeftChanged(EventArgs e) { + base.OnRightToLeftChanged (e); + } + + protected virtual void OnSelectionChanged(EventArgs e) { + EventHandler eh = (EventHandler)(Events [SelectionChangedEvent]); + if (eh != null) + eh (this, e); + } + + protected virtual void OnVScroll(EventArgs e) { + EventHandler eh = (EventHandler)(Events [VScrollEvent]); + if (eh != null) + eh (this, e); + } + + protected override void WndProc(ref Message m) { + base.WndProc (ref m); + } + + protected override bool ProcessCmdKey (ref Message m, Keys keyData) + { + return base.ProcessCmdKey (ref m, keyData); + } + #endregion // Protected Instance Methods + + #region Events + static object ContentsResizedEvent = new object (); + static object HScrollEvent = new object (); + static object ImeChangeEvent = new object (); + static object LinkClickedEvent = new object (); + static object ProtectedEvent = new object (); + static object SelectionChangedEvent = new object (); + static object VScrollEvent = new object (); + + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler BackgroundImageChanged { + add { base.BackgroundImageChanged += value; } + remove { base.BackgroundImageChanged -= value; } + } + + [Browsable (false)] + [EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler BackgroundImageLayoutChanged { + add { base.BackgroundImageLayoutChanged += value; } + remove { base.BackgroundImageLayoutChanged -= value; } + } + + public event ContentsResizedEventHandler ContentsResized { + add { Events.AddHandler (ContentsResizedEvent, value); } + remove { Events.RemoveHandler (ContentsResizedEvent, value); } + } + + [Browsable(false)] + public new event DragEventHandler DragDrop { + add { base.DragDrop += value; } + remove { base.DragDrop -= value; } + } + + [Browsable(false)] + public new event DragEventHandler DragEnter { + add { base.DragEnter += value; } + remove { base.DragEnter -= value; } + } + + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler DragLeave { + add { base.DragLeave += value; } + remove { base.DragLeave -= value; } + } + + + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + public new event DragEventHandler DragOver { + add { base.DragOver += value; } + remove { base.DragOver -= value; } + } + + + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + public new event GiveFeedbackEventHandler GiveFeedback { + add { base.GiveFeedback += value; } + remove { base.GiveFeedback -= value; } + } + + public event EventHandler HScroll { + add { Events.AddHandler (HScrollEvent, value); } + remove { Events.RemoveHandler (HScrollEvent, value); } + } + + public event EventHandler ImeChange { + add { Events.AddHandler (ImeChangeEvent, value); } + remove { Events.RemoveHandler (ImeChangeEvent, value); } + } + + public event LinkClickedEventHandler LinkClicked { + add { Events.AddHandler (LinkClickedEvent, value); } + remove { Events.RemoveHandler (LinkClickedEvent, value); } + } + + public event EventHandler Protected { + add { Events.AddHandler (ProtectedEvent, value); } + remove { Events.RemoveHandler (ProtectedEvent, value); } + } + + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + public new event QueryContinueDragEventHandler QueryContinueDrag { + add { base.QueryContinueDrag += value; } + remove { base.QueryContinueDrag -= value; } + } + + [MonoTODO ("Event never raised")] + public event EventHandler SelectionChanged { + add { Events.AddHandler (SelectionChangedEvent, value); } + remove { Events.RemoveHandler (SelectionChangedEvent, value); } + } + + public event EventHandler VScroll { + add { Events.AddHandler (VScrollEvent, value); } + remove { Events.RemoveHandler (VScrollEvent, value); } + } + #endregion // Events + + #region Private Methods + + internal override void SelectWord () + { + document.ExpandSelection(CaretSelection.Word, false); + } + + private class RtfSectionStyle : ICloneable { + internal Color rtf_color; + internal RTF.Font rtf_rtffont; + internal int rtf_rtffont_size; + internal FontStyle rtf_rtfstyle; + internal HorizontalAlignment rtf_rtfalign; + internal int rtf_par_line_left_indent; + internal bool rtf_visible; + internal int rtf_skip_width; + + public object Clone () + { + RtfSectionStyle new_style = new RtfSectionStyle (); + + new_style.rtf_color = rtf_color; + new_style.rtf_par_line_left_indent = rtf_par_line_left_indent; + new_style.rtf_rtfalign = rtf_rtfalign; + new_style.rtf_rtffont = rtf_rtffont; + new_style.rtf_rtffont_size = rtf_rtffont_size; + new_style.rtf_rtfstyle = rtf_rtfstyle; + new_style.rtf_visible = rtf_visible; + new_style.rtf_skip_width = rtf_skip_width; + + return new_style; + } + } + + // To allow us to keep track of the sections and revert formatting + // as we go in and out of sections of the document. + private void HandleGroup (RTF.RTF rtf) + { + //start group - save the current formatting on to a stack + //end group - go back to the formatting at the current group + if (rtf_section_stack == null) { + rtf_section_stack = new Stack (); + } + + if (rtf.Major == RTF.Major.BeginGroup) { + rtf_section_stack.Push (rtf_style.Clone ()); + //spec specifies resetting unicode ignore at begin group as an attempt at error + //recovery. + rtf_skip_count = 0; + } else if (rtf.Major == RTF.Major.EndGroup) { + if (rtf_section_stack.Count > 0) { + FlushText (rtf, false); + + rtf_style = (RtfSectionStyle) rtf_section_stack.Pop (); + } + } + } + + [MonoInternalNote ("Add QuadJust support for justified alignment")] + private void HandleControl(RTF.RTF rtf) { + switch(rtf.Major) { + case RTF.Major.Unicode: { + switch(rtf.Minor) { + case RTF.Minor.UnicodeCharBytes: { + rtf_style.rtf_skip_width = rtf.Param; + break; + } + + case RTF.Minor.UnicodeChar: { + FlushText (rtf, false); + rtf_skip_count += rtf_style.rtf_skip_width; + rtf_line.Append((char)rtf.Param); + break; + } + } + break; + } + + case RTF.Major.Destination: { +// Console.Write("[Got Destination control {0}]", rtf.Minor); + rtf.SkipGroup(); + break; + } + + case RTF.Major.PictAttr: + if (rtf.Picture != null && rtf.Picture.IsValid ()) { + Line line = document.GetLine (rtf_cursor_y); + document.InsertPicture (line, 0, rtf.Picture); + rtf_cursor_x++; + + FlushText (rtf, true); + rtf.Picture = null; + } + break; + + case RTF.Major.CharAttr: { + switch(rtf.Minor) { + case RTF.Minor.ForeColor: { + ShiftUI.RTF.Color color; + + color = ShiftUI.RTF.Color.GetColor(rtf, rtf.Param); + + if (color != null) { + FlushText(rtf, false); + if (color.Red == -1 && color.Green == -1 && color.Blue == -1) { + this.rtf_style.rtf_color = ForeColor; + } else { + this.rtf_style.rtf_color = Color.FromArgb(color.Red, color.Green, color.Blue); + } + FlushText (rtf, false); + } + break; + } + + case RTF.Minor.FontSize: { + FlushText(rtf, false); + this.rtf_style.rtf_rtffont_size = rtf.Param / 2; + break; + } + + case RTF.Minor.FontNum: { + ShiftUI.RTF.Font font; + + font = ShiftUI.RTF.Font.GetFont(rtf, rtf.Param); + if (font != null) { + FlushText(rtf, false); + this.rtf_style.rtf_rtffont = font; + } + break; + } + + case RTF.Minor.Plain: { + FlushText(rtf, false); + rtf_style.rtf_rtfstyle = FontStyle.Regular; + break; + } + + case RTF.Minor.Bold: { + FlushText(rtf, false); + if (rtf.Param == RTF.RTF.NoParam) { + rtf_style.rtf_rtfstyle |= FontStyle.Bold; + } else { + rtf_style.rtf_rtfstyle &= ~FontStyle.Bold; + } + break; + } + + case RTF.Minor.Italic: { + FlushText(rtf, false); + if (rtf.Param == RTF.RTF.NoParam) { + rtf_style.rtf_rtfstyle |= FontStyle.Italic; + } else { + rtf_style.rtf_rtfstyle &= ~FontStyle.Italic; + } + break; + } + + case RTF.Minor.StrikeThru: { + FlushText(rtf, false); + if (rtf.Param == RTF.RTF.NoParam) { + rtf_style.rtf_rtfstyle |= FontStyle.Strikeout; + } else { + rtf_style.rtf_rtfstyle &= ~FontStyle.Strikeout; + } + break; + } + + case RTF.Minor.Underline: { + FlushText(rtf, false); + if (rtf.Param == RTF.RTF.NoParam) { + rtf_style.rtf_rtfstyle |= FontStyle.Underline; + } else { + rtf_style.rtf_rtfstyle = rtf_style.rtf_rtfstyle & ~FontStyle.Underline; + } + break; + } + + case RTF.Minor.Invisible: { + FlushText (rtf, false); + rtf_style.rtf_visible = false; + break; + } + + case RTF.Minor.NoUnderline: { + FlushText(rtf, false); + rtf_style.rtf_rtfstyle &= ~FontStyle.Underline; + break; + } + } + break; + } + + case RTF.Major.ParAttr: { + switch (rtf.Minor) { + + case RTF.Minor.ParDef: + FlushText (rtf, false); + rtf_style.rtf_par_line_left_indent = 0; + rtf_style.rtf_rtfalign = HorizontalAlignment.Left; + break; + + case RTF.Minor.LeftIndent: + using (Graphics g = CreateGraphics ()) + rtf_style.rtf_par_line_left_indent = (int) (((float) rtf.Param / 1440.0F) * g.DpiX + 0.5F); + break; + + case RTF.Minor.QuadCenter: + FlushText (rtf, false); + rtf_style.rtf_rtfalign = HorizontalAlignment.Center; + break; + + case RTF.Minor.QuadJust: + FlushText (rtf, false); + rtf_style.rtf_rtfalign = HorizontalAlignment.Center; + break; + + case RTF.Minor.QuadLeft: + FlushText (rtf, false); + rtf_style.rtf_rtfalign = HorizontalAlignment.Left; + break; + + case RTF.Minor.QuadRight: + FlushText (rtf, false); + rtf_style.rtf_rtfalign = HorizontalAlignment.Right; + break; + } + break; + } + + case RTF.Major.SpecialChar: { + //Console.Write("[Got SpecialChar control {0}]", rtf.Minor); + SpecialChar (rtf); + break; + } + } + } + + private void SpecialChar(RTF.RTF rtf) { + switch(rtf.Minor) { + case RTF.Minor.Page: + case RTF.Minor.Sect: + case RTF.Minor.Row: + case RTF.Minor.Line: + case RTF.Minor.Par: { + FlushText(rtf, true); + break; + } + + case RTF.Minor.Cell: { + Console.Write(" "); + break; + } + + case RTF.Minor.NoBrkSpace: { + Console.Write(" "); + break; + } + + case RTF.Minor.Tab: { + rtf_line.Append ("\t"); +// FlushText (rtf, false); + break; + } + + case RTF.Minor.NoReqHyphen: + case RTF.Minor.NoBrkHyphen: { + rtf_line.Append ("-"); +// FlushText (rtf, false); + break; + } + + case RTF.Minor.Bullet: { + Console.WriteLine("*"); + break; + } + + case RTF.Minor.WidowCtrl: + break; + + case RTF.Minor.EmDash: { + rtf_line.Append ("\u2014"); + break; + } + + case RTF.Minor.EnDash: { + rtf_line.Append ("\u2013"); + break; + } +/* + case RTF.Minor.LQuote: { + Console.Write("\u2018"); + break; + } + + case RTF.Minor.RQuote: { + Console.Write("\u2019"); + break; + } + + case RTF.Minor.LDblQuote: { + Console.Write("\u201C"); + break; + } + + case RTF.Minor.RDblQuote: { + Console.Write("\u201D"); + break; + } +*/ + default: { +// Console.WriteLine ("skipped special char: {0}", rtf.Minor); +// rtf.SkipGroup(); + break; + } + } + } + + private void HandleText(RTF.RTF rtf) { + string str = rtf.EncodedText; + + //todo - simplistically skips characters, should skip bytes? + if (rtf_skip_count > 0 && str.Length > 0) { + int iToRemove = Math.Min (rtf_skip_count, str.Length); + + str = str.Substring (iToRemove); + rtf_skip_count-=iToRemove; + } + + /* + if ((RTF.StandardCharCode)rtf.Minor != RTF.StandardCharCode.nothing) { + rtf_line.Append(rtf_text_map[(RTF.StandardCharCode)rtf.Minor]); + } else { + if ((int)rtf.Major > 31 && (int)rtf.Major < 128) { + rtf_line.Append((char)rtf.Major); + } else { + //rtf_line.Append((char)rtf.Major); + Console.Write("[Literal:0x{0:X2}]", (int)rtf.Major); + } + } + */ + + if (rtf_style.rtf_visible) + rtf_line.Append (str); + } + + private void FlushText(RTF.RTF rtf, bool newline) { + int length; + Font font; + + length = rtf_line.Length; + if (!newline && (length == 0)) { + return; + } + + if (rtf_style.rtf_rtffont == null) { + // First font in table is default + rtf_style.rtf_rtffont = ShiftUI.RTF.Font.GetFont (rtf, 0); + } + + font = new Font (rtf_style.rtf_rtffont.Name, rtf_style.rtf_rtffont_size, rtf_style.rtf_rtfstyle); + + if (rtf_style.rtf_color == Color.Empty) { + ShiftUI.RTF.Color color; + + // First color in table is default + color = ShiftUI.RTF.Color.GetColor (rtf, 0); + + if ((color == null) || (color.Red == -1 && color.Green == -1 && color.Blue == -1)) { + rtf_style.rtf_color = ForeColor; + } else { + rtf_style.rtf_color = Color.FromArgb (color.Red, color.Green, color.Blue); + } + + } + + rtf_chars += rtf_line.Length; + + // Try to re-use if we are told so - this usually happens when we are inserting a flow of rtf text + // with an already alive line. + if (rtf_cursor_x == 0 && !reuse_line) { + if (newline && rtf_line.ToString ().EndsWith (Environment.NewLine) == false) + rtf_line.Append (Environment.NewLine); + + document.Add (rtf_cursor_y, rtf_line.ToString (), rtf_style.rtf_rtfalign, font, rtf_style.rtf_color, + newline ? LineEnding.Rich : LineEnding.Wrap); + if (rtf_style.rtf_par_line_left_indent != 0) { + Line line = document.GetLine (rtf_cursor_y); + line.indent = rtf_style.rtf_par_line_left_indent; + } + } else { + Line line; + + line = document.GetLine (rtf_cursor_y); + line.indent = rtf_style.rtf_par_line_left_indent; + if (rtf_line.Length > 0) { + document.InsertString (line, rtf_cursor_x, rtf_line.ToString ()); + document.FormatText (line, rtf_cursor_x + 1, line, rtf_cursor_x + 1 + length, + font, rtf_style.rtf_color, Color.Empty, + FormatSpecified.Font | FormatSpecified.Color); + } + if (newline) { + line = document.GetLine (rtf_cursor_y); + line.ending = LineEnding.Rich; + + if (line.Text.EndsWith (Environment.NewLine) == false) + line.Text += Environment.NewLine; + } + + reuse_line = false; // sanity assignment - in this case we have already re-used one line. + } + + if (newline) { + rtf_cursor_x = 0; + rtf_cursor_y++; + } else { + rtf_cursor_x += length; + } + rtf_line.Length = 0; // Empty line + } + + private void InsertRTFFromStream(Stream data, int cursor_x, int cursor_y) { + int x; + int y; + int chars; + + InsertRTFFromStream(data, cursor_x, cursor_y, out x, out y, out chars); + } + + private void InsertRTFFromStream(Stream data, int cursor_x, int cursor_y, out int to_x, out int to_y, out int chars) { + RTF.RTF rtf; + + rtf = new RTF.RTF(data); + + // Prepare + rtf.ClassCallback[RTF.TokenClass.Text] = new RTF.ClassDelegate(HandleText); + rtf.ClassCallback[RTF.TokenClass.Widget] = new RTF.ClassDelegate(HandleControl); + rtf.ClassCallback[RTF.TokenClass.Group] = new RTF.ClassDelegate(HandleGroup); + + rtf_skip_count = 0; + rtf_line = new StringBuilder(); + rtf_style.rtf_color = Color.Empty; + rtf_style.rtf_rtffont_size = (int)this.Font.Size; + rtf_style.rtf_rtfalign = HorizontalAlignment.Left; + rtf_style.rtf_rtfstyle = FontStyle.Regular; + rtf_style.rtf_rtffont = null; + rtf_style.rtf_visible = true; + rtf_style.rtf_skip_width = 1; + rtf_cursor_x = cursor_x; + rtf_cursor_y = cursor_y; + rtf_chars = 0; + rtf.DefaultFont(this.Font.Name); + + rtf_text_map = new RTF.TextMap(); + RTF.TextMap.SetupStandardTable(rtf_text_map.Table); + + document.SuspendRecalc (); + + try { + rtf.Read(); // That's it + FlushText(rtf, false); + + } + + + catch (RTF.RTFException e) { +#if DEBUG + throw e; +#endif + // Seems to be plain text or broken RTF + } + + to_x = rtf_cursor_x; + to_y = rtf_cursor_y; + chars = rtf_chars; + + // clear the section stack if it was used + if (rtf_section_stack != null) + rtf_section_stack.Clear(); + + document.RecalculateDocument(CreateGraphicsInternal(), cursor_y, document.Lines, false); + document.ResumeRecalc (true); + + document.Invalidate (document.GetLine(cursor_y), 0, document.GetLine(document.Lines), -1); + } + + private void RichTextBox_HScrolled(object sender, EventArgs e) { + OnHScroll(e); + } + + private void RichTextBox_VScrolled(object sender, EventArgs e) { + OnVScroll(e); + } + + private void PointToTagPos(Point pt, out LineTag tag, out int pos) { + Point p; + + p = pt; + + if (p.X >= document.ViewPortWidth) { + p.X = document.ViewPortWidth - 1; + } else if (p.X < 0) { + p.X = 0; + } + + if (p.Y >= document.ViewPortHeight) { + p.Y = document.ViewPortHeight - 1; + } else if (p.Y < 0) { + p.Y = 0; + } + + tag = document.FindCursor(p.X + document.ViewPortX, p.Y + document.ViewPortY, out pos); + } + + private void EmitRTFFontProperties(StringBuilder rtf, int prev_index, int font_index, Font prev_font, Font font) { + if (prev_index != font_index) { + rtf.Append(String.Format("\\f{0}", font_index)); // Font table entry + } + + if ((prev_font == null) || (prev_font.Size != font.Size)) { + rtf.Append(String.Format("\\fs{0}", (int)(font.Size * 2))); // Font size + } + + if ((prev_font == null) || (font.Bold != prev_font.Bold)) { + if (font.Bold) { + rtf.Append("\\b"); + } else { + if (prev_font != null) { + rtf.Append("\\b0"); + } + } + } + + if ((prev_font == null) || (font.Italic != prev_font.Italic)) { + if (font.Italic) { + rtf.Append("\\i"); + } else { + if (prev_font != null) { + rtf.Append("\\i0"); + } + } + } + + if ((prev_font == null) || (font.Strikeout != prev_font.Strikeout)) { + if (font.Strikeout) { + rtf.Append("\\strike"); + } else { + if (prev_font != null) { + rtf.Append("\\strike0"); + } + } + } + + if ((prev_font == null) || (font.Underline != prev_font.Underline)) { + if (font.Underline) { + rtf.Append("\\ul"); + } else { + if (prev_font != null) { + rtf.Append("\\ul0"); + } + } + } + } + + static readonly char [] ReservedRTFChars = new char [] { '\\', '{', '}' }; + + private void EmitRTFText(StringBuilder rtf, string text) { + int start = rtf.Length; + int count = text.Length; + + // First emit simple unicode chars as escaped + EmitEscapedUnicode (rtf, text); + + // This method emits user text *only*, so it's safe to escape any reserved rtf chars + // Escape '\' first, since it is used later to escape the other chars + if (text.IndexOfAny (ReservedRTFChars) > -1) { + rtf.Replace ("\\", "\\\\", start, count); + rtf.Replace ("{", "\\{", start, count); + rtf.Replace ("}", "\\}", start, count); + } + } + + // The chars to be escaped use "\'" + its hexadecimal value. + private void EmitEscapedUnicode (StringBuilder sb, string text) + { + int pos; + int start = 0; + + while ((pos = IndexOfNonAscii (text, start)) > -1) { + sb.Append (text, start, pos - start); + + int n = (int)text [pos]; + sb.Append ("\\'"); + sb.Append (n.ToString ("X")); + + start = pos + 1; + } + + // Append remaining (maybe all) the text value. + if (start < text.Length) + sb.Append (text, start, text.Length - start); + } + + // MS seems to be escaping values larger than 0x80 + private int IndexOfNonAscii (string text, int startIndex) + { + for (int i = startIndex; i < text.Length; i++) { + int n = (int)text [i]; + if (n < 0 || n >= 0x80) + return i; + } + + return -1; + } + + // start_pos and end_pos are 0-based + private StringBuilder GenerateRTF(Line start_line, int start_pos, Line end_line, int end_pos) { + StringBuilder sb; + ArrayList fonts; + ArrayList colors; + Color color; + Font font; + Line line; + LineTag tag; + int pos; + int line_no; + int line_len; + int i; + int length; + + sb = new StringBuilder(); + fonts = new ArrayList(10); + colors = new ArrayList(10); + + // Two runs, first we parse to determine tables; + // and unlike most of our processing here we work on tags + + line = start_line; + line_no = start_line.line_no; + pos = start_pos; + + // Add default font and color; to optimize document content we don't + // use this.Font and this.ForeColor but the font/color from the first tag + tag = LineTag.FindTag(start_line, pos); + font = tag.Font; + color = tag.Color; + fonts.Add(font.Name); + colors.Add(color); + + while (line_no <= end_line.line_no) { + line = document.GetLine(line_no); + tag = LineTag.FindTag(line, pos); + + if (line_no != end_line.line_no) { + line_len = line.text.Length; + } else { + line_len = end_pos; + } + + while (pos < line_len) { + if (tag.Font.Name != font.Name) { + font = tag.Font; + if (!fonts.Contains(font.Name)) { + fonts.Add(font.Name); + } + } + + if (tag.Color != color) { + color = tag.Color; + if (!colors.Contains(color)) { + colors.Add(color); + } + } + + pos = tag.Start + tag.Length - 1; + tag = tag.Next; + } + pos = 0; + line_no++; + } + + // We have the tables, emit the header + sb.Append("{\\rtf1\\ansi"); + sb.Append("\\ansicpg1252"); // FIXME - is this correct? + + // Default Font + sb.Append(String.Format("\\deff{0}", fonts.IndexOf(this.Font.Name))); + + // Default Language + sb.Append("\\deflang1033" + Environment.NewLine); // FIXME - always 1033? + + // Emit the font table + sb.Append("{\\fonttbl"); + for (i = 0; i < fonts.Count; i++) { + sb.Append(String.Format("{{\\f{0}", i)); // {Font + sb.Append("\\fnil"); // Family + sb.Append("\\fcharset0 "); // Charset ANSI<space> + sb.Append((string)fonts[i]); // Font name + sb.Append(";}"); // } + } + sb.Append("}"); + sb.Append(Environment.NewLine); + + // Emit the color table (if needed) + if ((colors.Count > 1) || ((((Color)colors[0]).R != this.ForeColor.R) || (((Color)colors[0]).G != this.ForeColor.G) || (((Color)colors[0]).B != this.ForeColor.B))) { + sb.Append("{\\colortbl "); // Header and NO! default color + for (i = 0; i < colors.Count; i++) { + sb.Append(String.Format("\\red{0}", ((Color)colors[i]).R)); + sb.Append(String.Format("\\green{0}", ((Color)colors[i]).G)); + sb.Append(String.Format("\\blue{0}", ((Color)colors[i]).B)); + sb.Append(";"); + } + sb.Append("}"); + sb.Append(Environment.NewLine); + } + + sb.Append("{\\*\\generator Mono RichTextBox;}"); + // Emit initial paragraph settings + tag = LineTag.FindTag(start_line, start_pos); + sb.Append("\\pard"); // Reset to default paragraph properties + EmitRTFFontProperties(sb, -1, fonts.IndexOf(tag.Font.Name), null, tag.Font); // Font properties + sb.Append(" "); // Space separator + + font = tag.Font; + color = (Color)colors[0]; + line = start_line; + line_no = start_line.line_no; + pos = start_pos; + + while (line_no <= end_line.line_no) { + line = document.GetLine(line_no); + tag = LineTag.FindTag(line, pos); + + if (line_no != end_line.line_no) { + line_len = line.text.Length; + } else { + line_len = end_pos; + } + + while (pos < line_len) { + length = sb.Length; + + if (tag.Font != font) { + EmitRTFFontProperties(sb, fonts.IndexOf(font.Name), fonts.IndexOf(tag.Font.Name), font, tag.Font); + font = tag.Font; + } + + if (tag.Color != color) { + color = tag.Color; + sb.Append(String.Format("\\cf{0}", colors.IndexOf(color))); + } + if (length != sb.Length) { + sb.Append(" "); // Emit space to separate keywords from text + } + + // Emit the string itself + if (line_no != end_line.line_no) { + EmitRTFText(sb, tag.Line.text.ToString(pos, tag.Start + tag.Length - pos - 1)); + } else { + if (end_pos < (tag.Start + tag.Length - 1)) { + // Emit partial tag only, end_pos is inside this tag + EmitRTFText(sb, tag.Line.text.ToString(pos, end_pos - pos)); + } else { + EmitRTFText(sb, tag.Line.text.ToString(pos, tag.Start + tag.Length - pos - 1)); + } + } + + pos = tag.Start + tag.Length - 1; + tag = tag.Next; + } + if (pos >= line.text.Length) { + if (line.ending != LineEnding.Wrap) { + sb.Append("\\par"); + sb.Append(Environment.NewLine); + } + } + pos = 0; + line_no++; + } + + sb.Append("}"); + sb.Append(Environment.NewLine); + + return sb; + } + #endregion // Private Methods + } +} diff --git a/source/ShiftUI/Widgets/RootGridEntry.cs b/source/ShiftUI/Widgets/RootGridEntry.cs new file mode 100644 index 0000000..534e166 --- /dev/null +++ b/source/ShiftUI/Widgets/RootGridEntry.cs @@ -0,0 +1,88 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2006 Novell, Inc. +// +// Authors: +// Chris Toshok ([email protected]) +// + +using System; +using System.Drawing; + +namespace ShiftUI.PropertyGridInternal +{ + /// <summary> + /// Summary description for PropertyGridRootGridItem + /// </summary> + [MonoInternalNote ("needs to implement IRootGridEntry")] + internal class RootGridEntry : GridEntry /*, IRootGridEntry */ + { + object[] val; + + public RootGridEntry (PropertyGrid owner, object[] obj) + : base (owner, null) + { + if (obj == null || obj.Length == 0) + throw new ArgumentNullException ("obj"); + val = obj; + } + + public override bool Expandable { + get { return true; } + } + + public override GridItemType GridItemType { + get { return GridItemType.Root; } + } + + public override string Label { + get { return val.Length > 1 ? val.GetType().ToString() : val[0].GetType().ToString(); } + } + + public override object Value { + get { return val.Length > 1 ? val : val[0]; } + } + + public override object[] Values { + get { return val; } + } + + public override bool Select () + { + return false; /* root entries aren't selectable */ + } + + public override bool IsReadOnly { + get { return true; } + } + + public override bool IsEditable { + get { return false; } + } + + public override bool IsResetable { + get { return false; } + } + + public override bool IsMerged { + get { return val.Length > 1; } + } + } +} diff --git a/source/ShiftUI/Widgets/ScrollBar.cs b/source/ShiftUI/Widgets/ScrollBar.cs new file mode 100644 index 0000000..9fceb0b --- /dev/null +++ b/source/ShiftUI/Widgets/ScrollBar.cs @@ -0,0 +1,1624 @@ +// +// ShiftUI.ScrollBar.cs +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (C) 2004-2005, Novell, Inc. +// +// Authors: +// Jordi Mas i Hernandez [email protected] +// +// + +// COMPLETE + +using System.Drawing; +using System.Drawing.Imaging; +using System.Drawing.Drawing2D; +using System.ComponentModel; +using System.Runtime.InteropServices; +using System; + +namespace ShiftUI +{ + [ComVisible (true)] + [ClassInterface (ClassInterfaceType.AutoDispatch)] + [DefaultEvent ("Scroll")] + [DefaultProperty ("Value")] + public abstract class ScrollBar : Widget + { + #region Local Variables + private int position; + private int minimum; + private int maximum; + private int large_change; + private int small_change; + internal int scrollbutton_height; + internal int scrollbutton_width; + private Rectangle first_arrow_area = new Rectangle (); // up or left + private Rectangle second_arrow_area = new Rectangle (); // down or right + private Rectangle thumb_pos = new Rectangle (); + private Rectangle thumb_area = new Rectangle (); + internal ButtonState firstbutton_state = ButtonState.Normal; + internal ButtonState secondbutton_state = ButtonState.Normal; + private bool firstbutton_pressed = false; + private bool secondbutton_pressed = false; + private bool thumb_pressed = false; + private float pixel_per_pos = 0; + private Timer timer = new Timer (); + private TimerType timer_type; + private int thumb_size = 40; + private const int thumb_min_size = 8; + private const int thumb_notshown_size = 40; + internal bool use_manual_thumb_size; + internal int manual_thumb_size; + internal bool vert; + internal bool implicit_Widget; + private int lastclick_pos; // Position of the last button-down event + private int thumbclick_offset; // Position of the last button-down event relative to the thumb edge + private Rectangle dirty; + + internal ThumbMoving thumb_moving = ThumbMoving.None; + bool first_button_entered; + bool second_button_entered; + bool thumb_entered; + #endregion // Local Variables + + private enum TimerType + { + HoldButton, + RepeatButton, + HoldThumbArea, + RepeatThumbArea + } + + internal enum ThumbMoving + { + None, + Forward, + Backwards, + } + + #region events + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler AutoSizeChanged { + add { base.AutoSizeChanged += value; } + remove { base.AutoSizeChanged -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler BackColorChanged { + add { base.BackColorChanged += value; } + remove { base.BackColorChanged -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler BackgroundImageChanged { + add { base.BackgroundImageChanged += value; } + remove { base.BackgroundImageChanged -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler BackgroundImageLayoutChanged { + add { base.BackgroundImageLayoutChanged += value; } + remove { base.BackgroundImageLayoutChanged -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler Click { + add { base.Click += value; } + remove { base.Click -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler DoubleClick { + add { base.DoubleClick += value; } + remove { base.DoubleClick -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler FontChanged { + add { base.FontChanged += value; } + remove { base.FontChanged -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler ForeColorChanged { + add { base.ForeColorChanged += value; } + remove { base.ForeColorChanged -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler ImeModeChanged { + add { base.ImeModeChanged += value; } + remove { base.ImeModeChanged -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event MouseEventHandler MouseClick { + add { base.MouseClick += value; } + remove { base.MouseClick -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event MouseEventHandler MouseDoubleClick { + add { base.MouseDoubleClick += value; } + remove { base.MouseDoubleClick -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event MouseEventHandler MouseDown { + add { base.MouseDown += value; } + remove { base.MouseDown -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event MouseEventHandler MouseMove { + add { base.MouseMove += value; } + remove { base.MouseMove -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event MouseEventHandler MouseUp { + add { base.MouseUp += value; } + remove { base.MouseUp -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event PaintEventHandler Paint { + add { base.Paint += value; } + remove { base.Paint -= value; } + } + + static object ScrollEvent = new object (); + static object ValueChangedEvent = new object (); + + public event ScrollEventHandler Scroll { + add { Events.AddHandler (ScrollEvent, value); } + remove { Events.RemoveHandler (ScrollEvent, value); } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler TextChanged { + add { base.TextChanged += value; } + remove { base.TextChanged -= value; } + } + + public event EventHandler ValueChanged { + add { Events.AddHandler (ValueChangedEvent, value); } + remove { Events.RemoveHandler (ValueChangedEvent, value); } + } + #endregion Events + + public ScrollBar () + { + position = 0; + minimum = 0; + maximum = 100; + large_change = 10; + small_change = 1; + + timer.Tick += new EventHandler (OnTimer); + MouseEnter += new EventHandler (OnMouseEnter); + MouseLeave += new EventHandler (OnMouseLeave); + base.KeyDown += new KeyEventHandler (OnKeyDownSB); + base.MouseDown += new MouseEventHandler (OnMouseDownSB); + base.MouseUp += new MouseEventHandler (OnMouseUpSB); + base.MouseMove += new MouseEventHandler (OnMouseMoveSB); + base.Resize += new EventHandler (OnResizeSB); + base.TabStop = false; + base.Cursor = Cursors.Default; + + SetStyle (Widgetstyles.UserPaint | Widgetstyles.StandardClick | Widgetstyles.UseTextForAccessibility, false); + } + + #region Internal & Private Properties + internal Rectangle FirstArrowArea { + get { + return this.first_arrow_area; + } + + set { + this.first_arrow_area = value; + } + } + + internal Rectangle SecondArrowArea { + get { + return this.second_arrow_area; + } + + set { + this.second_arrow_area = value; + } + } + + int MaximumAllowed { + get { + return use_manual_thumb_size ? maximum - manual_thumb_size + 1 : + maximum - LargeChange + 1; + } + } + + internal Rectangle ThumbPos { + get { + return thumb_pos; + } + + set { + thumb_pos = value; + } + } + + internal bool FirstButtonEntered { + get { return first_button_entered; } + private set { + if (first_button_entered == value) + return; + first_button_entered = value; + if (ThemeEngine.Current.ScrollBarHasHotElementStyles) + Invalidate (first_arrow_area); + } + } + + internal bool SecondButtonEntered { + get { return second_button_entered; } + private set { + if (second_button_entered == value) + return; + second_button_entered = value; + if (ThemeEngine.Current.ScrollBarHasHotElementStyles) + Invalidate (second_arrow_area); + } + } + + internal bool ThumbEntered { + get { return thumb_entered; } + private set { + if (thumb_entered == value) + return; + thumb_entered = value; + if (ThemeEngine.Current.ScrollBarHasHotElementStyles) + Invalidate (thumb_pos); + } + } + + internal bool ThumbPressed { + get { return thumb_pressed; } + private set { + if (thumb_pressed == value) + return; + thumb_pressed = value; + if (ThemeEngine.Current.ScrollBarHasPressedThumbStyle) + Invalidate (thumb_pos); + } + } + + #endregion // Internal & Private Properties + + #region Public Properties + //[EditorBrowsable (EditorBrowsableState.Never)] + [Browsable (false)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public override bool AutoSize { + get { return base.AutoSize; } + set { base.AutoSize = value; } + } + + //[EditorBrowsable (EditorBrowsableState.Never)] + [Browsable (false)] + public override Color BackColor + { + get { return base.BackColor; } + set { + if (base.BackColor == value) + return; + base.BackColor = value; + Refresh (); + } + } + + //[EditorBrowsable (EditorBrowsableState.Never)] + [Browsable (false)] + public override Image BackgroundImage + { + get { return base.BackgroundImage; } + set { + if (base.BackgroundImage == value) + return; + + base.BackgroundImage = value; + } + } + + //[EditorBrowsable (EditorBrowsableState.Never)] + [Browsable (false)] + public override ImageLayout BackgroundImageLayout { + get { return base.BackgroundImageLayout; } + set { base.BackgroundImageLayout = value; } + } + + protected override CreateParams CreateParams + { + get { return base.CreateParams; } + } + + protected override Padding DefaultMargin { + get { return Padding.Empty; } + } + + protected override ImeMode DefaultImeMode + { + get { return ImeMode.Disable; } + } + + //[EditorBrowsable (EditorBrowsableState.Never)] + [Browsable (false)] + public override Font Font + { + get { return base.Font; } + set { + if (base.Font.Equals (value)) + return; + + base.Font = value; + } + } + + //[EditorBrowsable (EditorBrowsableState.Never)] + [Browsable (false)] + public override Color ForeColor + { + get { return base.ForeColor; } + set { + if (base.ForeColor == value) + return; + + base.ForeColor = value; + Refresh (); + } + } + + //[EditorBrowsable (EditorBrowsableState.Never)] + [Browsable (false)] + public new ImeMode ImeMode + { + get { return base.ImeMode; } + set { + if (base.ImeMode == value) + return; + + base.ImeMode = value; + } + } + + [DefaultValue (10)] + [RefreshProperties(RefreshProperties.Repaint)] + [MWFDescription("Scroll amount when clicking in the scroll area"), MWFCategory("Behaviour")] + public int LargeChange { + get { return Math.Min (large_change, maximum - minimum + 1); } + set { + if (value < 0) + throw new ArgumentOutOfRangeException ("LargeChange", string.Format ("Value '{0}' must be greater than or equal to 0.", value)); + + if (large_change != value) { + large_change = value; + + // thumb area depends on large change value, + // so we need to recalculate it. + CalcThumbArea (); + UpdatePos (Value, true); + InvalidateDirty (); + + // UIA Framework: Generate UIA Event to indicate LargeChange change + OnUIAValueChanged (new ScrollEventArgs (ScrollEventType.LargeIncrement, value)); + } + } + } + + [DefaultValue (100)] + [RefreshProperties(RefreshProperties.Repaint)] + [MWFDescription("Highest value for scrollbar"), MWFCategory("Behaviour")] + public int Maximum { + get { return maximum; } + set { + if (maximum == value) + return; + + maximum = value; + + // UIA Framework: Generate UIA Event to indicate Maximum change + OnUIAValueChanged (new ScrollEventArgs (ScrollEventType.Last, value)); + + if (maximum < minimum) + minimum = maximum; + if (Value > maximum) + Value = maximum; + + // thumb area depends on maximum value, + // so we need to recalculate it. + CalcThumbArea (); + UpdatePos (Value, true); + InvalidateDirty (); + } + } + + internal void SetValues (int maximum, int large_change) + { + SetValues (-1, maximum, -1, large_change); + } + + internal void SetValues (int minimum, int maximum, int small_change, int large_change) + { + bool update = false; + + if (-1 != minimum && this.minimum != minimum) { + this.minimum = minimum; + + if (minimum > this.maximum) + this.maximum = minimum; + update = true; + + // change the position if it is out of range now + position = Math.Max (position, minimum); + } + + if (-1 != maximum && this.maximum != maximum) { + this.maximum = maximum; + + if (maximum < this.minimum) + this.minimum = maximum; + update = true; + + // change the position if it is out of range now + position = Math.Min (position, maximum); + } + + if (-1 != small_change && this.small_change != small_change) { + this.small_change = small_change; + } + + if (this.large_change != large_change) { + this.large_change = large_change; + update = true; + } + + if (update) { + CalcThumbArea (); + UpdatePos (Value, true); + InvalidateDirty (); + } + } + + [DefaultValue (0)] + [RefreshProperties(RefreshProperties.Repaint)] + [MWFDescription("Smallest value for scrollbar"), MWFCategory("Behaviour")] + public int Minimum { + get { return minimum; } + set { + if (minimum == value) + return; + + minimum = value; + + // UIA Framework: Generate UIA Event to indicate Minimum change + OnUIAValueChanged (new ScrollEventArgs (ScrollEventType.First, value)); + + if (minimum > maximum) + maximum = minimum; + + // thumb area depends on minimum value, + // so we need to recalculate it. + CalcThumbArea (); + UpdatePos (Value, true); + InvalidateDirty (); + } + } + + [DefaultValue (1)] + [MWFDescription("Scroll amount when clicking scroll arrows"), MWFCategory("Behaviour")] + public int SmallChange { + get { return small_change > LargeChange ? LargeChange : small_change; } + set { + if ( value < 0 ) + throw new ArgumentOutOfRangeException ("SmallChange", string.Format ("Value '{0}' must be greater than or equal to 0.", value)); + + if (small_change != value) { + small_change = value; + UpdatePos (Value, true); + InvalidateDirty (); + + // UIA Framework: Generate UIA Event to indicate SmallChange change + OnUIAValueChanged (new ScrollEventArgs (ScrollEventType.SmallIncrement, value)); + } + } + } + + [DefaultValue (false)] + public new bool TabStop { + get { return base.TabStop; } + set { base.TabStop = value; } + } + + //[EditorBrowsable (EditorBrowsableState.Never)] + [Bindable (false)] + [Browsable (false)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public override string Text { + get { return base.Text; } + set { base.Text = value; } + } + + [Bindable(true)] + [DefaultValue (0)] + [MWFDescription("Current value for scrollbar"), MWFCategory("Behaviour")] + public int Value { + get { return position; } + set { + if ( value < minimum || value > maximum ) + throw new ArgumentOutOfRangeException ("Value", string.Format ("'{0}' is not a valid value for 'Value'. 'Value' should be between 'Minimum' and 'Maximum'", value)); + + if (position != value){ + position = value; + + OnValueChanged (EventArgs.Empty); + + if (IsHandleCreated) { + Rectangle thumb_rect = thumb_pos; + + UpdateThumbPos ((vert ? thumb_area.Y : thumb_area.X) + (int)(((float)(position - minimum)) * pixel_per_pos), false, false); + + MoveThumb (thumb_rect, vert ? thumb_pos.Y : thumb_pos.X); + } + } + } + } + + #endregion //Public Properties + + #region Public Methods + protected override Rectangle GetScaledBounds (Rectangle bounds, SizeF factor, BoundsSpecified specified) + { + // Basically, we want to keep our small edge and scale the long edge + // ie: if we are vertical, don't scale our width + if (vert) + return base.GetScaledBounds (bounds, factor, (specified & BoundsSpecified.Height) | (specified & BoundsSpecified.Location)); + else + return base.GetScaledBounds (bounds, factor, (specified & BoundsSpecified.Width) | (specified & BoundsSpecified.Location)); + } + + protected override void OnEnabledChanged (EventArgs e) + { + base.OnEnabledChanged (e); + + if (Enabled) + firstbutton_state = secondbutton_state = ButtonState.Normal; + else + firstbutton_state = secondbutton_state = ButtonState.Inactive; + + Refresh (); + } + + protected override void OnHandleCreated (System.EventArgs e) + { + base.OnHandleCreated (e); + + CalcButtonSizes (); + CalcThumbArea (); + UpdateThumbPos (thumb_area.Y + (int)(((float)(position - minimum)) * pixel_per_pos), true, false); + } + + protected virtual void OnScroll (ScrollEventArgs se) + { + ScrollEventHandler eh = (ScrollEventHandler)(Events [ScrollEvent]); + if (eh == null) + return; + + if (se.NewValue < Minimum) { + se.NewValue = Minimum; + } + + if (se.NewValue > Maximum) { + se.NewValue = Maximum; + } + + eh (this, se); + } + + private void SendWMScroll(ScrollBarCommands cmd) { + if ((Parent != null) && Parent.IsHandleCreated) { + if (vert) { + XplatUI.SendMessage(Parent.Handle, Msg.WM_VSCROLL, (IntPtr)cmd, implicit_Widget ? IntPtr.Zero : Handle); + } else { + XplatUI.SendMessage(Parent.Handle, Msg.WM_HSCROLL, (IntPtr)cmd, implicit_Widget ? IntPtr.Zero : Handle); + } + } + } + + protected virtual void OnValueChanged (EventArgs e) + { + EventHandler eh = (EventHandler)(Events [ValueChangedEvent]); + if (eh != null) + eh (this, e); + } + + public override string ToString() + { + return string.Format("{0}, Minimum: {1}, Maximum: {2}, Value: {3}", + GetType( ).FullName, minimum, maximum, position); + } + + protected void UpdateScrollInfo () + { + Refresh (); + } + + protected override void WndProc (ref Message m) + { + base.WndProc (ref m); + } + + #endregion //Public Methods + + #region Private Methods + + private void CalcButtonSizes () + { + if (vert) { + if (Height < ThemeEngine.Current.ScrollBarButtonSize * 2) + scrollbutton_height = Height /2; + else + scrollbutton_height = ThemeEngine.Current.ScrollBarButtonSize; + + } else { + if (Width < ThemeEngine.Current.ScrollBarButtonSize * 2) + scrollbutton_width = Width /2; + else + scrollbutton_width = ThemeEngine.Current.ScrollBarButtonSize; + } + } + + private void CalcThumbArea () + { + int lchange = use_manual_thumb_size ? manual_thumb_size : LargeChange; + + // Thumb area + if (vert) { + + thumb_area.Height = Height - scrollbutton_height - scrollbutton_height; + thumb_area.X = 0; + thumb_area.Y = scrollbutton_height; + thumb_area.Width = Width; + + if (Height < thumb_notshown_size) + thumb_size = 0; + else { + double per = ((double) lchange / (double)((1 + maximum - minimum))); + thumb_size = 1 + (int) (thumb_area.Height * per); + + if (thumb_size < thumb_min_size) + thumb_size = thumb_min_size; + + // Give the user something to drag if LargeChange is zero + if (LargeChange == 0) + thumb_size = 17; + } + + pixel_per_pos = ((float)(thumb_area.Height - thumb_size) / (float) ((maximum - minimum - lchange) + 1)); + + } else { + + thumb_area.Y = 0; + thumb_area.X = scrollbutton_width; + thumb_area.Height = Height; + thumb_area.Width = Width - scrollbutton_width - scrollbutton_width; + + if (Width < thumb_notshown_size) + thumb_size = 0; + else { + double per = ((double) lchange / (double)((1 + maximum - minimum))); + thumb_size = 1 + (int) (thumb_area.Width * per); + + if (thumb_size < thumb_min_size) + thumb_size = thumb_min_size; + + // Give the user something to drag if LargeChange is zero + if (LargeChange == 0) + thumb_size = 17; + } + + pixel_per_pos = ((float)(thumb_area.Width - thumb_size) / (float) ((maximum - minimum - lchange) + 1)); + } + } + + private void LargeIncrement () + { + ScrollEventArgs event_args; + int pos = Math.Min (MaximumAllowed, position + large_change); + + event_args = new ScrollEventArgs (ScrollEventType.LargeIncrement, pos); + OnScroll (event_args); + Value = event_args.NewValue; + + event_args = new ScrollEventArgs (ScrollEventType.EndScroll, Value); + OnScroll (event_args); + Value = event_args.NewValue; + + // UIA Framework event invoked when the "LargeIncrement + // Button" is "clicked" either by using the Invoke Pattern + // or the space between the thumb and the bottom/right button + OnUIAScroll (new ScrollEventArgs (ScrollEventType.LargeIncrement, Value)); + } + + private void LargeDecrement () + { + ScrollEventArgs event_args; + int pos = Math.Max (Minimum, position - large_change); + + event_args = new ScrollEventArgs (ScrollEventType.LargeDecrement, pos); + OnScroll (event_args); + Value = event_args.NewValue; + + event_args = new ScrollEventArgs (ScrollEventType.EndScroll, Value); + OnScroll (event_args); + Value = event_args.NewValue; + + // UIA Framework event invoked when the "LargeDecrement + // Button" is "clicked" either by using the Invoke Pattern + // or the space between the thumb and the top/left button + OnUIAScroll (new ScrollEventArgs (ScrollEventType.LargeDecrement, Value)); + } + + private void OnResizeSB (Object o, EventArgs e) + { + if (Width <= 0 || Height <= 0) + return; + + CalcButtonSizes (); + CalcThumbArea (); + UpdatePos (position, true); + + Refresh (); + } + + internal override void OnPaintInternal (PaintEventArgs pevent) + { + ThemeEngine.Current.DrawScrollBar (pevent.Graphics, pevent.ClipRectangle, this); + } + + private void OnTimer (Object source, EventArgs e) + { + ClearDirty (); + + switch (timer_type) { + + case TimerType.HoldButton: + SetRepeatButtonTimer (); + break; + + case TimerType.RepeatButton: + { + if ((firstbutton_state & ButtonState.Pushed) == ButtonState.Pushed && position != Minimum) { + SmallDecrement(); + SendWMScroll(ScrollBarCommands.SB_LINEUP); + } + + if ((secondbutton_state & ButtonState.Pushed) == ButtonState.Pushed && position != Maximum) { + SmallIncrement(); + SendWMScroll(ScrollBarCommands.SB_LINEDOWN); + } + + break; + } + + case TimerType.HoldThumbArea: + SetRepeatThumbAreaTimer (); + break; + + case TimerType.RepeatThumbArea: + { + Point pnt, pnt_screen; + Rectangle thumb_area_screen = thumb_area; + + pnt_screen = PointToScreen (new Point (thumb_area.X, thumb_area.Y)); + thumb_area_screen.X = pnt_screen.X; + thumb_area_screen.Y = pnt_screen.Y; + + if (thumb_area_screen.Contains (MousePosition) == false) { + timer.Enabled = false; + thumb_moving = ThumbMoving.None; + DirtyThumbArea (); + InvalidateDirty (); + } + + pnt = PointToClient (MousePosition); + + if (vert) + lastclick_pos = pnt.Y; + else + lastclick_pos = pnt.X; + + if (thumb_moving == ThumbMoving.Forward) { + if ((vert && (thumb_pos.Y + thumb_size > lastclick_pos)) || + (!vert && (thumb_pos.X + thumb_size > lastclick_pos)) || + (thumb_area.Contains (pnt) == false)) { + timer.Enabled = false; + thumb_moving = ThumbMoving.None; + Refresh (); + return; + } else { + LargeIncrement (); + SendWMScroll(ScrollBarCommands.SB_PAGEDOWN); + } + } else { + if ((vert && (thumb_pos.Y < lastclick_pos)) || + (!vert && (thumb_pos.X < lastclick_pos))){ + timer.Enabled = false; + thumb_moving = ThumbMoving.None; + SendWMScroll(ScrollBarCommands.SB_PAGEUP); + Refresh (); + } else { + LargeDecrement (); + SendWMScroll(ScrollBarCommands.SB_PAGEUP); + } + } + + break; + } + default: + break; + } + + InvalidateDirty (); + } + + private void MoveThumb (Rectangle original_thumbpos, int value) + { + /* so, the reason this works can best be + * described by the following 1 dimensional + * pictures + * + * say you have a scrollbar thumb positioned + * thusly: + * + * <---------------------| |------------------------------> + * + * and you want it to end up looking like this: + * + * <-----------------------------| |----------------------> + * + * that can be done with the scrolling api by + * extending the rectangle to encompass both + * positions: + * + * start of range end of range + * \ / + * <---------------------| |-------|----------------------> + * + * so, we end up scrolling just this little region: + * + * | |-------| + * + * and end up with ********| | + * + * where ****** is space that is automatically + * redrawn. + * + * It's clear that in both cases (left to + * right, right to left) we need to extend the + * size of the scroll rectangle to encompass + * both. In the right to left case, we also + * need to decrement the X coordinate. + * + * We call Update after scrolling to make sure + * there's no garbage left in the window to be + * copied again if we're called before the + * paint events have been handled. + * + */ + int delta; + + if (vert) { + delta = value - original_thumbpos.Y; + + if (delta < 0) { + original_thumbpos.Y += delta; + original_thumbpos.Height -= delta; + } + else { + original_thumbpos.Height += delta; + } + + XplatUI.ScrollWindow (Handle, original_thumbpos, 0, delta, false); + } + else { + delta = value - original_thumbpos.X; + + if (delta < 0) { + original_thumbpos.X += delta; + original_thumbpos.Width -= delta; + } + else { + original_thumbpos.Width += delta; + } + + XplatUI.ScrollWindow (Handle, original_thumbpos, delta, 0, false); + } + + Update (); + } + + private void OnMouseMoveSB (object sender, MouseEventArgs e) + { + if (Enabled == false) + return; + + FirstButtonEntered = first_arrow_area.Contains (e.Location); + SecondButtonEntered = second_arrow_area.Contains (e.Location); + + if (thumb_size == 0) + return; + + ThumbEntered = thumb_pos.Contains (e.Location); + + if (firstbutton_pressed) { + if (!first_arrow_area.Contains (e.X, e.Y) && ((firstbutton_state & ButtonState.Pushed) == ButtonState.Pushed)) { + firstbutton_state = ButtonState.Normal; + Invalidate (first_arrow_area); + Update(); + return; + } else if (first_arrow_area.Contains (e.X, e.Y) && ((firstbutton_state & ButtonState.Normal) == ButtonState.Normal)) { + firstbutton_state = ButtonState.Pushed; + Invalidate (first_arrow_area); + Update(); + return; + } + } else if (secondbutton_pressed) { + if (!second_arrow_area.Contains (e.X, e.Y) && ((secondbutton_state & ButtonState.Pushed) == ButtonState.Pushed)) { + secondbutton_state = ButtonState.Normal; + Invalidate (second_arrow_area); + Update(); + return; + } else if (second_arrow_area.Contains (e.X, e.Y) && ((secondbutton_state & ButtonState.Normal) == ButtonState.Normal)) { + secondbutton_state = ButtonState.Pushed; + Invalidate (second_arrow_area); + Update(); + return; + } + } else if (thumb_pressed == true) { + if (vert) { + int thumb_edge = e.Y - thumbclick_offset; + + if (thumb_edge < thumb_area.Y) + thumb_edge = thumb_area.Y; + else if (thumb_edge > thumb_area.Bottom - thumb_size) + thumb_edge = thumb_area.Bottom - thumb_size; + + if (thumb_edge != thumb_pos.Y) { + Rectangle thumb_rect = thumb_pos; + + UpdateThumbPos (thumb_edge, false, true); + + MoveThumb (thumb_rect, thumb_pos.Y); + + OnScroll (new ScrollEventArgs (ScrollEventType.ThumbTrack, position)); + } + SendWMScroll(ScrollBarCommands.SB_THUMBTRACK); + } else { + int thumb_edge = e.X - thumbclick_offset; + + if (thumb_edge < thumb_area.X) + thumb_edge = thumb_area.X; + else if (thumb_edge > thumb_area.Right - thumb_size) + thumb_edge = thumb_area.Right - thumb_size; + + if (thumb_edge != thumb_pos.X) { + Rectangle thumb_rect = thumb_pos; + + UpdateThumbPos (thumb_edge, false, true); + + MoveThumb (thumb_rect, thumb_pos.X); + + OnScroll (new ScrollEventArgs (ScrollEventType.ThumbTrack, position)); + } + SendWMScroll(ScrollBarCommands.SB_THUMBTRACK); + } + + } + + } + + private void OnMouseDownSB (object sender, MouseEventArgs e) + { + ClearDirty (); + + if (Enabled == false || (e.Button & MouseButtons.Left) == 0) + return; + + if (firstbutton_state != ButtonState.Inactive && first_arrow_area.Contains (e.X, e.Y)) { + SendWMScroll(ScrollBarCommands.SB_LINEUP); + firstbutton_state = ButtonState.Pushed; + firstbutton_pressed = true; + Invalidate (first_arrow_area); + Update(); + if (!timer.Enabled) { + SetHoldButtonClickTimer (); + timer.Enabled = true; + } + } + + if (secondbutton_state != ButtonState.Inactive && second_arrow_area.Contains (e.X, e.Y)) { + SendWMScroll(ScrollBarCommands.SB_LINEDOWN); + secondbutton_state = ButtonState.Pushed; + secondbutton_pressed = true; + Invalidate (second_arrow_area); + Update(); + if (!timer.Enabled) { + SetHoldButtonClickTimer (); + timer.Enabled = true; + } + } + + if (thumb_size > 0 && thumb_pos.Contains (e.X, e.Y)) { + ThumbPressed = true; + SendWMScroll(ScrollBarCommands.SB_THUMBTRACK); + if (vert) { + thumbclick_offset = e.Y - thumb_pos.Y; + lastclick_pos = e.Y; + } + else { + thumbclick_offset = e.X - thumb_pos.X; + lastclick_pos = e.X; + } + } else { + if (thumb_size > 0 && thumb_area.Contains (e.X, e.Y)) { + + if (vert) { + lastclick_pos = e.Y; + + if (e.Y > thumb_pos.Y + thumb_pos.Height) { + SendWMScroll(ScrollBarCommands.SB_PAGEDOWN); + LargeIncrement (); + thumb_moving = ThumbMoving.Forward; + Dirty (new Rectangle (0, thumb_pos.Y + thumb_pos.Height, + ClientRectangle.Width, + ClientRectangle.Height - (thumb_pos.Y + thumb_pos.Height) - + scrollbutton_height)); + } else { + SendWMScroll(ScrollBarCommands.SB_PAGEUP); + LargeDecrement (); + thumb_moving = ThumbMoving.Backwards; + Dirty (new Rectangle (0, scrollbutton_height, + ClientRectangle.Width, + thumb_pos.Y - scrollbutton_height)); + } + } else { + + lastclick_pos = e.X; + + if (e.X > thumb_pos.X + thumb_pos.Width) { + SendWMScroll(ScrollBarCommands.SB_PAGEDOWN); + thumb_moving = ThumbMoving.Forward; + LargeIncrement (); + Dirty (new Rectangle (thumb_pos.X + thumb_pos.Width, 0, + ClientRectangle.Width - (thumb_pos.X + thumb_pos.Width) - + scrollbutton_width, + ClientRectangle.Height)); + } else { + SendWMScroll(ScrollBarCommands.SB_PAGEUP); + thumb_moving = ThumbMoving.Backwards; + LargeDecrement (); + Dirty (new Rectangle (scrollbutton_width, 0, + thumb_pos.X - scrollbutton_width, + ClientRectangle.Height)); + } + } + + SetHoldThumbAreaTimer (); + timer.Enabled = true; + InvalidateDirty (); + } + } + } + + private void OnMouseUpSB (object sender, MouseEventArgs e) + { + ClearDirty (); + + if (Enabled == false) + return; + + timer.Enabled = false; + if (thumb_moving != ThumbMoving.None) { + DirtyThumbArea (); + thumb_moving = ThumbMoving.None; + } + + if (firstbutton_pressed) { + firstbutton_state = ButtonState.Normal; + if (first_arrow_area.Contains (e.X, e.Y)) { + SmallDecrement (); + } + SendWMScroll(ScrollBarCommands.SB_LINEUP); + firstbutton_pressed = false; + Dirty (first_arrow_area); + } else if (secondbutton_pressed) { + secondbutton_state = ButtonState.Normal; + if (second_arrow_area.Contains (e.X, e.Y)) { + SmallIncrement (); + } + SendWMScroll(ScrollBarCommands.SB_LINEDOWN); + Dirty (second_arrow_area); + secondbutton_pressed = false; + } else if (thumb_pressed == true) { + OnScroll (new ScrollEventArgs (ScrollEventType.ThumbPosition, position)); + OnScroll (new ScrollEventArgs (ScrollEventType.EndScroll, position)); + SendWMScroll(ScrollBarCommands.SB_THUMBPOSITION); + ThumbPressed = false; + return; + } + + InvalidateDirty (); + } + + private void OnKeyDownSB (Object o, KeyEventArgs key) + { + if (Enabled == false) + return; + + ClearDirty (); + + switch (key.KeyCode){ + case Keys.Up: + { + SmallDecrement (); + break; + } + case Keys.Down: + { + SmallIncrement (); + break; + } + case Keys.PageUp: + { + LargeDecrement (); + break; + } + case Keys.PageDown: + { + LargeIncrement (); + break; + } + case Keys.Home: + { + SetHomePosition (); + break; + } + case Keys.End: + { + SetEndPosition (); + break; + } + default: + break; + } + + InvalidateDirty (); + } + + // I hate to do this, but we don't have the resources to track + // down everything internal that is setting a value outside the + // correct range, so we'll clamp it to the acceptable values. + internal void SafeValueSet (int value) + { + value = Math.Min (value, maximum); + value = Math.Max (value, minimum); + + Value = value; + } + + private void SetEndPosition () + { + ScrollEventArgs event_args; + int pos = MaximumAllowed; + + event_args = new ScrollEventArgs (ScrollEventType.Last, pos); + OnScroll (event_args); + pos = event_args.NewValue; + + event_args = new ScrollEventArgs (ScrollEventType.EndScroll, pos); + OnScroll (event_args); + pos = event_args.NewValue; + + SetValue (pos); + } + + private void SetHomePosition () + { + ScrollEventArgs event_args; + int pos = Minimum; + + event_args = new ScrollEventArgs (ScrollEventType.First, pos); + OnScroll (event_args); + pos = event_args.NewValue; + + event_args = new ScrollEventArgs (ScrollEventType.EndScroll, pos); + OnScroll (event_args); + pos = event_args.NewValue; + + SetValue (pos); + } + + private void SmallIncrement () + { + ScrollEventArgs event_args; + int pos = Math.Min (MaximumAllowed, position + SmallChange); + + event_args = new ScrollEventArgs (ScrollEventType.SmallIncrement, pos); + OnScroll (event_args); + Value = event_args.NewValue; + + event_args = new ScrollEventArgs (ScrollEventType.EndScroll, Value); + OnScroll (event_args); + Value = event_args.NewValue; + + // UIA Framework event invoked when the "SmallIncrement + // Button" (a.k.a bottom/right button) is "clicked" either + // by using the Invoke Pattern or the button itself + OnUIAScroll (new ScrollEventArgs (ScrollEventType.SmallIncrement, Value)); + } + + private void SmallDecrement () + { + ScrollEventArgs event_args; + int pos = Math.Max (Minimum, position - SmallChange); + + event_args = new ScrollEventArgs (ScrollEventType.SmallDecrement, pos); + OnScroll (event_args); + Value = event_args.NewValue; + + event_args = new ScrollEventArgs (ScrollEventType.EndScroll, Value); + OnScroll (event_args); + Value = event_args.NewValue; + + // UIA Framework event invoked when the "SmallDecrement + // Button" (a.k.a top/left button) is "clicked" either + // by using the Invoke Pattern or the button itself + OnUIAScroll (new ScrollEventArgs (ScrollEventType.SmallDecrement, Value)); + } + + private void SetHoldButtonClickTimer () + { + timer.Enabled = false; + timer.Interval = 200; + timer_type = TimerType.HoldButton; + timer.Enabled = true; + } + + private void SetRepeatButtonTimer () + { + timer.Enabled = false; + timer.Interval = 50; + timer_type = TimerType.RepeatButton; + timer.Enabled = true; + } + + private void SetHoldThumbAreaTimer () + { + timer.Enabled = false; + timer.Interval = 200; + timer_type = TimerType.HoldThumbArea; + timer.Enabled = true; + } + + private void SetRepeatThumbAreaTimer () + { + timer.Enabled = false; + timer.Interval = 50; + timer_type = TimerType.RepeatThumbArea; + timer.Enabled = true; + } + + private void UpdatePos (int newPos, bool update_thumbpos) + { + int pos; + + if (newPos < minimum) + pos = minimum; + else + if (newPos > MaximumAllowed) + pos = MaximumAllowed; + else + pos = newPos; + + // pos can't be less than minimum or greater than maximum + if (pos < minimum) + pos = minimum; + if (pos > maximum) + pos = maximum; + + if (update_thumbpos) { + if (vert) + UpdateThumbPos (thumb_area.Y + (int)(((float)(pos - minimum)) * pixel_per_pos), true, false); + else + UpdateThumbPos (thumb_area.X + (int)(((float)(pos - minimum)) * pixel_per_pos), true, false); + SetValue (pos); + } + else { + position = pos; // Updates directly the value to avoid thumb pos update + + + // XXX some reason we don't call OnValueChanged? + EventHandler eh = (EventHandler)(Events [ValueChangedEvent]); + if (eh != null) + eh (this, EventArgs.Empty); + } + } + + private void UpdateThumbPos (int pixel, bool dirty, bool update_value) + { + float new_pos = 0; + + if (vert) { + if (dirty) + Dirty (thumb_pos); + if (pixel < thumb_area.Y) + thumb_pos.Y = thumb_area.Y; + else if (pixel > thumb_area.Bottom - thumb_size) + thumb_pos.Y = thumb_area.Bottom - thumb_size; + else + thumb_pos.Y = pixel; + + thumb_pos.X = 0; + thumb_pos.Width = ThemeEngine.Current.ScrollBarButtonSize; + thumb_pos.Height = thumb_size; + new_pos = (float) (thumb_pos.Y - thumb_area.Y); + new_pos = new_pos / pixel_per_pos; + if (dirty) + Dirty (thumb_pos); + } else { + if (dirty) + Dirty (thumb_pos); + if (pixel < thumb_area.X) + thumb_pos.X = thumb_area.X; + else if (pixel > thumb_area.Right - thumb_size) + thumb_pos.X = thumb_area.Right - thumb_size; + else + thumb_pos.X = pixel; + + thumb_pos.Y = 0; + thumb_pos.Width = thumb_size; + thumb_pos.Height = ThemeEngine.Current.ScrollBarButtonSize; + new_pos = (float) (thumb_pos.X - thumb_area.X); + new_pos = new_pos / pixel_per_pos; + + if (dirty) + Dirty (thumb_pos); + } + + if (update_value) + UpdatePos ((int) new_pos + minimum, false); + } + + private void SetValue (int value) + { + if ( value < minimum || value > maximum ) + throw new ArgumentException( + String.Format("'{0}' is not a valid value for 'Value'. 'Value' should be between 'Minimum' and 'Maximum'", value)); + + if (position != value){ + position = value; + + OnValueChanged (EventArgs.Empty); + UpdatePos (value, true); + } + } + + private void ClearDirty () + { + dirty = Rectangle.Empty; + } + + private void Dirty (Rectangle r) + { + if (dirty == Rectangle.Empty) { + dirty = r; + return; + } + dirty = Rectangle.Union (dirty, r); + } + + private void DirtyThumbArea () + { + if (thumb_moving == ThumbMoving.Forward) { + if (vert) { + Dirty (new Rectangle (0, thumb_pos.Y + thumb_pos.Height, + ClientRectangle.Width, + ClientRectangle.Height - (thumb_pos.Y + thumb_pos.Height) - + scrollbutton_height)); + } else { + Dirty (new Rectangle (thumb_pos.X + thumb_pos.Width, 0, + ClientRectangle.Width - (thumb_pos.X + thumb_pos.Width) - + scrollbutton_width, + ClientRectangle.Height)); + } + } else if (thumb_moving == ThumbMoving.Backwards) { + if (vert) { + Dirty(new Rectangle (0, scrollbutton_height, + ClientRectangle.Width, + thumb_pos.Y - scrollbutton_height)); + } else { + Dirty (new Rectangle (scrollbutton_width, 0, + thumb_pos.X - scrollbutton_width, + ClientRectangle.Height)); + } + } + } + + private void InvalidateDirty () + { + Invalidate (dirty); + Update(); + dirty = Rectangle.Empty; + } + + void OnMouseEnter (object sender, EventArgs e) + { + if (ThemeEngine.Current.ScrollBarHasHoverArrowButtonStyle) { + Region region_to_invalidate = new Region (first_arrow_area); + region_to_invalidate.Union (second_arrow_area); + Invalidate (region_to_invalidate); + } + } + + void OnMouseLeave (object sender, EventArgs e) + { + Region region_to_invalidate = new Region (); + region_to_invalidate.MakeEmpty (); + bool dirty = false; + if (ThemeEngine.Current.ScrollBarHasHoverArrowButtonStyle) { + region_to_invalidate.Union (first_arrow_area); + region_to_invalidate.Union (second_arrow_area); + dirty = true; + } else + if (ThemeEngine.Current.ScrollBarHasHotElementStyles) + if (first_button_entered) { + region_to_invalidate.Union (first_arrow_area); + dirty = true; + } else if (second_button_entered) { + region_to_invalidate.Union (second_arrow_area); + dirty = true; + } + if (ThemeEngine.Current.ScrollBarHasHotElementStyles) + if (thumb_entered) { + region_to_invalidate.Union (thumb_pos); + dirty = true; + } + first_button_entered = false; + second_button_entered = false; + thumb_entered = false; + if (dirty) + Invalidate (region_to_invalidate); + region_to_invalidate.Dispose (); + } + #endregion //Private Methods + protected override void OnMouseWheel (MouseEventArgs e) + { + base.OnMouseWheel (e); + } + + #region UIA Framework Section: Events, Methods and Properties. + + //NOTE: + // We are using Reflection to add/remove internal events. + // Class ScrollBarButtonInvokePatternInvokeEvent uses the events. + // + // Types used to generate UIA InvokedEvent + // * args.Type = ScrollEventType.LargeIncrement. Space between Thumb and bottom/right Button + // * args.Type = ScrollEventType.LargeDecrement. Space between Thumb and top/left Button + // * args.Type = ScrollEventType.SmallIncrement. Small increment UIA Button (bottom/right Button) + // * args.Type = ScrollEventType.SmallDecrement. Small decrement UIA Button (top/left Button) + // Types used to generate RangeValue-related events + // * args.Type = ScrollEventType.LargeIncrement. LargeChange event + // * args.Type = ScrollEventType.Last. Maximum event + // * args.Type = ScrollEventType.First. Minimum event + // * args.Type = ScrollEventType.SmallIncrement. SmallChange event + static object UIAScrollEvent = new object (); + static object UIAValueChangeEvent = new object (); + + internal event ScrollEventHandler UIAScroll { + add { Events.AddHandler (UIAScrollEvent, value); } + remove { Events.RemoveHandler (UIAScrollEvent, value); } + } + + internal event ScrollEventHandler UIAValueChanged { + add { Events.AddHandler (UIAValueChangeEvent, value); } + remove { Events.RemoveHandler (UIAValueChangeEvent, value); } + } + + internal void OnUIAScroll (ScrollEventArgs args) + { + ScrollEventHandler eh = (ScrollEventHandler) Events [UIAScrollEvent]; + if (eh != null) + eh (this, args); + } + + internal void OnUIAValueChanged (ScrollEventArgs args) + { + ScrollEventHandler eh = (ScrollEventHandler) Events [UIAValueChangeEvent]; + if (eh != null) + eh (this, args); + } + + //NOTE: + // Wrapper methods used by the Reflection. + // Class ScrollBarButtonInvokeProviderBehavior uses the events. + // + internal void UIALargeIncrement () + { + LargeIncrement (); + } + + internal void UIALargeDecrement () + { + LargeDecrement (); + } + + internal void UIASmallIncrement () + { + SmallIncrement (); + } + + internal void UIASmallDecrement () + { + SmallDecrement (); + } + + internal Rectangle UIAThumbArea { + get { return thumb_area; } + } + + internal Rectangle UIAThumbPosition { + get { return thumb_pos; } + } + + #endregion UIA Framework Section: Events, Methods and Properties. + + } +} + + + diff --git a/source/ShiftUI/Widgets/ScrollBarRenderer.cs b/source/ShiftUI/Widgets/ScrollBarRenderer.cs new file mode 100644 index 0000000..3030c72 --- /dev/null +++ b/source/ShiftUI/Widgets/ScrollBarRenderer.cs @@ -0,0 +1,326 @@ +// +// ScrollBarRenderer.cs +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2006 Novell, Inc. +// +// Authors: +// Jonathan Pobst ([email protected]) +// + +using System.Drawing; +using System.Windows.Forms.VisualStyles; + +namespace ShiftUI +{ + public sealed class ScrollBarRenderer + { + #region Private Constructor + private ScrollBarRenderer () { } + #endregion + + #region Public Static Methods + public static void DrawArrowButton (Graphics g, Rectangle bounds, ScrollBarArrowButtonState state) + { + if (!IsSupported) + throw new InvalidOperationException (); + + VisualStyleRenderer vsr; + + switch (state) { + case ScrollBarArrowButtonState.DownDisabled: + vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.ArrowButton.DownDisabled); + break; + case ScrollBarArrowButtonState.DownHot: + vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.ArrowButton.DownHot); + break; + case ScrollBarArrowButtonState.DownNormal: + default: + vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.ArrowButton.DownNormal); + break; + case ScrollBarArrowButtonState.DownPressed: + vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.ArrowButton.DownPressed); + break; + case ScrollBarArrowButtonState.LeftDisabled: + vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.ArrowButton.LeftDisabled); + break; + case ScrollBarArrowButtonState.LeftHot: + vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.ArrowButton.LeftHot); + break; + case ScrollBarArrowButtonState.LeftNormal: + vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.ArrowButton.LeftNormal); + break; + case ScrollBarArrowButtonState.LeftPressed: + vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.ArrowButton.LeftPressed); + break; + case ScrollBarArrowButtonState.RightDisabled: + vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.ArrowButton.RightDisabled); + break; + case ScrollBarArrowButtonState.RightHot: + vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.ArrowButton.RightHot); + break; + case ScrollBarArrowButtonState.RightNormal: + vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.ArrowButton.RightNormal); + break; + case ScrollBarArrowButtonState.RightPressed: + vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.ArrowButton.RightPressed); + break; + case ScrollBarArrowButtonState.UpDisabled: + vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.ArrowButton.UpDisabled); + break; + case ScrollBarArrowButtonState.UpHot: + vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.ArrowButton.UpHot); + break; + case ScrollBarArrowButtonState.UpNormal: + vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.ArrowButton.UpNormal); + break; + case ScrollBarArrowButtonState.UpPressed: + vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.ArrowButton.UpPressed); + break; + } + + vsr.DrawBackground(g, bounds); + } + + public static void DrawHorizontalThumb (Graphics g, Rectangle bounds, ScrollBarState state) + { + if (!IsSupported) + throw new InvalidOperationException (); + + VisualStyleRenderer vsr; + + switch (state) { + case ScrollBarState.Disabled: + vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.ThumbButtonHorizontal.Disabled); + break; + case ScrollBarState.Hot: + vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.ThumbButtonHorizontal.Hot); + break; + case ScrollBarState.Normal: + default: + vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.ThumbButtonHorizontal.Normal); + break; + case ScrollBarState.Pressed: + vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.ThumbButtonHorizontal.Pressed); + break; + } + + vsr.DrawBackground (g, bounds); + } + + public static void DrawHorizontalThumbGrip (Graphics g, Rectangle bounds, ScrollBarState state) + { + if (!IsSupported) + throw new InvalidOperationException (); + + VisualStyleRenderer vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.GripperHorizontal.Normal); + + vsr.DrawBackground (g, bounds); + } + + public static void DrawLeftHorizontalTrack (Graphics g, Rectangle bounds, ScrollBarState state) + { + if (!IsSupported) + throw new InvalidOperationException (); + + VisualStyleRenderer vsr; + + switch (state) { + case ScrollBarState.Disabled: + vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.LeftTrackHorizontal.Disabled); + break; + case ScrollBarState.Hot: + vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.LeftTrackHorizontal.Hot); + break; + case ScrollBarState.Normal: + default: + vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.LeftTrackHorizontal.Normal); + break; + case ScrollBarState.Pressed: + vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.LeftTrackHorizontal.Pressed); + break; + } + + vsr.DrawBackground (g, bounds); + } + + public static void DrawLowerVerticalTrack (Graphics g, Rectangle bounds, ScrollBarState state) + { + if (!IsSupported) + throw new InvalidOperationException (); + + VisualStyleRenderer vsr; + + switch (state) { + case ScrollBarState.Disabled: + vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.LowerTrackVertical.Disabled); + break; + case ScrollBarState.Hot: + vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.LowerTrackVertical.Hot); + break; + case ScrollBarState.Normal: + default: + vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.LowerTrackVertical.Normal); + break; + case ScrollBarState.Pressed: + vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.LowerTrackVertical.Pressed); + break; + } + + vsr.DrawBackground (g, bounds); + } + + public static void DrawRightHorizontalTrack (Graphics g, Rectangle bounds, ScrollBarState state) + { + if (!IsSupported) + throw new InvalidOperationException (); + + VisualStyleRenderer vsr; + + switch (state) { + case ScrollBarState.Disabled: + vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.RightTrackHorizontal.Disabled); + break; + case ScrollBarState.Hot: + vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.RightTrackHorizontal.Hot); + break; + case ScrollBarState.Normal: + default: + vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.RightTrackHorizontal.Normal); + break; + case ScrollBarState.Pressed: + vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.RightTrackHorizontal.Pressed); + break; + } + + vsr.DrawBackground (g, bounds); + } + + public static void DrawSizeBox (Graphics g, Rectangle bounds, ScrollBarSizeBoxState state) + { + if (!IsSupported) + throw new InvalidOperationException (); + + VisualStyleRenderer vsr; + + switch (state) { + case ScrollBarSizeBoxState.LeftAlign: + default: + vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.SizeBox.LeftAlign); + break; + case ScrollBarSizeBoxState.RightAlign: + vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.SizeBox.RightAlign); + break; + } + + vsr.DrawBackground (g, bounds); + } + + public static void DrawUpperVerticalTrack (Graphics g, Rectangle bounds, ScrollBarState state) + { + if (!IsSupported) + throw new InvalidOperationException (); + + VisualStyleRenderer vsr; + + switch (state) { + case ScrollBarState.Disabled: + vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.UpperTrackVertical.Disabled); + break; + case ScrollBarState.Hot: + vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.UpperTrackVertical.Hot); + break; + case ScrollBarState.Normal: + default: + vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.UpperTrackVertical.Normal); + break; + case ScrollBarState.Pressed: + vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.UpperTrackVertical.Pressed); + break; + } + + vsr.DrawBackground (g, bounds); + } + + public static void DrawVerticalThumb (Graphics g, Rectangle bounds, ScrollBarState state) + { + if (!IsSupported) + throw new InvalidOperationException (); + + VisualStyleRenderer vsr; + + switch (state) { + case ScrollBarState.Disabled: + vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.ThumbButtonVertical.Disabled); + break; + case ScrollBarState.Hot: + vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.ThumbButtonVertical.Hot); + break; + case ScrollBarState.Normal: + default: + vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.ThumbButtonVertical.Normal); + break; + case ScrollBarState.Pressed: + vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.ThumbButtonVertical.Pressed); + break; + } + + vsr.DrawBackground (g, bounds); + } + + public static void DrawVerticalThumbGrip (Graphics g, Rectangle bounds, ScrollBarState state) + { + if (!IsSupported) + throw new InvalidOperationException (); + + VisualStyleRenderer vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.GripperVertical.Normal); ; + + vsr.DrawBackground (g, bounds); + } + + public static Size GetSizeBoxSize (Graphics g, ScrollBarState state) + { + if (!IsSupported) + throw new InvalidOperationException (); + + VisualStyleRenderer vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.SizeBox.LeftAlign); + + return vsr.GetPartSize(g, ThemeSizeType.Draw); + } + + public static Size GetThumbGripSize (Graphics g, ScrollBarState state) + { + if (!IsSupported) + throw new InvalidOperationException (); + + VisualStyleRenderer vsr = new VisualStyleRenderer (VisualStyleElement.ScrollBar.GripperVertical.Normal); + + return vsr.GetPartSize (g, ThemeSizeType.Draw); + } + #endregion + + #region Public Static Properties + public static bool IsSupported { + get { return VisualStyleInformation.IsEnabledByUser && (Application.VisualStyleState == VisualStyleState.ClientAndNonClientAreasEnabled || Application.VisualStyleState == VisualStyleState.ClientAreaEnabled); } + } + #endregion + } +} diff --git a/source/ShiftUI/Widgets/ScrollButton.cs b/source/ShiftUI/Widgets/ScrollButton.cs new file mode 100644 index 0000000..2c95da8 --- /dev/null +++ b/source/ShiftUI/Widgets/ScrollButton.cs @@ -0,0 +1,39 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2004 Novell, Inc. +// +// Authors: +// Peter Bartok [email protected] +// +// + + +// COMPLETE + +namespace ShiftUI { + public enum ScrollButton { + Min = 0, + Up = 0, + Down = 1, + Left = 2, + Right = 3, + Max = 3 + } +} diff --git a/source/ShiftUI/Widgets/SizeGrip.cs b/source/ShiftUI/Widgets/SizeGrip.cs new file mode 100644 index 0000000..3e33462 --- /dev/null +++ b/source/ShiftUI/Widgets/SizeGrip.cs @@ -0,0 +1,293 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2005 Novell, Inc. +// +// Authors: +// Jackson Harper ([email protected]) + +using System; +using System.Drawing; + +namespace ShiftUI { + + internal class SizeGrip : Widget { + #region Local Variables + private Point capture_point; + private Widget captured_Widget; + private int window_w; + private int window_h; + private bool hide_pending; + private bool captured; + private bool is_virtual; // If virtual the size grip is painted directly on the captured Widgets' surface. + private bool enabled; + private bool fill_background; + private Rectangle last_painted_area; // The last area that was painted (to know which area to invalidate when resizing). + #endregion // Local Variables + + #region Constructors + public SizeGrip (Widget CapturedWidget) + { + this.Cursor = Cursors.SizeNWSE; + enabled = true; + fill_background = true; + this.Size = GetDefaultSize (); + this.CapturedWidget = CapturedWidget; + } + #endregion // Constructors + + #region Properties + public bool FillBackground { + get { + return fill_background; + } + set { + fill_background = value; + } + } + + public bool Virtual { + get { + return is_virtual; + } + set { + if (is_virtual == value) + return; + + is_virtual = value; + if (is_virtual) { + CapturedWidget.MouseMove += new MouseEventHandler(HandleMouseMove); + CapturedWidget.MouseUp += new MouseEventHandler(HandleMouseUp); + CapturedWidget.MouseDown += new MouseEventHandler(HandleMouseDown); + CapturedWidget.EnabledChanged += new EventHandler(HandleEnabledChanged); + CapturedWidget.Resize += new EventHandler(HandleResize); + } else { + CapturedWidget.MouseMove -= new MouseEventHandler (HandleMouseMove); + CapturedWidget.MouseUp -= new MouseEventHandler (HandleMouseUp); + CapturedWidget.MouseDown -= new MouseEventHandler (HandleMouseDown); + CapturedWidget.EnabledChanged -= new EventHandler (HandleEnabledChanged); + CapturedWidget.Resize -= new EventHandler (HandleResize); + } + } + } + + public Widget CapturedWidget { + get { + return captured_Widget; + } + set { + captured_Widget = value; + } + } + + #endregion // Properties + + #region Methods + static internal Size GetDefaultSize () { + return new Size (SystemInformation.VerticalScrollBarWidth, SystemInformation.HorizontalScrollBarHeight); + } + + static internal Rectangle GetDefaultRectangle (Widget Parent) + { + Size size = GetDefaultSize (); + return new Rectangle (Parent.ClientSize.Width - size.Width, Parent.ClientSize.Height - size.Height, size.Width, size.Height); + } + + private void HandleResize (object sender, EventArgs e) + { + Widget ctrl = (Widget) sender; + ctrl.Invalidate (last_painted_area); + } + + private void HandleEnabledChanged (object sender, EventArgs e) + { + Widget ctrl = (Widget) sender; + enabled = ctrl.Enabled; + Cursor cursor; + if (enabled) { + cursor = Cursors.SizeNWSE; + } else { + cursor = Cursors.Default; + } + if (is_virtual) { + if (CapturedWidget != null) + CapturedWidget.Cursor = cursor; + } else { + this.Cursor = cursor; + } + ctrl.Invalidate (GetDefaultRectangle (ctrl)); + + } + + // This method needs to be internal, since the captured Widget must be able to call + // it. We can't use events to hook it up, since then the paint ordering won't be correct + internal void HandlePaint (object sender, PaintEventArgs e) + { + if (Visible) { + Widget destination = (Widget) sender; + Graphics gr = e.Graphics; + Rectangle rect = GetDefaultRectangle (destination); + + if (!is_virtual || fill_background) { + gr.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (ThemeEngine.Current.ColorControl), rect); + } + if (enabled) { + WidgetPaint.DrawSizeGrip (gr, BackColor, rect); + } + last_painted_area = rect; + } + } + + private void HandleMouseCaptureChanged (object sender, EventArgs e) + { + Widget ctrl = (Widget) sender; + if (captured && !ctrl.Capture) { + captured = false; + CapturedWidget.Size = new Size (window_w, window_h); + } + } + + internal void HandleMouseDown (object sender, MouseEventArgs e) + { + if (enabled) { + Widget ctrl = (Widget)sender; + if (!GetDefaultRectangle (ctrl).Contains (e.X, e.Y)) { + return; + } + + ctrl.Capture = true; + captured = true; + capture_point = Widget.MousePosition; + + window_w = CapturedWidget.Width; + window_h = CapturedWidget.Height; + } + } + + internal void HandleMouseMove (object sender, MouseEventArgs e) + { + Widget ctrl = (Widget) sender; + Rectangle rect = GetDefaultRectangle (ctrl); + + if (rect.Contains (e.X, e.Y)) { + ctrl.Cursor = Cursors.SizeNWSE; + } else { + ctrl.Cursor = Cursors.Default; + } + + if (captured) { + int delta_x; + int delta_y; + Point current_point; + + current_point = Widget.MousePosition; + + delta_x = current_point.X - capture_point.X; + delta_y = current_point.Y - capture_point.Y; + + Widget parent = CapturedWidget; + Form form_parent = parent as Form; + Size new_size = new Size (window_w + delta_x, window_h + delta_y); + Size max_size = form_parent != null ? form_parent.MaximumSize : Size.Empty; + Size min_size = form_parent != null ? form_parent.MinimumSize : Size.Empty; + + if (new_size.Width > max_size.Width && max_size.Width > 0) + new_size.Width = max_size.Width; + else if (new_size.Width < min_size.Width) + new_size.Width = min_size.Width; + + if (new_size.Height > max_size.Height && max_size.Height > 0) + new_size.Height = max_size.Height; + else if (new_size.Height < min_size.Height) + new_size.Height = min_size.Height; + + if (new_size != parent.Size) { + parent.Size = new_size; + } + } + } + + internal void HandleMouseUp (object sender, MouseEventArgs e) + { + if (captured) { + Widget ctrl = (Widget) sender; + captured = false; + ctrl.Capture = false; + ctrl.Invalidate (last_painted_area); + + if (Parent is ScrollableWidget) { + ((ScrollableWidget)Parent).UpdateSizeGripVisible (); + } + if (hide_pending) { + Hide(); + hide_pending = false; + } + } + } + + + protected override void SetVisibleCore(bool value) { + if (Capture) { + if (value == false) { + hide_pending = true; + } else { + hide_pending = false; + } + return; + } + base.SetVisibleCore (value); + } + + protected override void OnPaint (PaintEventArgs pe) + { + HandlePaint (this, pe); + base.OnPaint (pe); + } + + protected override void OnMouseCaptureChanged (EventArgs e) + { + base.OnMouseCaptureChanged (e); + HandleMouseCaptureChanged (this, e); + } + + protected override void OnEnabledChanged (EventArgs e) + { + base.OnEnabledChanged (e); + HandleEnabledChanged (this, e); + } + + protected override void OnMouseDown (MouseEventArgs e) + { + HandleMouseDown (this, e); + } + + protected override void OnMouseMove(MouseEventArgs e) + { + HandleMouseMove (this, e); + } + + protected override void OnMouseUp (MouseEventArgs e) + { + HandleMouseUp (this, e); + } + #endregion // Methods + } +} + + diff --git a/source/ShiftUI/Widgets/SizeGripStyle.cs b/source/ShiftUI/Widgets/SizeGripStyle.cs new file mode 100644 index 0000000..cdfc82c --- /dev/null +++ b/source/ShiftUI/Widgets/SizeGripStyle.cs @@ -0,0 +1,37 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2004 Novell, Inc. (http://www.novell.com) +// +// Author: +// Ravindra ([email protected]) +// + +// COMPLETE + + +namespace ShiftUI +{ + public enum SizeGripStyle + { + Auto = 0, + Show = 1, + Hide = 2 + } +} diff --git a/source/ShiftUI/Widgets/StatusBar.cs b/source/ShiftUI/Widgets/StatusBar.cs new file mode 100644 index 0000000..2e99958 --- /dev/null +++ b/source/ShiftUI/Widgets/StatusBar.cs @@ -0,0 +1,806 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2004-2005 Novell, Inc. +// +// Authors: +// Jackson Harper ([email protected]) + +// +// TODO: +// - Change cursor when mouse is over grip +// + +using System.Collections; +using System.ComponentModel; +using System.ComponentModel.Design; +using System.Drawing; +using System.Drawing.Text; +using System.Drawing.Imaging; +using System.Runtime.InteropServices; +using System; + +namespace ShiftUI { + [ComVisible (true)] + [ClassInterface (ClassInterfaceType.AutoDispatch)] + [DefaultEvent("PanelClick")] + //[Designer("ShiftUI.Design.StatusBarDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")] + [DefaultProperty("Text")] + public class StatusBar : Widget { + #region Fields + private StatusBarPanelCollection panels; + + private bool show_panels = false; + private bool sizing_grip = true; + + // Stuff for panel Tooltips + private Timer tooltip_timer; + private ToolTip tooltip_window; + private StatusBarPanel tooltip_currently_showing; + #endregion // Fields + + #region Public Constructors + public StatusBar () + { + Dock = DockStyle.Bottom; + this.TabStop = false; + this.SetStyle(Widgetstyles.UserPaint | Widgetstyles.Selectable, false); + + // For displaying/hiding tooltips + MouseMove += new MouseEventHandler (StatusBar_MouseMove); + MouseLeave += new EventHandler (StatusBar_MouseLeave); + } + #endregion // Public Constructors + + #region Public Instance Properties + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public override Color BackColor { + get { return base.BackColor; } + set { base.BackColor = value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public override Image BackgroundImage { + get { return base.BackgroundImage; } + set { base.BackgroundImage = value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public override ImageLayout BackgroundImageLayout { + get { + return base.BackgroundImageLayout; + } + set { + base.BackgroundImageLayout = value; + } + } + + [Localizable(true)] + [DefaultValue(DockStyle.Bottom)] + public override DockStyle Dock { + get { return base.Dock; } + set { base.Dock = value; } + } + + //[EditorBrowsable (EditorBrowsableState.Never)] + protected override bool DoubleBuffered { + get { + return base.DoubleBuffered; + } + set { + base.DoubleBuffered = value; + } + } + + [Localizable(true)] + public override Font Font { + get { return base.Font; } + set { + if (value == Font) + return; + base.Font = value; + UpdateStatusBar (); + } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public override Color ForeColor { + get { return base.ForeColor; } + set { base.ForeColor = value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new ImeMode ImeMode { + get { return base.ImeMode; } + set { base.ImeMode = value; } + } + + [MergableProperty(false)] + [Localizable(true)] + //[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] + public StatusBarPanelCollection Panels { + get { + if (panels == null) + panels = new StatusBarPanelCollection (this); + return panels; + } + } + + [DefaultValue(false)] + public bool ShowPanels { + get { return show_panels; } + set { + if (show_panels == value) + return; + show_panels = value; + UpdateStatusBar (); + } + } + + [DefaultValue(true)] + public bool SizingGrip { + get { return sizing_grip; } + set { + if (sizing_grip == value) + return; + sizing_grip = value; + UpdateStatusBar (); + } + } + + [DefaultValue(false)] + public new bool TabStop { + get { return base.TabStop; } + set { base.TabStop = value; } + } + + [Localizable(true)] + public override string Text { + get { return base.Text; } + set { + if (value == Text) + return; + base.Text = value; + UpdateStatusBar (); + } + + } + + #endregion Public Instance Properties + + #region Protected Instance Properties + protected override CreateParams CreateParams { + get { + return base.CreateParams; + } + } + + protected override ImeMode DefaultImeMode { + get { return ImeMode.Disable; } + } + + protected override Size DefaultSize { + get { return ThemeEngine.Current.StatusBarDefaultSize; } + } + + #endregion // Protected Instance Properties + + #region Public Instance Methods + public override string ToString () { + return base.ToString () + ", Panels.Count: " + Panels.Count + + (Panels.Count > 0 ? ", Panels[0]: " + Panels [0] : String.Empty); + } + + #endregion // Public Instance Methods + + #region Protected Instance Methods + protected override void CreateHandle () + { + base.CreateHandle (); + } + + protected override void Dispose (bool disposing) { + base.Dispose (disposing); + } + + protected virtual void OnDrawItem (StatusBarDrawItemEventArgs sbdievent) { + StatusBarDrawItemEventHandler eh = (StatusBarDrawItemEventHandler)(Events [DrawItemEvent]); + if (eh != null) + eh (this, sbdievent); + } + + protected override void OnHandleCreated (EventArgs e) { + base.OnHandleCreated (e); + CalcPanelSizes (); + } + + protected override void OnHandleDestroyed (EventArgs e) { + base.OnHandleDestroyed (e); + } + + protected override void OnLayout (LayoutEventArgs levent) { + base.OnLayout (levent); + } + + protected override void OnMouseDown (MouseEventArgs e) { + if (panels == null) + return; + + float prev_x = 0; + float gap = ThemeEngine.Current.StatusBarHorzGapWidth; + for (int i = 0; i < panels.Count; i++) { + float x = panels [i].Width + prev_x + (i == panels.Count - 1 ? gap : gap / 2); + if (e.X >= prev_x && e.X <= x) { + OnPanelClick (new StatusBarPanelClickEventArgs (panels [i], + e.Button, e.Clicks, e.X, e.Y)); + break; + } + prev_x = x; + } + + base.OnMouseDown (e); + } + + protected virtual void OnPanelClick (StatusBarPanelClickEventArgs e) { + StatusBarPanelClickEventHandler eh = (StatusBarPanelClickEventHandler)(Events [PanelClickEvent]); + if (eh != null) + eh (this, e); + } + + protected override void OnResize (EventArgs e) + { + base.OnResize (e); + + if (Width <= 0 || Height <= 0) + return; + + UpdateStatusBar (); + } + + protected override void WndProc(ref Message m) { + base.WndProc (ref m); + } + + #endregion // Methods + + + #region Internal Methods + internal void OnDrawItemInternal (StatusBarDrawItemEventArgs e) + { + OnDrawItem (e); + } + + internal void UpdatePanel (StatusBarPanel panel) + { + if (panel.AutoSize == StatusBarPanelAutoSize.Contents) { + UpdateStatusBar (); + return; + } + + UpdateStatusBar (); + } + + internal void UpdatePanelContents (StatusBarPanel panel) + { + if (panel.AutoSize == StatusBarPanelAutoSize.Contents) { + UpdateStatusBar (); + Invalidate (); + return; + } + + Invalidate (new Rectangle (panel.X + 2, 2, panel.Width - 4, bounds.Height - 4)); + } + + void UpdateStatusBar () + { + CalcPanelSizes (); + Refresh (); + } + + internal override void OnPaintInternal (PaintEventArgs pevent) + { + Draw (pevent.Graphics, pevent.ClipRectangle); + } + + private void CalcPanelSizes () + { + if (panels == null || !show_panels) + return; + + if (Width == 0 || Height == 0) + return; + + int border = 2; + int gap = ThemeEngine.Current.StatusBarHorzGapWidth; + int taken = 0; + ArrayList springs = null; + + taken = border; + for (int i = 0; i < panels.Count; i++) { + StatusBarPanel p = panels [i]; + + if (p.AutoSize == StatusBarPanelAutoSize.None) { + taken += p.Width; + taken += gap; + continue; + } + if (p.AutoSize == StatusBarPanelAutoSize.Contents) { + int len = (int)(TextRenderer.MeasureString (p.Text, Font).Width + 0.5F); + if (p.Icon != null) { + len += 21; + } + p.SetWidth (len + 8); + taken += p.Width; + taken += gap; + continue; + } + if (p.AutoSize == StatusBarPanelAutoSize.Spring) { + if (springs == null) + springs = new ArrayList (); + springs.Add (p); + taken += gap; + continue; + } + } + + if (springs != null) { + int spring_total = springs.Count; + int total_width = Width - taken - (SizingGrip ? ThemeEngine.Current.StatusBarSizeGripWidth : 0); + for (int i = 0; i < spring_total; i++) { + StatusBarPanel p = (StatusBarPanel)springs[i]; + int width = total_width / spring_total; + p.SetWidth(width >= p.MinWidth ? width : p.MinWidth); + } + } + + taken = border; + for (int i = 0; i < panels.Count; i++) { + StatusBarPanel p = panels [i]; + p.X = taken; + taken += p.Width + gap; + } + } + + private void Draw (Graphics dc, Rectangle clip) + { + ThemeEngine.Current.DrawStatusBar (dc, clip, this); + + } + #endregion // Internal Methods + + #region Stuff for ToolTips + private void StatusBar_MouseMove (object sender, MouseEventArgs e) + { + if (!show_panels) + return; + + StatusBarPanel p = GetPanelAtPoint (e.Location); + + if (p != tooltip_currently_showing) + MouseLeftPanel (tooltip_currently_showing); + + if (p != null && tooltip_currently_showing == null) + MouseEnteredPanel (p); + } + + private void StatusBar_MouseLeave (object sender, EventArgs e) + { + if (tooltip_currently_showing != null) + MouseLeftPanel (tooltip_currently_showing); + } + + private StatusBarPanel GetPanelAtPoint (Point point) + { + foreach (StatusBarPanel p in Panels) + if (point.X >= p.X && point.X <= (p.X + p.Width)) + return p; + + return null; + } + + private void MouseEnteredPanel (StatusBarPanel item) + { + tooltip_currently_showing = item; + ToolTipTimer.Start (); + } + + private void MouseLeftPanel (StatusBarPanel item) + { + ToolTipTimer.Stop (); + ToolTipWindow.Hide (this); + tooltip_currently_showing = null; + } + + private Timer ToolTipTimer { + get { + if (tooltip_timer == null) { + tooltip_timer = new Timer (); + tooltip_timer.Enabled = false; + tooltip_timer.Interval = 500; + tooltip_timer.Tick += new EventHandler (ToolTipTimer_Tick); + } + + return tooltip_timer; + } + } + + private ToolTip ToolTipWindow { + get { + if (tooltip_window == null) + tooltip_window = new ToolTip (); + + return tooltip_window; + } + } + + private void ToolTipTimer_Tick (object o, EventArgs args) + { + string tooltip = tooltip_currently_showing.ToolTipText; + + if (tooltip != null && tooltip.Length > 0) + ToolTipWindow.Present (this, tooltip); + + ToolTipTimer.Stop (); + } + #endregion + + #region Events + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler BackColorChanged { + add { base.BackColorChanged += value; } + remove { base.BackColorChanged -= value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler BackgroundImageChanged { + add { base.BackgroundImageChanged += value; } + remove { base.BackgroundImageChanged -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler BackgroundImageLayoutChanged + { + add { base.BackgroundImageLayoutChanged += value; } + remove { base.BackgroundImageLayoutChanged -= value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler ForeColorChanged { + add { base.ForeColorChanged += value; } + remove { base.ForeColorChanged -= value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler ImeModeChanged { + add { base.ImeModeChanged += value; } + remove { base.ImeModeChanged -= value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event PaintEventHandler Paint { + add { base.Paint += value; } + remove { base.Paint -= value; } + } + + static object DrawItemEvent = new object (); + static object PanelClickEvent = new object (); + + public event StatusBarDrawItemEventHandler DrawItem { + add { Events.AddHandler (DrawItemEvent, value); } + remove { Events.RemoveHandler (DrawItemEvent, value); } + } + + public event StatusBarPanelClickEventHandler PanelClick { + add { Events.AddHandler (PanelClickEvent, value); } + remove { Events.RemoveHandler (PanelClickEvent, value); } + } + #endregion // Events + + + #region Subclass StatusBarPanelCollection + [ListBindable (false)] + public class StatusBarPanelCollection : IList, ICollection, IEnumerable { + #region Fields + private StatusBar owner; + private ArrayList panels = new ArrayList (); + private int last_index_by_key; + #endregion // Fields + + #region UIA Framework Events + static object UIACollectionChangedEvent = new object (); + + internal event CollectionChangeEventHandler UIACollectionChanged { + add { owner.Events.AddHandler (UIACollectionChangedEvent, value); } + remove { owner.Events.RemoveHandler (UIACollectionChangedEvent, value); } + } + + internal void OnUIACollectionChanged (CollectionChangeEventArgs e) + { + CollectionChangeEventHandler eh + = (CollectionChangeEventHandler) owner.Events [UIACollectionChangedEvent]; + if (eh != null) + eh (owner, e); + } + #endregion + + #region Public Constructors + public StatusBarPanelCollection (StatusBar owner) + { + this.owner = owner; + } + + #endregion // Public Constructors + + #region Private & Internal Methods + private int AddInternal (StatusBarPanel p, bool refresh) { + if (p == null) + throw new ArgumentNullException ("value"); + + p.SetParent (owner); + int res = panels.Add (p); + + if (refresh) { + owner.CalcPanelSizes (); + owner.Refresh (); + } + + // UIA Framework Event: Panel Added + OnUIACollectionChanged (new CollectionChangeEventArgs (CollectionChangeAction.Add, res)); + + return res; + } + + #endregion // Private & Internal Methods + + #region Public Instance Properties + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public int Count { + get { return panels.Count; } + } + + public bool IsReadOnly { + get { return false; } + } + + public virtual StatusBarPanel this [int index] { + get { + if (index < 0 || index >= Count) + throw new ArgumentOutOfRangeException ("index"); + return (StatusBarPanel) panels [index]; + } + set { + if (value == null) + throw new ArgumentNullException ("index"); + if (index < 0 || index >= Count) + throw new ArgumentOutOfRangeException ("index"); + + // UIA Framework Event: Panel Removed + OnUIACollectionChanged (new CollectionChangeEventArgs (CollectionChangeAction.Remove, index)); + + value.SetParent (owner); + + panels [index] = value; + + // UIA Framework Event: Panel Added + OnUIACollectionChanged (new CollectionChangeEventArgs (CollectionChangeAction.Add, index)); + } + } + + public virtual StatusBarPanel this [string key] { + get { + int index = IndexOfKey (key); + if (index >= 0 && index < Count) { + return (StatusBarPanel) panels [index]; + } + return null; + } + } + + #endregion // Public Instance Properties + + #region Public Instance Methods + public virtual int Add (StatusBarPanel value) { + return AddInternal (value, true); + } + + public virtual StatusBarPanel Add (string text) { + StatusBarPanel res = new StatusBarPanel (); + res.Text = text; + Add (res); + return res; + } + + public virtual void AddRange (StatusBarPanel [] panels) { + if (panels == null) + throw new ArgumentNullException ("panels"); + if (panels.Length == 0) + return; + + for (int i = 0; i < panels.Length; i++) + AddInternal (panels [i], false); + owner.Refresh (); + } + + public virtual void Clear () { + panels.Clear (); + + owner.Refresh (); + + // UIA Framework Event: Panel Cleared + OnUIACollectionChanged (new CollectionChangeEventArgs (CollectionChangeAction.Refresh, -1)); + } + + public bool Contains (StatusBarPanel panel) { + return panels.Contains (panel); + } + + public virtual bool ContainsKey (string key) + { + int index = IndexOfKey (key); + return index >= 0 && index < Count; + } + + public IEnumerator GetEnumerator () { + return panels.GetEnumerator (); + } + + public int IndexOf (StatusBarPanel panel) { + return panels.IndexOf (panel); + } + + public virtual int IndexOfKey (string key) + { + if (key == null || key == string.Empty) + return -1; + + if (last_index_by_key >= 0 && last_index_by_key < Count && + String.Compare (((StatusBarPanel)panels [last_index_by_key]).Name, key, StringComparison.OrdinalIgnoreCase) == 0) { + return last_index_by_key; + } + + for (int i = 0; i < Count; i++) { + StatusBarPanel item; + item = panels [i] as StatusBarPanel; + if (item != null && String.Compare (item.Name, key, StringComparison.OrdinalIgnoreCase) == 0) { + last_index_by_key = i; + return i; + } + } + + return -1; + } + + public virtual void Insert (int index, StatusBarPanel value) { + if (value == null) + throw new ArgumentNullException ("value"); + if (index > Count) + throw new ArgumentOutOfRangeException ("index"); + // TODO: InvalidArgumentException for bad AutoSize values + // although it seems impossible to set it to a bad value + value.SetParent (owner); + + panels.Insert(index, value); + owner.Refresh (); + + // UIA Framework Event: Panel Added + OnUIACollectionChanged (new CollectionChangeEventArgs (CollectionChangeAction.Add, index)); + } + + public virtual void Remove (StatusBarPanel value) { + int index = IndexOf (value); + panels.Remove (value); + + // UIA Framework Event: Panel Removed + if (index >= 0) + OnUIACollectionChanged (new CollectionChangeEventArgs (CollectionChangeAction.Remove, index)); + } + + public virtual void RemoveAt (int index) { + panels.RemoveAt (index); + + // UIA Framework Event: Panel Removed + OnUIACollectionChanged (new CollectionChangeEventArgs (CollectionChangeAction.Remove, index)); + } + + public virtual void RemoveByKey (string key) + { + int index = IndexOfKey (key); + if (index >= 0 && index < Count) + RemoveAt (index); + } + + #endregion // Public Instance Methods + + #region IList & ICollection Interfaces + bool ICollection.IsSynchronized { + get { return panels.IsSynchronized; } + } + + object ICollection.SyncRoot { + get { return panels.SyncRoot; } + } + + void ICollection.CopyTo (Array dest, int index) + { + panels.CopyTo (dest, index); + } + + + object IList.this [int index] { + get { return this[index]; } + set { + if (!(value is StatusBarPanel)) + throw new ArgumentException ("Value must be of type StatusBarPanel.", "value"); + + this[index] = (StatusBarPanel)value; + } + } + + int IList.Add (object value) { + if (!(value is StatusBarPanel)) + throw new ArgumentException ("Value must be of type StatusBarPanel.", "value"); + + return AddInternal ((StatusBarPanel)value, true); + } + + bool IList.Contains (object panel) { + return panels.Contains (panel); + } + + int IList.IndexOf (object panel) + { + return panels.IndexOf (panel); + } + + void IList.Insert (int index, object value) + { + if (!(value is StatusBarPanel)) + throw new ArgumentException ("Value must be of type StatusBarPanel.", "value"); + + Insert (index, (StatusBarPanel)value); + } + + bool IList.IsFixedSize { + get { return false; } + } + + void IList.Remove (object value) + { + StatusBarPanel s = value as StatusBarPanel; + Remove (s); + } + #endregion // IList & ICollection Interfaces + } + #endregion // Subclass StatusBarPanelCollection + } + +} + diff --git a/source/ShiftUI/Widgets/StatusBarPanel.cs b/source/ShiftUI/Widgets/StatusBarPanel.cs new file mode 100644 index 0000000..bdbfa64 --- /dev/null +++ b/source/ShiftUI/Widgets/StatusBarPanel.cs @@ -0,0 +1,272 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2004-2005 Novell, Inc. +// +// Authors: +// Jackson Harper ([email protected]) + +// COMPLETE + +using System; +using System.Drawing; +using System.ComponentModel; +using System.ComponentModel.Design; +using System.Runtime.InteropServices; + +namespace ShiftUI { + [ToolboxItem (false)] + [DefaultProperty("Text")] + [DesignTimeVisible(false)] + public class StatusBarPanel : Component, ISupportInitialize { + #region Local Variables + private StatusBar parent; + + private bool initializing; + private string text = String.Empty; + private string tool_tip_text = String.Empty; + + private Icon icon; + private HorizontalAlignment alignment = HorizontalAlignment.Left; + private StatusBarPanelAutoSize auto_size = StatusBarPanelAutoSize.None; + private StatusBarPanelBorderStyle border_style = StatusBarPanelBorderStyle.Sunken; + private StatusBarPanelStyle style = StatusBarPanelStyle.Text; + private int width = 100; + private int min_width = 10; + internal int X; + + private string name; + private object tag; + #endregion // Local Variables + + #region UIA Framework Events + static object UIATextChangedEvent = new object (); + + internal event EventHandler UIATextChanged { + add { Events.AddHandler (UIATextChangedEvent, value); } + remove { Events.RemoveHandler (UIATextChangedEvent, value); } + } + + internal void OnUIATextChanged (EventArgs e) + { + EventHandler eh = (EventHandler) Events [UIATextChangedEvent]; + if (eh != null) + eh (this, e); + } + #endregion + + #region Constructors + public StatusBarPanel () + { + } + #endregion // Constructors + + [DefaultValue(HorizontalAlignment.Left)] + [Localizable(true)] + public HorizontalAlignment Alignment { + get { return alignment; } + set { + alignment = value; + InvalidateContents (); + } + } + + [RefreshProperties (RefreshProperties.All)] + [DefaultValue(StatusBarPanelAutoSize.None)] + public StatusBarPanelAutoSize AutoSize { + get { return auto_size; } + set { + auto_size = value; + Invalidate (); + } + } + + [DefaultValue(StatusBarPanelBorderStyle.Sunken)] + [DispId(-504)] + public StatusBarPanelBorderStyle BorderStyle { + get { return border_style; } + set { + border_style = value; + Invalidate (); + } + } + + [DefaultValue(null)] + [Localizable(true)] + public Icon Icon { + get { return icon; } + set { + icon = value; + InvalidateContents (); + } + } + + [DefaultValue(10)] + [Localizable(true)] + [RefreshProperties(RefreshProperties.All)] + public int MinWidth { + get { + /* + MSDN says that when AutoSize = None then MinWidth is automatically + set to Width, but neither v1.1 nor v2.0 behave that way. + */ + return min_width; + } + set { + if (value < 0) + throw new ArgumentOutOfRangeException ("value"); + + min_width = value; + if (min_width > width) + width = min_width; + + Invalidate (); + } + } + + [Localizable (true)] + public string Name { + get { + if (name == null) + return string.Empty; + return name; + } + set { + name = value; + } + } + + [DefaultValue(100)] + [Localizable(true)] + public int Width { + get { return width; } + set { + if (value < 0) + throw new ArgumentException ("value"); + + if (initializing) + width = value; + else + SetWidth(value); + + Invalidate (); + } + } + + [DefaultValue(StatusBarPanelStyle.Text)] + public StatusBarPanelStyle Style { + get { return style; } + set { + style = value; + Invalidate (); + } + } + + [TypeConverter (typeof (StringConverter))] + [Localizable (false)] + [Bindable (true)] + [DefaultValue (null)] + public object Tag { + get { + return tag; + } + set { + tag = value; + } + } + + [DefaultValue("")] + [Localizable(true)] + public string Text { + get { return text; } + set { + text = value; + InvalidateContents (); + + // UIA Framework Event: Text Changed + OnUIATextChanged (EventArgs.Empty); + } + } + + [DefaultValue("")] + [Localizable(true)] + public string ToolTipText { + get { return tool_tip_text; } + set { tool_tip_text = value; } + } + + [Browsable(false)] + public StatusBar Parent { + get { return parent; } + } + + private void Invalidate () + { + if (parent == null) + return; + parent.UpdatePanel (this); + } + + private void InvalidateContents () + { + if (parent == null) + return; + parent.UpdatePanelContents (this); + } + + internal void SetParent (StatusBar parent) + { + this.parent = parent; + } + + internal void SetWidth (int width) + { + this.width = width; + if (min_width > this.width) + this.width = min_width; + } + + public override string ToString () + { + return "StatusBarPanel: {" + Text +"}"; + } + + protected override void Dispose (bool disposing) + { + } + + public void BeginInit () + { + initializing = true; + } + + public void EndInit () + { + if (!initializing) + return; + + if (min_width > width) + width = min_width; + + initializing = false; + } + } +} + + diff --git a/source/ShiftUI/Widgets/StatusStrip.cs b/source/ShiftUI/Widgets/StatusStrip.cs new file mode 100644 index 0000000..3ac3600 --- /dev/null +++ b/source/ShiftUI/Widgets/StatusStrip.cs @@ -0,0 +1,313 @@ +// +// StatusStrip.cs +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2006 Jonathan Pobst +// +// Authors: +// Jonathan Pobst ([email protected]) +// + +using System; +using System.Drawing; +using System.ComponentModel; +using System.Runtime.InteropServices; + +namespace ShiftUI +{ + [ClassInterface (ClassInterfaceType.AutoDispatch)] + [ComVisible (true)] + public class StatusStrip : ToolStrip + { + private bool sizing_grip; + + public StatusStrip () + { + SetStyle (Widgetstyles.ResizeRedraw, true); + + base.CanOverflow = false; + this.GripStyle = ToolStripGripStyle.Hidden; + base.LayoutStyle = ToolStripLayoutStyle.Table; + base.RenderMode = ToolStripRenderMode.System; + this.sizing_grip = true; + base.Stretch = true; + } + + #region Public Properties + [DefaultValue (DockStyle.Bottom)] + public override DockStyle Dock { + get { return base.Dock; } + set { base.Dock = value; } + } + + [Browsable (false)] + [DefaultValue (false)] + public new bool CanOverflow { + get { return base.CanOverflow; } + set { base.CanOverflow = value; } + } + + [DefaultValue (ToolStripGripStyle.Hidden)] + public new ToolStripGripStyle GripStyle { + get { return base.GripStyle; } + set { base.GripStyle = value; } + } + + [DefaultValue (ToolStripLayoutStyle.Table)] + public new ToolStripLayoutStyle LayoutStyle { + get { return base.LayoutStyle; } + set { base.LayoutStyle = value; } + } + + [Browsable (false)] + public new Padding Padding { + get { return base.Padding; } + set { base.Padding = value; } + } + + [DefaultValue (false)] + public new bool ShowItemToolTips { + get { return base.ShowItemToolTips; } + set { base.ShowItemToolTips = value; } + } + + [Browsable (false)] + public Rectangle SizeGripBounds { + get { return new Rectangle (this.Width - 12, 0, 12, this.Height); } + } + + [DefaultValue (true)] + public bool SizingGrip { + get { return this.sizing_grip; } + set { this.sizing_grip = value; } + } + + [DefaultValue (true)] + public new bool Stretch { + get { return base.Stretch; } + set { base.Stretch = value; } + } + #endregion + + #region Protected Properties + protected override DockStyle DefaultDock { + get { return DockStyle.Bottom; } + } + + protected override Padding DefaultPadding { + get { return new Padding (1, 0, 14, 0); } + } + + protected override bool DefaultShowItemToolTips { + get { return false; } + } + + protected override Size DefaultSize { + get { return new Size (200, 22); } + } + #endregion + + #region Protected Methods + protected override AccessibleObject CreateAccessibilityInstance () + { + return new StatusStripAccessibleObject (); + } + + protected internal override ToolStripItem CreateDefaultItem (string text, Image image, EventHandler onClick) + { + if (text == "-") + return new ToolStripSeparator (); + + return new ToolStripLabel (text, image, false, onClick); + } + + protected override void Dispose (bool disposing) + { + base.Dispose (disposing); + } + + protected override void OnLayout (LayoutEventArgs levent) + { + this.OnSpringTableLayoutCore (); + this.Invalidate (); + } + + protected override void OnPaintBackground (PaintEventArgs e) + { + base.OnPaintBackground (e); + + if (this.sizing_grip) + this.Renderer.DrawStatusStripSizingGrip (new ToolStripRenderEventArgs (e.Graphics, this, Bounds, SystemColors.Control)); + } + + protected virtual void OnSpringTableLayoutCore () + { + if (!this.Created) + return; + + ToolStripItemOverflow[] overflow = new ToolStripItemOverflow[this.Items.Count]; + ToolStripItemPlacement[] placement = new ToolStripItemPlacement[this.Items.Count]; + Size proposedSize = new Size (0, Bounds.Height); + int[] widths = new int[this.Items.Count]; + int total_width = 0; + int toolstrip_width = DisplayRectangle.Width; + int i = 0; + int spring_count = 0; + + foreach (ToolStripItem tsi in this.Items) { + overflow[i] = tsi.Overflow; + widths[i] = tsi.GetPreferredSize (proposedSize).Width + tsi.Margin.Horizontal; + placement[i] = tsi.Overflow == ToolStripItemOverflow.Always ? ToolStripItemPlacement.None : ToolStripItemPlacement.Main; + placement[i] = tsi.Available && tsi.InternalVisible ? placement[i] : ToolStripItemPlacement.None; + total_width += placement[i] == ToolStripItemPlacement.Main ? widths[i] : 0; + if (tsi is ToolStripStatusLabel && (tsi as ToolStripStatusLabel).Spring) + spring_count++; + + i++; + } + + while (total_width > toolstrip_width) { + bool removed_one = false; + + // Start at the right, removing Overflow.AsNeeded first + for (int j = widths.Length - 1; j >= 0; j--) + if (overflow[j] == ToolStripItemOverflow.AsNeeded && placement[j] == ToolStripItemPlacement.Main) { + placement[j] = ToolStripItemPlacement.None; + total_width -= widths[j]; + removed_one = true; + break; + } + + // If we didn't remove any AsNeeded ones, we have to start removing Never ones + // These are not put on the Overflow, they are simply not shown + if (!removed_one) + for (int j = widths.Length - 1; j >= 0; j--) + if (overflow[j] == ToolStripItemOverflow.Never && placement[j] == ToolStripItemPlacement.Main) { + placement[j] = ToolStripItemPlacement.None; + total_width -= widths[j]; + removed_one = true; + break; + } + + // There's nothing left to remove, break or we will loop forever + if (!removed_one) + break; + } + + if (spring_count > 0) { + int per_item = (toolstrip_width - total_width) / spring_count; + i = 0; + + foreach (ToolStripItem tsi in this.Items) { + if (tsi is ToolStripStatusLabel && (tsi as ToolStripStatusLabel).Spring) + widths[i] += per_item; + + i++; + } + } + + i = 0; + Point layout_pointer = new Point (this.DisplayRectangle.Left, this.DisplayRectangle.Top); + int button_height = this.DisplayRectangle.Height; + + // Now we should know where everything goes, so lay everything out + foreach (ToolStripItem tsi in this.Items) { + tsi.SetPlacement (placement[i]); + + if (placement[i] == ToolStripItemPlacement.Main) { + tsi.SetBounds (new Rectangle (layout_pointer.X + tsi.Margin.Left, layout_pointer.Y + tsi.Margin.Top, widths[i] - tsi.Margin.Horizontal, button_height - tsi.Margin.Vertical)); + layout_pointer.X += widths[i]; + } + + i++; + } + + this.SetDisplayedItems (); + } + + protected override void SetDisplayedItems () + { + // Only clean the internal collection, without modifying Owner/Parent on items. + this.displayed_items.ClearInternal (); + + foreach (ToolStripItem tsi in this.Items) + if (tsi.Placement == ToolStripItemPlacement.Main && tsi.Available) { + this.displayed_items.AddNoOwnerOrLayout (tsi); + tsi.Parent = this; + } + } + + protected override void WndProc (ref Message m) + { + switch ((Msg)m.Msg) { + // If the mouse is over the size grip, change the cursor + case Msg.WM_MOUSEMOVE: { + if (FromParamToMouseButtons ((int) m.WParam.ToInt32()) == MouseButtons.None) { + Point p = new Point (LowOrder ((int) m.LParam.ToInt32 ()), HighOrder ((int) m.LParam.ToInt32 ())); + + if (this.SizingGrip && this.SizeGripBounds.Contains (p)) { + this.Cursor = Cursors.SizeNWSE; + return; + } else + this.Cursor = Cursors.Default; + } + + break; + } + // If the left mouse button is pushed over the size grip, + // send the WM a message to begin a window resize operation + case Msg.WM_LBUTTONDOWN: { + Point p = new Point (LowOrder ((int)m.LParam.ToInt32 ()), HighOrder ((int)m.LParam.ToInt32 ())); + Form form = FindForm (); + + if (this.SizingGrip && this.SizeGripBounds.Contains (p)) { + // For top level forms it's not enoug to send a NCLBUTTONDOWN message, so + // we make a direct call to our XplatUI engine. + if (!form.IsMdiChild) + XplatUI.BeginMoveResize (form.Handle); + + XplatUI.SendMessage (form.Handle, Msg.WM_NCLBUTTONDOWN, (IntPtr) HitTest.HTBOTTOMRIGHT, IntPtr.Zero); + return; + } + + break; + } + } + + base.WndProc (ref m); + } + #endregion + + #region Public Events + [Browsable (false)] + public new event EventHandler PaddingChanged { + add { base.PaddingChanged += value; } + remove { base.PaddingChanged -= value; } + } + #endregion + + #region StatusStripAccessibleObject + private class StatusStripAccessibleObject : AccessibleObject + { + } + #endregion + } +} diff --git a/source/ShiftUI/Widgets/TabControl.cs b/source/ShiftUI/Widgets/TabControl.cs new file mode 100644 index 0000000..0404510 --- /dev/null +++ b/source/ShiftUI/Widgets/TabControl.cs @@ -0,0 +1,2008 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2004-2005 Novell, Inc. +// +// Authors: +// Jackson Harper ([email protected]) + + +using System; +using System.Collections; +using System.ComponentModel; +using System.ComponentModel.Design; +using System.Drawing; +using System.Runtime.InteropServices; +using ShiftUI.Theming; +using ShiftUI.VisualStyles; + +namespace ShiftUI { + [ComVisibleAttribute (true)] + [ClassInterfaceAttribute (ClassInterfaceType.AutoDispatch)] + [DefaultEvent("SelectedIndexChanged")] + [DefaultProperty("TabPages")] + //[Designer("ShiftUI.Design.TabControlDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")] + [ToolboxWidget] + public class TabWidget : Widget { + #region Fields + private int selected_index = -1; + private TabAlignment alignment; + private TabAppearance appearance; + private TabDrawMode draw_mode; + private bool multiline; + private ImageList image_list; + private Size item_size = Size.Empty; + private bool item_size_manual; + private Point padding; + private int row_count = 0; + private bool hottrack; + private TabPageCollection tab_pages; + private bool show_tool_tips; + private TabSizeMode size_mode; + private bool show_slider = false; + private PushButtonState right_slider_state = PushButtonState.Normal; + private PushButtonState left_slider_state = PushButtonState.Normal; + private int slider_pos = 0; + TabPage entered_tab_page; + bool mouse_down_on_a_tab_page; + ToolTip tooltip; + ToolTip.TipState tooltip_state = ToolTip.TipState.Down; + Timer tooltip_timer; + + private bool rightToLeftLayout; + #endregion // Fields + + #region UIA Framework Events + static object UIAHorizontallyScrollableChangedEvent = new object (); + + internal event EventHandler UIAHorizontallyScrollableChanged { + add { Events.AddHandler (UIAHorizontallyScrollableChangedEvent, value); } + remove { Events.RemoveHandler (UIAHorizontallyScrollableChangedEvent, value); } + } + + internal void OnUIAHorizontallyScrollableChanged (EventArgs e) + { + EventHandler eh + = (EventHandler) Events [UIAHorizontallyScrollableChangedEvent]; + if (eh != null) + eh (this, e); + } + + static object UIAHorizontallyScrolledEvent = new object (); + + internal event EventHandler UIAHorizontallyScrolled { + add { Events.AddHandler (UIAHorizontallyScrolledEvent, value); } + remove { Events.RemoveHandler (UIAHorizontallyScrolledEvent, value); } + } + + internal void OnUIAHorizontallyScrolled (EventArgs e) + { + EventHandler eh + = (EventHandler) Events [UIAHorizontallyScrolledEvent]; + if (eh != null) + eh (this, e); + } + #endregion + + #region UIA Framework Property + internal double UIAHorizontalViewSize { + get { return LeftScrollButtonArea.Left * 100 / TabPages [TabCount - 1].TabBounds.Right; } + } + #endregion + + #region Public Constructors + public TabWidget () + { + tab_pages = new TabPageCollection (this); + SetStyle (Widgetstyles.UserPaint, false); + padding = ThemeEngine.Current.TabControlDefaultPadding; + + MouseDown += new MouseEventHandler (MouseDownHandler); + MouseLeave += new EventHandler (OnMouseLeave); + MouseMove += new MouseEventHandler (OnMouseMove); + MouseUp += new MouseEventHandler (MouseUpHandler); + SizeChanged += new EventHandler (SizeChangedHandler); + } + + #endregion // Public Constructors + + #region Public Instance Properties + [DefaultValue(TabAlignment.Top)] + [Localizable(true)] + [RefreshProperties(RefreshProperties.All)] + public TabAlignment Alignment { + get { return alignment; } + set { + if (alignment == value) + return; + alignment = value; + if (alignment == TabAlignment.Left || alignment == TabAlignment.Right) + multiline = true; + Redraw (); + } + } + + [DefaultValue(TabAppearance.Normal)] + [Localizable(true)] + public TabAppearance Appearance { + get { return appearance; } + set { + if (appearance == value) + return; + appearance = value; + Redraw (); + } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public override Color BackColor { + get { return ThemeEngine.Current.ColorControl; } + set { /* nothing happens on set on MS */ } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public override Image BackgroundImage { + get { return base.BackgroundImage; } + set { base.BackgroundImage = value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public override ImageLayout BackgroundImageLayout { + get { return base.BackgroundImageLayout; } + set { base.BackgroundImageLayout = value; } + } + + public override Rectangle DisplayRectangle { + get { + return ThemeEngine.Current.TabControlGetDisplayRectangle (this); + } + } + + //[EditorBrowsable (EditorBrowsableState.Never)] + protected override bool DoubleBuffered { + get { return base.DoubleBuffered; } + set { base.DoubleBuffered = value; } + } + + [DefaultValue(TabDrawMode.Normal)] + public TabDrawMode DrawMode { + get { return draw_mode; } + set { + if (draw_mode == value) + return; + draw_mode = value; + Redraw (); + } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public override Color ForeColor { + get { return base.ForeColor; } + set { base.ForeColor = value; } + } + + [DefaultValue(false)] + public bool HotTrack { + get { return hottrack; } + set { + if (hottrack == value) + return; + hottrack = value; + Redraw (); + } + } + + [RefreshProperties (RefreshProperties.Repaint)] + [DefaultValue(null)] + public ImageList ImageList { + get { return image_list; } + set { + image_list = value; + Redraw (); + } + } + + [Localizable(true)] + public Size ItemSize { + get { + if (item_size_manual) + return item_size; + + if (!IsHandleCreated) + return Size.Empty; + + Size size = item_size; + if (SizeMode != TabSizeMode.Fixed) { + size.Width += padding.X * 2; + size.Height += padding.Y * 2; + } + + if (tab_pages.Count == 0) + size.Width = 0; + + return size; + } + set { + if (value.Height < 0 || value.Width < 0) + throw new ArgumentException ("'" + value + "' is not a valid value for 'ItemSize'."); + item_size = value; + item_size_manual = true; + Redraw (); + } + } + + [DefaultValue(false)] + public bool Multiline { + get { return multiline; } + set { + if (multiline == value) + return; + multiline = value; + if (!multiline && alignment == TabAlignment.Left || alignment == TabAlignment.Right) + alignment = TabAlignment.Top; + Redraw (); + } + } + + [Localizable(true)] + public + new + Point Padding { + get { return padding; } + set { + if (value.X < 0 || value.Y < 0) + throw new ArgumentException ("'" + value + "' is not a valid value for 'Padding'."); + if (padding == value) + return; + padding = value; + Redraw (); + } + + } + + [MonoTODO ("RTL not supported")] + [Localizable (true)] + [DefaultValue (false)] + public virtual bool RightToLeftLayout { + get { return this.rightToLeftLayout; } + set { + if (value != this.rightToLeftLayout) { + this.rightToLeftLayout = value; + this.OnRightToLeftLayoutChanged (EventArgs.Empty); + } + } + } + + [Browsable(false)] + //[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public int RowCount { + get { return row_count; } + } + + [DefaultValue(-1)] + [Browsable(false)] + public int SelectedIndex { + get { return selected_index; } + set { + + if (value < -1) { + throw new ArgumentOutOfRangeException ("SelectedIndex", "Value of '" + value + "' is valid for 'SelectedIndex'. " + + "'SelectedIndex' must be greater than or equal to -1."); + } + if (!this.IsHandleCreated) { + if (selected_index != value) { + selected_index = value; + } + return; + } + + if (value >= TabCount) { + if (value != selected_index) + OnSelectedIndexChanged (EventArgs.Empty); + return; + } + + if (value == selected_index) { + if (selected_index > -1) + Invalidate(GetTabRect (selected_index)); + return; + } + + TabControlCancelEventArgs ret = new TabControlCancelEventArgs (SelectedTab, selected_index, false, TabControlAction.Deselecting); + OnDeselecting (ret); + if (ret.Cancel) + return; + + Focus (); + int old_index = selected_index; + int new_index = value; + + selected_index = new_index; + + ret = new TabControlCancelEventArgs (SelectedTab, selected_index, false, TabControlAction.Selecting); + OnSelecting (ret); + if (ret.Cancel) { + selected_index = old_index; + return; + } + + SuspendLayout (); + + Rectangle invalid = Rectangle.Empty; + bool refresh = false; + + if (new_index != -1 && show_slider && new_index < slider_pos) { + slider_pos = new_index; + refresh = true; + } + + if (new_index != -1) { + int le = TabPages[new_index].TabBounds.Right; + int re = LeftScrollButtonArea.Left; + if (show_slider && le > re) { + int i = 0; + for (i = 0; i < new_index; i++) { + if (TabPages [i].TabBounds.Left < 0) // tab scrolled off the visible area, ignore + continue; + + if (TabPages [new_index].TabBounds.Right - TabPages[i].TabBounds.Right < re) { + i++; + break; + } + } + slider_pos = i; + refresh = true; + } + } + + if (old_index != -1 && new_index != -1) { + if (!refresh) + invalid = GetTabRect (old_index); + ((TabPage) Widgets[old_index]).SetVisible (false); + } + + TabPage selected = null; + + if (new_index != -1) { + selected = (TabPage) Widgets[new_index]; + invalid = Rectangle.Union (invalid, GetTabRect (new_index)); + selected.SetVisible (true); + } + + OnSelectedIndexChanged (EventArgs.Empty); + + ResumeLayout (); + + if (refresh) { + SizeTabs (); + Refresh (); + } else if (new_index != -1 && selected.Row != BottomRow) { + DropRow (TabPages[new_index].Row); + // calculating what to invalidate here seems to be slower then just + // refreshing the whole thing + SizeTabs (); + Refresh (); + } else { + SizeTabs (); + // The lines are drawn on the edges of the tabs so the invalid area should + // needs to include the extra pixels of line width (but should not + // overflow the control bounds). + if (appearance == TabAppearance.Normal) { + invalid.Inflate (6, 4); + invalid.Intersect (ClientRectangle); + } + Invalidate (invalid); + } + } + } + + [Browsable(false)] + //[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public TabPage SelectedTab { + get { + if (selected_index == -1) + return null; + return tab_pages [selected_index]; + } + set { + int index = IndexForTabPage (value); + if (index == selected_index) + return; + SelectedIndex = index; + } + } + + [DefaultValue(false)] + [Localizable(true)] + public bool ShowToolTips { + get { return show_tool_tips; } + set { + if (show_tool_tips == value) + return; + show_tool_tips = value; + } + } + + [DefaultValue(TabSizeMode.Normal)] + [RefreshProperties(RefreshProperties.Repaint)] + public TabSizeMode SizeMode { + get { return size_mode; } + set { + if (size_mode == value) + return; + size_mode = value; + Redraw (); + } + } + + [Browsable(false)] + //[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public int TabCount { + get { + return tab_pages.Count; + } + } + + //[Editor ("ShiftUI.Design.TabPageCollectionEditor, " + Consts.AssemblySystem_Design, typeof (System.Drawing.Design.UITypeEditor))] + //[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + [MergableProperty(false)] + public TabPageCollection TabPages { + get { return tab_pages; } + } + + [Browsable(false)] + [Bindable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public override string Text { + get { return base.Text; } + set { base.Text = value; } + } + #endregion // Public Instance Properties + + #region Internal Properties + internal bool ShowSlider { + get { return show_slider; } + set { + show_slider = value; + + // UIA Framework Event: HorizontallyScrollable Changed + OnUIAHorizontallyScrollableChanged (EventArgs.Empty); + } + } + + internal int SliderPos { + get { return slider_pos; } + } + + internal PushButtonState RightSliderState { + get { return right_slider_state; } + private set { + if (right_slider_state == value) + return; + PushButtonState old_value = right_slider_state; + right_slider_state = value; + if (NeedsToInvalidateScrollButton (old_value, value)) + Invalidate (RightScrollButtonArea); + } + } + + internal PushButtonState LeftSliderState { + get { return left_slider_state; } + set { + if (left_slider_state == value) + return; + PushButtonState old_value = left_slider_state; + left_slider_state = value; + if (NeedsToInvalidateScrollButton (old_value, value)) + Invalidate (LeftScrollButtonArea); + } + } + + bool NeedsToInvalidateScrollButton (PushButtonState oldState, PushButtonState newState) + { + if ((oldState == PushButtonState.Hot && newState == PushButtonState.Normal) || + (oldState == PushButtonState.Normal && newState == PushButtonState.Hot)) + return HasHotElementStyles; + return true; + } + + internal TabPage EnteredTabPage { + get { return entered_tab_page; } + private set { + if (entered_tab_page == value) + return; + if (HasHotElementStyles) { + Region area_to_invalidate = new Region (); + area_to_invalidate.MakeEmpty (); + if (entered_tab_page != null) + area_to_invalidate.Union (entered_tab_page.TabBounds); + entered_tab_page = value; + if (entered_tab_page != null) + area_to_invalidate.Union (entered_tab_page.TabBounds); + Invalidate (area_to_invalidate); + area_to_invalidate.Dispose (); + } else + entered_tab_page = value; + + if (value == null) + CloseToolTip (); + else + SetToolTip (GetToolTipText (value)); + } + } + #endregion // Internal Properties + + #region Protected Instance Properties + protected override CreateParams CreateParams { + get { + CreateParams c = base.CreateParams; + return c; + } + } + + protected override Size DefaultSize { + get { return new Size (200, 100); } + } + + #endregion // Protected Instance Properties + + #region Public Instance Methods + public Rectangle GetTabRect (int index) + { + TabPage page = GetTab (index); + return page.TabBounds; + } + + public Widget GetControl (int index) + { + return GetTab (index); + } + + public void SelectTab (TabPage tabPage) + { + if (tabPage == null) + throw new ArgumentNullException ("tabPage"); + + SelectTab (this.tab_pages [tabPage]); + } + + public void SelectTab (string tabPageName) + { + if (tabPageName == null) + throw new ArgumentNullException ("tabPageName"); + + SelectTab (this.tab_pages [tabPageName]); + } + + public void SelectTab (int index) + { + if (index < 0 || index > this.tab_pages.Count - 1) + throw new ArgumentOutOfRangeException ("index"); + + SelectedIndex = index; + } + + public void DeselectTab (TabPage tabPage) + { + if (tabPage == null) + throw new ArgumentNullException ("tabPage"); + + DeselectTab (this.tab_pages [tabPage]); + } + + public void DeselectTab (string tabPageName) + { + if (tabPageName == null) + throw new ArgumentNullException ("tabPageName"); + + DeselectTab (this.tab_pages [tabPageName]); + } + + public void DeselectTab (int index) + { + if (index == SelectedIndex) { + if (index >= 0 && index < this.tab_pages.Count - 1) + SelectedIndex = ++index; + else + SelectedIndex = 0; + } + } + + + public override string ToString () + { + string res = String.Concat (base.ToString (), + ", TabPages.Count: ", + TabCount); + if (TabCount > 0) + res = String.Concat (res, ", TabPages[0]: ", + TabPages [0]); + return res; + } + + #endregion // Public Instance Methods + + #region Protected Instance Methods + + #region Handles + protected override Widget.WidgetCollection CreateWidgetsInstance () + { + return new TabWidget.WidgetCollection (this); + } + + protected override void CreateHandle () + { + base.CreateHandle (); + selected_index = (selected_index >= TabCount ? (TabCount > 0 ? 0 : -1) : selected_index); + + if (TabCount > 0) { + if (selected_index > -1) + this.SelectedTab.SetVisible(true); + else + tab_pages[0].SetVisible(true); + } + ResizeTabPages (); + } + + protected override void OnHandleCreated (EventArgs e) + { + base.OnHandleCreated (e); + } + + protected override void OnHandleDestroyed (EventArgs e) + { + base.OnHandleDestroyed (e); + } + + protected override void Dispose (bool disposing) + { + CloseToolTip (); + base.Dispose (disposing); + } + + #endregion + + #region Events + protected virtual void OnDrawItem (DrawItemEventArgs e) + { + if (DrawMode != TabDrawMode.OwnerDrawFixed) + return; + + DrawItemEventHandler eh = (DrawItemEventHandler)(Events [DrawItemEvent]); + if (eh != null) + eh (this, e); + } + + internal void OnDrawItemInternal (DrawItemEventArgs e) + { + OnDrawItem (e); + } + + protected override void OnFontChanged (EventArgs e) + { + base.OnFontChanged (e); + ResizeTabPages (); + } + + protected override void OnResize (EventArgs e) + { + base.OnResize (e); + } + + protected override void OnStyleChanged (EventArgs e) + { + base.OnStyleChanged (e); + } + + protected virtual void OnSelectedIndexChanged (EventArgs e) + { + EventHandler eh = (EventHandler) (Events[SelectedIndexChangedEvent]); + if (eh != null) + eh (this, e); + } + + internal override void OnPaintInternal (PaintEventArgs pe) + { + if (GetStyle (Widgetstyles.UserPaint)) + return; + + Draw (pe.Graphics, pe.ClipRectangle); + pe.Handled = true; + } + + protected override void OnEnter (EventArgs e) + { + base.OnEnter (e); + if (SelectedTab != null) + SelectedTab.FireEnter (); + } + + protected override void OnLeave (EventArgs e) + { + if (SelectedTab != null) + SelectedTab.FireLeave (); + base.OnLeave (e); + } + + //[EditorBrowsable (EditorBrowsableState.Advanced)] + protected virtual void OnRightToLeftLayoutChanged (EventArgs e) + { + EventHandler eh = (EventHandler) (Events[RightToLeftLayoutChangedEvent]); + if (eh != null) + eh (this, e); + } + + //[EditorBrowsable (EditorBrowsableState.Never)] + protected override void ScaleCore (float dx, float dy) + { + base.ScaleCore (dx, dy); + } + + protected virtual void OnDeselecting (TabControlCancelEventArgs e) + { + TabControlCancelEventHandler eh = (TabControlCancelEventHandler) (Events[DeselectingEvent]); + if (eh != null) + eh (this, e); + + if (!e.Cancel) + OnDeselected (new TabControlEventArgs (SelectedTab, selected_index, TabControlAction.Deselected)); + } + + protected virtual void OnDeselected (TabControlEventArgs e) + { + TabControlEventHandler eh = (TabControlEventHandler) (Events[DeselectedEvent]); + if (eh != null) + eh (this, e); + + if (this.SelectedTab != null) + this.SelectedTab.FireLeave (); + } + + protected virtual void OnSelecting (TabControlCancelEventArgs e) + { + TabControlCancelEventHandler eh = (TabControlCancelEventHandler) (Events[SelectingEvent]); + if (eh != null) + eh (this, e); + + if (!e.Cancel) + OnSelected (new TabControlEventArgs (SelectedTab, selected_index, TabControlAction.Selected)); + } + + protected virtual void OnSelected (TabControlEventArgs e) + { + TabControlEventHandler eh = (TabControlEventHandler) (Events[SelectedEvent]); + if (eh != null) + eh (this, e); + + if (this.SelectedTab != null) + this.SelectedTab.FireEnter (); + } + + #endregion + + #region Keys + protected override bool ProcessKeyPreview (ref Message m) + { + return base.ProcessKeyPreview (ref m); + } + + protected override void OnKeyDown (KeyEventArgs ke) + { + base.OnKeyDown (ke); + if (ke.Handled) + return; + + if (ke.KeyCode == Keys.Tab && (ke.KeyData & Keys.Widget) != 0) { + if ((ke.KeyData & Keys.Shift) == 0) + SelectedIndex = (SelectedIndex + 1) % TabCount; + else + SelectedIndex = (SelectedIndex + TabCount - 1) % TabCount; + ke.Handled = true; + } else if (ke.KeyCode == Keys.PageUp && (ke.KeyData & Keys.Widget) != 0) { + SelectedIndex = (SelectedIndex + TabCount - 1) % TabCount; + ke.Handled = true; + } else if (ke.KeyCode == Keys.PageDown && (ke.KeyData & Keys.Widget) != 0) { + SelectedIndex = (SelectedIndex + 1) % TabCount; + ke.Handled = true; + } else if (ke.KeyCode == Keys.Home) { + SelectedIndex = 0; + ke.Handled = true; + } else if (ke.KeyCode == Keys.End) { + SelectedIndex = TabCount - 1; + ke.Handled = true; + } else if (NavigateTabs (ke.KeyCode)) + ke.Handled = true; + } + + protected override bool IsInputKey (Keys keyData) + { + switch (keyData & Keys.KeyCode) { + case Keys.Home: + case Keys.End: + case Keys.Left: + case Keys.Right: + case Keys.Up: + case Keys.Down: + return true; + } + return base.IsInputKey (keyData); + } + + private bool NavigateTabs (Keys keycode) + { + bool move_left = false; + bool move_right = false; + + if (alignment == TabAlignment.Bottom || alignment == TabAlignment.Top) { + if (keycode == Keys.Left) + move_left = true; + else if (keycode == Keys.Right) + move_right = true; + } else { + if (keycode == Keys.Up) + move_left = true; + else if (keycode == Keys.Down) + move_right = true; + } + + if (move_left) { + if (SelectedIndex > 0) { + SelectedIndex--; + return true; + } + } + + if (move_right) { + if (SelectedIndex < TabCount - 1) { + SelectedIndex++; + return true; + } + } + + return false; + } + #endregion + + #region Pages Collection + protected void RemoveAll () + { + Widgets.Clear (); + } + + protected virtual object [] GetItems () + { + TabPage [] pages = new TabPage [Widgets.Count]; + Widgets.CopyTo (pages, 0); + return pages; + } + + protected virtual object [] GetItems (Type baseType) + { + object[] pages = (object[])Array.CreateInstance (baseType, Widgets.Count); + Widgets.CopyTo (pages, 0); + return pages; + } + #endregion + + protected void UpdateTabSelection (bool updateFocus) + { + ResizeTabPages (); + } + + protected string GetToolTipText (object item) + { + TabPage page = (TabPage) item; + return page.ToolTipText; + } + + protected override void WndProc (ref Message m) + { + switch ((Msg)m.Msg) { + case Msg.WM_SETFOCUS: + if (selected_index != -1) + Invalidate(GetTabRect(selected_index)); + base.WndProc (ref m); + break; + case Msg.WM_KILLFOCUS: + if (selected_index != -1) + Invalidate(GetTabRect(selected_index)); + base.WndProc (ref m); + break; + default: + base.WndProc (ref m); + break; + } + } + + #endregion // Protected Instance Methods + + #region Internal & Private Methods + private bool CanScrollRight { + get { + return (slider_pos < TabCount - 1); + } + } + + private bool CanScrollLeft { + get { return slider_pos > 0; } + } + + private void MouseDownHandler (object sender, MouseEventArgs e) + { + if ((e.Button & MouseButtons.Left) == 0) + return; + + if (ShowSlider) { + Rectangle right = RightScrollButtonArea; + Rectangle left = LeftScrollButtonArea; + if (right.Contains (e.X, e.Y)) { + right_slider_state = PushButtonState.Pressed; + if (CanScrollRight) { + slider_pos++; + SizeTabs (); + + // UIA Framework Event: Horizontally Scrolled + OnUIAHorizontallyScrolled (EventArgs.Empty); + + switch (this.Alignment) { + case TabAlignment.Top: + Invalidate (new Rectangle (0, 0, Width, ItemSize.Height)); + break; + case TabAlignment.Bottom: + Invalidate (new Rectangle (0, DisplayRectangle.Bottom, Width, Height - DisplayRectangle.Bottom)); + break; + case TabAlignment.Left: + Invalidate (new Rectangle (0, 0, DisplayRectangle.Left, Height)); + break; + case TabAlignment.Right: + Invalidate (new Rectangle (DisplayRectangle.Right, 0, Width - DisplayRectangle.Right, Height)); + break; + } + + } else { + Invalidate (right); + } + return; + } else if (left.Contains (e.X, e.Y)) { + left_slider_state = PushButtonState.Pressed; + if (CanScrollLeft) { + slider_pos--; + SizeTabs (); + + // UIA Framework Event: Horizontally Scrolled + OnUIAHorizontallyScrolled (EventArgs.Empty); + + switch (this.Alignment) { + case TabAlignment.Top: + Invalidate (new Rectangle (0, 0, Width, ItemSize.Height)); + break; + case TabAlignment.Bottom: + Invalidate (new Rectangle (0, DisplayRectangle.Bottom, Width, Height - DisplayRectangle.Bottom)); + break; + case TabAlignment.Left: + Invalidate (new Rectangle (0, 0, DisplayRectangle.Left, Height)); + break; + case TabAlignment.Right: + Invalidate (new Rectangle (DisplayRectangle.Right, 0, Width - DisplayRectangle.Right, Height)); + break; + } + } else { + Invalidate (left); + } + return; + } + } + + int count = Widgets.Count; + for (int i = SliderPos; i < count; i++) { + if (!GetTabRect (i).Contains (e.X, e.Y)) + continue; + SelectedIndex = i; + mouse_down_on_a_tab_page = true; + break; + } + } + + private void MouseUpHandler (object sender, MouseEventArgs e) + { + mouse_down_on_a_tab_page = false; + if (ShowSlider && (left_slider_state == PushButtonState.Pressed || right_slider_state == PushButtonState.Pressed)) { + Rectangle invalid; + if (left_slider_state == PushButtonState.Pressed) { + invalid = LeftScrollButtonArea; + left_slider_state = GetScrollButtonState (invalid, e.Location); + } else { + invalid = RightScrollButtonArea; + right_slider_state = GetScrollButtonState (invalid, e.Location); + } + Invalidate (invalid); + } + } + + bool HasHotElementStyles { + get { + return ThemeElements.CurrentTheme.TabWidgetPainter.HasHotElementStyles (this); + } + } + + Rectangle LeftScrollButtonArea { + get { + return ThemeElements.CurrentTheme.TabWidgetPainter.GetLeftScrollRect (this); + } + } + + Rectangle RightScrollButtonArea { + get { + return ThemeElements.CurrentTheme.TabWidgetPainter.GetRightScrollRect (this); + } + } + + static PushButtonState GetScrollButtonState (Rectangle scrollButtonArea, Point cursorLocation) + { + return scrollButtonArea.Contains (cursorLocation) ? PushButtonState.Hot : PushButtonState.Normal; + } + + private void SizeChangedHandler (object sender, EventArgs e) + { + Redraw (); + } + + internal int IndexForTabPage (TabPage page) + { + for (int i = 0; i < tab_pages.Count; i++) { + if (page == tab_pages [i]) + return i; + } + return -1; + } + + private void ResizeTabPages () + { + CalcTabRows (); + SizeTabs (); + Rectangle r = DisplayRectangle; + foreach (TabPage page in Widgets) { + page.Bounds = r; + } + } + + private int MinimumTabWidth { + get { + return ThemeEngine.Current.TabControlMinimumTabWidth; + } + } + + private Size TabSpacing { + get { + return ThemeEngine.Current.TabControlGetSpacing (this); + } + } + + private void CalcTabRows () + { + switch (Alignment) { + case TabAlignment.Right: + case TabAlignment.Left: + CalcTabRows (Height); + break; + default: + CalcTabRows (Width); + break; + } + } + + private void CalcTabRows (int row_width) + { + int xpos = 0; + int ypos = 0; + Size spacing = TabSpacing; + + if (TabPages.Count > 0) + row_count = 1; + show_slider = false; + + CalculateItemSize (); + + for (int i = 0; i < TabPages.Count; i++) { + TabPage page = TabPages [i]; + int aux = 0; + SizeTab (page, i, row_width, ref xpos, ref ypos, spacing, 0, ref aux, true); + } + + if (SelectedIndex != -1 && TabPages.Count > SelectedIndex && TabPages[SelectedIndex].Row != BottomRow) + DropRow (TabPages [SelectedIndex].Row); + } + + // ItemSize per-se is used mostly only to retrieve the Height, + // since the actual Width of the tabs is computed individually, + // except when SizeMode is TabSizeMode.Fixed, where Width is used as well. + private void CalculateItemSize () + { + if (item_size_manual) + return; + + SizeF size; + if (tab_pages.Count > 0) { + // .Net uses the first tab page if available. + size = TextRenderer.MeasureString (tab_pages [0].Text, Font); + + } else { + size = TextRenderer.MeasureString ("a", Font); + size.Width = 0; + } + + if (size_mode == TabSizeMode.Fixed) + size.Width = 96; + if (size.Width < MinimumTabWidth) + size.Width = MinimumTabWidth; + if (image_list != null && image_list.ImageSize.Height > size.Height) + size.Height = image_list.ImageSize.Height; + + item_size = size.ToSize (); + } + + private int BottomRow { + get { return 1; } + } + + private int Direction + { + get { + return 1; + } + } + + private void DropRow (int row) + { + if (Appearance != TabAppearance.Normal) + return; + + int bottom = BottomRow; + int direction = Direction; + + foreach (TabPage page in TabPages) { + if (page.Row == row) { + page.Row = bottom; + } else if (direction == 1 && page.Row < row) { + page.Row += direction; + } else if (direction == -1 && page.Row > row) { + page.Row += direction; + } + } + } + + private int CalcYPos () + { + if (Alignment == TabAlignment.Bottom || Alignment == TabAlignment.Left) + return ThemeEngine.Current.TabControlGetPanelRect (this).Bottom; + + if (Appearance == TabAppearance.Normal) + return this.ClientRectangle.Y + ThemeEngine.Current.TabWidgetselectedDelta.Y; + + return this.ClientRectangle.Y; + + } + + private int CalcXPos () + { + if (Alignment == TabAlignment.Right) + return ThemeEngine.Current.TabControlGetPanelRect (this).Right; + + if (Appearance == TabAppearance.Normal) + return this.ClientRectangle.X + ThemeEngine.Current.TabWidgetselectedDelta.X; + + return this.ClientRectangle.X; + } + + private void SizeTabs () + { + switch (Alignment) { + case TabAlignment.Right: + case TabAlignment.Left: + SizeTabs (Height, true); + break; + default: + SizeTabs (Width, false); + break; + } + } + + private void SizeTabs (int row_width, bool vertical) + { + int ypos = 0; + int xpos = 0; + int prev_row = 1; + Size spacing = TabSpacing; + int begin_prev = 0; + + if (TabPages.Count == 0) + return; + + prev_row = TabPages [0].Row; + + // Reset the slider position if the slider isn't needed + // anymore (ie window size was increased so all tabs are visible) + if (!show_slider) + slider_pos = 0; + else { + // set X = -1 for marking tabs that are not visible due to scrolling + for (int i = 0; i < slider_pos; i++) { + TabPage page = TabPages[i]; + Rectangle x = page.TabBounds; + x.X = -1; + page.TabBounds = x; + } + } + + for (int i = slider_pos; i < TabPages.Count; i++) { + TabPage page = TabPages[i]; + SizeTab (page, i, row_width, ref xpos, ref ypos, spacing, prev_row, ref begin_prev, false); + prev_row = page.Row; + } + + if (SizeMode == TabSizeMode.FillToRight && !ShowSlider) { + FillRow (begin_prev, TabPages.Count - 1, + ((row_width - TabPages [TabPages.Count - 1].TabBounds.Right) / (TabPages.Count - begin_prev)), + spacing, vertical); + } + + if (SelectedIndex != -1) { + ExpandSelected (TabPages [SelectedIndex], 0, row_width - 1); + } + } + + private void SizeTab (TabPage page, int i, int row_width, ref int xpos, ref int ypos, + Size spacing, int prev_row, ref int begin_prev, bool widthOnly) + { + int width, height = 0; + + if (SizeMode == TabSizeMode.Fixed) { + width = item_size.Width; + } else { + width = MeasureStringWidth (DeviceContext, page.Text, Font); + width += (Padding.X * 2) + 2; + + if (ImageList != null && page.ImageIndex >= 0) { + width += ImageList.ImageSize.Width + ThemeEngine.Current.TabControlImagePadding.X; + + int image_size = ImageList.ImageSize.Height + ThemeEngine.Current.TabControlImagePadding.Y; + if (item_size.Height < image_size) + item_size.Height = image_size; + } + + if (width < MinimumTabWidth) + width = MinimumTabWidth; + } + + // Use ItemSize property to recover the padding info as well. + height = ItemSize.Height - ThemeEngine.Current.TabWidgetselectedDelta.Height; // full height only for selected tab + + if (i == SelectedIndex) + width += ThemeEngine.Current.TabWidgetselectedSpacing; + + if (widthOnly) { + page.TabBounds = new Rectangle (xpos, 0, width, 0); + page.Row = row_count; + if (xpos + width > row_width && multiline) { + xpos = 0; + row_count++; + } else if (xpos + width > row_width) { + show_slider = true; + } + if (i == selected_index && show_slider) { + for (int j = i-1; j >= 0; j--) { + if (TabPages [j].TabBounds.Left < xpos + width - row_width) { + slider_pos = j+1; + break; + } + } + } + } else { + if (page.Row != prev_row) { + xpos = 0; + } + + switch (Alignment) { + case TabAlignment.Top: + case TabAlignment.Bottom: + page.TabBounds = new Rectangle ( + xpos + CalcXPos (), + ypos + (height + spacing.Height) * (row_count - page.Row) + CalcYPos (), + width, + height); + break; + case TabAlignment.Left: + if (Appearance == TabAppearance.Normal) { + // tab rows are positioned right to left + page.TabBounds = new Rectangle ( + ypos + (height + spacing.Height) * (row_count - page.Row) + CalcXPos (), + xpos, + height, + width); + } else { + // tab rows are positioned left to right + page.TabBounds = new Rectangle ( + ypos + (height + spacing.Height) * (page.Row - 1) + CalcXPos (), + xpos, + height, + width); + } + + break; + case TabAlignment.Right: + if (Appearance == TabAppearance.Normal) { + // tab rows are positioned left to right + page.TabBounds = new Rectangle ( + ypos + (height + spacing.Height) * (page.Row - 1) + CalcXPos (), + xpos, + height, + width); + } else { + // tab rows are positioned right to left + page.TabBounds = new Rectangle ( + ypos + (height + spacing.Height) * (row_count - page.Row) + CalcXPos (), + xpos, + height, + width); + } + + break; + } + + if (page.Row != prev_row) { + if (SizeMode == TabSizeMode.FillToRight && !ShowSlider) { + bool vertical = alignment == TabAlignment.Right || alignment == TabAlignment.Left; + int offset = vertical ? TabPages [i - 1].TabBounds.Bottom : TabPages [i - 1].TabBounds.Right; + FillRow (begin_prev, i - 1, ((row_width - offset) / (i - begin_prev)), spacing, + vertical); + } + begin_prev = i; + } + } + + xpos += width + spacing.Width + ThemeEngine.Current.TabControlColSpacing; + } + + private void FillRow (int start, int end, int amount, Size spacing, bool vertical) + { + if (vertical) + FillRowV (start, end, amount, spacing); + else + FillRow (start, end, amount, spacing); + } + + private void FillRow (int start, int end, int amount, Size spacing) + { + int xpos = TabPages [start].TabBounds.Left; + for (int i = start; i <= end; i++) { + TabPage page = TabPages [i]; + int left = xpos; + int width = (i == end ? Width - left - 3 : page.TabBounds.Width + amount); + + page.TabBounds = new Rectangle (left, page.TabBounds.Top, + width, page.TabBounds.Height); + xpos = page.TabBounds.Right + 1 + spacing.Width; + } + } + + private void FillRowV (int start, int end, int amount, Size spacing) + { + int ypos = TabPages [start].TabBounds.Top; + for (int i = start; i <= end; i++) { + TabPage page = TabPages [i]; + int top = ypos; + int height = (i == end ? Height - top - 5 : page.TabBounds.Height + amount); + + page.TabBounds = new Rectangle (page.TabBounds.Left, top, + page.TabBounds.Width, height); + ypos = page.TabBounds.Bottom + 1; + } + } + + private void ExpandSelected (TabPage page, int left_edge, int right_edge) + { + if (Appearance != TabAppearance.Normal) + return; + + Rectangle r = page.TabBounds; + r.Y -= ThemeEngine.Current.TabWidgetselectedDelta.Y; + r.X -= ThemeEngine.Current.TabWidgetselectedDelta.X; + + r.Width += ThemeEngine.Current.TabWidgetselectedDelta.Width; + r.Height += ThemeEngine.Current.TabWidgetselectedDelta.Height; + if (r.Left < left_edge) + r.X = left_edge; + // Adjustment can't be used for right alignment, since it is + // the only one that has a different X origin than 0 + if (r.Right > right_edge && SizeMode != TabSizeMode.Normal && + alignment != TabAlignment.Right) + r.Width = right_edge - r.X; + page.TabBounds = r; + } + + private void Draw (Graphics dc, Rectangle clip) + { + ThemeEngine.Current.DrawTabControl (dc, clip, this); + } + + private TabPage GetTab (int index) + { + return Widgets [index] as TabPage; + } + + private void SetTab (int index, TabPage value) + { + if (!tab_pages.Contains (value)) { + this.Widgets.Add (value); + } + this.Widgets.RemoveAt (index); + this.Widgets.SetChildIndex (value, index); + Redraw (); + } + private void InsertTab (int index, TabPage value) + { + if (!tab_pages.Contains (value)) { + this.Widgets.Add (value); + } + this.Widgets.SetChildIndex (value, index); + Redraw (); + } + internal void Redraw () + { + if (!IsHandleCreated) + return; + + ResizeTabPages (); + Refresh (); + } + + private int MeasureStringWidth (Graphics graphics, string text, Font font) + { + if (text == String.Empty) + return 0; + StringFormat format = new StringFormat(); + RectangleF rect = new RectangleF(0, 0, 1000, 1000); + CharacterRange[] ranges = { new CharacterRange(0, text.Length) }; + Region[] regions = new Region[1]; + + format.SetMeasurableCharacterRanges(ranges); + format.FormatFlags = StringFormatFlags.NoClip; + format.FormatFlags |= StringFormatFlags.NoWrap; + regions = graphics.MeasureCharacterRanges(text + "I", font, rect, format); + rect = regions[0].GetBounds(graphics); + + return (int)(rect.Width); + } + + void SetToolTip (string text) + { + if (!show_tool_tips) + return; + + if (text == null || text.Length == 0) { + CloseToolTip (); + return; + } + + if (tooltip == null) { + tooltip = new ToolTip (); + tooltip_timer = new Timer (); + tooltip_timer.Tick += new EventHandler (ToolTipTimerTick); + } + + CloseToolTip (); + + tooltip_state = ToolTip.TipState.Initial; + tooltip_timer.Interval = 500; + tooltip_timer.Start (); + } + + void CloseToolTip () + { + if (tooltip == null) + return; + + tooltip.Hide (this); + tooltip_timer.Stop (); + tooltip_state = ToolTip.TipState.Down; + } + + void ToolTipTimerTick (object o, EventArgs args) + { + switch (tooltip_state) { + case ToolTip.TipState.Initial: + tooltip_timer.Stop (); + tooltip_timer.Interval = 5000; + tooltip_timer.Start (); + tooltip_state = ToolTip.TipState.Show; + tooltip.Present (this, GetToolTipText (EnteredTabPage)); + break; + case ToolTip.TipState.Show: + CloseToolTip (); + break; + } + } + + void OnMouseMove (object sender, MouseEventArgs e) + { + if (!mouse_down_on_a_tab_page && ShowSlider) { + if (LeftSliderState == PushButtonState.Pressed || + RightSliderState == PushButtonState.Pressed) + return; + if (LeftScrollButtonArea.Contains (e.Location)) { + LeftSliderState = PushButtonState.Hot; + RightSliderState = PushButtonState.Normal; + EnteredTabPage = null; + return; + } + if (RightScrollButtonArea.Contains (e.Location)) { + RightSliderState = PushButtonState.Hot; + LeftSliderState = PushButtonState.Normal; + EnteredTabPage = null; + return; + } + LeftSliderState = PushButtonState.Normal; + RightSliderState = PushButtonState.Normal; + } + if (EnteredTabPage != null && EnteredTabPage.TabBounds.Contains (e.Location)) + return; + for (int index = 0; index < TabCount; index++) { + TabPage tab_page = TabPages[index]; + if (tab_page.TabBounds.Contains (e.Location)) { + EnteredTabPage = tab_page; + return; + } + } + EnteredTabPage = null; + } + + void OnMouseLeave (object sender, EventArgs e) + { + if (ShowSlider) { + LeftSliderState = PushButtonState.Normal; + RightSliderState = PushButtonState.Normal; + } + EnteredTabPage = null; + } + #endregion // Internal & Private Methods + + #region Events + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler BackColorChanged { + add { base.BackColorChanged += value; } + remove { base.BackColorChanged -= value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler BackgroundImageChanged { + add { base.BackgroundImageChanged += value; } + remove { base.BackgroundImageChanged -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler BackgroundImageLayoutChanged + { + add { base.BackgroundImageLayoutChanged += value; } + remove { base.BackgroundImageLayoutChanged -= value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler ForeColorChanged { + add { base.ForeColorChanged += value; } + remove { base.ForeColorChanged -= value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event PaintEventHandler Paint { + add { base.Paint += value; } + remove { base.Paint -= value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler TextChanged { + add { base.TextChanged += value; } + remove { base.TextChanged -= value; } + } + + static object DrawItemEvent = new object (); + static object SelectedIndexChangedEvent = new object (); + + public event DrawItemEventHandler DrawItem { + add { Events.AddHandler (DrawItemEvent, value); } + remove { Events.RemoveHandler (DrawItemEvent, value); } + } + + public event EventHandler SelectedIndexChanged { + add { Events.AddHandler (SelectedIndexChangedEvent, value); } + remove { Events.RemoveHandler (SelectedIndexChangedEvent, value); } + } + + static object SelectedEvent = new object (); + + public event TabControlEventHandler Selected { + add { Events.AddHandler (SelectedEvent, value); } + remove { Events.RemoveHandler (SelectedEvent, value); } + } + + static object DeselectedEvent = new object (); + + public event TabControlEventHandler Deselected + { + add { Events.AddHandler (DeselectedEvent, value); } + remove { Events.RemoveHandler (DeselectedEvent, value); } + } + + static object SelectingEvent = new object (); + + public event TabControlCancelEventHandler Selecting + { + add { Events.AddHandler (SelectingEvent, value); } + remove { Events.RemoveHandler (SelectingEvent, value); } + } + + static object DeselectingEvent = new object (); + + public event TabControlCancelEventHandler Deselecting + { + add { Events.AddHandler (DeselectingEvent, value); } + remove { Events.RemoveHandler (DeselectingEvent, value); } + } + + static object RightToLeftLayoutChangedEvent = new object (); + public event EventHandler RightToLeftLayoutChanged + { + add { Events.AddHandler (RightToLeftLayoutChangedEvent, value); } + remove { Events.RemoveHandler (RightToLeftLayoutChangedEvent, value); } + } + #endregion // Events + + + #region Class TaControl.ControlCollection + [ComVisible (false)] + public new class ControlCollection : Widget.WidgetCollection { + + private TabWidget owner; + + public ControlCollection (TabWidget owner) : base (owner) + { + this.owner = owner; + } + + public override void Add (Widget value) + { + TabPage page = value as TabPage; + if (page == null) + throw new ArgumentException ("Cannot add " + + value.GetType ().Name + " to TabControl. " + + "Only TabPages can be directly added to TabWidgets."); + + page.SetVisible (false); + base.Add (value); + if (owner.TabCount == 1 && owner.selected_index < 0) + owner.SelectedIndex = 0; + owner.Redraw (); + } + + public override void Remove (Widget value) + { + bool change_index = false; + + TabPage page = value as TabPage; + if (page != null && owner.Widgets.Contains (page)) { + int index = owner.IndexForTabPage (page); + if (index < owner.SelectedIndex || owner.SelectedIndex == Count - 1) + change_index = true; + } + + base.Remove (value); + + // We don't want to raise SelectedIndexChanged until after we + // have removed from the collection, so TabCount will be + // correct for the user. + if (change_index && Count > 0) { + // Clear the selected index internally, to avoid trying to access the previous + // selected tab when setting the new one - this is what .net seems to do + int prev_selected_index = owner.SelectedIndex; + owner.selected_index = -1; + + owner.SelectedIndex = --prev_selected_index; + owner.Invalidate (); + } else if (change_index) { + owner.selected_index = -1; + owner.OnSelectedIndexChanged (EventArgs.Empty); + owner.Invalidate (); + } else + owner.Redraw (); + } + } + #endregion // Class TabControl.ControlCollection + + #region Class TabPage.TabPageCollection + public class TabPageCollection : IList, ICollection, IEnumerable { + + private TabWidget owner; + + public TabPageCollection (TabWidget owner) + { + if (owner == null) + throw new ArgumentNullException ("Value cannot be null."); + this.owner = owner; + } + + [Browsable(false)] + public int Count { + get { return owner.Widgets.Count; } + } + + public bool IsReadOnly { + get { return false; } + } + + public virtual TabPage this [int index] { + get { + return owner.GetTab (index); + } + set { + owner.SetTab (index, value); + } + } + public virtual TabPage this [string key] { + get { + if (string.IsNullOrEmpty (key)) + return null; + + int index = this.IndexOfKey (key); + if (index < 0 || index >= this.Count) + return null; + + return this[index]; + } + } + + internal int this[TabPage tabPage] { + get { + if (tabPage == null) + return -1; + + for (int i = 0; i < this.Count; i++) + if (this[i].Equals (tabPage)) + return i; + + return -1; + } + } + + bool ICollection.IsSynchronized { + get { return false; } + } + + object ICollection.SyncRoot { + get { return this; } + } + + bool IList.IsFixedSize { + get { return false; } + } + + object IList.this [int index] { + get { + return owner.GetTab (index); + } + set { + owner.SetTab (index, (TabPage) value); + } + } + + public void Add (TabPage value) + { + if (value == null) + throw new ArgumentNullException ("Value cannot be null."); + owner.Widgets.Add (value); + } + + public void Add (string text) + { + TabPage page = new TabPage (text); + this.Add (page); + } + + public void Add (string key, string text) + { + TabPage page = new TabPage (text); + page.Name = key; + this.Add (page); + } + + public void Add (string key, string text, int imageIndex) + { + TabPage page = new TabPage (text); + page.Name = key; + page.ImageIndex = imageIndex; + this.Add (page); + } + + // .Net sets the ImageKey, but does not show the image when this is used + public void Add (string key, string text, string imageKey) + { + TabPage page = new TabPage (text); + page.Name = key; + page.ImageKey = imageKey; + this.Add (page); + } + + public void AddRange (TabPage [] pages) + { + if (pages == null) + throw new ArgumentNullException ("Value cannot be null."); + owner.Widgets.AddRange (pages); + } + + public virtual void Clear () + { + owner.Widgets.Clear (); + owner.Invalidate (); + } + + public bool Contains (TabPage page) + { + if (page == null) + throw new ArgumentNullException ("Value cannot be null."); + return owner.Widgets.Contains (page); + } + + public virtual bool ContainsKey (string key) + { + int index = this.IndexOfKey (key); + return (index >= 0 && index < this.Count); + } + + public IEnumerator GetEnumerator () + { + return owner.Widgets.GetEnumerator (); + } + + public int IndexOf (TabPage page) + { + return owner.Widgets.IndexOf (page); + } + + public virtual int IndexOfKey(string key) + { + if (string.IsNullOrEmpty (key)) + return -1; + + for (int i = 0; i < this.Count; i++) { + if (string.Compare (this[i].Name, key, true, + System.Globalization.CultureInfo.InvariantCulture) == 0) { + return i; + } + } + + return -1; + } + + public void Remove (TabPage value) + { + owner.Widgets.Remove (value); + owner.Invalidate (); + } + + public void RemoveAt (int index) + { + owner.Widgets.RemoveAt (index); + owner.Invalidate (); + } + + public virtual void RemoveByKey (string key) + { + int index = this.IndexOfKey (key); + if (index >= 0 && index < this.Count) + this.RemoveAt (index); + } + + void ICollection.CopyTo (Array dest, int index) + { + owner.Widgets.CopyTo (dest, index); + } + + int IList.Add (object value) + { + TabPage page = value as TabPage; + if (value == null) + throw new ArgumentException ("value"); + owner.Widgets.Add (page); + return owner.Widgets.IndexOf (page); + } + + bool IList.Contains (object page) + { + TabPage tabPage = page as TabPage; + if (tabPage == null) + return false; + return Contains (tabPage); + } + + int IList.IndexOf (object page) + { + TabPage tabPage = page as TabPage; + if (tabPage == null) + return -1; + return IndexOf (tabPage); + } + + void IList.Insert (int index, object tabPage) + { + throw new NotSupportedException (); + } + + public void Insert (int index, string text) + { + owner.InsertTab (index, new TabPage (text)); + } + + public void Insert (int index, TabPage tabPage) + { + owner.InsertTab (index, tabPage); + } + + public void Insert (int index, string key, string text) + { + TabPage page = new TabPage(text); + page.Name = key; + owner.InsertTab (index, page); + } + + public void Insert (int index, string key, string text, int imageIndex) + { + TabPage page = new TabPage(text); + page.Name = key; + owner.InsertTab (index, page); + page.ImageIndex = imageIndex; + } + + public void Insert (int index, string key, string text, string imageKey) + { + TabPage page = new TabPage(text); + page.Name = key; + owner.InsertTab (index, page); + page.ImageKey = imageKey; + } + void IList.Remove (object value) + { + TabPage page = value as TabPage; + if (page == null) + return; + Remove ((TabPage) value); + } + } + #endregion // Class TabPage.TabPageCollection + } +} + + diff --git a/source/ShiftUI/Widgets/TabPage.cs b/source/ShiftUI/Widgets/TabPage.cs new file mode 100644 index 0000000..7ae0165 --- /dev/null +++ b/source/ShiftUI/Widgets/TabPage.cs @@ -0,0 +1,399 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2004-2005 Novell, Inc. +// +// Authors: +// Jackson Harper ([email protected]) + + +using System; +using System.ComponentModel; +using System.ComponentModel.Design; +using System.Drawing; +using System.Runtime.InteropServices; + +namespace ShiftUI { + [ComVisible (true)] + [ClassInterface (ClassInterfaceType.AutoDispatch)] + [DefaultEvent("Click")] + [DesignTimeVisible(false)] + [DefaultProperty("Text")] + //[Designer("ShiftUI.Design.TabPageDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")] + [ToolboxItem(false)] + public class TabPage : Panel { + #region Fields + private int imageIndex = -1; + private string imageKey; + private string tooltip_text = String.Empty; + private Rectangle tab_bounds; + private int row; + private bool use_visual_style_back_color; + #endregion // Fields + + #region Public Constructors + public TabPage () + { + Visible = true; + + SetStyle (Widgetstyles.CacheText, true); + } + + public TabPage (string text) : base () + { + base.Text = text; + } + + #endregion // Public Constructors + + #region .NET 2.0 Public Instance Properties + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public override bool AutoSize { + get { return base.AutoSize; } + set { base.AutoSize = value; } + } + + [Browsable (false)] + [Localizable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public override AutoSizeMode AutoSizeMode { + get { return base.AutoSizeMode; } + set { base.AutoSizeMode = value; } + } + + [Browsable (false)] + [DefaultValue ("{Width=0, Height=0}")] + //[EditorBrowsable (EditorBrowsableState.Never)] + public override Size MaximumSize { + get { return base.MaximumSize; } + set { base.MaximumSize = value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public override Size MinimumSize { + get { return base.MinimumSize; } + set { base.MinimumSize = value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new Size PreferredSize { + get { return base.PreferredSize; } + } + + [DefaultValue (false)] + public bool UseVisualStyleBackColor { + get { return use_visual_style_back_color; } + set { use_visual_style_back_color = value; } + } + + public override Color BackColor { + get { return base.BackColor; } + set { use_visual_style_back_color = false; base.BackColor = value; } + } + #endregion + + #region Public Instance Properties + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public override AnchorStyles Anchor { + get { return base.Anchor; } + set { base.Anchor = value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public override DockStyle Dock { + get { return base.Dock; } + set { base.Dock = value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new bool Enabled { + get { return base.Enabled; } + set { base.Enabled = value; } + } + + [RefreshProperties (RefreshProperties.Repaint)] + [DefaultValue(-1)] + //[Editor("ShiftUI.Design.ImageIndexEditor, " + Consts.AssemblySystem_Design, typeof(System.Drawing.Design.UITypeEditor))] + [Localizable(true)] + [TypeConverter(typeof(ImageIndexConverter))] + public int ImageIndex { + get { return imageIndex; } + set { + if (imageIndex == value) + return; + imageIndex = value; + UpdateOwner (); + } + } + + [Localizable (true)] + [RefreshProperties (RefreshProperties.Repaint)] + [DefaultValue ("")] + //[Editor ("ShiftUI.Design.ImageIndexEditor, " + Consts.AssemblySystem_Design, + //"System.Drawing.Design.UITypeEditor, " + Consts.AssemblySystem_Drawing)] + [TypeConverter (typeof (ImageKeyConverter))] + public string ImageKey + { + get { return imageKey; } + set { + imageKey = value; + TabWidget control = this.Parent as TabWidget; + if (control != null) { + ImageIndex = control.ImageList.Images.IndexOfKey (imageKey); + } + } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new int TabIndex { + get { return base.TabIndex; } + set { base.TabIndex = value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new bool TabStop { + get { return base.TabStop; } + set { base.TabStop = value; } + } + + //[EditorBrowsable (EditorBrowsableState.Always)] + [Browsable(true)] + [Localizable(true)] + public override string Text { + get { return base.Text; } + set { + if (value == base.Text) + return; + base.Text = value; + UpdateOwner (); + } + } + + [Localizable(true)] + [DefaultValue("")] + public string ToolTipText { + get { return tooltip_text; } + set { + if (value == null) + value = String.Empty; + tooltip_text = value; + } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new bool Visible { + get { return base.Visible; } + set { /* according to MS docs we can ignore this */ } + } + + #endregion // Public Instance Properties + + #region Public Static Methods + public static TabPage GetTabPageOfComponent (object comp) + { + Widget control = comp as Widget; + if (control == null) + return null; + control = control.Parent; + while (control != null) { + if (control is TabPage) + break; + control = control.Parent; + } + return control as TabPage; + } + + #endregion // Public Static Methods + + #region Public Instance Methods + public override string ToString () + { + return "TabPage: {" + Text + "}"; + } + + #endregion // Public Instance Methods + + #region Internal & Private Methods and Properties + internal Rectangle TabBounds { + get { return tab_bounds; } + set { tab_bounds = value; } + } + + internal int Row { + get { return row; } + set { row = value; } + } + + private void UpdateOwner () + { + if (Owner != null) { + Owner.Redraw (); + } + } + + private TabWidget Owner { + get { return base.Parent as TabWidget; } + } + + internal void SetVisible (bool value) + { + base.Visible = value; + } + + #endregion // Internal & Private Methods and Properties + + #region Protected Instance Methods + protected override WidgetCollection CreateWidgetsInstance () + { + return new TabPageControlCollection (this); + } + + protected override void SetBoundsCore (int x, int y, int width, int height, BoundsSpecified specified) + { + if (Owner != null && Owner.IsHandleCreated) { + Rectangle display = Owner.DisplayRectangle; + + base.SetBoundsCore (display.X, display.Y, + display.Width, display.Height, + BoundsSpecified.All); + } else { + base.SetBoundsCore (x, y, width, height, specified); + } + } + + protected override void OnEnter (EventArgs e) + { + base.OnEnter (e); + } + + protected override void OnLeave (EventArgs e) + { + base.OnLeave (e); + } + + protected override void OnPaintBackground (PaintEventArgs e) + { + base.OnPaintBackground (e); + } + #endregion // Protected Instance Methods + + #region Events + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler AutoSizeChanged { + add { base.AutoSizeChanged += value; } + remove { base.AutoSizeChanged -= value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler DockChanged { + add { base.DockChanged += value; } + remove { base.DockChanged -= value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler EnabledChanged { + add { base.EnabledChanged += value; } + remove { base.EnabledChanged -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler LocationChanged { + add { base.LocationChanged += value; } + remove { base.LocationChanged -= value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler TabIndexChanged { + add { base.TabIndexChanged += value; } + remove { base.TabIndexChanged -= value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler TabStopChanged { + add { base.TabStopChanged += value; } + remove { base.TabStopChanged -= value; } + } + + [Browsable (true)] + //[EditorBrowsable (EditorBrowsableState.Always)] + public new event EventHandler TextChanged { + add { base.TextChanged += value; } + remove { base.TextChanged -= value; } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler VisibleChanged { + add { base.VisibleChanged += value; } + remove { base.VisibleChanged -= value; } + } + + #endregion // Events + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new Point Location { + get { + return base.Location; + } + + set { + base.Location = value; + } + } + + #region Class TabPageControlCollection + [ComVisible (false)] + public class TabPageControlCollection : WidgetCollection { + + //private TabPage owner; + + public TabPageControlCollection (TabPage owner) : base (owner) + { + //this.owner = owner; + } + + public override void Add (Widget value) + { + base.Add (value); + } + } + #endregion // Class TabPageControlCollection + + } + + +} diff --git a/source/ShiftUI/Widgets/TableLayoutPanel.cs b/source/ShiftUI/Widgets/TableLayoutPanel.cs new file mode 100644 index 0000000..e327795 --- /dev/null +++ b/source/ShiftUI/Widgets/TableLayoutPanel.cs @@ -0,0 +1,788 @@ +// +// TableLayoutPanel.cs +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2006 Jonathan Pobst +// +// Authors: +// Jonathan Pobst ([email protected]) +// + +using System; +using System.Drawing; +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.InteropServices; +using ShiftUI.Layout; +using System.ComponentModel.Design.Serialization; + +namespace ShiftUI +{ + [ComVisible (true)] + [ClassInterface (ClassInterfaceType.AutoDispatch)] + [ProvideProperty ("CellPosition", typeof (Widget))] + [ProvideProperty ("Column", typeof (Widget))] + [ProvideProperty ("ColumnSpan", typeof (Widget))] + [ProvideProperty ("Row", typeof (Widget))] + [ProvideProperty ("RowSpan", typeof (Widget))] + [DefaultProperty ("ColumnCount")] + [Docking (DockingBehavior.Never)] + //[Designer ("ShiftUI.Design.TableLayoutPanelDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")] + //[DesignerSerializer ("ShiftUI.Design.TableLayoutPanelCodeDomSerializer, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.Serialization.CodeDomSerializer, " + Consts.AssemblySystem_Design)] + [ToolboxWidget] + public class TableLayoutPanel : Panel, IExtenderProvider + { + private TableLayoutSettings settings; + private static TableLayout layout_engine = new TableLayout (); + private TableLayoutPanelCellBorderStyle cell_border_style; + + // This is the row/column the Widget actually got placed + internal Widget[,] actual_positions; + + // Widths and heights of each column/row + internal int[] column_widths; + internal int[] row_heights; + + #region Public Constructor + public TableLayoutPanel () + { + settings = new TableLayoutSettings(this); + cell_border_style = TableLayoutPanelCellBorderStyle.None; + column_widths = new int[0]; + row_heights = new int[0]; + CreateDockPadding (); + } + #endregion + + #region Public Properties + [Localizable (true)] + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + new public BorderStyle BorderStyle { + get { return base.BorderStyle; } + set { base.BorderStyle = value; } + } + + [Localizable (true)] + [DefaultValue (TableLayoutPanelCellBorderStyle.None)] + public TableLayoutPanelCellBorderStyle CellBorderStyle { + get { return this.cell_border_style; } + set { + if (this.cell_border_style != value) { + this.cell_border_style = value; + this.PerformLayout (this, "CellBorderStyle"); + this.Invalidate (); + } + } + } + + [Localizable (true)] + [DefaultValue (0)] + public int ColumnCount { + get { return settings.ColumnCount; } + set { settings.ColumnCount = value; } + } + + [Browsable (false)] + [DisplayName ("Columns")] + [MergableProperty (false)] + //[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] + public TableLayoutColumnStyleCollection ColumnStyles { + get { return settings.ColumnStyles; } + } + + [Browsable (false)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Content)] + new public TableLayoutControlCollection Widgets { + get { return (TableLayoutControlCollection) base.Widgets; } + } + + [DefaultValue (TableLayoutPanelGrowStyle.AddRows)] + public TableLayoutPanelGrowStyle GrowStyle { + get { return settings.GrowStyle; } + set { settings.GrowStyle = value; } + } + + public override ShiftUI.Layout.LayoutEngine LayoutEngine { + get { return TableLayoutPanel.layout_engine; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public TableLayoutSettings LayoutSettings { + get { return this.settings; } + set { + if (value.isSerialized) { + // Serialized version doesn't calculate these. + value.ColumnCount = value.ColumnStyles.Count; + value.RowCount = value.RowStyles.Count; + value.panel = this; + + this.settings = value; + } else + throw new NotSupportedException ("LayoutSettings value cannot be set directly."); + } + } + + [Localizable (true)] + [DefaultValue (0)] + public int RowCount { + get { return settings.RowCount; } + set { settings.RowCount = value; } + } + + [Browsable (false)] + [DisplayName ("Rows")] + [MergableProperty (false)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Content)] + public TableLayoutRowStyleCollection RowStyles { + get { return settings.RowStyles; } + } + #endregion + + #region Public Methods + [DefaultValue (-1)] + [DisplayName ("Cell")] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public TableLayoutPanelCellPosition GetCellPosition (Widget control) + { + return settings.GetCellPosition (control); + } + + [DisplayName ("Column")] + [DefaultValue (-1)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public int GetColumn (Widget control) + { + return settings.GetColumn (control); + } + + [DisplayName ("ColumnSpan")] + [DefaultValue (1)] + public int GetColumnSpan (Widget control) + { + return settings.GetColumnSpan (control); + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public int[] GetColumnWidths () + { + return this.column_widths; + } + + public Widget GetControlFromPosition (int column, int row) + { + if (column < 0 || row < 0) + throw new ArgumentException (); + + TableLayoutPanelCellPosition pos = new TableLayoutPanelCellPosition (column, row); + + foreach (Widget c in this.Widgets) + if (settings.GetCellPosition (c) == pos) + return c; + + return null; + } + + public TableLayoutPanelCellPosition GetPositionFromControl (Widget control) + { + for (int x = 0; x < this.actual_positions.GetLength (0); x++) + for (int y = 0; y < this.actual_positions.GetLength (1); y++) + if (this.actual_positions[x, y] == control) + return new TableLayoutPanelCellPosition (x, y); + + return new TableLayoutPanelCellPosition (-1, -1); + } + + [DisplayName ("Row")] + [DefaultValue ("-1")] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public int GetRow (Widget control) + { + return settings.GetRow (control); + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public int[] GetRowHeights () + { + return this.row_heights; + } + + [DisplayName ("RowSpan")] + [DefaultValue (1)] + public int GetRowSpan (Widget control) + { + return settings.GetRowSpan (control); + } + + public void SetCellPosition (Widget control, TableLayoutPanelCellPosition position) + { + settings.SetCellPosition (control, position); + } + + public void SetColumn (Widget control, int column) + { + settings.SetColumn (control, column); + } + + public void SetColumnSpan (Widget control, int value) + { + settings.SetColumnSpan (control, value); + } + + public void SetRow (Widget control, int row) + { + settings.SetRow (control, row); + } + + public void SetRowSpan (Widget control, int value) + { + settings.SetRowSpan (control, value); + } + #endregion + + #region Protected Methods + //[EditorBrowsable (EditorBrowsableState.Advanced)] + protected override WidgetCollection CreateWidgetsInstance () + { + return new TableLayoutControlCollection (this); + } + + protected virtual void OnCellPaint (TableLayoutCellPaintEventArgs e) + { + TableLayoutCellPaintEventHandler eh = (TableLayoutCellPaintEventHandler)(Events [CellPaintEvent]); + if (eh != null) + eh (this, e); + } + + //[EditorBrowsable (EditorBrowsableState.Advanced)] + protected override void OnLayout (LayoutEventArgs levent) + { + base.OnLayout (levent); + Invalidate (); + } + + protected override void OnPaintBackground (PaintEventArgs e) + { + base.OnPaintBackground (e); + + DrawCellBorders (e); + + int border_width = GetCellBorderWidth (CellBorderStyle); + + int x = border_width; + int y = border_width; + + for (int i = 0; i < column_widths.Length; i++) { + for (int j = 0; j < row_heights.Length; j++) { + this.OnCellPaint (new TableLayoutCellPaintEventArgs (e.Graphics, e.ClipRectangle, new Rectangle (x, y, column_widths[i] + border_width, row_heights[j] + border_width), i, j)); + y += row_heights[j] + border_width; + } + + x += column_widths[i] + border_width; + y = border_width; + } + } + + protected override void ScaleWidget (SizeF factor, BoundsSpecified specified) + { + base.ScaleWidget (factor, specified); + } + + //[EditorBrowsable (EditorBrowsableState.Never)] + protected override void ScaleCore (float dx, float dy) + { + base.ScaleCore (dx, dy); + } + #endregion + + #region Internal Methods + internal static int GetCellBorderWidth (TableLayoutPanelCellBorderStyle style) + { + switch (style) { + case TableLayoutPanelCellBorderStyle.Single: + return 1; + case TableLayoutPanelCellBorderStyle.Inset: + case TableLayoutPanelCellBorderStyle.Outset: + return 2; + case TableLayoutPanelCellBorderStyle.InsetDouble: + case TableLayoutPanelCellBorderStyle.OutsetPartial: + case TableLayoutPanelCellBorderStyle.OutsetDouble: + return 3; + } + + return 0; + } + + private void DrawCellBorders (PaintEventArgs e) + { + Rectangle paint_here = new Rectangle (Point.Empty, this.Size); + + switch (CellBorderStyle) { + case TableLayoutPanelCellBorderStyle.Single: + DrawSingleBorder (e.Graphics, paint_here); + break; + case TableLayoutPanelCellBorderStyle.Inset: + DrawInsetBorder (e.Graphics, paint_here); + break; + case TableLayoutPanelCellBorderStyle.InsetDouble: + DrawInsetDoubleBorder (e.Graphics, paint_here); + break; + case TableLayoutPanelCellBorderStyle.Outset: + DrawOutsetBorder (e.Graphics, paint_here); + break; + case TableLayoutPanelCellBorderStyle.OutsetDouble: + case TableLayoutPanelCellBorderStyle.OutsetPartial: + DrawOutsetDoubleBorder (e.Graphics, paint_here); + break; + } + } + + private void DrawSingleBorder (Graphics g, Rectangle rect) + { + WidgetPaint.DrawBorder (g, rect, SystemColors.ControlDark, ButtonBorderStyle.Solid); + + int x = DisplayRectangle.X; + int y = DisplayRectangle.Y; + + for (int i = 0; i < column_widths.Length - 1; i++) { + x += column_widths[i] + 1; + + g.DrawLine (SystemPens.ControlDark, new Point (x, 1), new Point (x, Bottom - 2)); + } + + for (int j = 0; j < row_heights.Length - 1; j++) { + y += row_heights[j] + 1; + + g.DrawLine (SystemPens.ControlDark, new Point (1, y), new Point (Right - 2, y)); + } + } + + private void DrawInsetBorder (Graphics g, Rectangle rect) + { + WidgetPaint.DrawBorder3D (g, rect, Border3DStyle.Etched); + + int x = DisplayRectangle.X; + int y = DisplayRectangle.Y; + + for (int i = 0; i < column_widths.Length - 1; i++) { + x += column_widths[i] + 2; + + g.DrawLine (SystemPens.ControlDark, new Point (x, 1), new Point (x, Bottom - 3)); + g.DrawLine (Pens.White, new Point (x + 1, 1), new Point (x + 1, Bottom - 3)); + } + + for (int j = 0; j < row_heights.Length - 1; j++) { + y += row_heights[j] + 2; + + g.DrawLine (SystemPens.ControlDark, new Point (1, y), new Point (Right - 3, y)); + g.DrawLine (Pens.White, new Point (1, y + 1), new Point (Right - 3, y + 1)); + } + } + + private void DrawOutsetBorder (Graphics g, Rectangle rect) + { + g.DrawRectangle (SystemPens.ControlDark, new Rectangle (rect.Left + 1, rect.Top + 1, rect.Width - 2, rect.Height - 2)); + g.DrawRectangle (Pens.White, new Rectangle (rect.Left, rect.Top, rect.Width - 2, rect.Height - 2)); + + int x = DisplayRectangle.X; + int y = DisplayRectangle.Y; + + for (int i = 0; i < column_widths.Length - 1; i++) { + x += column_widths[i] + 2; + + g.DrawLine (Pens.White, new Point (x, 1), new Point (x, Bottom - 3)); + g.DrawLine (SystemPens.ControlDark, new Point (x + 1, 1), new Point (x + 1, Bottom - 3)); + } + + for (int j = 0; j < row_heights.Length - 1; j++) { + y += row_heights[j] + 2; + + g.DrawLine (Pens.White, new Point (1, y), new Point (Right - 3, y)); + g.DrawLine (SystemPens.ControlDark, new Point (1, y + 1), new Point (Right - 3, y + 1)); + } + } + + private void DrawOutsetDoubleBorder (Graphics g, Rectangle rect) + { + rect.Width -= 1; + rect.Height -= 1; + + g.DrawRectangle (SystemPens.ControlDark, new Rectangle (rect.Left + 2, rect.Top + 2, rect.Width - 2, rect.Height - 2)); + g.DrawRectangle (Pens.White, new Rectangle (rect.Left, rect.Top, rect.Width - 2, rect.Height - 2)); + + int x = DisplayRectangle.X; + int y = DisplayRectangle.Y; + + for (int i = 0; i < column_widths.Length - 1; i++) { + x += column_widths[i] + 3; + + g.DrawLine (Pens.White, new Point (x, 3), new Point (x, Bottom - 5)); + g.DrawLine (SystemPens.ControlDark, new Point (x + 2, 3), new Point (x + 2, Bottom - 5)); + } + + for (int j = 0; j < row_heights.Length - 1; j++) { + y += row_heights[j] + 3; + + g.DrawLine (Pens.White, new Point (3, y), new Point (Right - 4, y)); + g.DrawLine (SystemPens.ControlDark, new Point (3, y + 2), new Point (Right - 4, y + 2)); + } + + x = DisplayRectangle.X; + y = DisplayRectangle.Y; + + for (int i = 0; i < column_widths.Length - 1; i++) { + x += column_widths[i] + 3; + + g.DrawLine (ThemeEngine.Current.ResPool.GetPen (BackColor), new Point (x + 1, 3), new Point (x + 1, Bottom - 5)); + } + + for (int j = 0; j < row_heights.Length - 1; j++) { + y += row_heights[j] + 3; + + g.DrawLine (ThemeEngine.Current.ResPool.GetPen (BackColor), new Point (3, y + 1), new Point (Right - 4, y + 1)); + } + } + + private void DrawInsetDoubleBorder (Graphics g, Rectangle rect) + { + rect.Width -= 1; + rect.Height -= 1; + + g.DrawRectangle (Pens.White, new Rectangle (rect.Left + 2, rect.Top + 2, rect.Width - 2, rect.Height - 2)); + g.DrawRectangle (SystemPens.ControlDark, new Rectangle (rect.Left, rect.Top, rect.Width - 2, rect.Height - 2)); + + int x = DisplayRectangle.X; + int y = DisplayRectangle.Y; + + for (int i = 0; i < column_widths.Length - 1; i++) { + x += column_widths[i] + 3; + + g.DrawLine (SystemPens.ControlDark, new Point (x, 3), new Point (x, Bottom - 5)); + g.DrawLine (Pens.White, new Point (x + 2, 3), new Point (x + 2, Bottom - 5)); + } + + for (int j = 0; j < row_heights.Length - 1; j++) { + y += row_heights[j] + 3; + + g.DrawLine (SystemPens.ControlDark, new Point (3, y), new Point (Right - 4, y)); + g.DrawLine (Pens.White, new Point (3, y + 2), new Point (Right - 4, y + 2)); + } + + x = DisplayRectangle.X; + y = DisplayRectangle.Y; + + for (int i = 0; i < column_widths.Length - 1; i++) { + x += column_widths[i] + 3; + + g.DrawLine (ThemeEngine.Current.ResPool.GetPen (BackColor), new Point (x + 1, 3), new Point (x + 1, Bottom - 5)); + } + + for (int j = 0; j < row_heights.Length - 1; j++) { + y += row_heights[j] + 3; + + g.DrawLine (ThemeEngine.Current.ResPool.GetPen (BackColor), new Point (3, y + 1), new Point (Right - 4, y + 1)); + } + } + + internal override Size GetPreferredSizeCore (Size proposedSize) + { + // If the tablelayoutowner is autosize, we have to make sure it is big enough + // to hold every non-autosize control + actual_positions = (LayoutEngine as TableLayout).CalculateWidgetPositions (this, Math.Max (ColumnCount, 1), Math.Max (RowCount, 1)); + + // Use actual row/column counts, not user set ones + int actual_cols = actual_positions.GetLength (0); + int actual_rows = actual_positions.GetLength (1); + + // Find the largest column-span/row-span values. A table entry that spans more than one + // column (row) should not be treated as though it's width (height) all belongs to the + // first column (row), but should be spread out across all the columns (rows) that are + // spanned. So we need to keep track of the widths (heights) of spans as well as + // individual columns (rows). + int max_colspan = 1, max_rowspan = 1; + foreach (Widget c in Widgets) + { + max_colspan = Math.Max(max_colspan, GetColumnSpan(c)); + max_rowspan = Math.Max(max_rowspan, GetRowSpan(c)); + } + + // Figure out how wide the owner needs to be + int[] column_widths = new int[actual_cols]; + // Keep track of widths for spans as well as columns. column_span_widths[i,j] stores + // the maximum width for items column i than have a span of j+1 (ie, covers columns + // i through i+j). + int[,] column_span_widths = new int[actual_cols, max_colspan]; + int[] biggest = new int[max_colspan]; + float total_column_percentage = 0f; + + // Figure out how wide each column wants to be + for (int i = 0; i < actual_cols; i++) { + if (i < ColumnStyles.Count && ColumnStyles[i].SizeType == SizeType.Percent) + total_column_percentage += ColumnStyles[i].Width; + int absolute_width = -1; + if (i < ColumnStyles.Count && ColumnStyles[i].SizeType == SizeType.Absolute) + absolute_width = (int)ColumnStyles[i].Width; // use the absolute width if it's absolute! + + for (int s = 0; s < max_colspan; ++s) + biggest[s] = 0; + + for (int j = 0; j < actual_rows; j++) { + Widget c = actual_positions[i, j]; + + if (c != null) { + int colspan = GetColumnSpan (c); + if (colspan == 0) + continue; + if (colspan == 1 && absolute_width > -1) + biggest[0] = absolute_width; // use the absolute width if the column has absolute width assigned! + else if (!c.AutoSize) + biggest[colspan-1] = Math.Max (biggest[colspan-1], c.ExplicitBounds.Width + c.Margin.Horizontal + Padding.Horizontal); + else + biggest[colspan-1] = Math.Max (biggest[colspan-1], c.PreferredSize.Width + c.Margin.Horizontal + Padding.Horizontal); + } + else if (absolute_width > -1) { + biggest[0] = absolute_width; + } + } + + for (int s = 0; s < max_colspan; ++s) + column_span_widths[i,s] = biggest[s]; + } + + for (int i = 0; i < actual_cols; ++i) { + for (int s = 1; s < max_colspan; ++s) { + if (column_span_widths[i,s] > 0) + AdjustWidthsForSpans (column_span_widths, i, s); + } + column_widths[i] = column_span_widths[i,0]; + } + + // Because percentage based rows divy up the remaining space, + // we have to make the owner big enough so that all the rows + // get bigger, even if we only need one to be bigger. + int non_percent_total_width = 0; + int percent_total_width = 0; + + for (int i = 0; i < actual_cols; i++) { + if (i < ColumnStyles.Count && ColumnStyles[i].SizeType == SizeType.Percent) + percent_total_width = Math.Max (percent_total_width, (int)(column_widths[i] / ((ColumnStyles[i].Width) / total_column_percentage))); + else + non_percent_total_width += column_widths[i]; + } + + int border_width = GetCellBorderWidth (CellBorderStyle); + int needed_width = non_percent_total_width + percent_total_width + (border_width * (actual_cols + 1)); + + // Figure out how tall the owner needs to be + int[] row_heights = new int[actual_rows]; + int[,] row_span_heights = new int[actual_rows, max_rowspan]; + biggest = new int[max_rowspan]; + float total_row_percentage = 0f; + + // Figure out how tall each row wants to be + for (int j = 0; j < actual_rows; j++) { + if (j < RowStyles.Count && RowStyles[j].SizeType == SizeType.Percent) + total_row_percentage += RowStyles[j].Height; + int absolute_height = -1; + if (j < RowStyles.Count && RowStyles[j].SizeType == SizeType.Absolute) + absolute_height = (int)RowStyles[j].Height; // use the absolute height if it's absolute! + + for (int s = 0; s < max_rowspan; ++s) + biggest[s] = 0; + + for (int i = 0; i < actual_cols; i++) { + Widget c = actual_positions[i, j]; + + if (c != null) { + int rowspan = GetRowSpan (c); + if (rowspan == 0) + continue; + if (rowspan == 1 && absolute_height > -1) + biggest[0] = absolute_height; // use the absolute height if the row has absolute height assigned! + else if (!c.AutoSize) + biggest[rowspan-1] = Math.Max (biggest[rowspan-1], c.ExplicitBounds.Height + c.Margin.Vertical + Padding.Vertical); + else + biggest[rowspan-1] = Math.Max (biggest[rowspan-1], c.PreferredSize.Height + c.Margin.Vertical + Padding.Vertical); + } + else if (absolute_height > -1) { + biggest[0] = absolute_height; + } + } + + for (int s = 0; s < max_rowspan; ++s) + row_span_heights[j,s] = biggest[s]; + } + + for (int j = 0; j < actual_rows; ++j) { + for (int s = 1; s < max_rowspan; ++s) { + if (row_span_heights[j,s] > 0) + AdjustHeightsForSpans (row_span_heights, j, s); + } + row_heights[j] = row_span_heights[j,0]; + } + + // Because percentage based rows divy up the remaining space, + // we have to make the owner big enough so that all the rows + // get bigger, even if we only need one to be bigger. + int non_percent_total_height = 0; + int percent_total_height = 0; + + for (int j = 0; j < actual_rows; j++) { + if (j < RowStyles.Count && RowStyles[j].SizeType == SizeType.Percent) + percent_total_height = Math.Max (percent_total_height, (int)(row_heights[j] / ((RowStyles[j].Height) / total_row_percentage))); + else + non_percent_total_height += row_heights[j]; + } + + int needed_height = non_percent_total_height + percent_total_height + (border_width * (actual_rows + 1)); + + return new Size (needed_width, needed_height); + } + + /// <summary> + /// Adjust the widths of the columns underlying a span if necessary. + /// </summary> + private void AdjustWidthsForSpans (int[,] widths, int col, int span) + { + // Get the combined width of the columns underlying the span. + int existing_width = 0; + for (int i = col; i <= col+span; ++i) + existing_width += widths[i,0]; + if (widths[col,span] > existing_width) + { + // We need to expand one or more of the underlying columns to fit the span, + // preferably ones that are not Absolute style. + int excess = widths[col,span] - existing_width; + int remaining = excess; + List<int> adjusting = new List<int>(); + List<float> adjusting_widths = new List<float>(); + for (int i = col; i <= col+span; ++i) { + if (i < ColumnStyles.Count && ColumnStyles[i].SizeType != SizeType.Absolute) { + adjusting.Add(i); + adjusting_widths.Add((float)widths[i,0]); + } + } + if (adjusting.Count == 0) { + // if every column is Absolute, spread the gain across every column + for (int i = col; i <= col+span; ++i) { + adjusting.Add(i); + adjusting_widths.Add((float)widths[i,0]); + } + } + float original_total = 0f; + foreach (var w in adjusting_widths) + original_total += w; + // Divide up the needed additional width proportionally. + for (int i = 0; i < adjusting.Count; ++i) { + var idx = adjusting[i]; + var percent = adjusting_widths[i] / original_total; + var adjust = (int)(percent * excess); + widths[idx,0] += adjust; + remaining -= adjust; + } + // Any remaining fragment (1 or 2 pixels?) is divided evenly. + while (remaining > 0) { + for (int i = 0; i < adjusting.Count && remaining > 0; ++i) { + ++widths[adjusting[i],0]; + --remaining; + } + } + } + } + + /// <summary> + /// Adjust the heights of the rows underlying a span if necessary. + /// </summary> + private void AdjustHeightsForSpans (int[,] heights, int row, int span) + { + // Get the combined height of the rows underlying the span. + int existing_height = 0; + for (int i = row; i <= row+span; ++i) + existing_height += heights[i,0]; + if (heights[row,span] > existing_height) + { + // We need to expand one or more of the underlying rows to fit the span, + // preferably ones that are not Absolute style. + int excess = heights[row,span] - existing_height; + int remaining = excess; + List<int> adjusting = new List<int>(); + List<float> adjusting_heights = new List<float>(); + for (int i = row; i <= row+span; ++i) { + if (i < RowStyles.Count && RowStyles[i].SizeType != SizeType.Absolute) { + adjusting.Add(i); + adjusting_heights.Add((float)heights[i,0]); + } + } + if (adjusting.Count == 0) { + // if every row is Absolute, spread the gain across every row + for (int i = row; i <= row+span; ++i) { + adjusting.Add(i); + adjusting_heights.Add((float)heights[i,0]); + } + } + float original_total = 0f; + foreach (var w in adjusting_heights) + original_total += w; + // Divide up the needed additional height proportionally. + for (int i = 0; i < adjusting.Count; ++i) { + var idx = adjusting[i]; + var percent = adjusting_heights[i] / original_total; + var adjust = (int)(percent * excess); + heights[idx,0] += adjust; + remaining -= adjust; + } + // Any remaining fragment (1 or 2 pixels?) is divided evenly. + while (remaining > 0) { + for (int i = 0; i < adjusting.Count && remaining > 0; ++i) { + ++heights[adjusting[i],0]; + --remaining; + } + } + } + } + #endregion + + #region Public Events + static object CellPaintEvent = new object (); + + public event TableLayoutCellPaintEventHandler CellPaint { + add { Events.AddHandler (CellPaintEvent, value); } + remove { Events.RemoveHandler (CellPaintEvent, value); } + } + #endregion + + #region IExtenderProvider + bool IExtenderProvider.CanExtend (object obj) + { + if (obj is Widget) + if ((obj as Widget).Parent == this) + return true; + + return false; + } + #endregion + + } +} diff --git a/source/ShiftUI/Widgets/TextBox.cs b/source/ShiftUI/Widgets/TextBox.cs new file mode 100644 index 0000000..1ef880d --- /dev/null +++ b/source/ShiftUI/Widgets/TextBox.cs @@ -0,0 +1,1025 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2004-2006 Novell, Inc. (http://www.novell.com) +// +// Authors: +// Peter Bartok [email protected] +// Daniel Nauck (dna(at)mono-project(dot)de) +// + +// NOT COMPLETE + +using System; +using System.Collections; +using System.ComponentModel; +using System.ComponentModel.Design; +using System.Drawing; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace ShiftUI { + + [ComVisible(true)] + [ClassInterface (ClassInterfaceType.AutoDispatch)] + //[Designer ("ShiftUI.Design.TextBoxDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")] + [ToolboxWidget] + public class TextBox : TextBoxBase { + #region Variables + private MenuItem undo; + private MenuItem cut; + private MenuItem copy; + private MenuItem paste; + private MenuItem delete; + private MenuItem select_all; + + private bool use_system_password_char; + private AutoCompleteStringCollection auto_complete_custom_source; + private AutoCompleteMode auto_complete_mode = AutoCompleteMode.None; + private AutoCompleteSource auto_complete_source = AutoCompleteSource.None; + private AutoCompleteListBox auto_complete_listbox; + private string auto_complete_original_text; + private int auto_complete_selected_index = -1; + private List<string> auto_complete_matches; + private ComboBox auto_complete_cb_source; + #endregion // Variables + + #region Public Constructors + public TextBox() { + + scrollbars = RichTextBoxScrollBars.None; + alignment = HorizontalAlignment.Left; + this.LostFocus +=new EventHandler(TextBox_LostFocus); + this.RightToLeftChanged += new EventHandler (TextBox_RightToLeftChanged); + MouseWheel += new MouseEventHandler (TextBox_MouseWheel); + + BackColor = ThemeEngine.Current.ColorControl; + ForeColor = ThemeEngine.Current.ColorControlText; + backcolor_set = false; + + SetStyle (Widgetstyles.StandardClick | Widgetstyles.StandardDoubleClick, false); + SetStyle (Widgetstyles.FixedHeight, true); + + + document.multiline = false; + } + + #endregion // Public Constructors + + #region Private & Internal Methods + + void TextBox_RightToLeftChanged (object sender, EventArgs e) + { + UpdateAlignment (); + } + + private void TextBox_LostFocus (object sender, EventArgs e) { + if (hide_selection) + document.InvalidateSelectionArea (); + if (auto_complete_listbox != null && auto_complete_listbox.Visible) + auto_complete_listbox.HideListBox (false); + } + + private void TextBox_MouseWheel (object o, MouseEventArgs args) + { + if (auto_complete_listbox == null || !auto_complete_listbox.Visible) + return; + + int lines = args.Delta / 120; + auto_complete_listbox.Scroll (-lines); + } + + // Receives either WM_KEYDOWN or WM_CHAR that will likely need the generation/lookup + // of new matches + private void ProcessAutoCompleteInput (ref Message m, bool deleting_chars) + { + // Need to call base.WndProc before to have access to + // the updated Text property value + base.WndProc (ref m); + auto_complete_original_text = Text; + ShowAutoCompleteListBox (deleting_chars); + } + + private void ShowAutoCompleteListBox (bool deleting_chars) + { + // + // We only support CustomSource by now + // + + IList source = auto_complete_cb_source == null ? auto_complete_custom_source : (IList)auto_complete_cb_source.Items; + + bool append = auto_complete_mode == AutoCompleteMode.Append || auto_complete_mode == AutoCompleteMode.SuggestAppend; + bool suggest = auto_complete_mode == AutoCompleteMode.Suggest || auto_complete_mode == AutoCompleteMode.SuggestAppend; + + if (Text.Length == 0) { + if (auto_complete_listbox != null) + auto_complete_listbox.HideListBox (false); + return; + } + + if (auto_complete_matches == null) + auto_complete_matches = new List<string> (); + + string text = Text; + auto_complete_matches.Clear (); + + for (int i = 0; i < source.Count; i++) { + string item_text = auto_complete_cb_source == null ? auto_complete_custom_source [i] : + auto_complete_cb_source.GetItemText (auto_complete_cb_source.Items [i]); + if (item_text.StartsWith (text, StringComparison.CurrentCultureIgnoreCase)) + auto_complete_matches.Add (item_text); + } + + auto_complete_matches.Sort (); + + // Return if we have a single exact match + if ((auto_complete_matches.Count == 0) || (auto_complete_matches.Count == 1 && + auto_complete_matches [0].Equals (text, StringComparison.CurrentCultureIgnoreCase))) { + + if (auto_complete_listbox != null && auto_complete_listbox.Visible) + auto_complete_listbox.HideListBox (false); + return; + } + + auto_complete_selected_index = suggest ? -1 : 0; + + if (suggest) { + if (auto_complete_listbox == null) + auto_complete_listbox = new AutoCompleteListBox (this); + + // Show or update auto complete listbox contents + auto_complete_listbox.Location = PointToScreen (new Point (0, Height)); + auto_complete_listbox.ShowListBox (); + } + + if (append && !deleting_chars) + AppendAutoCompleteMatch (0); + + document.MoveCaret (CaretDirection.End); + } + + internal void HideAutoCompleteList () + { + if (auto_complete_listbox != null) + auto_complete_listbox.HideListBox (false); + } + + internal bool IsAutoCompleteAvailable { + get { + if (auto_complete_source == AutoCompleteSource.None || auto_complete_mode == AutoCompleteMode.None) + return false; + + // We only support CustomSource by now, as well as an internal custom source used by ComboBox + if (auto_complete_source != AutoCompleteSource.CustomSource) + return false; + IList custom_source = auto_complete_cb_source == null ? auto_complete_custom_source : (IList)auto_complete_cb_source.Items; + if (custom_source == null || custom_source.Count == 0) + return false; + + return true; + } + } + + internal ComboBox AutoCompleteInternalSource { + get { + return auto_complete_cb_source; + } + set { + auto_complete_cb_source = value; + } + } + + internal bool CanNavigateAutoCompleteList { + get { + if (auto_complete_mode == AutoCompleteMode.None) + return false; + if (auto_complete_matches == null || auto_complete_matches.Count == 0) + return false; + + bool suggest_window_visible = auto_complete_listbox != null && auto_complete_listbox.Visible; + if (auto_complete_mode == AutoCompleteMode.Suggest && !suggest_window_visible) + return false; + + return true; + } + } + + bool NavigateAutoCompleteList (Keys key) + { + if (auto_complete_matches == null || auto_complete_matches.Count == 0) + return false; + + bool suggest_window_visible = auto_complete_listbox != null && auto_complete_listbox.Visible; + if (!suggest_window_visible && auto_complete_mode == AutoCompleteMode.Suggest) + return false; + + int index = auto_complete_selected_index; + + switch (key) { + case Keys.Up: + index -= 1; + if (index < -1) + index = auto_complete_matches.Count - 1; + break; + case Keys.Down: + index += 1; + if (index >= auto_complete_matches.Count) + index = -1; + break; + case Keys.PageUp: + if (auto_complete_mode == AutoCompleteMode.Append || !suggest_window_visible) + goto case Keys.Up; + + if (index == -1) + index = auto_complete_matches.Count - 1; + else if (index == 0) + index = -1; + else { + index -= auto_complete_listbox.page_size - 1; + if (index < 0) + index = 0; + } + break; + case Keys.PageDown: + if (auto_complete_mode == AutoCompleteMode.Append || !suggest_window_visible) + goto case Keys.Down; + + if (index == -1) + index = 0; + else if (index == auto_complete_matches.Count - 1) + index = -1; + else { + index += auto_complete_listbox.page_size - 1; + if (index >= auto_complete_matches.Count) + index = auto_complete_matches.Count - 1; + } + break; + default: + break; + } + + // In SuggestAppend mode the navigation mode depends on the visibility of the suggest lb. + bool suggest = auto_complete_mode == AutoCompleteMode.Suggest || auto_complete_mode == AutoCompleteMode.SuggestAppend; + if (suggest && suggest_window_visible) { + Text = index == -1 ? auto_complete_original_text : auto_complete_matches [index]; + auto_complete_listbox.HighlightedIndex = index; + } else + // Append only, not suggest at all + AppendAutoCompleteMatch (index < 0 ? 0 : index); + + auto_complete_selected_index = index; + document.MoveCaret (CaretDirection.End); + + return true; + } + + void AppendAutoCompleteMatch (int index) + { + Text = auto_complete_original_text + auto_complete_matches [index].Substring (auto_complete_original_text.Length); + SelectionStart = auto_complete_original_text.Length; + SelectionLength = auto_complete_matches [index].Length - auto_complete_original_text.Length; + } + + // this is called when the user selects a value from the autocomplete list + // *with* the mouse + internal virtual void OnAutoCompleteValueSelected (EventArgs args) + { + } + + private void UpdateAlignment () + { + HorizontalAlignment new_alignment = alignment; + RightToLeft rtol = GetInheritedRtoL (); + + if (rtol == RightToLeft.Yes) { + if (new_alignment == HorizontalAlignment.Left) + new_alignment = HorizontalAlignment.Right; + else if (new_alignment == HorizontalAlignment.Right) + new_alignment = HorizontalAlignment.Left; + } + + document.alignment = new_alignment; + + // MS word-wraps if alignment isn't left + if (Multiline) { + if (alignment != HorizontalAlignment.Left) { + document.Wrap = true; + } else { + document.Wrap = word_wrap; + } + } + + for (int i = 1; i <= document.Lines; i++) { + document.GetLine (i).Alignment = new_alignment; + } + + document.RecalculateDocument (CreateGraphicsInternal ()); + + Invalidate (); // Make sure we refresh + } + + internal override Color ChangeBackColor (Color backColor) + { + if (backColor == Color.Empty) { + if (!ReadOnly) + backColor = SystemColors.Window; + + backcolor_set = false; + } + + return backColor; + } + + void OnAutoCompleteCustomSourceChanged(object sender, CollectionChangeEventArgs e) { + if(auto_complete_source == AutoCompleteSource.CustomSource) { + //FIXME: handle add, remove and refresh events in AutoComplete algorithm. + } + } + #endregion // Private & Internal Methods + + #region Public Instance Properties + [MonoTODO("AutoCompletion algorithm is currently not implemented.")] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Content)] + [Browsable (true)] + //[EditorBrowsable (EditorBrowsableState.Always)] + [Localizable (true)] + //[Editor ("ShiftUI.Design.ListWidgetStringCollectionEditor, " + Consts.AssemblySystem_Design, + //"System.Drawing.Design.UITypeEditor, " + Consts.AssemblySystem_Drawing)] + public AutoCompleteStringCollection AutoCompleteCustomSource { + get { + if(auto_complete_custom_source == null) { + auto_complete_custom_source = new AutoCompleteStringCollection (); + auto_complete_custom_source.CollectionChanged += new CollectionChangeEventHandler (OnAutoCompleteCustomSourceChanged); + } + return auto_complete_custom_source; + } + set { + if(auto_complete_custom_source == value) + return; + + if(auto_complete_custom_source != null) //remove eventhandler from old collection + auto_complete_custom_source.CollectionChanged -= new CollectionChangeEventHandler (OnAutoCompleteCustomSourceChanged); + + auto_complete_custom_source = value; + + if(auto_complete_custom_source != null) + auto_complete_custom_source.CollectionChanged += new CollectionChangeEventHandler (OnAutoCompleteCustomSourceChanged); + } + } + + [MonoTODO("AutoCompletion algorithm is currently not implemented.")] + [Browsable (true)] + //[EditorBrowsable (EditorBrowsableState.Always)] + [DefaultValue (AutoCompleteMode.None)] + public AutoCompleteMode AutoCompleteMode { + get { return auto_complete_mode; } + set { + if(auto_complete_mode == value) + return; + + if((value < AutoCompleteMode.None) || (value > AutoCompleteMode.SuggestAppend)) + throw new InvalidEnumArgumentException (String.Format ("Enum argument value '{0}' is not valid for AutoCompleteMode", value)); + + auto_complete_mode = value; + } + } + + [MonoTODO("AutoCompletion algorithm is currently not implemented.")] + [Browsable (true)] + //[EditorBrowsable (EditorBrowsableState.Always)] + [DefaultValue (AutoCompleteSource.None)] + [TypeConverter (typeof (TextBoxAutoCompleteSourceConverter))] + public AutoCompleteSource AutoCompleteSource { + get { return auto_complete_source; } + set { + if(auto_complete_source == value) + return; + + if(!Enum.IsDefined (typeof (AutoCompleteSource), value)) + throw new InvalidEnumArgumentException (String.Format ("Enum argument value '{0}' is not valid for AutoCompleteSource", value)); + + auto_complete_source = value; + } + } + + [DefaultValue(false)] + [RefreshProperties (RefreshProperties.Repaint)] + public bool UseSystemPasswordChar { + get { + return use_system_password_char; + } + + set { + if (use_system_password_char != value) { + use_system_password_char = value; + + if (!Multiline) + document.PasswordChar = PasswordChar.ToString (); + else + document.PasswordChar = string.Empty; + Invalidate (); + } + } + } + + [DefaultValue(false)] + [MWFCategory("Behavior")] + public bool AcceptsReturn { + get { + return accepts_return; + } + + set { + if (value != accepts_return) { + accepts_return = value; + } + } + } + + [DefaultValue(CharacterCasing.Normal)] + [MWFCategory("Behavior")] + public CharacterCasing CharacterCasing { + get { + return character_casing; + } + + set { + if (value != character_casing) { + character_casing = value; + } + } + } + + [Localizable(true)] + [DefaultValue('\0')] + [MWFCategory("Behavior")] + [RefreshProperties (RefreshProperties.Repaint)] + public char PasswordChar { + get { + if (use_system_password_char) { + return '*'; + } + return password_char; + } + + set { + if (value != password_char) { + password_char = value; + if (!Multiline) { + document.PasswordChar = PasswordChar.ToString (); + } else { + document.PasswordChar = string.Empty; + } + this.CalculateDocument(); + } + } + } + + [DefaultValue(ScrollBars.None)] + [Localizable(true)] + [MWFCategory("Appearance")] + public ScrollBars ScrollBars { + get { + return (ScrollBars)scrollbars; + } + + set { + if (!Enum.IsDefined (typeof (ScrollBars), value)) + throw new InvalidEnumArgumentException ("value", (int) value, + typeof (ScrollBars)); + + if (value != (ScrollBars)scrollbars) { + scrollbars = (RichTextBoxScrollBars)value; + base.CalculateScrollBars(); + } + } + } + + public override string Text { + get { + return base.Text; + } + + set { + base.Text = value; + } + } + + [DefaultValue(HorizontalAlignment.Left)] + [Localizable(true)] + [MWFCategory("Appearance")] + public HorizontalAlignment TextAlign { + get { + return alignment; + } + + set { + if (value != alignment) { + alignment = value; + + UpdateAlignment (); + + OnTextAlignChanged(EventArgs.Empty); + } + } + } + #endregion // Public Instance Properties + + public void Paste (string text) + { + document.ReplaceSelection (CaseAdjust (text), false); + + ScrollToCaret(); + OnTextChanged(EventArgs.Empty); + } + #region Protected Instance Methods + protected override CreateParams CreateParams { + get { + return base.CreateParams; + } + } + + protected override void Dispose (bool disposing) + { + base.Dispose (disposing); + } + + protected override bool IsInputKey (Keys keyData) + { + return base.IsInputKey (keyData); + } + + protected override void OnGotFocus (EventArgs e) + { + base.OnGotFocus (e); + if (selection_length == -1 && !has_been_focused) + SelectAllNoScroll (); + has_been_focused = true; + } + + protected override void OnHandleCreated (EventArgs e) + { + base.OnHandleCreated (e); + } + + protected virtual void OnTextAlignChanged (EventArgs e) + { + EventHandler eh = (EventHandler)(Events [TextAlignChangedEvent]); + if (eh != null) + eh (this, e); + } + + protected override void WndProc (ref Message m) + { + switch ((Msg)m.Msg) { + case Msg.WM_KEYDOWN: + if (!IsAutoCompleteAvailable) + break; + + Keys key_data = (Keys)m.WParam.ToInt32 (); + switch (key_data) { + case Keys.Down: + case Keys.Up: + case Keys.PageDown: + case Keys.PageUp: + if (NavigateAutoCompleteList (key_data)) { + m.Result = IntPtr.Zero; + return; + } + break; + case Keys.Enter: + if (auto_complete_listbox != null && auto_complete_listbox.Visible) + auto_complete_listbox.HideListBox (false); + SelectAll (); + break; + case Keys.Escape: + if (auto_complete_listbox != null && auto_complete_listbox.Visible) + auto_complete_listbox.HideListBox (false); + break; + case Keys.Delete: + ProcessAutoCompleteInput (ref m, true); + return; + default: + break; + } + break; + case Msg.WM_CHAR: + if (!IsAutoCompleteAvailable) + break; + + // Don't handle either Enter or Esc - they are handled in the WM_KEYDOWN case + int char_value = m.WParam.ToInt32 (); + if (char_value == 13 || char_value == 27) + break; + + ProcessAutoCompleteInput (ref m, char_value == 8); + return; + case Msg.WM_LBUTTONDOWN: + // When the textbox gets focus by LBUTTON (but not by middle or right) + // it does not do the select all / scroll thing. + has_been_focused = true; + FocusInternal (true); + break; + } + + base.WndProc(ref m); + } + #endregion // Protected Instance Methods + + #region Events + static object TextAlignChangedEvent = new object (); + + public event EventHandler TextAlignChanged { + add { Events.AddHandler (TextAlignChangedEvent, value); } + remove { Events.RemoveHandler (TextAlignChangedEvent, value); } + } + #endregion // Events + + #region Private Methods + + + + internal void RestoreContextMenu () + { + //ContextMenuInternal = menu; + } + + private void menu_Popup(object sender, EventArgs e) { + if (SelectionLength == 0) { + cut.Enabled = false; + copy.Enabled = false; + } else { + cut.Enabled = true; + copy.Enabled = true; + } + + if (SelectionLength == TextLength) { + select_all.Enabled = false; + } else { + select_all.Enabled = true; + } + + if (!CanUndo) { + undo.Enabled = false; + } else { + undo.Enabled = true; + } + + if (ReadOnly) { + undo.Enabled = cut.Enabled = paste.Enabled = delete.Enabled = false; + } + } + + private void undo_Click(object sender, EventArgs e) { + Undo(); + } + + private void cut_Click(object sender, EventArgs e) { + Cut(); + } + + private void copy_Click(object sender, EventArgs e) { + Copy(); + } + + private void paste_Click(object sender, EventArgs e) { + Paste(); + } + + private void delete_Click(object sender, EventArgs e) { + SelectedText = string.Empty; + } + + private void select_all_Click(object sender, EventArgs e) { + SelectAll(); + } + #endregion // Private Methods + + public override bool Multiline { + get { + return base.Multiline; + } + + set { + base.Multiline = value; + } + } + + protected override void OnBackColorChanged (EventArgs e) + { + base.OnBackColorChanged (e); + } + + protected override void OnFontChanged (EventArgs e) + { + base.OnFontChanged (e); + } + + protected override void OnHandleDestroyed (EventArgs e) + { + base.OnHandleDestroyed (e); + } + + class AutoCompleteListBox : Widget + { + TextBox owner; + VScrollBar vscroll; + int top_item; + int last_item; + internal int page_size; + int item_height; + int highlighted_index = -1; + bool user_defined_size; + bool resizing; + Rectangle resizer_bounds; + + const int DefaultDropDownItems = 7; + + public AutoCompleteListBox (TextBox tb) + { + owner = tb; + item_height = FontHeight + 2; + + vscroll = new VScrollBar (); + vscroll.ValueChanged += VScrollValueChanged; + Widgets.Add (vscroll); + + is_visible = false; + InternalBorderStyle = BorderStyle.FixedSingle; + } + + protected override CreateParams CreateParams { + get { + CreateParams cp = base.CreateParams; + + cp.Style ^= (int)WindowStyles.WS_CHILD; + cp.Style ^= (int)WindowStyles.WS_VISIBLE; + cp.Style |= (int)WindowStyles.WS_POPUP; + cp.ExStyle |= (int)WindowExStyles.WS_EX_TOPMOST | (int)WindowExStyles.WS_EX_TOOLWINDOW; + return cp; + } + } + + public int HighlightedIndex { + get { + return highlighted_index; + } + set { + if (value == highlighted_index) + return; + + if (highlighted_index != -1) + Invalidate (GetItemBounds (highlighted_index)); + highlighted_index = value; + if (highlighted_index != -1) + Invalidate (GetItemBounds (highlighted_index)); + + if (highlighted_index != -1) + EnsureVisible (highlighted_index); + } + } + + public void Scroll (int lines) + { + int max = vscroll.Maximum - page_size + 1; + int val = vscroll.Value + lines; + if (val > max) + val = max; + else if (val < vscroll.Minimum) + val = vscroll.Minimum; + + vscroll.Value = val; + } + + public void EnsureVisible (int index) + { + if (index < top_item) { + vscroll.Value = index; + } else { + int max = vscroll.Maximum - page_size + 1; + int rows = Height / item_height; + if (index > top_item + rows - 1) { + index = index - rows + 1; + vscroll.Value = index > max ? max : index; + } + } + } + + internal override bool ActivateOnShow { + get { + return false; + } + } + + void VScrollValueChanged (object o, EventArgs args) + { + if (top_item == vscroll.Value) + return; + + top_item = vscroll.Value; + last_item = GetLastVisibleItem (); + Invalidate (); + } + + int GetLastVisibleItem () + { + int top_y = Height; + + for (int i = top_item; i < owner.auto_complete_matches.Count; i++) { + int pos = i - top_item; // relative to visible area + if ((pos * item_height) + item_height >= top_y) + return i; + } + + return owner.auto_complete_matches.Count - 1; + } + + Rectangle GetItemBounds (int index) + { + int pos = index - top_item; + Rectangle bounds = new Rectangle (0, pos * item_height, Width, item_height); + if (vscroll.Visible) + bounds.Width -= vscroll.Width; + + return bounds; + } + + int GetItemAt (Point loc) + { + if (loc.Y > (last_item - top_item) * item_height + item_height) + return -1; + + int retval = loc.Y / item_height; + retval += top_item; + + return retval; + } + + void LayoutListBox () + { + int total_height = owner.auto_complete_matches.Count * item_height; + page_size = Math.Max (Height / item_height, 1); + last_item = GetLastVisibleItem (); + + if (Height < total_height) { + vscroll.Visible = true; + vscroll.Maximum = owner.auto_complete_matches.Count - 1; + vscroll.LargeChange = page_size; + vscroll.Location = new Point (Width - vscroll.Width, 0); + vscroll.Height = Height - item_height; + } else + vscroll.Visible = false; + + resizer_bounds = new Rectangle (Width - item_height, Height - item_height, + item_height, item_height); + } + + public void HideListBox (bool set_text) + { + if (set_text) + owner.Text = owner.auto_complete_matches [HighlightedIndex]; + + Capture = false; + Hide (); + } + + public void ShowListBox () + { + if (!user_defined_size) { + // This should call the Layout routine for us + int height = owner.auto_complete_matches.Count > DefaultDropDownItems ? + DefaultDropDownItems * item_height : (owner.auto_complete_matches.Count + 1) * item_height; + Size = new Size (owner.Width, height); + } else + LayoutListBox (); + + vscroll.Value = 0; + HighlightedIndex = -1; + + Show (); + // make sure we are on top - call the raw routine, since we are parentless + XplatUI.SetZOrder (Handle, IntPtr.Zero, true, false); + Invalidate (); + } + + protected override void OnResize (EventArgs args) + { + base.OnResize (args); + + LayoutListBox (); + Refresh (); + } + + protected override void OnMouseDown (MouseEventArgs args) + { + base.OnMouseDown (args); + + if (!resizer_bounds.Contains (args.Location)) + return; + + user_defined_size = true; + resizing = true; + Capture = true; + } + + protected override void OnMouseMove (MouseEventArgs args) + { + base.OnMouseMove (args); + + if (resizing) { + Point mouse_loc = Widget.MousePosition; + Point ctrl_loc = PointToScreen (Point.Empty); + + Size new_size = new Size (mouse_loc.X - ctrl_loc.X, mouse_loc.Y - ctrl_loc.Y); + if (new_size.Height < item_height) + new_size.Height = item_height; + if (new_size.Width < item_height) + new_size.Width = item_height; + + Size = new_size; + return; + } + + Cursor = resizer_bounds.Contains (args.Location) ? Cursors.SizeNWSE : Cursors.Default; + + int item_idx = GetItemAt (args.Location); + if (item_idx != -1) + HighlightedIndex = item_idx; + } + + protected override void OnMouseUp (MouseEventArgs args) + { + base.OnMouseUp (args); + + int item_idx = GetItemAt (args.Location); + if (item_idx != -1 && !resizing) + HideListBox (true); + + owner.OnAutoCompleteValueSelected (EventArgs.Empty); // internal + resizing = false; + Capture = false; + } + + internal override void OnPaintInternal (PaintEventArgs args) + { + Graphics g = args.Graphics; + Brush brush = ThemeEngine.Current.ResPool.GetSolidBrush (ForeColor); + + int highlighted_idx = HighlightedIndex; + + int y = 0; + int last = GetLastVisibleItem (); + for (int i = top_item; i <= last; i++) { + Rectangle item_bounds = GetItemBounds (i); + if (!item_bounds.IntersectsWith (args.ClipRectangle)) + continue; + + if (i == highlighted_idx) { + g.FillRectangle (SystemBrushes.Highlight, item_bounds); + g.DrawString (owner.auto_complete_matches [i], Font, SystemBrushes.HighlightText, item_bounds); + } else + g.DrawString (owner.auto_complete_matches [i], Font, brush, item_bounds); + + y += item_height; + } + + ThemeEngine.Current.CPDrawSizeGrip (g, SystemColors.Control, resizer_bounds); + } + } + } + + internal class TextBoxAutoCompleteSourceConverter : EnumConverter + { + public TextBoxAutoCompleteSourceConverter(Type type) + : base(type) + { } + + public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) + { + StandardValuesCollection stdv = base.GetStandardValues(context); + AutoCompleteSource[] arr = new AutoCompleteSource[stdv.Count]; + stdv.CopyTo(arr, 0); + AutoCompleteSource[] arr2 = Array.FindAll(arr, delegate (AutoCompleteSource value) { + // No "ListItems" in a TextBox. + return value != AutoCompleteSource.ListItems; + }); + return new StandardValuesCollection(arr2); + } + } +} diff --git a/source/ShiftUI/Widgets/TextBoxBase.cs b/source/ShiftUI/Widgets/TextBoxBase.cs new file mode 100644 index 0000000..aca2dee --- /dev/null +++ b/source/ShiftUI/Widgets/TextBoxBase.cs @@ -0,0 +1,2493 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions:c +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2004-2006 Novell, Inc. (http://www.novell.com) +// +// Authors: +// Peter Bartok [email protected] +// +// + +// NOT COMPLETE + + +#undef Debug +#undef DebugClick + +using System; +using System.ComponentModel; +using System.ComponentModel.Design; +using System.Drawing; +using System.Drawing.Text; +using System.Text; +using System.Runtime.InteropServices; +using System.Collections; + +namespace ShiftUI +{ + [ComVisible (true)] + [DefaultBindingProperty ("Text")] + [ClassInterface (ClassInterfaceType.AutoDispatch)] + [DefaultEvent("TextChanged")] + //[Designer("ShiftUI.Design.TextBoxBaseDesigner, " + Consts.AssemblySystem_Design)] + public abstract class TextBoxBase : Widget + { + #region Local Variables + internal HorizontalAlignment alignment; + internal bool accepts_tab; + internal bool accepts_return; + internal bool auto_size; + internal bool backcolor_set; + internal CharacterCasing character_casing; + internal bool hide_selection; + int max_length; + internal bool modified; + internal char password_char; + internal bool read_only; + internal bool word_wrap; + internal Document document; + internal LineTag caret_tag; // tag our cursor is in + internal int caret_pos; // position on the line our cursor is in (can be 0 = beginning of line) + internal ImplicitHScrollBar hscroll; + internal ImplicitVScrollBar vscroll; + internal RichTextBoxScrollBars scrollbars; + internal Timer scroll_timer; + internal bool richtext; + internal bool show_selection; // set to true to always show selection, even if no focus is set + internal ArrayList list_links; // currently showing links + private LinkRectangle current_link; // currently hovering link + private bool enable_links; // whether links are enabled + + internal bool has_been_focused; + + internal int selection_length = -1; // set to the user-specified selection length, or -1 if none + internal bool show_caret_w_selection; // TextBox shows the caret when the selection is visible + internal int canvas_width; + internal int canvas_height; + static internal int track_width = 2; // + static internal int track_border = 5; // + internal DateTime click_last; + internal int click_point_x; + internal int click_point_y; + internal CaretSelection click_mode; + internal BorderStyle actual_border_style; + internal bool shortcuts_enabled = true; + #if Debug + internal static bool draw_lines = false; + #endif + + #endregion // Local Variables + + #region Internal Constructor + // Constructor will go when complete, only for testing - pdb + internal TextBoxBase () + { + alignment = HorizontalAlignment.Left; + accepts_return = false; + accepts_tab = false; + auto_size = true; + InternalBorderStyle = BorderStyle.Fixed3D; + actual_border_style = BorderStyle.Fixed3D; + character_casing = CharacterCasing.Normal; + hide_selection = true; + max_length = short.MaxValue; + password_char = '\0'; + read_only = false; + word_wrap = true; + richtext = false; + show_selection = false; + enable_links = false; + list_links = new ArrayList (); + current_link = null; + show_caret_w_selection = (this is TextBox); + document = new Document(this); + document.WidthChanged += new EventHandler(document_WidthChanged); + document.HeightChanged += new EventHandler(document_HeightChanged); + //document.CaretMoved += new EventHandler(CaretMoved); + document.Wrap = false; + click_last = DateTime.Now; + click_mode = CaretSelection.Position; + + MouseDown += new MouseEventHandler(TextBoxBase_MouseDown); + MouseUp += new MouseEventHandler(TextBoxBase_MouseUp); + MouseMove += new MouseEventHandler(TextBoxBase_MouseMove); + SizeChanged += new EventHandler(TextBoxBase_SizeChanged); + FontChanged += new EventHandler(TextBoxBase_FontOrColorChanged); + ForeColorChanged += new EventHandler(TextBoxBase_FontOrColorChanged); + MouseWheel += new MouseEventHandler(TextBoxBase_MouseWheel); + RightToLeftChanged += new EventHandler (TextBoxBase_RightToLeftChanged); + + scrollbars = RichTextBoxScrollBars.None; + + hscroll = new ImplicitHScrollBar(); + hscroll.ValueChanged += new EventHandler(hscroll_ValueChanged); + hscroll.SetStyle (Widgetstyles.Selectable, false); + hscroll.Enabled = false; + hscroll.Visible = false; + hscroll.Maximum = Int32.MaxValue; + + vscroll = new ImplicitVScrollBar(); + vscroll.ValueChanged += new EventHandler(vscroll_ValueChanged); + vscroll.SetStyle (Widgetstyles.Selectable, false); + vscroll.Enabled = false; + vscroll.Visible = false; + vscroll.Maximum = Int32.MaxValue; + + SuspendLayout (); + this.Widgets.AddImplicit (hscroll); + this.Widgets.AddImplicit (vscroll); + ResumeLayout (); + + SetStyle(Widgetstyles.UserPaint | Widgetstyles.StandardClick, false); + SetStyle(Widgetstyles.UseTextForAccessibility, false); + + base.SetAutoSizeMode (AutoSizeMode.GrowAndShrink); + + canvas_width = ClientSize.Width; + canvas_height = ClientSize.Height; + document.ViewPortWidth = canvas_width; + document.ViewPortHeight = canvas_height; + + Cursor = Cursors.IBeam; + } + #endregion // Internal Constructor + + #region Private and Internal Methods + internal string CaseAdjust (string s) + { + if (character_casing == CharacterCasing.Normal) + return s; + if (character_casing == CharacterCasing.Lower) + return s.ToLower(); + return s.ToUpper(); + } + + internal override Size GetPreferredSizeCore (Size proposedSize) + { + return new Size (Width, Height); + } + + internal override void HandleClick (int clicks, MouseEventArgs me) + { + // MS seems to fire the click event in spite of the styles they set + bool click_set = GetStyle (Widgetstyles.StandardClick); + bool doubleclick_set = GetStyle (Widgetstyles.StandardDoubleClick); + + // so explicitly set them to true first + SetStyle (Widgetstyles.StandardClick | Widgetstyles.StandardDoubleClick, true); + + base.HandleClick (clicks, me); + + // then revert to our previous state + if (!click_set) + SetStyle (Widgetstyles.StandardClick, false); + if (!doubleclick_set) + SetStyle (Widgetstyles.StandardDoubleClick, false); + } + + internal override void PaintWidgetBackground (PaintEventArgs pevent) + { + if (!ThemeEngine.Current.TextBoxBaseShouldPaintBackground (this)) + return; + base.PaintWidgetBackground (pevent); + } + #endregion // Private and Internal Methods + + #region Public Instance Properties + [DefaultValue(false)] + [MWFCategory("Behavior")] + public bool AcceptsTab { + get { + return accepts_tab; + } + + set { + if (value != accepts_tab) { + accepts_tab = value; + OnAcceptsTabChanged(EventArgs.Empty); + } + } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + [DefaultValue(true)] + [Localizable(true)] + [RefreshProperties(RefreshProperties.Repaint)] + [MWFCategory("Behavior")] + public override bool AutoSize { + get { + return auto_size; + } + + set { + if (value != auto_size) { + auto_size = value; + if (auto_size) { + if (PreferredHeight != Height) { + Height = PreferredHeight; + } + } + } + } + } + + [DispId(-501)] + public override System.Drawing.Color BackColor { + get { + return base.BackColor; + } + set { + backcolor_set = true; + base.BackColor = ChangeBackColor (value); + } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public override System.Drawing.Image BackgroundImage { + get { + return base.BackgroundImage; + } + set { + base.BackgroundImage = value; + } + } + + [DefaultValue(BorderStyle.Fixed3D)] + [DispId(-504)] + [MWFCategory("Appearance")] + public BorderStyle BorderStyle { + get { return actual_border_style; } + set { + if (value == actual_border_style) + return; + + if (actual_border_style != BorderStyle.Fixed3D || value != BorderStyle.Fixed3D) + Invalidate (); + + actual_border_style = value; + document.UpdateMargins (); + + if (value != BorderStyle.Fixed3D) + value = BorderStyle.None; + + InternalBorderStyle = value; + OnBorderStyleChanged(EventArgs.Empty); + } + } + + [Browsable(false)] + //[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool CanUndo { + get { + return document.undo.CanUndo; + } + } + + [DispId(-513)] + public override System.Drawing.Color ForeColor { + get { + return base.ForeColor; + } + set { + base.ForeColor = value; + } + } + + [DefaultValue(true)] + [MWFCategory("Behavior")] + public bool HideSelection { + get { + return hide_selection; + } + + set { + if (value != hide_selection) { + hide_selection = value; + OnHideSelectionChanged(EventArgs.Empty); + } + document.selection_visible = !hide_selection; + document.InvalidateSelectionArea(); + } + } + + [MergableProperty (false)] + //[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + //[Editor("ShiftUI.Design.StringArrayEditor, " + Consts.AssemblySystem_Design, typeof(System.Drawing.Design.UITypeEditor))] + [Localizable(true)] + [MWFCategory("Appearance")] + public string[] Lines { + get { + int count; + ArrayList lines; + + count = document.Lines; + + // Handle empty document + if ((count == 1) && (document.GetLine (1).text.Length == 0)) { + return new string [0]; + } + + lines = new ArrayList (); + + int i = 1; + while (i <= count) { + Line line; + StringBuilder lt = new StringBuilder (); + + do { + line = document.GetLine (i++); + lt.Append (line.TextWithoutEnding ()); + } while (line.ending == LineEnding.Wrap && i <= count); + + lines.Add (lt.ToString ()); + } + + return (string []) lines.ToArray (typeof (string)); + } + + set { + StringBuilder sb = new StringBuilder (); + + for (int i = 0; i < value.Length; i++) { + // Don't add the last line if it is just an empty line feed + // the line feed is reflected in the previous line's ending + if (i == value.Length - 1 && value[i].Length == 0) + break; + + sb.Append (value[i] + Environment.NewLine); + } + + int newline_length = Environment.NewLine.Length; + + // We want to remove the final new line character + if (sb.Length >= newline_length) + sb.Remove (sb.Length - newline_length, newline_length); + + Text = sb.ToString (); + } + } + + [DefaultValue(32767)] + [Localizable(true)] + [MWFCategory("Behavior")] + public virtual int MaxLength { + get { + if (max_length == (int.MaxValue - 1)) { // We don't distinguish between single and multi-line limits + return 0; + } + return max_length; + } + + set { + if (value != max_length) { + if (value == 0) + value = int.MaxValue - 1; + + max_length = value; + } + } + } + + [Browsable(false)] + //[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool Modified { + get { + return modified; + } + + set { + if (value != modified) { + modified = value; + OnModifiedChanged(EventArgs.Empty); + } + } + } + + [DefaultValue(false)] + [Localizable(true)] + [RefreshProperties(RefreshProperties.All)] + [MWFCategory("Behavior")] + public virtual bool Multiline { + get { + return document.multiline; + } + + set { + if (value != document.multiline) { + document.multiline = value; + + if (this is TextBox) + SetStyle (Widgetstyles.FixedHeight, !value); + + // SetBoundsCore overrides the Height for multiline if it needs to, + // so we don't need to worry about it here. + SetBoundsCore (Left, Top, Width, ExplicitBounds.Height, BoundsSpecified.None); + + if (Parent != null) + Parent.PerformLayout (); + + OnMultilineChanged(EventArgs.Empty); + } + + if (document.multiline) { + document.Wrap = word_wrap; + document.PasswordChar = ""; + + } else { + document.Wrap = false; + if (this.password_char != '\0') { + if (this is TextBox) + document.PasswordChar = (this as TextBox).PasswordChar.ToString (); + } else { + document.PasswordChar = ""; + } + } + + if (IsHandleCreated) + CalculateDocument (); + } + } + + [Browsable(false)] + //[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + //[EditorBrowsable(EditorBrowsableState.Advanced)] + // This returns the preferred outer height, not the client height. + public int PreferredHeight { + get { + int clientDelta = Height - ClientSize.Height; + if (BorderStyle != BorderStyle.None) + return Font.Height + 7 + clientDelta; + + // usually in borderless mode the top margin is 0, but + // try to access it, in case it was set manually, as ToolStrip* Widgets do + return Font.Height + TopMargin + clientDelta; + } + } + + [RefreshProperties (RefreshProperties.Repaint)] + [DefaultValue(false)] + [MWFCategory("Behavior")] + public bool ReadOnly { + get { + return read_only; + } + + set { + if (value != read_only) { + read_only = value; + if (!backcolor_set) { + if (read_only) + background_color = SystemColors.Control; + else + background_color = SystemColors.Window; + } + OnReadOnlyChanged(EventArgs.Empty); + Invalidate (); + } + } + } + + [Browsable(false)] + //[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual string SelectedText { + get { + string retval = document.GetSelection (); + + return retval; + } + + set { + if (value == null) + value = String.Empty; + + document.ReplaceSelection(CaseAdjust(value), false); + + ScrollToCaret(); + OnTextChanged(EventArgs.Empty); + } + } + + [Browsable(false)] + //[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual int SelectionLength { + get { + int res = document.SelectionLength (); + + return res; + } + + set { + if (value < 0) { + string msg = String.Format ("'{0}' is not a valid value for 'SelectionLength'", value); + throw new ArgumentOutOfRangeException ("SelectionLength", msg); + } + + document.InvalidateSelectionArea (); + if (value != 0) { + int start; + Line line; + LineTag tag; + int pos; + + selection_length = value; + start = document.LineTagToCharIndex (document.selection_start.line, document.selection_start.pos); + document.CharIndexToLineTag (start + value, out line, out tag, out pos); + document.SetSelectionEnd (line, pos, true); + document.PositionCaret (line, pos); + } else { + selection_length = -1; + document.SetSelectionEnd (document.selection_start.line, document.selection_start.pos, true); + document.PositionCaret (document.selection_start.line, document.selection_start.pos); + } + } + } + + [Browsable(false)] + //[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public int SelectionStart { + get { + return document.LineTagToCharIndex(document.selection_start.line, + document.selection_start.pos); + } + + set { + if (value < 0) { + string msg = String.Format ("'{0}' is not a valid value for 'SelectionStart'", value); + throw new ArgumentOutOfRangeException ("SelectionStart", msg); + } + + // If SelectionStart has been used, we don't highlight on focus + has_been_focused = true; + + document.InvalidateSelectionArea (); + document.SetSelectionStart (value, false); + if (selection_length > -1) + document.SetSelectionEnd (value + selection_length, true); + else + document.SetSelectionEnd (value, true); + document.PositionCaret (document.selection_start.line, document.selection_start.pos); + ScrollToCaret (); + } + } + + [DefaultValue (true)] + public virtual bool ShortcutsEnabled { + get { return shortcuts_enabled; } + set { shortcuts_enabled = value; } + } + + //[Editor ("System.ComponentModel.Design.MultilineStringEditor, " + Consts.AssemblySystem_Design, + //"System.Drawing.Design.UITypeEditor, " + Consts.AssemblySystem_Drawing)] + [Localizable(true)] + public override string Text { + get { + if (document == null || document.Root == null || document.Root.text == null) + return string.Empty; + + StringBuilder sb = new StringBuilder(); + + Line line = null; + for (int i = 1; i <= document.Lines; i++) { + line = document.GetLine (i); + sb.Append(line.text.ToString ()); + } + + return sb.ToString(); + } + + set { + // reset to force a select all next time the box gets focus + has_been_focused = false; + + if (value == Text) + return; + + document.Empty (); + if ((value != null) && (value != "")) { + document.Insert (document.GetLine (1), 0, false, value); + } else { + if (IsHandleCreated) { + document.SetSelectionToCaret (true); + CalculateDocument (); + } + } + + document.PositionCaret (document.GetLine (1), 0); + document.SetSelectionToCaret (true); + + ScrollToCaret (); + + OnTextChanged(EventArgs.Empty); + } + } + + [Browsable(false)] + public virtual int TextLength { + get { + if (document == null || document.Root == null || document.Root.text == null) + return 0; + return Text.Length; + } + } + + [DefaultValue(true)] + [Localizable(true)] + [MWFCategory("Behavior")] + public bool WordWrap { + get { + return word_wrap; + } + + set { + if (value != word_wrap) { + if (document.multiline) { + word_wrap = value; + document.Wrap = value; + } + CalculateDocument (); + } + } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public override ImageLayout BackgroundImageLayout { + get { return base.BackgroundImageLayout; } + set { base.BackgroundImageLayout = value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public new Padding Padding { + get { return base.Padding; } + set { base.Padding = value; } + } + + protected override Cursor DefaultCursor { + get { return Cursors.IBeam; } + } + #endregion // Public Instance Properties + + #region Protected Instance Properties + protected override bool CanEnableIme { + get { + if (ReadOnly || password_char != '\0') + return false; + + return true; + } + } + + protected override CreateParams CreateParams { + get { + return base.CreateParams; + } + } + + protected override System.Drawing.Size DefaultSize { + get { + return new Size(100, 20); + } + } + + // Currently our double buffering breaks our scrolling, so don't let people enable this + //[EditorBrowsable (EditorBrowsableState.Never)] + protected override bool DoubleBuffered { + get { return false; } + set { } + } + + #endregion // Protected Instance Properties + + #region Public Instance Methods + public void AppendText (string text) + { + // Save some cycles and only check the Text if we are one line + bool is_empty = document.Lines == 1 && Text == String.Empty; + + // make sure the caret begins at the end + if (document.caret.line.line_no != document.Lines || + (document.caret.pos) != document.caret.line.TextLengthWithoutEnding ()) { + document.MoveCaret (CaretDirection.CtrlEnd); + } + document.Insert (document.caret.line, document.caret.pos, false, text, document.CaretTag); + document.MoveCaret (CaretDirection.CtrlEnd); + document.SetSelectionToCaret (true); + + if (!is_empty) + ScrollToCaret (); + + // + // Avoid the initial focus selecting all when append text is used + // + has_been_focused = true; + + Modified = false; + OnTextChanged(EventArgs.Empty); + } + + public void Clear () + { + Modified = false; + Text = string.Empty; + } + + public void ClearUndo () + { + document.undo.Clear(); + } + + public void Copy () + { + DataObject o; + + o = new DataObject(DataFormats.Text, SelectedText); + if (this is RichTextBox) + o.SetData(DataFormats.Rtf, ((RichTextBox)this).SelectedRtf); + Clipboard.SetDataObject(o); + } + + public void Cut () + { + DataObject o; + + o = new DataObject(DataFormats.Text, SelectedText); + if (this is RichTextBox) + o.SetData(DataFormats.Rtf, ((RichTextBox)this).SelectedRtf); + Clipboard.SetDataObject (o); + + document.undo.BeginUserAction (String.Format ("Cut")); + document.ReplaceSelection (String.Empty, false); + document.undo.EndUserAction (); + + Modified = true; + OnTextChanged (EventArgs.Empty); + } + + public void Paste () + { + Paste (Clipboard.GetDataObject(), null, false); + } + + public void ScrollToCaret () + { + if (IsHandleCreated) + CaretMoved (this, EventArgs.Empty); + } + + public void Select(int start, int length) + { + SelectionStart = start; + SelectionLength = length; + } + + public void SelectAll () + { + Line last; + + last = document.GetLine(document.Lines); + document.SetSelectionStart(document.GetLine(1), 0, false); + document.SetSelectionEnd(last, last.text.Length, true); + document.PositionCaret (document.selection_end.line, document.selection_end.pos); + selection_length = -1; + + CaretMoved (this, null); + + document.DisplayCaret (); + } + + internal void SelectAllNoScroll () + { + Line last; + + last = document.GetLine(document.Lines); + document.SetSelectionStart(document.GetLine(1), 0, false); + document.SetSelectionEnd(last, last.text.Length, false); + document.PositionCaret (document.selection_end.line, document.selection_end.pos); + selection_length = -1; + + document.DisplayCaret (); + } + + public override string ToString () + { + return String.Concat (base.ToString (), ", Text: ", Text); + } + + [MonoInternalNote ("Deleting is classed as Typing, instead of its own Undo event")] + public void Undo () + { + if (document.undo.Undo ()) { + Modified = true; + OnTextChanged (EventArgs.Empty); + } + } + + public void DeselectAll () + { + SelectionLength = 0; + } + + public virtual char GetCharFromPosition (Point pt) + { + return GetCharFromPositionInternal (pt); + } + + internal virtual char GetCharFromPositionInternal (Point p) + { + int index; + LineTag tag = document.FindCursor (p.X, p.Y, out index); + if (tag == null) + return (char) 0; // Shouldn't happen + + if (index >= tag.Line.text.Length) { + + if (tag.Line.ending == LineEnding.Wrap) { + // If we have wrapped text, we return the first char of the next line + Line line = document.GetLine (tag.Line.line_no + 1); + if (line != null) + return line.text [0]; + + } + + if (tag.Line.line_no == document.Lines) { + // Last line returns the last char + return tag.Line.text [tag.Line.text.Length - 1]; + } + + // This really shouldn't happen + return (char) 0; + } + return tag.Line.text [index]; + } + + public virtual int GetCharIndexFromPosition (Point pt) + { + int line_index; + LineTag tag = document.FindCursor (pt.X, pt.Y, out line_index); + if (tag == null) + return 0; + + if (line_index >= tag.Line.text.Length) { + + if (tag.Line.ending == LineEnding.Wrap) { + // If we have wrapped text, we return the first char of the next line + Line line = document.GetLine (tag.Line.line_no + 1); + if (line != null) + return document.LineTagToCharIndex (line, 0); + } + + if (tag.Line.line_no == document.Lines) { + // Last line returns the last char + return document.LineTagToCharIndex (tag.Line, tag.Line.text.Length - 1); + } + + return 0; + } + + return document.LineTagToCharIndex (tag.Line, line_index); + } + + public virtual Point GetPositionFromCharIndex (int index) + { + int pos; + Line line; + LineTag tag; + + document.CharIndexToLineTag (index, out line, out tag, out pos); + + return new Point ((int) (line.widths [pos] + + line.X + document.viewport_x), + line.Y + document.viewport_y + tag.Shift); + } + + public int GetFirstCharIndexFromLine (int lineNumber) + { + Line line = document.GetLine (lineNumber + 1); + if (line == null) + return -1; + + return document.LineTagToCharIndex (line, 0); + } + + public int GetFirstCharIndexOfCurrentLine () + { + return document.LineTagToCharIndex (document.caret.line, 0); + } + #endregion // Public Instance Methods + + #region Protected Instance Methods + protected override void CreateHandle () + { + CalculateDocument (); + base.CreateHandle (); + document.AlignCaret(); + ScrollToCaret(); + } + + internal virtual void HandleLinkClicked (LinkRectangle link_clicked) + { + } + + protected override bool IsInputKey (Keys keyData) + { + if ((keyData & Keys.Alt) != 0) + return base.IsInputKey(keyData); + + switch (keyData & Keys.KeyCode) { + case Keys.Enter: { + return (accepts_return && document.multiline); + } + + case Keys.Tab: { + if (accepts_tab && document.multiline) + if ((keyData & Keys.Widget) == 0) + return true; + return false; + } + + case Keys.Left: + case Keys.Right: + case Keys.Up: + case Keys.Down: + case Keys.PageUp: + case Keys.PageDown: + case Keys.Home: + case Keys.End: { + return true; + } + } + return false; + } + + protected virtual void OnAcceptsTabChanged(EventArgs e) + { + EventHandler eh = (EventHandler)(Events [AcceptsTabChangedEvent]); + if (eh != null) + eh (this, e); + } + + protected virtual void OnBorderStyleChanged (EventArgs e) + { + EventHandler eh = (EventHandler)(Events [BorderStyleChangedEvent]); + if (eh != null) + eh (this, e); + } + + protected override void OnFontChanged (EventArgs e) + { + base.OnFontChanged (e); + + if (auto_size && !document.multiline) { + if (PreferredHeight != Height) { + Height = PreferredHeight; + } + } + } + + protected override void OnHandleCreated (EventArgs e) + { + base.OnHandleCreated (e); + FixupHeight (); + } + + protected override void OnHandleDestroyed (EventArgs e) + { + base.OnHandleDestroyed (e); + } + + protected virtual void OnHideSelectionChanged (EventArgs e) + { + EventHandler eh = (EventHandler)(Events [HideSelectionChangedEvent]); + if (eh != null) + eh (this, e); + } + + protected virtual void OnModifiedChanged (EventArgs e) + { + EventHandler eh = (EventHandler)(Events [ModifiedChangedEvent]); + if (eh != null) + eh (this, e); + } + + protected virtual void OnMultilineChanged (EventArgs e) + { + EventHandler eh = (EventHandler)(Events [MultilineChangedEvent]); + if (eh != null) + eh (this, e); + } + + protected override void OnPaddingChanged (EventArgs e) + { + base.OnPaddingChanged (e); + } + + protected virtual void OnReadOnlyChanged (EventArgs e) + { + EventHandler eh = (EventHandler)(Events [ReadOnlyChangedEvent]); + if (eh != null) + eh (this, e); + } + + protected override bool ProcessCmdKey (ref Message msg, Keys keyData) + { + return base.ProcessCmdKey (ref msg, keyData); + } + protected override bool ProcessDialogKey (Keys keyData) + { + // The user can use Ctrl-Tab or Ctrl-Shift-Tab to move Widget focus + // instead of inserting a Tab. However, the focus-moving-tab-stuffs + // doesn't work if Ctrl is pushed, so we remove it before sending it. + if (accepts_tab && (keyData & (Keys.Widget | Keys.Tab)) == (Keys.Widget | Keys.Tab)) + keyData ^= Keys.Widget; + + return base.ProcessDialogKey(keyData); + } + + private bool ProcessKey (Keys keyData) + { + bool control; + bool shift; + + control = (Widget.ModifierKeys & Keys.Widget) != 0; + shift = (Widget.ModifierKeys & Keys.Shift) != 0; + + if (shortcuts_enabled) { + switch (keyData & Keys.KeyCode) { + case Keys.X: + if (control && read_only == false) { + Cut(); + return true; + } + return false; + + case Keys.C: + if (control) { + Copy(); + return true; + } + return false; + + case Keys.V: + if (control && read_only == false) { + return Paste(Clipboard.GetDataObject(), null, true); + } + return false; + + case Keys.Z: + if (control && read_only == false) { + Undo (); + return true; + } + return false; + + case Keys.A: + if (control) { + SelectAll(); + return true; + } + return false; + + case Keys.Insert: + + if (read_only == false) { + if (shift) { + Paste (Clipboard.GetDataObject (), null, true); + return true; + } + + if (control) { + Copy (); + return true; + } + } + + return false; + + case Keys.Delete: + + if (read_only) + break; + + if (shift && read_only == false) { + Cut (); + return true; + } + + if (document.selection_visible) { + document.ReplaceSelection("", false); + } else { + // DeleteChar only deletes on the line, doesn't do the combine + if (document.CaretPosition >= document.CaretLine.TextLengthWithoutEnding ()) { + if (document.CaretLine.LineNo < document.Lines) { + Line line; + + line = document.GetLine(document.CaretLine.LineNo + 1); + + // this line needs to be invalidated before it is combined + // because once it is combined, all it's coordinates will + // have changed + document.Invalidate (line, 0, line, line.text.Length); + document.Combine(document.CaretLine, line); + + document.UpdateView(document.CaretLine, + document.Lines, 0); + + } + } else { + if (!control) { + document.DeleteChar(document.CaretTag.Line, document.CaretPosition, true); + } else { + int end_pos; + + end_pos = document.CaretPosition; + + while ((end_pos < document.CaretLine.Text.Length) && !Document.IsWordSeparator(document.CaretLine.Text[end_pos])) { + end_pos++; + } + + if (end_pos < document.CaretLine.Text.Length) { + end_pos++; + } + document.DeleteChars(document.CaretTag.Line, document.CaretPosition, end_pos - document.CaretPosition); + } + } + } + + document.AlignCaret(); + document.UpdateCaret(); + CaretMoved(this, null); + + Modified = true; + OnTextChanged (EventArgs.Empty); + + return true; + } + } + + switch (keyData & Keys.KeyCode) { + case Keys.Left: { + if (control) { + document.MoveCaret(CaretDirection.WordBack); + } else { + if (!document.selection_visible || shift) { + document.MoveCaret(CaretDirection.CharBack); + } else { + document.MoveCaret(CaretDirection.SelectionStart); + } + } + + if (!shift) { + document.SetSelectionToCaret(true); + } else { + document.SetSelectionToCaret(false); + } + + CaretMoved(this, null); + return true; + } + + case Keys.Right: { + if (control) { + document.MoveCaret(CaretDirection.WordForward); + } else { + if (!document.selection_visible || shift) { + document.MoveCaret(CaretDirection.CharForward); + } else { + document.MoveCaret(CaretDirection.SelectionEnd); + } + } + if (!shift) { + document.SetSelectionToCaret(true); + } else { + document.SetSelectionToCaret(false); + } + + CaretMoved(this, null); + return true; + } + + case Keys.Up: { + if (control) { + if (document.CaretPosition == 0) { + document.MoveCaret(CaretDirection.LineUp); + } else { + document.MoveCaret(CaretDirection.Home); + } + } else { + document.MoveCaret(CaretDirection.LineUp); + } + + if ((Widget.ModifierKeys & Keys.Shift) == 0) { + document.SetSelectionToCaret(true); + } else { + document.SetSelectionToCaret(false); + } + + CaretMoved(this, null); + return true; + } + + case Keys.Down: { + if (control) { + if (document.CaretPosition == document.CaretLine.Text.Length) { + document.MoveCaret(CaretDirection.LineDown); + } else { + document.MoveCaret(CaretDirection.End); + } + } else { + document.MoveCaret(CaretDirection.LineDown); + } + + if ((Widget.ModifierKeys & Keys.Shift) == 0) { + document.SetSelectionToCaret(true); + } else { + document.SetSelectionToCaret(false); + } + + CaretMoved(this, null); + return true; + } + + case Keys.Home: { + if ((Widget.ModifierKeys & Keys.Widget) != 0) { + document.MoveCaret(CaretDirection.CtrlHome); + } else { + document.MoveCaret(CaretDirection.Home); + } + + if ((Widget.ModifierKeys & Keys.Shift) == 0) { + document.SetSelectionToCaret(true); + } else { + document.SetSelectionToCaret(false); + } + + CaretMoved(this, null); + return true; + } + + case Keys.End: { + if ((Widget.ModifierKeys & Keys.Widget) != 0) { + document.MoveCaret(CaretDirection.CtrlEnd); + } else { + document.MoveCaret(CaretDirection.End); + } + + if ((Widget.ModifierKeys & Keys.Shift) == 0) { + document.SetSelectionToCaret(true); + } else { + document.SetSelectionToCaret(false); + } + + CaretMoved(this, null); + return true; + } + + //case Keys.Enter: { + // // ignoring accepts_return, fixes bug #76355 + // if (!read_only && document.multiline && (accepts_return || (FindForm() != null && FindForm().AcceptButton == null) || ((Widget.ModifierKeys & Keys.Widget) != 0))) { + // Line line; + + // if (document.selection_visible) { + // document.ReplaceSelection("\n", false); + // } + + // line = document.CaretLine; + + // document.Split (document.CaretLine, document.CaretTag, document.CaretPosition); + // line.ending = LineEnding.Rich; + // document.InsertString (line, line.text.Length, + // document.LineEndingToString (line.ending)); + // OnTextChanged(EventArgs.Empty); + + // document.UpdateView (line, document.Lines - line.line_no, 0); + // CaretMoved(this, null); + // return true; + // } + // break; + //} + + case Keys.Tab: { + if (!read_only && accepts_tab && document.multiline) { + document.InsertCharAtCaret ('\t', true); + + CaretMoved(this, null); + Modified = true; + OnTextChanged (EventArgs.Empty); + + return true; + } + break; + } + + case Keys.PageUp: { + if ((Widget.ModifierKeys & Keys.Widget) != 0) { + document.MoveCaret(CaretDirection.CtrlPgUp); + } else { + document.MoveCaret(CaretDirection.PgUp); + } + document.DisplayCaret (); + return true; + } + + case Keys.PageDown: { + if ((Widget.ModifierKeys & Keys.Widget) != 0) { + document.MoveCaret(CaretDirection.CtrlPgDn); + } else { + document.MoveCaret(CaretDirection.PgDn); + } + document.DisplayCaret (); + return true; + } + } + + return false; + } + + internal virtual void RaiseSelectionChanged () + { + // Do nothing, overridden in RTB + } + + private void HandleBackspace (bool Widget) + { + bool fire_changed; + + fire_changed = false; + + // delete only deletes on the line, doesn't do the combine + if (document.selection_visible) { + document.undo.BeginUserAction (String.Format ("Delete")); + document.ReplaceSelection("", false); + document.undo.EndUserAction (); + fire_changed = true; + document.SetSelectionToCaret (true); + } else { + document.SetSelectionToCaret (true); + + if (document.CaretPosition == 0) { + if (document.CaretLine.LineNo > 1) { + Line line; + int new_caret_pos; + + line = document.GetLine(document.CaretLine.LineNo - 1); + new_caret_pos = line.TextLengthWithoutEnding (); + + // Invalidate the old line position before we do the combine + document.Invalidate (line, 0, line, line.text.Length); + document.Combine(line, document.CaretLine); + + document.UpdateView(line, document.Lines - line.LineNo, 0); + document.PositionCaret(line, new_caret_pos); + document.SetSelectionToCaret (true); + document.UpdateCaret(); + fire_changed = true; + } + } else { + if (!Widget || document.CaretPosition == 0) { + + // Move before we delete because the delete will change positions around + // if we cross a wrap border + LineTag tag = document.CaretTag; + int pos = document.CaretPosition; + document.MoveCaret (CaretDirection.CharBack); + document.DeleteChar (tag.Line, pos, false); + document.SetSelectionToCaret (true); + } else { + int start_pos; + + + start_pos = document.CaretPosition - 1; + while ((start_pos > 0) && !Document.IsWordSeparator(document.CaretLine.Text[start_pos - 1])) { + start_pos--; + } + + document.undo.BeginUserAction (String.Format ("Delete")); + document.DeleteChars(document.CaretTag.Line, start_pos, document.CaretPosition - start_pos); + document.undo.EndUserAction (); + document.PositionCaret(document.CaretLine, start_pos); + document.SetSelectionToCaret (true); + } + document.UpdateCaret(); + fire_changed = true; + } + } + + CaretMoved (this, null); + + if (fire_changed) { + Modified = true; + OnTextChanged(EventArgs.Empty); + } + } + + private void HandleEnter () + { + // ignoring accepts_return, fixes bug #76355 + if (!read_only && document.multiline && (accepts_return || (FindForm() != null && FindForm().AcceptButton == null) || ((Widget.ModifierKeys & Keys.Widget) != 0))) { + Line line; + + if (document.selection_visible) + document.ReplaceSelection ("", false); + + line = document.CaretLine; + + document.Split (document.CaretLine, document.CaretTag, document.CaretPosition); + line.ending = document.StringToLineEnding (Environment.NewLine); + document.InsertString (line, line.text.Length, document.LineEndingToString (line.ending)); + + document.UpdateView (line, document.Lines - line.line_no, 0); + CaretMoved (this, null); + Modified = true; + OnTextChanged (EventArgs.Empty); + } + } + + protected override void SetBoundsCore (int x, int y, int width, int height, BoundsSpecified specified) + { + // Make sure we don't get sized bigger than we want to be + + if (!richtext) { + if (!document.multiline) { + if (height != PreferredHeight) { + // If the specified has Height, we need to store that in the + // ExplicitBounds because we are going to override it + if ((specified & BoundsSpecified.Height) != 0) { + Rectangle r = ExplicitBounds; + r.Height = height; + ExplicitBounds = r; + specified &= ~BoundsSpecified.Height; + } + + height = PreferredHeight; + } + } + } + + base.SetBoundsCore (x, y, width, height, specified); + } + + protected override void WndProc (ref Message m) + { + switch ((Msg)m.Msg) { + case Msg.WM_KEYDOWN: { + if (ProcessKeyMessage(ref m) || ProcessKey((Keys)m.WParam.ToInt32() | XplatUI.State.ModifierKeys)) { + m.Result = IntPtr.Zero; + return; + } + DefWndProc (ref m); + return; + } + + case Msg.WM_CHAR: { + int ch; + + if (ProcessKeyMessage(ref m)) { + m.Result = IntPtr.Zero; + return; + } + + if (read_only) { + return; + } + + m.Result = IntPtr.Zero; + + ch = m.WParam.ToInt32(); + + if (ch == 127) { + HandleBackspace(true); + } else if (ch >= 32) { + if (document.selection_visible) { + document.ReplaceSelection("", false); + } + + char c = (char)m.WParam; + switch (character_casing) { + case CharacterCasing.Upper: + c = Char.ToUpper((char) m.WParam); + break; + case CharacterCasing.Lower: + c = Char.ToLower((char) m.WParam); + break; + } + + if (document.Length < max_length) { + document.InsertCharAtCaret(c, true); + OnTextUpdate (); + CaretMoved (this, null); + Modified = true; + OnTextChanged(EventArgs.Empty); + + } else { + XplatUI.AudibleAlert(AlertType.Default); + } + return; + } else if (ch == 8) { + HandleBackspace(false); + } else if (ch == 13) + HandleEnter (); + + return; + } + + case Msg.WM_SETFOCUS: + base.WndProc(ref m); + document.CaretHasFocus (); + break; + + case Msg.WM_KILLFOCUS: + base.WndProc(ref m); + document.CaretLostFocus (); + break; + + case Msg.WM_NCPAINT: + if (!ThemeEngine.Current.TextBoxBaseHandleWmNcPaint (this, ref m)) + base.WndProc(ref m); + break; + + default: + base.WndProc(ref m); + return; + } + } + + #endregion // Protected Instance Methods + + #region Events + static object AcceptsTabChangedEvent = new object (); + static object AutoSizeChangedEvent = new object (); + static object BorderStyleChangedEvent = new object (); + static object HideSelectionChangedEvent = new object (); + static object ModifiedChangedEvent = new object (); + static object MultilineChangedEvent = new object (); + static object ReadOnlyChangedEvent = new object (); + static object HScrolledEvent = new object (); + static object VScrolledEvent = new object (); + + public event EventHandler AcceptsTabChanged { + add { Events.AddHandler (AcceptsTabChangedEvent, value); } + remove { Events.RemoveHandler (AcceptsTabChangedEvent, value); } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler AutoSizeChanged { + add { Events.AddHandler (AutoSizeChangedEvent, value); } + remove { Events.RemoveHandler (AutoSizeChangedEvent, value); } + } + + public event EventHandler BorderStyleChanged { + add { Events.AddHandler (BorderStyleChangedEvent, value); } + remove { Events.RemoveHandler (BorderStyleChangedEvent, value); } + } + + public event EventHandler HideSelectionChanged { + add { Events.AddHandler (HideSelectionChangedEvent, value); } + remove { Events.RemoveHandler (HideSelectionChangedEvent, value); } + } + + public event EventHandler ModifiedChanged { + add { Events.AddHandler (ModifiedChangedEvent, value); } + remove { Events.RemoveHandler (ModifiedChangedEvent, value); } + } + + public event EventHandler MultilineChanged { + add { Events.AddHandler (MultilineChangedEvent, value); } + remove { Events.RemoveHandler (MultilineChangedEvent, value); } + } + + public event EventHandler ReadOnlyChanged { + add { Events.AddHandler (ReadOnlyChangedEvent, value); } + remove { Events.RemoveHandler (ReadOnlyChangedEvent, value); } + } + + internal event EventHandler HScrolled { + add { Events.AddHandler (HScrolledEvent, value); } + remove { Events.RemoveHandler (HScrolledEvent, value); } + } + + internal event EventHandler VScrolled { + add { Events.AddHandler (VScrolledEvent, value); } + remove { Events.RemoveHandler (VScrolledEvent, value); } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler BackgroundImageChanged { + add { base.BackgroundImageChanged += value; } + remove { base.BackgroundImageChanged -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler BackgroundImageLayoutChanged { + add { base.BackgroundImageLayoutChanged += value; } + remove { base.BackgroundImageLayoutChanged -= value; } + } + + [Browsable (true)] + //[EditorBrowsable (EditorBrowsableState.Always)] + public new event MouseEventHandler MouseClick { + add { base.MouseClick += value; } + remove { base.MouseClick -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public new event EventHandler PaddingChanged { + add { base.PaddingChanged += value; } + remove { base.PaddingChanged -= value; } + } + + [Browsable (true)] + //[EditorBrowsable (EditorBrowsableState.Always)] + public new event EventHandler Click { + add { base.Click += value; } + remove { base.Click -= value; } + } + + // XXX should this not manipulate base.Paint? +#pragma warning disable 0067 + [MonoTODO] + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event PaintEventHandler Paint; +#pragma warning restore 0067 + + #endregion // Events + + #region Private Methods + internal Document Document { + get { + return document; + } + + set { + document = value; + } + } + + internal bool EnableLinks { + get { return enable_links; } + set { + enable_links = value; + + document.EnableLinks = value; + } + } + + internal override bool ScaleChildrenInternal { + get { return false; } + } + + internal bool ShowSelection { + get { + if (show_selection || !hide_selection) { + return true; + } + + return has_focus; + } + + set { + if (show_selection == value) + return; + + show_selection = value; + // Currently InvalidateSelectionArea is commented out so do a full invalidate + document.InvalidateSelectionArea(); + } + } + + internal int TopMargin { + get { + return document.top_margin; + } + set { + document.top_margin = value; + } + } + + #region UIA Framework Properties + + internal ScrollBar UIAHScrollBar { + get { return hscroll; } + } + + internal ScrollBar UIAVScrollBar { + get { return vscroll; } + } + + #endregion UIA Framework Properties + + internal Graphics CreateGraphicsInternal () + { + if (IsHandleCreated) + return base.CreateGraphics(); + + return DeviceContext; + } + + internal override void OnPaintInternal (PaintEventArgs pevent) + { + Draw (pevent.Graphics, pevent.ClipRectangle); + + // + // OnPaint does not get raised on MS (see bug #80639) + // + pevent.Handled = true; + } + + internal void Draw (Graphics g, Rectangle clippingArea) + { + ThemeEngine.Current.TextBoxBaseFillBackground (this, g, clippingArea); + + // Draw the viewable document + document.Draw(g, clippingArea); + } + + private void FixupHeight () + { + if (!richtext) { + if (!document.multiline) { + if (PreferredHeight != Height) { + Height = PreferredHeight; + } + } + } + } + + private bool IsDoubleClick (MouseEventArgs e) + { + TimeSpan interval = DateTime.Now - click_last; + if (interval.TotalMilliseconds > SystemInformation.DoubleClickTime) + return false; + Size dcs = SystemInformation.DoubleClickSize; + if (e.X < click_point_x - dcs.Width / 2 || e.X > click_point_x + dcs.Width / 2) + return false; + if (e.Y < click_point_y - dcs.Height / 2 || e.Y > click_point_y + dcs.Height / 2) + return false; + return true; + } + + private void TextBoxBase_MouseDown (object sender, MouseEventArgs e) + { + bool dbliclick = false; + + if (e.Button == MouseButtons.Left) { + + // Special case when shift key is pressed and + // left mouse is clicked.. set selection from + // current cursor to mouse + if ((Widget.ModifierKeys & Keys.Shift) > 0) { + document.PositionCaret (e.X + document.ViewPortX, e.Y + document.ViewPortY); + document.SetSelectionToCaret (false); + document.DisplayCaret (); + return; + } + + dbliclick = IsDoubleClick (e); + + if (current_link != null) { + HandleLinkClicked (current_link); + return; + } + + //ensure nothing is selected anymore BEFORE we + //position the caret, so the caret is recreated + //(caret is only visible when nothing is selected) + if (document.selection_visible && dbliclick == false) { + document.SetSelectionToCaret (true); + click_mode = CaretSelection.Position; + } + + document.PositionCaret(e.X + document.ViewPortX, e.Y + document.ViewPortY); + + if (dbliclick) { + switch (click_mode) { + case CaretSelection.Position: + SelectWord (); + click_mode = CaretSelection.Word; + break; + case CaretSelection.Word: + + if (this is TextBox) { + document.SetSelectionToCaret (true); + click_mode = CaretSelection.Position; + break; + } + + document.ExpandSelection (CaretSelection.Line, false); + click_mode = CaretSelection.Line; + break; + case CaretSelection.Line: + + // Gotta do this first because Exanding to a word + // from a line doesn't really work + document.SetSelectionToCaret (true); + + SelectWord (); + click_mode = CaretSelection.Word; + break; + } + } else { + document.SetSelectionToCaret (true); + click_mode = CaretSelection.Position; + } + + click_point_x = e.X; + click_point_y = e.Y; + click_last = DateTime.Now; + } + + if ((e.Button == MouseButtons.Middle) && XplatUI.RunningOnUnix) { + Document.Marker marker; + + marker.tag = document.FindCursor(e.X + document.ViewPortX, e.Y + document.ViewPortY, out marker.pos); + marker.line = marker.tag.Line; + marker.height = marker.tag.Height; + + document.SetSelection(marker.line, marker.pos, marker.line, marker.pos); + Paste (Clipboard.GetDataObject (true), null, true); + } + } + + private void TextBoxBase_MouseUp (object sender, MouseEventArgs e) + { + if (e.Button == MouseButtons.Left) { + if (click_mode == CaretSelection.Position) { + document.SetSelectionToCaret(false); + document.DisplayCaret(); + + // Only raise if there is text. + if (Text.Length > 0) + RaiseSelectionChanged (); + } + + if (scroll_timer != null) { + scroll_timer.Enabled = false; + } + return; + } + } + + private void SizeWidgets () + { + if (hscroll.Visible) { + //vscroll.Maximum += hscroll.Height; + canvas_height = ClientSize.Height - hscroll.Height; + } else { + canvas_height = ClientSize.Height; + } + + if (vscroll.Visible) { + //hscroll.Maximum += vscroll.Width; + canvas_width = ClientSize.Width - vscroll.Width; + + if (GetInheritedRtoL () == RightToLeft.Yes) { + document.OffsetX = vscroll.Width; + } else { + document.OffsetX = 0; + } + + } else { + canvas_width = ClientSize.Width; + document.OffsetX = 0; + } + + document.ViewPortWidth = canvas_width; + document.ViewPortHeight = canvas_height; + } + + private void PositionWidgets () + { + if (canvas_height < 1 || canvas_width < 1) + return; + + int hmod = vscroll.Visible ? vscroll.Width : 0; + int vmod = hscroll.Visible ? hscroll.Height : 0; + + if (GetInheritedRtoL () == RightToLeft.Yes) { + hscroll.Bounds = new Rectangle (ClientRectangle.Left + hmod, + Math.Max(0, ClientRectangle.Height - hscroll.Height), + ClientSize.Width, + hscroll.Height); + + vscroll.Bounds = new Rectangle (ClientRectangle.Left, + ClientRectangle.Top, + vscroll.Width, + Math.Max(0, ClientSize.Height - (vmod))); + } else { + hscroll.Bounds = new Rectangle (ClientRectangle.Left, + Math.Max(0, ClientRectangle.Height - hscroll.Height), + Math.Max(0, ClientSize.Width - hmod), + hscroll.Height); + + vscroll.Bounds = new Rectangle ( + Math.Max(0, ClientRectangle.Right - vscroll.Width), + ClientRectangle.Top, + vscroll.Width, + Math.Max(0, ClientSize.Height - vmod)); + } + } + + internal RightToLeft GetInheritedRtoL () + { + for (Widget c = this; c != null; c = c.Parent) + if (c.RightToLeft != RightToLeft.Inherit) + return c.RightToLeft; + return RightToLeft.No; + } + + private void TextBoxBase_SizeChanged (object sender, EventArgs e) + { + if (IsHandleCreated) + CalculateDocument (); + } + + private void TextBoxBase_RightToLeftChanged (object o, EventArgs e) + { + if (IsHandleCreated) + CalculateDocument (); + } + + private void TextBoxBase_MouseWheel (object sender, MouseEventArgs e) + { + if (!vscroll.Enabled) + return; + + if (e.Delta < 0) + vscroll.Value = Math.Min (vscroll.Value + SystemInformation.MouseWheelScrollLines * 5, + Math.Max (0, vscroll.Maximum - document.ViewPortHeight + 1)); + else + vscroll.Value = Math.Max (0, vscroll.Value - SystemInformation.MouseWheelScrollLines * 5); + } + + internal virtual void SelectWord () + { + StringBuilder s = document.caret.line.text; + int start = document.caret.pos; + int end = document.caret.pos; + + if (s.Length < 1) { + if (document.caret.line.line_no >= document.Lines) + return; + Line line = document.GetLine (document.caret.line.line_no + 1); + document.PositionCaret (line, 0); + return; + } + + if (start > 0) { + start--; + end--; + } + + // skip whitespace until we hit a word + while (start > 0 && s [start] == ' ') + start--; + if (start > 0) { + while (start > 0 && (s [start] != ' ')) + start--; + if (s [start] == ' ') + start++; + } + + if (s [end] == ' ') { + while (end < s.Length && s [end] == ' ') + end++; + } else { + while (end < s.Length && s [end] != ' ') + end++; + while (end < s.Length && s [end] == ' ') + end++; + } + + document.SetSelection (document.caret.line, start, document.caret.line, end); + document.PositionCaret (document.selection_end.line, document.selection_end.pos); + document.DisplayCaret(); + } + + internal void CalculateDocument() + { + CalculateScrollBars (); + document.RecalculateDocument (CreateGraphicsInternal ()); + + + if (document.caret.line != null && document.caret.line.Y < document.ViewPortHeight) { + // The window has probably been resized, making the entire thing visible, so + // we need to set the scroll position back to zero. + vscroll.Value = 0; + } + + Invalidate(); + } + + internal void CalculateScrollBars () + { + // FIXME - need separate calculations for center and right alignment + SizeWidgets (); + + if (document.Width >= document.ViewPortWidth) { + hscroll.SetValues (0, Math.Max (1, document.Width), -1, + document.ViewPortWidth < 0 ? 0 : document.ViewPortWidth); + if (document.multiline) + hscroll.Enabled = true; + } else { + hscroll.Enabled = false; + hscroll.Maximum = document.ViewPortWidth; + } + + if (document.Height >= document.ViewPortHeight) { + vscroll.SetValues (0, Math.Max (1, document.Height), -1, + document.ViewPortHeight < 0 ? 0 : document.ViewPortHeight); + if (document.multiline) + vscroll.Enabled = true; + } else { + vscroll.Enabled = false; + vscroll.Maximum = document.ViewPortHeight; + } + + if (!WordWrap) { + switch (scrollbars) { + case RichTextBoxScrollBars.Both: + case RichTextBoxScrollBars.Horizontal: + if (richtext) + hscroll.Visible = hscroll.Enabled; + else + hscroll.Visible = this.Multiline; + break; + case RichTextBoxScrollBars.ForcedBoth: + case RichTextBoxScrollBars.ForcedHorizontal: + hscroll.Visible = true; + break; + default: + hscroll.Visible = false; + break; + } + } else { + hscroll.Visible = false; + } + + switch (scrollbars) { + case RichTextBoxScrollBars.Both: + case RichTextBoxScrollBars.Vertical: + if (richtext) + vscroll.Visible = vscroll.Enabled; + else + vscroll.Visible = this.Multiline; + break; + case RichTextBoxScrollBars.ForcedBoth: + case RichTextBoxScrollBars.ForcedVertical: + vscroll.Visible = true; + break; + default: + vscroll.Visible = false; + break; + } + + PositionWidgets (); + + SizeWidgets (); //Update sizings now we've decided whats visible + } + + private void document_WidthChanged (object sender, EventArgs e) + { + CalculateScrollBars(); + } + + private void document_HeightChanged (object sender, EventArgs e) + { + CalculateScrollBars(); + } + + private void ScrollLinks (int xChange, int yChange) + { + foreach (LinkRectangle link in list_links) + link.Scroll (xChange, yChange); + } + + private void hscroll_ValueChanged (object sender, EventArgs e) + { + int old_viewport_x; + + old_viewport_x = document.ViewPortX; + document.ViewPortX = this.hscroll.Value; + + // + // Before scrolling we want to destroy the caret, then draw a new one after the scroll + // the reason for this is that scrolling changes the coordinates of the caret, and we + // will get tracers if we don't + // + if (Focused) + document.CaretLostFocus (); + + if (vscroll.Visible) { + if (GetInheritedRtoL () == RightToLeft.Yes) { + XplatUI.ScrollWindow (this.Handle, new Rectangle (vscroll.Width, 0, ClientSize.Width - vscroll.Width, ClientSize.Height), old_viewport_x - this.hscroll.Value, 0, false); + } else { + XplatUI.ScrollWindow (this.Handle, new Rectangle (0, 0, ClientSize.Width - vscroll.Width, ClientSize.Height), old_viewport_x - this.hscroll.Value, 0, false); + } + } else { + XplatUI.ScrollWindow(this.Handle, ClientRectangle, old_viewport_x - this.hscroll.Value, 0, false); + } + + ScrollLinks (old_viewport_x - this.hscroll.Value, 0); + + if (Focused) + document.CaretHasFocus (); + + EventHandler eh = (EventHandler)(Events [HScrolledEvent]); + if (eh != null) + eh (this, EventArgs.Empty); + } + + private void vscroll_ValueChanged (object sender, EventArgs e) + { + int old_viewport_y; + + old_viewport_y = document.ViewPortY; + document.ViewPortY = this.vscroll.Value; + + // + // Before scrolling we want to destroy the caret, then draw a new one after the scroll + // the reason for this is that scrolling changes the coordinates of the caret, and we + // will get tracers if we don't + // + if (Focused) + document.CaretLostFocus (); + + if (hscroll.Visible) { + XplatUI.ScrollWindow(this.Handle, new Rectangle(0, 0, ClientSize.Width, ClientSize.Height - hscroll.Height), 0, old_viewport_y - this.vscroll.Value, false); + } else { + XplatUI.ScrollWindow(this.Handle, ClientRectangle, 0, old_viewport_y - this.vscroll.Value, false); + } + + ScrollLinks (0, old_viewport_y - this.vscroll.Value); + + if (Focused) + document.CaretHasFocus (); + + EventHandler eh = (EventHandler)(Events [VScrolledEvent]); + if (eh != null) + eh (this, EventArgs.Empty); + } + + private void TextBoxBase_MouseMove (object sender, MouseEventArgs e) + { + // FIXME - handle auto-scrolling if mouse is to the right/left of the window + if (e.Button == MouseButtons.Left && Capture) { + if (!ClientRectangle.Contains (e.X, e.Y)) { + if (scroll_timer == null) { + scroll_timer = new Timer (); + scroll_timer.Interval = 100; + scroll_timer.Tick += new EventHandler (ScrollTimerTickHandler); + } + + if (!scroll_timer.Enabled) { + scroll_timer.Start (); + + // Force the first tick + ScrollTimerTickHandler (null, EventArgs.Empty); + } + } + + document.PositionCaret(e.X + document.ViewPortX, e.Y + document.ViewPortY); + if (click_mode == CaretSelection.Position) { + document.SetSelectionToCaret(false); + document.DisplayCaret(); + } + } + + //search through link boxes to see if the mouse is over one of them + + bool found_link = false; + foreach (LinkRectangle link in list_links) { + if (link.LinkAreaRectangle.Contains (e.X, e.Y)) { + XplatUI.SetCursor (window.Handle, Cursors.Hand.handle); + + found_link = true; + current_link = link; + break; + } + } + + if (found_link == false) { + XplatUI.SetCursor (window.Handle, DefaultCursor.handle); + current_link = null; + } + } + + private void TextBoxBase_FontOrColorChanged (object sender, EventArgs e) + { + Line line; + + document.SuspendRecalc (); + // Font changes apply to the whole document + for (int i = 1; i <= document.Lines; i++) { + line = document.GetLine(i); + if (LineTag.FormatText(line, 1, line.text.Length, Font, ForeColor, + Color.Empty, FormatSpecified.Font | FormatSpecified.Color)) + document.RecalculateDocument (CreateGraphicsInternal (), line.LineNo, line.LineNo, false); + } + document.ResumeRecalc (false); + + // Make sure the caret height is matching the new font height + document.AlignCaret(); + } + + private void ScrollTimerTickHandler (object sender, EventArgs e) + { + Point pt = Cursor.Position; + + pt = PointToClient (pt); + + if (pt.X < ClientRectangle.Left) { + document.MoveCaret(CaretDirection.CharBackNoWrap); + document.SetSelectionToCaret(false); + + CaretMoved(this, null); + } else if (pt.X > ClientRectangle.Right) { + document.MoveCaret(CaretDirection.CharForwardNoWrap); + document.SetSelectionToCaret(false); + + CaretMoved(this, null); + } else if (pt.Y > ClientRectangle.Bottom) { + document.MoveCaret(CaretDirection.LineDown); + document.SetSelectionToCaret(false); + + CaretMoved(this, null); + } else if (pt.Y < ClientRectangle.Top) { + document.MoveCaret(CaretDirection.LineUp); + document.SetSelectionToCaret(false); + + CaretMoved(this, null); + } + } + + /// <summary>Ensure the caret is always visible</summary> + internal void CaretMoved (object sender, EventArgs e) + { + Point pos; + int height; + + if (!IsHandleCreated || canvas_width < 1 || canvas_height < 1) + return; + + document.MoveCaretToTextTag (); + pos = document.Caret; + + //Console.WriteLine("Caret now at {0} (Thumb: {1}x{2}, Canvas: {3}x{4}, Document {5}x{6})", pos, hscroll.Value, vscroll.Value, canvas_width, canvas_height, document.Width, document.Height); + + + // Horizontal scrolling: + // If the caret moves to the left outside the visible area, we jump the document into view, not just one + // character, but 1/3 of the width of the document + // If the caret moves to the right outside the visible area, we scroll just enough to keep the caret visible + + // Handle horizontal scrolling + if (document.CaretLine.alignment == HorizontalAlignment.Left) { + // Check if we moved out of view to the left + if (pos.X < (document.ViewPortX)) { + do { + if ((hscroll.Value - document.ViewPortWidth / 3) >= hscroll.Minimum) { + hscroll.SafeValueSet (hscroll.Value - document.ViewPortWidth / 3); + } else { + hscroll.Value = hscroll.Minimum; + } + } while (hscroll.Value > pos.X); + } + + // Check if we moved out of view to the right + if ((pos.X >= (document.ViewPortWidth + document.ViewPortX)) && (hscroll.Value != hscroll.Maximum)) { + if ((pos.X - document.ViewPortWidth + 1) <= hscroll.Maximum) { + if (pos.X - document.ViewPortWidth >= 0) { + hscroll.SafeValueSet (pos.X - document.ViewPortWidth + 1); + } else { + hscroll.Value = 0; + } + } else { + hscroll.Value = hscroll.Maximum; + } + } + } else if (document.CaretLine.alignment == HorizontalAlignment.Right) { +// hscroll.Value = pos.X; + +// if ((pos.X > (this.canvas_width + document.ViewPortX)) && (hscroll.Enabled && (hscroll.Value != hscroll.Maximum))) { +// hscroll.Value = hscroll.Maximum; +// } + } else { + // FIXME - implement center cursor alignment + } + + if (Text.Length > 0) + RaiseSelectionChanged (); + + if (!document.multiline) + return; + + // Handle vertical scrolling + height = document.CaretLine.Height + 1; + + if (pos.Y < document.ViewPortY) + vscroll.SafeValueSet (pos.Y); + if ((pos.Y + height) > (document.ViewPortY + canvas_height)) + vscroll.Value = Math.Min (vscroll.Maximum, pos.Y - canvas_height + height); + } + + internal bool Paste (IDataObject clip, DataFormats.Format format, bool obey_length) + { + string s; + + if (clip == null) + return false; + + if (format == null) { + if ((this is RichTextBox) && clip.GetDataPresent(DataFormats.Rtf)) { + format = DataFormats.GetFormat(DataFormats.Rtf); + } else if ((this is RichTextBox) && clip.GetDataPresent (DataFormats.Bitmap)) { + format = DataFormats.GetFormat (DataFormats.Bitmap); + } else if (clip.GetDataPresent(DataFormats.UnicodeText)) { + format = DataFormats.GetFormat(DataFormats.UnicodeText); + } else if (clip.GetDataPresent(DataFormats.Text)) { + format = DataFormats.GetFormat(DataFormats.Text); + } else { + return false; + } + } else { + if ((format.Name == DataFormats.Rtf) && !(this is RichTextBox)) { + return false; + } + + if (!clip.GetDataPresent(format.Name)) { + return false; + } + } + + if (format.Name == DataFormats.Rtf) { + document.undo.BeginUserAction (String.Format ("Paste")); + ((RichTextBox)this).SelectedRtf = (string)clip.GetData(DataFormats.Rtf); + document.undo.EndUserAction (); + Modified = true; + return true; + } else if (format.Name == DataFormats.Bitmap) { + document.undo.BeginUserAction (String.Format ("Paste")); + // document.InsertImage (document.caret.line, document.caret.pos, (Image) clip.GetData (DataFormats.Bitmap)); + document.MoveCaret (CaretDirection.CharForward); + document.undo.EndUserAction (); + return true; + } else if (format.Name == DataFormats.UnicodeText) { + s = (string)clip.GetData(DataFormats.UnicodeText); + } else if (format.Name == DataFormats.Text) { + s = (string)clip.GetData(DataFormats.Text); + } else { + return false; + } + + if (!obey_length) { + document.undo.BeginUserAction (String.Format ("Paste")); + this.SelectedText = s; + document.undo.EndUserAction (); + } else { + if ((s.Length + (document.Length - SelectedText.Length)) < max_length) { + document.undo.BeginUserAction (String.Format ("Paste")); + this.SelectedText = s; + document.undo.EndUserAction (); + } else if ((document.Length - SelectedText.Length) < max_length) { + document.undo.BeginUserAction (String.Format ("Paste")); + this.SelectedText = s.Substring (0, max_length - (document.Length - SelectedText.Length)); + document.undo.EndUserAction (); + } + } + + Modified = true; + return true; + } + + internal virtual Color ChangeBackColor (Color backColor) + { + return backColor; + } + + internal override bool IsInputCharInternal (char charCode) + { + return true; + } + #endregion // Private Methods + + #region Private Classes + internal class LinkRectangle { + private Rectangle link_area_rectangle; + private LineTag link_tag; + + public LinkRectangle (Rectangle rect) + { + link_tag = null; + link_area_rectangle = rect; + } + + public Rectangle LinkAreaRectangle { + get { return link_area_rectangle; } + set { link_area_rectangle = value; } + } + + public LineTag LinkTag { + get { return link_tag; } + set { link_tag = value; } + } + + public void Scroll (int x_change, int y_change) + { + link_area_rectangle.X += x_change; + link_area_rectangle.Y += y_change; + } + } + #endregion + + // This is called just before OnTextChanged is called. + internal virtual void OnTextUpdate () + { + } + + protected override void OnTextChanged (EventArgs e) + { + base.OnTextChanged (e); + } + + public virtual int GetLineFromCharIndex (int index) + { + Line line_out; + LineTag tag_out; + int pos; + + document.CharIndexToLineTag (index, out line_out, out tag_out, out pos); + + return line_out.LineNo; + } + + protected override void OnMouseUp (MouseEventArgs mevent) + { + base.OnMouseUp (mevent); + } + } +} diff --git a/source/ShiftUI/Widgets/TrackBar.cs b/source/ShiftUI/Widgets/TrackBar.cs new file mode 100644 index 0000000..a0952a8 --- /dev/null +++ b/source/ShiftUI/Widgets/TrackBar.cs @@ -0,0 +1,907 @@ +// +// ShiftUI.TrackBar.cs +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// +// Copyright (c) 2004-2006 Novell, Inc. +// +// Authors: +// Jordi Mas i Hernandez, [email protected] +// Rolf Bjarne Kvinge, [email protected] +// +// TODO: +// - The AutoSize functionality seems quite broken for vertical Widgets in .Net 1.1. Not +// sure if we are implementing it the right way. +// + +// NOT COMPLETE + +using System.ComponentModel; +using System.ComponentModel.Design; +using System.Drawing; +using System.Drawing.Imaging; +using System.Drawing.Drawing2D; +using System.Timers; +using System.Runtime.InteropServices; +using System; + +namespace ShiftUI +{ + [DefaultBindingProperty ("Value")] + [ComVisible (true)] + [ClassInterface (ClassInterfaceType.AutoDispatch)] + //[Designer("ShiftUI.Design.TrackBarDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")] + [DefaultEvent ("Scroll")] + [DefaultProperty("Value")] + [ToolboxWidget] + public class TrackBar : Widget, ISupportInitialize + { + private int minimum; + private int maximum; + internal int tickFrequency; + private bool autosize; + private int position; + private int smallChange; + private int largeChange; + private Orientation orientation; + private TickStyle tickStyle; + private Rectangle thumb_pos = new Rectangle (); /* Current position and size of the thumb */ + private Rectangle thumb_area = new Rectangle (); /* Area where the thumb can scroll */ + internal bool thumb_pressed = false; + private System.Timers.Timer holdclick_timer = new System.Timers.Timer (); + internal int thumb_mouseclick; + private bool mouse_clickmove; + private bool is_moving_right; // which way the thumb should move when mouse is down (right=up, left=down) + internal int mouse_down_x_offset; // how far from left side of thumb was the mouse clicked. + internal bool mouse_moved; // has the mouse moved since it was clicked? + private const int size_of_autosize = 45; + private bool right_to_left_layout; + bool thumb_entered; + + #region events + //[EditorBrowsable (EditorBrowsableState.Always)] + [Browsable (true)] + public new event EventHandler AutoSizeChanged { + add {base.AutoSizeChanged += value;} + remove {base.AutoSizeChanged -= value;} + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler BackgroundImageChanged { + add { base.BackgroundImageChanged += value; } + remove { base.BackgroundImageChanged -= value; } + } + + //[EditorBrowsable (EditorBrowsableState.Never)] + [Browsable (false)] + public new event EventHandler BackgroundImageLayoutChanged + { + add { base.BackgroundImageLayoutChanged += value; } + remove { base.BackgroundImageLayoutChanged -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler Click { + add { base.Click += value; } + remove { base.Click -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler DoubleClick { + add { base.DoubleClick += value; } + remove { base.DoubleClick -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler FontChanged { + add { base.FontChanged += value; } + remove { base.FontChanged -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler ForeColorChanged { + add { base.ForeColorChanged += value; } + remove { base.ForeColorChanged -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler ImeModeChanged { + add { base.ImeModeChanged += value; } + remove { base.ImeModeChanged -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event MouseEventHandler MouseClick { + add {base.MouseClick += value;} + remove {base.MouseClick -= value;} + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event MouseEventHandler MouseDoubleClick + { + add { base.MouseDoubleClick += value; } + remove { base.MouseDoubleClick -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler PaddingChanged + { + add { base.PaddingChanged += value; } + remove { base.PaddingChanged -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event PaintEventHandler Paint { + add { base.Paint += value; } + remove { base.Paint -= value; } + } + + public event EventHandler RightToLeftLayoutChanged { + add {Events.AddHandler (RightToLeftLayoutChangedEvent, value);} + remove {Events.RemoveHandler (RightToLeftLayoutChangedEvent, value);} + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler TextChanged { + add { base.TextChanged += value; } + remove { base.TextChanged -= value; } + } + + static object RightToLeftLayoutChangedEvent = new object (); + static object ScrollEvent = new object (); + static object ValueChangedEvent = new object (); + + public event EventHandler Scroll { + add { Events.AddHandler (ScrollEvent, value); } + remove { Events.RemoveHandler (ScrollEvent, value); } + } + + public event EventHandler ValueChanged { + add { Events.AddHandler (ValueChangedEvent, value); } + remove { Events.RemoveHandler (ValueChangedEvent, value); } + } + + #endregion // Events + + #region UIA FrameWork Events + static object UIAValueParamChangedEvent = new object (); + + internal event EventHandler UIAValueParamChanged { + add { Events.AddHandler (UIAValueParamChangedEvent, value); } + remove { Events.RemoveHandler (UIAValueParamChangedEvent, value); } + } + + internal void OnUIAValueParamChanged () + { + EventHandler eh = (EventHandler) Events [UIAValueParamChangedEvent]; + if (eh != null) + eh (this, EventArgs.Empty); + } + #endregion + + public TrackBar () + { + orientation = Orientation.Horizontal; + minimum = 0; + maximum = 10; + tickFrequency = 1; + autosize = true; + position = 0; + tickStyle = TickStyle.BottomRight; + smallChange = 1; + largeChange = 5; + mouse_clickmove = false; + MouseDown += new MouseEventHandler (OnMouseDownTB); + MouseUp += new MouseEventHandler (OnMouseUpTB); + MouseMove += new MouseEventHandler (OnMouseMoveTB); + MouseLeave += new EventHandler (OnMouseLeave); + KeyDown += new KeyEventHandler (OnKeyDownTB); + LostFocus += new EventHandler (OnLostFocusTB); + GotFocus += new EventHandler (OnGotFocusTB); + holdclick_timer.Elapsed += new ElapsedEventHandler (OnFirstClickTimer); + + SetStyle (Widgetstyles.UserPaint | Widgetstyles.Opaque | Widgetstyles.UseTextForAccessibility, false); + } + + #region Private & Internal Properties + internal Rectangle ThumbPos { + get { + return thumb_pos; + } + + set { + thumb_pos = value; + } + } + + internal Rectangle ThumbArea { + get { + return thumb_area; + } + + set { + thumb_area = value; + } + } + + internal bool ThumbEntered { + get { return thumb_entered; } + set { + if (thumb_entered == value) + return; + thumb_entered = value; + if (ThemeEngine.Current.TrackBarHasHotThumbStyle) + Invalidate (GetRealThumbRectangle ()); + } + } + #endregion // Private & Internal Properties + + #region Public Properties + + [Browsable (true)] + //[EditorBrowsable (EditorBrowsableState.Always)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Visible)] + [DefaultValue (true)] + public override bool AutoSize { + get { return autosize; } + set { autosize = value;} + } + + //[EditorBrowsable (EditorBrowsableState.Never)] + [Browsable (false)] + public override Image BackgroundImage { + get { return base.BackgroundImage; } + set { base.BackgroundImage = value; } + } + + //[EditorBrowsable (EditorBrowsableState.Never)] + [Browsable (false)] + public override ImageLayout BackgroundImageLayout { + get { + return base.BackgroundImageLayout; + } + set { + base.BackgroundImageLayout = value; + } + } + + protected override CreateParams CreateParams { + get { + return base.CreateParams; + } + } + + protected override ImeMode DefaultImeMode { + get {return ImeMode.Disable; } + } + + protected override Size DefaultSize { + get { return ThemeEngine.Current.TrackBarDefaultSize; } + } + + //[EditorBrowsable (EditorBrowsableState.Never)] + protected override bool DoubleBuffered { + get { + return base.DoubleBuffered; + } + set { + base.DoubleBuffered = value; + } + } + + [Browsable(false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public override Font Font { + get { return base.Font; } + set { base.Font = value; } + } + + //[EditorBrowsable (EditorBrowsableState.Never)] + [Browsable (false)] + public override Color ForeColor { + get { return base.ForeColor; } + set { base.ForeColor = value; } + } + + //[EditorBrowsable (EditorBrowsableState.Never)] + [Browsable (false)] + public new ImeMode ImeMode { + get { return base.ImeMode; } + set { base.ImeMode = value; } + } + + [DefaultValue (5)] + public int LargeChange + { + get { return largeChange; } + set { + if (value < 0) + throw new ArgumentOutOfRangeException (string.Format ("Value '{0}' must be greater than or equal to 0.", value)); + + largeChange = value; + + OnUIAValueParamChanged (); + } + } + + [DefaultValue (10)] + [RefreshProperties (RefreshProperties.All)] + public int Maximum { + get { return maximum; } + set { + if (maximum != value) { + maximum = value; + + if (maximum < minimum) + minimum = maximum; + + Refresh (); + + OnUIAValueParamChanged (); + } + } + } + + [DefaultValue (0)] + [RefreshProperties (RefreshProperties.All)] + public int Minimum { + get { return minimum; } + set { + + if (Minimum != value) { + minimum = value; + + if (minimum > maximum) + maximum = minimum; + + Refresh (); + + OnUIAValueParamChanged (); + } + } + } + + [DefaultValue (Orientation.Horizontal)] + [Localizable (true)] + public Orientation Orientation { + get { return orientation; } + set { + if (!Enum.IsDefined (typeof (Orientation), value)) + throw new InvalidEnumArgumentException (string.Format("Enum argument value '{0}' is not valid for Orientation", value)); + + /* Orientation can be changed once the control has been created */ + if (orientation != value) { + orientation = value; + + if (this.IsHandleCreated) { + Size = new Size (Height, Width); + Refresh (); + } + } + } + } + + [Browsable (false)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new Padding Padding { + get { + return base.Padding; + } + set { + base.Padding = value; + } + } + + [Localizable (true)] + [DefaultValue (false)] + public virtual bool RightToLeftLayout { + get { + return right_to_left_layout; + } + set { + if (value != right_to_left_layout) { + right_to_left_layout = value; + OnRightToLeftLayoutChanged (EventArgs.Empty); + } + } + } + + [DefaultValue (1)] + public int SmallChange { + get { return smallChange;} + set { + if (value < 0) + throw new ArgumentOutOfRangeException (string.Format ("Value '{0}' must be greater than or equal to 0.", value)); + + if (smallChange != value) { + smallChange = value; + + OnUIAValueParamChanged (); + } + } + } + + //[EditorBrowsable (EditorBrowsableState.Never)] + [Bindable (false)] + [Browsable (false)] + public override string Text { + get { return base.Text; } + set { base.Text = value; } + } + + [DefaultValue (1)] + public int TickFrequency { + get { return tickFrequency; } + set { + if ( value > 0 ) { + tickFrequency = value; + Refresh (); + } + } + } + + [DefaultValue (TickStyle.BottomRight)] + public TickStyle TickStyle { + get { return tickStyle; } + set { + if (!Enum.IsDefined (typeof (TickStyle), value)) + throw new InvalidEnumArgumentException (string.Format("Enum argument value '{0}' is not valid for TickStyle", value)); + + if (tickStyle != value) { + tickStyle = value; + Refresh (); + } + } + } + + [DefaultValue (0)] + [Bindable (true)] + public int Value { + get { return position; } + set { + SetValue (value, false); + } + } + + void SetValue (int value, bool fire_onscroll) + { + if (value < Minimum || value > Maximum) + throw new ArgumentException( + String.Format ("'{0}' is not a valid value for 'Value'. 'Value' should be between 'Minimum' and 'Maximum'", value)); + + if (position == value) + return; + + position = value; + + // OnScroll goes before OnValueChanged + if (fire_onscroll) + OnScroll (EventArgs.Empty); + + // XXX any reason we don't call OnValueChanged here? + EventHandler eh = (EventHandler)(Events [ValueChangedEvent]); + if (eh != null) + eh (this, EventArgs.Empty); + + Invalidate (thumb_area); + } + + #endregion //Public Properties + + #region Public Methods + + public void BeginInit () + { + + } + + protected override void CreateHandle () + { + base.CreateHandle (); + } + + protected override void SetBoundsCore (int x, int y,int width, int height, BoundsSpecified specified) + { + if (AutoSize) { + if (orientation == Orientation.Vertical) { + width = size_of_autosize; + } else { + height = size_of_autosize; + } + } + base.SetBoundsCore (x, y, width, height, specified); + } + + public void EndInit () + { + + } + + protected override bool IsInputKey (Keys keyData) + { + if ((keyData & Keys.Alt) == 0) { + switch (keyData & Keys.KeyCode) { + case Keys.Down: + case Keys.Right: + case Keys.Up: + case Keys.Left: + case Keys.PageUp: + case Keys.PageDown: + case Keys.Home: + case Keys.End: + return true; + } + } + return base.IsInputKey (keyData); + } + + protected override void OnBackColorChanged (EventArgs e) + { + base.OnBackColorChanged (e); + } + + protected override void OnHandleCreated (EventArgs e) + { + base.OnHandleCreated (e); + + if (AutoSize) + if (Orientation == Orientation.Horizontal) + Size = new Size (Width, 40); + else + Size = new Size (50, Height); + + UpdatePos (Value, true); + } + + //[EditorBrowsable (EditorBrowsableState.Advanced)] + protected override void OnMouseWheel (MouseEventArgs e) + { + base.OnMouseWheel (e); + + if (!Enabled) return; + + if (e.Delta > 0) + SmallDecrement (); + else + SmallIncrement (); + } + + //[EditorBrowsable (EditorBrowsableState.Advanced)] + protected virtual void OnRightToLeftLayoutChanged (EventArgs e) + { + EventHandler eh = (EventHandler)Events [RightToLeftLayoutChangedEvent]; + if (eh != null) + eh (this, e); + } + + protected virtual void OnScroll (EventArgs e) + { + EventHandler eh = (EventHandler)(Events [ScrollEvent]); + if (eh != null) + eh (this, e); + } + + protected override void OnSystemColorsChanged (EventArgs e) + { + base.OnSystemColorsChanged (e); + Invalidate (); + } + + protected virtual void OnValueChanged (EventArgs e) + { + EventHandler eh = (EventHandler)(Events [ValueChangedEvent]); + if (eh != null) + eh (this, e); + } + + public void SetRange (int minValue, int maxValue) + { + Minimum = minValue; + Maximum = maxValue; + } + + public override string ToString() + { + return string.Format("ShiftUI.TrackBar, Minimum: {0}, Maximum: {1}, Value: {2}", + Minimum, Maximum, Value); + } + + + protected override void WndProc (ref Message m) + { + base.WndProc (ref m); + + // Basically we want Widgetstyles.ResizeRedraw but + // tests say we can't set that flag + if ((Msg)m.Msg == Msg.WM_WINDOWPOSCHANGED && Visible) + Invalidate (); + } + + #endregion Public Methods + + #region Private Methods + + private void UpdatePos (int newPos, bool update_trumbpos) + { + if (newPos < minimum){ + SetValue (minimum, true); + } + else { + if (newPos > maximum) { + SetValue (maximum, true); + } + else { + SetValue (newPos, true); + } + } + } + + // Used by UIA implementation, so making internal + internal void LargeIncrement () + { + UpdatePos (position + LargeChange, true); + Invalidate (thumb_area); + } + + // Used by UIA implementation, so making internal + internal void LargeDecrement () + { + UpdatePos (position - LargeChange, true); + Invalidate (thumb_area); + } + + private void SmallIncrement () + { + UpdatePos (position + SmallChange, true); + Invalidate (thumb_area); + } + + private void SmallDecrement () + { + UpdatePos (position - SmallChange, true); + Invalidate (thumb_area); + } + + private void OnMouseUpTB (object sender, MouseEventArgs e) + { + if (!Enabled) return; + + if (thumb_pressed == true || mouse_clickmove == true) { + thumb_pressed = false; + holdclick_timer.Enabled = false; + this.Capture = false; + Invalidate (thumb_area); + } + } + + private void OnMouseDownTB (object sender, MouseEventArgs e) + { + if (!Enabled) return; + + mouse_moved = false; + + bool fire_timer = false; + + Point point = new Point (e.X, e.Y); + + if (orientation == Orientation.Horizontal) { + + if (thumb_pos.Contains (point)) { + this.Capture = true; + thumb_pressed = true; + thumb_mouseclick = e.X; + mouse_down_x_offset = e.X - thumb_pos.X; + Invalidate (thumb_area); + } + else { + if (thumb_area.Contains (point)) { + is_moving_right = e.X > thumb_pos.X + thumb_pos.Width; + if (is_moving_right) + LargeIncrement (); + else + LargeDecrement (); + + Invalidate (thumb_area); + fire_timer = true; + mouse_clickmove = true; + } + } + } + else { + Rectangle vertical_thumb_pos = thumb_pos; + vertical_thumb_pos.Width = thumb_pos.Height; + vertical_thumb_pos.Height = thumb_pos.Width; + if (vertical_thumb_pos.Contains (point)) { + this.Capture = true; + thumb_pressed = true; + thumb_mouseclick = e.Y; + mouse_down_x_offset = e.Y - thumb_pos.Y; + Invalidate (thumb_area); + } + else { + if (thumb_area.Contains (point)) { + is_moving_right = e.Y > thumb_pos.Y + thumb_pos.Width; + if (is_moving_right) + LargeDecrement (); + else + LargeIncrement (); + + Invalidate (thumb_area); + fire_timer = true; + mouse_clickmove = true; + } + } + } + + if (fire_timer) { + holdclick_timer.Interval = 300; + holdclick_timer.Enabled = true; + } + } + + private void OnMouseMoveTB (object sender, MouseEventArgs e) + { + if (!Enabled) return; + + mouse_moved = true; + + /* Moving the thumb */ + if (thumb_pressed) + SetValue (ThemeEngine.Current.TrackBarValueFromMousePosition (e.X, e.Y, this), true); + + ThumbEntered = GetRealThumbRectangle ().Contains (e.Location); + } + + Rectangle GetRealThumbRectangle () + { + Rectangle result = thumb_pos; + if (Orientation == Orientation.Vertical) { + result.Width = thumb_pos.Height; + result.Height = thumb_pos.Width; + } + return result; + } + + internal override void OnPaintInternal (PaintEventArgs pevent) + { + ThemeEngine.Current.DrawTrackBar (pevent.Graphics, pevent.ClipRectangle, this); + } + + private void OnLostFocusTB (object sender, EventArgs e) + { + Invalidate(); + } + + private void OnGotFocusTB (object sender, EventArgs e) + { + Invalidate(); + } + private void OnKeyDownTB (object sender, KeyEventArgs e) + { + bool horiz = Orientation == Orientation.Horizontal; + switch (e.KeyCode) { + + case Keys.Down: + case Keys.Right: + if(horiz) + SmallIncrement(); + else + SmallDecrement (); + break; + + case Keys.Up: + case Keys.Left: + if (horiz) + SmallDecrement(); + else + SmallIncrement(); + break; + + case Keys.PageUp: + if (horiz) + LargeDecrement(); + else + LargeIncrement(); + break; + + case Keys.PageDown: + if (horiz) + LargeIncrement(); + else + LargeDecrement(); + break; + + case Keys.Home: + if (horiz) + SetValue (Minimum, true); + else + SetValue (Maximum, true); + break; + + case Keys.End: + if (horiz) + SetValue (Maximum, true); + else + SetValue (Minimum, true); + break; + + default: + break; + } + } + + private void OnFirstClickTimer (Object source, ElapsedEventArgs e) + { + Point pnt; + pnt = PointToClient (MousePosition); + /* + On Win32 the thumb only moves in one direction after a click, + if the thumb passes the clicked point it will never go in the + other way unless the mouse is released and clicked again. This + is also true if the mouse moves while beeing hold down. + */ + + if (thumb_area.Contains (pnt)) { + bool invalidate = false; + if (orientation == Orientation.Horizontal) { + if (pnt.X > thumb_pos.X + thumb_pos.Width && is_moving_right) { + LargeIncrement (); + invalidate = true; + } else if (pnt.X < thumb_pos.X && !is_moving_right) { + LargeDecrement (); + invalidate = true; + } + } else { + if (pnt.Y > thumb_pos.Y + thumb_pos.Width && is_moving_right) { + LargeDecrement (); + invalidate = true; + } else if (pnt.Y < thumb_pos.Y && !is_moving_right) { + LargeIncrement (); + invalidate = true; + } + } + if (invalidate) + // A Refresh is necessary because the mouse is down and if we just invalidate + // we'll only get paint events once in a while. + Refresh(); + } + } + + void OnMouseLeave (object sender, EventArgs e) + { + ThumbEntered = false; + } + #endregion // Private Methods + } +} + diff --git a/source/ShiftUI/Widgets/TreeNode.cs b/source/ShiftUI/Widgets/TreeNode.cs new file mode 100644 index 0000000..0a4a13b --- /dev/null +++ b/source/ShiftUI/Widgets/TreeNode.cs @@ -0,0 +1,1087 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2004-2005 Novell, Inc. +// +// Authors: +// Jackson Harper ([email protected]) +// Kazuki Oikawa ([email protected]) + +using System; +using System.ComponentModel; +using System.Drawing; +using System.Runtime.Serialization; +using System.Text; + +namespace ShiftUI +{ + [DefaultProperty ("Text")] + [TypeConverter(typeof(TreeNodeConverter))] + [Serializable] + public class TreeNode : MarshalByRefObject, ICloneable, ISerializable + { + #region Fields + private TreeView tree_view; + internal TreeNode parent; + + private string text; + private int image_index = -1; + private int selected_image_index = -1; + private ContextMenuStrip context_menu_strip; + private string image_key = String.Empty; + private string selected_image_key = String.Empty; + private int state_image_index = -1; + private string state_image_key = String.Empty; + private string tool_tip_text = String.Empty; + internal TreeNodeCollection nodes; + internal TreeViewAction check_reason = TreeViewAction.Unknown; + + internal int visible_order = 0; + internal int width = -1; + + internal bool is_expanded = false; + private bool check; + internal OwnerDrawPropertyBag prop_bag; + + private object tag; + + internal IntPtr handle; + + private string name = string.Empty; + #endregion // Fields + + #region Internal Constructors + internal TreeNode (TreeView tree_view) : this () + { + this.tree_view = tree_view; + is_expanded = true; + } + + protected TreeNode (SerializationInfo serializationInfo, StreamingContext context) : this () + { + SerializationInfoEnumerator en; + SerializationEntry e; + int children; + + en = serializationInfo.GetEnumerator(); + children = 0; + while (en.MoveNext()) { + e = en.Current; + switch(e.Name) { + case "Text": Text = (string)e.Value; break; + case "PropBag": prop_bag = (OwnerDrawPropertyBag)e.Value; break; + case "ImageIndex": image_index = (int)e.Value; break; + case "SelectedImageIndex": selected_image_index = (int)e.Value; break; + case "Tag": tag = e.Value; break; + case "IsChecked": check = (bool)e.Value; break; + case "ChildCount": children = (int)e.Value; break; + } + } + if (children > 0) { + for (int i = 0; i < children; i++) { + TreeNode node = (TreeNode) serializationInfo.GetValue ("children" + i, typeof (TreeNode)); + Nodes.Add (node); + } + } + } + #endregion // Internal Constructors + + #region Public Constructors + public TreeNode () + { + nodes = new TreeNodeCollection (this); + } + + public TreeNode (string text) : this () + { + Text = text; + } + + public TreeNode (string text, TreeNode [] children) : this (text) + { + Nodes.AddRange (children); + } + + public TreeNode (string text, int imageIndex, int selectedImageIndex) : this (text) + { + this.image_index = imageIndex; + this.selected_image_index = selectedImageIndex; + } + + public TreeNode (string text, int imageIndex, int selectedImageIndex, + TreeNode[] children) + : this (text, imageIndex, selectedImageIndex) + { + Nodes.AddRange (children); + } + + #endregion // Public Constructors + + #region ICloneable Members + public virtual object Clone () + { + TreeNode tn = (TreeNode)Activator.CreateInstance (GetType ()); + tn.name = name; + tn.text = text; + tn.image_key = image_key; + tn.image_index = image_index; + tn.selected_image_index = selected_image_index; + tn.selected_image_key = selected_image_key; + tn.state_image_index = state_image_index; + tn.state_image_key = state_image_key; + tn.tag = tag; + tn.check = check; + tn.tool_tip_text = tool_tip_text; + tn.context_menu_strip = context_menu_strip; + if (nodes != null) { + foreach (TreeNode child in nodes) + tn.nodes.Add ((TreeNode)child.Clone ()); + } + if (prop_bag != null) + tn.prop_bag = OwnerDrawPropertyBag.Copy (prop_bag); + return tn; + } + + #endregion // ICloneable Members + + #region ISerializable Members + void ISerializable.GetObjectData (SerializationInfo si, StreamingContext context) + { + si.AddValue ("Text", Text); + si.AddValue ("prop_bag", prop_bag, typeof (OwnerDrawPropertyBag)); + si.AddValue ("ImageIndex", ImageIndex); + si.AddValue ("SelectedImageIndex", SelectedImageIndex); + si.AddValue ("Tag", Tag); + si.AddValue ("Checked", Checked); + + si.AddValue ("NumberOfChildren", Nodes.Count); + for (int i = 0; i < Nodes.Count; i++) + si.AddValue ("Child-" + i, Nodes [i], typeof (TreeNode)); + } + + protected virtual void Deserialize (SerializationInfo serializationInfo, StreamingContext context) + { + Text = serializationInfo.GetString ("Text"); + prop_bag = (OwnerDrawPropertyBag)serializationInfo.GetValue ("prop_bag", typeof (OwnerDrawPropertyBag)); + ImageIndex = serializationInfo.GetInt32 ("ImageIndex"); + SelectedImageIndex = serializationInfo.GetInt32 ("SelectedImageIndex"); + Tag = serializationInfo.GetValue ("Tag", typeof (Object)); + Checked = serializationInfo.GetBoolean ("Checked"); + + int count = serializationInfo.GetInt32 ("NumberOfChildren"); + + for (int i = 0; i < count; i++) + Nodes.Add ((TreeNode)serializationInfo.GetValue ("Child-" + i, typeof (TreeNode))); + } + + protected virtual void Serialize (SerializationInfo si, StreamingContext context) + { + si.AddValue ("Text", Text); + si.AddValue ("prop_bag", prop_bag, typeof (OwnerDrawPropertyBag)); + si.AddValue ("ImageIndex", ImageIndex); + si.AddValue ("SelectedImageIndex", SelectedImageIndex); + si.AddValue ("Tag", Tag); + si.AddValue ("Checked", Checked); + + si.AddValue ("NumberOfChildren", Nodes.Count); + for (int i = 0; i < Nodes.Count; i++) + si.AddValue ("Child-" + i, Nodes[i], typeof (TreeNode)); + } + #endregion // ISerializable Members + + #region Public Instance Properties + public Color BackColor { + get { + if (prop_bag != null) + return prop_bag.BackColor; + return Color.Empty; + } + set { + if (prop_bag == null) + prop_bag = new OwnerDrawPropertyBag (); + prop_bag.BackColor = value; + + TreeView tree_view = TreeView; + if (tree_view != null) + tree_view.UpdateNode (this); + } + } + + [Browsable (false)] + public Rectangle Bounds { + get { + if (TreeView == null) + return Rectangle.Empty; + + int x = GetX (); + int y = GetY (); + + if (width == -1) + width = TreeView.GetNodeWidth (this); + + Rectangle res = new Rectangle (x, y, width, TreeView.ActualItemHeight); + return res; + } + } + + internal int GetY () + { + if (TreeView == null) + return 0; + return (visible_order - 1) * TreeView.ActualItemHeight - (TreeView.skipped_nodes * TreeView.ActualItemHeight); + } + + internal int GetX () + { + if (TreeView == null) + return 0; + int indent_level = IndentLevel; + int roots = (TreeView.ShowRootLines ? 1 : 0); + int cb = (TreeView.CheckBoxes ? 19 : 0); + if (!TreeView.CheckBoxes && StateImage != null) + cb = 19; + int imgs = (TreeView.ImageList != null ? TreeView.ImageList.ImageSize.Width + 3 : 0); + return ((indent_level + roots) * TreeView.Indent) + cb + imgs - TreeView.hbar_offset; + } + + internal int GetLinesX () + { + int roots = (TreeView.ShowRootLines ? 1 : 0); + return (IndentLevel + roots) * TreeView.Indent - TreeView.hbar_offset; + } + + internal int GetImageX () + { + return GetLinesX () + (TreeView.CheckBoxes || StateImage != null ? 19 : 0); + } + + // In theory we should be able to track this instead of computing + // every single time we need it, however for now I am going to + // do it this way to reduce bugs in my new bounds computing code + internal int IndentLevel { + get { + TreeNode walk = this; + int res = 0; + while (walk.Parent != null) { + walk = walk.Parent; + res++; + } + + return res; + } + } + + [DefaultValue (false)] + public bool Checked { + get { return check; } + set { + if (check == value) + return; + TreeViewCancelEventArgs args = new TreeViewCancelEventArgs (this, false, check_reason); + if (TreeView != null) + TreeView.OnBeforeCheck (args); + if (!args.Cancel) { + check = value; + + // TreeView can become null after OnAfterCheck, this the double null check + if (TreeView != null) + TreeView.OnAfterCheck (new TreeViewEventArgs (this, check_reason)); + if (TreeView != null) + TreeView.UpdateNode (this); + } + check_reason = TreeViewAction.Unknown; + } + } + + [DefaultValue (null)] + public virtual ContextMenuStrip ContextMenuStrip { + get { return context_menu_strip; } + set { context_menu_strip = value; } + } + + [Browsable (false)] + public TreeNode FirstNode { + get { + if (nodes.Count > 0) + return nodes [0]; + return null; + } + } + + public Color ForeColor { + get { + if (prop_bag != null) + return prop_bag.ForeColor; + if (TreeView != null) + return TreeView.ForeColor; + return Color.Empty; + } + set { + if (prop_bag == null) + prop_bag = new OwnerDrawPropertyBag (); + prop_bag.ForeColor = value; + + TreeView tree_view = TreeView; + if (tree_view != null) + tree_view.UpdateNode (this); + } + } + + [Browsable (false)] + public string FullPath { + get { + if (TreeView == null) + throw new InvalidOperationException ("No TreeView associated"); + + StringBuilder builder = new StringBuilder (); + BuildFullPath (builder); + return builder.ToString (); + } + } + + [DefaultValue (-1)] + [RelatedImageList ("TreeView.ImageList")] + [TypeConverter (typeof (TreeViewImageIndexConverter))] + [RefreshProperties (RefreshProperties.Repaint)] + //[Editor ("ShiftUI.Design.ImageIndexEditor, " + Consts.AssemblySystem_Design, typeof (System.Drawing.Design.UITypeEditor))] + [Localizable(true)] + public int ImageIndex { + get { return image_index; } + set { + if (image_index == value) + return; + image_index = value; + image_key = string.Empty; + TreeView tree = TreeView; + if (tree != null) + tree.UpdateNode (this); + } + } + + [Localizable(true)] + [DefaultValue ("")] + [RelatedImageList ("TreeView.ImageList")] + [TypeConverter (typeof (TreeViewImageKeyConverter))] + [RefreshProperties (RefreshProperties.Repaint)] + //[Editor ("ShiftUI.Design.ImageIndexEditor, " + Consts.AssemblySystem_Design, typeof (System.Drawing.Design.UITypeEditor))] + public string ImageKey { + get { return image_key; } + set { + if (image_key == value) + return; + image_key = value; + image_index = -1; + + TreeView tree = TreeView; + if (tree != null) + tree.UpdateNode(this); + } + } + + [Browsable (false)] + public bool IsEditing { + get { + TreeView tv = TreeView; + if (tv == null) + return false; + return tv.edit_node == this; + } + } + + [Browsable (false)] + public bool IsExpanded { + get { + TreeView tv = TreeView; + + if (tv != null && tv.IsHandleCreated) { + // This is ridiculous + bool found = false; + foreach (TreeNode walk in TreeView.Nodes) { + if (walk.Nodes.Count > 0) + found = true; + } + + if (!found) + return false; + } + + return is_expanded; + } + } + + [Browsable (false)] + public bool IsSelected { + get { + if (TreeView == null || !TreeView.IsHandleCreated) + return false; + return TreeView.SelectedNode == this; + } + } + + [Browsable (false)] + public bool IsVisible { + get { + if (TreeView == null || !TreeView.IsHandleCreated || !TreeView.Visible) + return false; + + if (visible_order <= TreeView.skipped_nodes || visible_order - TreeView.skipped_nodes > TreeView.VisibleCount) + return false; + + return ArePreviousNodesExpanded; + } + } + + [Browsable (false)] + public TreeNode LastNode { + get { + return (nodes == null || nodes.Count == 0) ? null : nodes [nodes.Count - 1]; + } + } + + [Browsable (false)] + public int Level { + get { return IndentLevel; } + } + + public string Name + { + get { return this.name; } + set { + // Value should never be null as per spec + this.name = (value == null) ? string.Empty : value; + } + } + + [Browsable (false)] + public TreeNode NextNode { + get { + if (parent == null) + return null; + int index = Index; + if (parent.Nodes.Count > index + 1) + return parent.Nodes [index + 1]; + return null; + } + } + + [Browsable (false)] + public TreeNode NextVisibleNode { + get { + OpenTreeNodeEnumerator o = new OpenTreeNodeEnumerator (this); + o.MoveNext (); // move to the node itself + + if (!o.MoveNext ()) + return null; + TreeNode c = o.CurrentNode; + if (!c.IsInClippingRect) + return null; + return c; + } + } + + [DefaultValue (null)] + [Localizable (true)] + public Font NodeFont { + get { + if (prop_bag != null) + return prop_bag.Font; + if (TreeView != null) + return TreeView.Font; + return null; + } + set { + if (prop_bag == null) + prop_bag = new OwnerDrawPropertyBag (); + prop_bag.Font = value; + Invalidate (); + } + } + + [Browsable (false)] + [ListBindable (false)] + public TreeNodeCollection Nodes { + get { + if (nodes == null) + nodes = new TreeNodeCollection (this); + return nodes; + } + } + + [Browsable (false)] + public TreeNode Parent { + get { + TreeView tree_view = TreeView; + if (tree_view != null && tree_view.root_node == parent) + return null; + return parent; + } + } + + [Browsable (false)] + public TreeNode PrevNode { + get { + if (parent == null) + return null; + int index = Index; + if (index <= 0 || index > parent.Nodes.Count) + return null; + return parent.Nodes [index - 1]; + } + } + + [Browsable (false)] + public TreeNode PrevVisibleNode { + get { + OpenTreeNodeEnumerator o = new OpenTreeNodeEnumerator (this); + o.MovePrevious (); // move to the node itself + + if (!o.MovePrevious ()) + return null; + TreeNode c = o.CurrentNode; + if (!c.IsInClippingRect) + return null; + return c; + } + } + + [DefaultValue (-1)] + [RelatedImageList ("TreeView.ImageList")] + [TypeConverter (typeof (TreeViewImageIndexConverter))] + [RefreshProperties (RefreshProperties.Repaint)] + //[Editor ("ShiftUI.Design.ImageIndexEditor, " + Consts.AssemblySystem_Design, typeof (System.Drawing.Design.UITypeEditor))] + [Localizable (true)] + public int SelectedImageIndex { + get { return selected_image_index; } + set { selected_image_index = value; } + } + + [Localizable (true)] + [DefaultValue ("")] + [RelatedImageList ("TreeView.ImageList")] + [TypeConverter (typeof (TreeViewImageKeyConverter))] + [RefreshProperties (RefreshProperties.Repaint)] + //[Editor ("ShiftUI.Design.ImageIndexEditor, " + Consts.AssemblySystem_Design, typeof (System.Drawing.Design.UITypeEditor))] + public string SelectedImageKey { + get { return selected_image_key; } + set { selected_image_key = value; } + } + + [Localizable (true)] + [DefaultValue (-1)] + [RelatedImageList ("TreeView.StateImageList")] + [TypeConverter (typeof (NoneExcludedImageIndexConverter))] + [RefreshProperties (RefreshProperties.Repaint)] + //[Editor ("ShiftUI.Design.ImageIndexEditor, " + Consts.AssemblySystem_Design, typeof (System.Drawing.Design.UITypeEditor))] + public int StateImageIndex { + get { return state_image_index; } + set { + if (state_image_index != value) { + state_image_index = value; + state_image_key = string.Empty; + Invalidate (); + } + } + } + + [Localizable (true)] + [DefaultValue ("")] + [RelatedImageList ("TreeView.StateImageList")] + [TypeConverter (typeof (ImageKeyConverter))] + [RefreshProperties (RefreshProperties.Repaint)] + //[Editor ("ShiftUI.Design.ImageIndexEditor, " + Consts.AssemblySystem_Design, typeof (System.Drawing.Design.UITypeEditor))] + public string StateImageKey { + get { return state_image_key; } + set { + if (state_image_key != value) { + state_image_key = value; + state_image_index = -1; + Invalidate (); + } + } + } + + [Bindable(true)] + [Localizable(false)] + [TypeConverter(typeof(System.ComponentModel.StringConverter))] + [DefaultValue(null)] + public object Tag { + get { return tag; } + set { tag = value; } + } + + [Localizable(true)] + public string Text { + get { + if (text == null) + return String.Empty; + return text; + } + set { + if (text == value) + return; + text = value; + Invalidate (); + // UIA Framework Event: Text Changed + TreeView view = TreeView; + if (view != null) + view.OnUIANodeTextChanged (new TreeViewEventArgs (this)); + } + } + + [DefaultValue ("")] + [Localizable (false)] + public string ToolTipText { + get { return tool_tip_text; } + set { tool_tip_text = value; } + } + + [Browsable (false)] + public TreeView TreeView { + get { + if (tree_view != null) + return tree_view; + TreeNode walk = parent; + while (walk != null) { + if (walk.TreeView != null) + break; + walk = walk.parent; + } + if (walk == null) + return null; + return walk.TreeView; + } + } + + [Browsable (false)] + public IntPtr Handle { + get { + // MS throws a NullReferenceException if the TreeView isn't set... + if (handle == IntPtr.Zero && TreeView != null) + handle = TreeView.CreateNodeHandle (); + return handle; + } + } + + #endregion // Public Instance Properties + + + public static TreeNode FromHandle (TreeView tree, IntPtr handle) + { + if (handle == IntPtr.Zero) + return null; + // No arg checking on MS it just throws a NullRef if treeview is null + return tree.NodeFromHandle (handle); + } + + #region Public Instance Methods + public void BeginEdit () + { + TreeView tv = TreeView; + if (tv != null) + tv.BeginEdit (this); + } + + public void Collapse () + { + CollapseInternal (false); + } + + public void Collapse (bool ignoreChildren) + { + if (ignoreChildren) + Collapse (); + else + CollapseRecursive (this); + } + + public void EndEdit (bool cancel) + { + TreeView tv = TreeView; + if (!cancel && tv != null) + tv.EndEdit (this); + else if (cancel && tv != null) + tv.CancelEdit (this); + } + + public void Expand () + { + Expand (false); + } + + public void ExpandAll () + { + ExpandRecursive (this); + if(TreeView != null) + TreeView.UpdateNode (TreeView.root_node); + } + + public void EnsureVisible () + { + if (TreeView == null) + return; + + if (this.Parent != null) + ExpandParentRecursive (this.Parent); + + Rectangle bounds = Bounds; + if (bounds.Y < 0) { + TreeView.SetTop (this); + } else if (bounds.Bottom > TreeView.ViewportRectangle.Bottom) { + TreeView.SetBottom (this); + } + } + + public int GetNodeCount (bool includeSubTrees) + { + if (!includeSubTrees) + return Nodes.Count; + + int count = 0; + GetNodeCountRecursive (this, ref count); + + return count; + } + + public void Remove () + { + if (parent == null) + return; + int index = Index; + parent.Nodes.RemoveAt (index); + } + + public void Toggle () + { + if (is_expanded) + Collapse (); + else + Expand (); + } + + public override String ToString () + { + return String.Concat ("TreeNode: ", Text); + } + + #endregion // Public Instance Methods + + #region Internal & Private Methods and Properties + + internal bool ArePreviousNodesExpanded { + get { + TreeNode parent = Parent; + while (parent != null) { + if (!parent.is_expanded) + return false; + parent = parent.Parent; + } + + return true; + } + } + + internal bool IsRoot { + get { + TreeView tree_view = TreeView; + if (tree_view == null) + return false; + if (tree_view.root_node == this) + return true; + return false; + } + } + + bool BuildFullPath (StringBuilder path) + { + if (parent == null) + return false; + + if (parent.BuildFullPath (path)) + path.Append (TreeView.PathSeparator); + + path.Append (text); + return true; + } + + public int Index { + get { + if (parent == null) + return 0; + return parent.Nodes.IndexOf (this); + } + } + + private void Expand (bool byInternal) + { + if (is_expanded || nodes.Count < 1) { + is_expanded = true; + return; + } + + bool cancel = false; + TreeView tree_view = TreeView; + if (tree_view != null) { + TreeViewCancelEventArgs e = new TreeViewCancelEventArgs (this, false, TreeViewAction.Expand); + tree_view.OnBeforeExpand (e); + cancel = e.Cancel; + } + + if (!cancel) { + is_expanded = true; + int count_to_next = CountToNext (); + + if (tree_view != null) { + tree_view.OnAfterExpand (new TreeViewEventArgs (this)); + + tree_view.RecalculateVisibleOrder (this); + tree_view.UpdateScrollBars (false); + + // ExpandBelow if we affect the visible area + if (visible_order < tree_view.skipped_nodes + tree_view.VisibleCount + 1 && ArePreviousNodesExpanded) + tree_view.ExpandBelow (this, count_to_next); + } + } + } + + private void CollapseInternal (bool byInternal) + { + if (!is_expanded || nodes.Count < 1) + return; + + if (IsRoot) + return; + + bool cancel = false; + TreeView tree_view = TreeView; + if (tree_view != null) { + TreeViewCancelEventArgs e = new TreeViewCancelEventArgs (this, false, TreeViewAction.Collapse); + tree_view.OnBeforeCollapse (e); + cancel = e.Cancel; + } + + if (!cancel) { + int count_to_next = CountToNext (); + + is_expanded = false; + + if (tree_view != null) { + tree_view.OnAfterCollapse (new TreeViewEventArgs (this)); + + bool hbar_visible = tree_view.hbar.Visible; + bool vbar_visible = tree_view.vbar.Visible; + + tree_view.RecalculateVisibleOrder (this); + tree_view.UpdateScrollBars (false); + + // CollapseBelow if we affect the visible area + if (visible_order < tree_view.skipped_nodes + tree_view.VisibleCount + 1 && ArePreviousNodesExpanded) + tree_view.CollapseBelow (this, count_to_next); + if(!byInternal && HasFocusInChildren ()) + tree_view.SelectedNode = this; + + // If one or both of our scrollbars disappeared, + // invalidate everything + if ((hbar_visible & !tree_view.hbar.Visible) || (vbar_visible & !tree_view.vbar.Visible)) + tree_view.Invalidate (); + } + } + } + + private int CountToNext () + { + bool expanded = is_expanded; + is_expanded = false; + OpenTreeNodeEnumerator walk = new OpenTreeNodeEnumerator (this); + + TreeNode next= null; + if (walk.MoveNext () && walk.MoveNext ()) + next = walk.CurrentNode; + + is_expanded = expanded; + walk.Reset (); + walk.MoveNext (); + + int count = 0; + while (walk.MoveNext () && walk.CurrentNode != next) + count++; + + return count; + } + + private bool HasFocusInChildren() + { + if (TreeView == null) + return false; + foreach (TreeNode node in nodes) { + if(node == TreeView.SelectedNode) + return true; + if(node.HasFocusInChildren ()) + return true; + } + return false; + } + + private void ExpandRecursive (TreeNode node) + { + node.Expand (true); + foreach (TreeNode child in node.Nodes) + ExpandRecursive (child); + } + + private void ExpandParentRecursive (TreeNode node) + { + node.Expand (true); + if (node.Parent != null) + ExpandParentRecursive (node.Parent); + } + + internal void CollapseAll () + { + CollapseRecursive (this); + } + + internal void CollapseAllUncheck () + { + CollapseUncheckRecursive (this); + } + + private void CollapseRecursive (TreeNode node) + { + node.Collapse (); + foreach (TreeNode child in node.Nodes) + CollapseRecursive (child); + } + + private void CollapseUncheckRecursive (TreeNode node) + { + node.Collapse (); + node.Checked = false; + foreach (TreeNode child in node.Nodes) + CollapseUncheckRecursive (child); + } + + internal void SetNodes (TreeNodeCollection nodes) + { + this.nodes = nodes; + } + + private void GetNodeCountRecursive (TreeNode node, ref int count) + { + count += node.Nodes.Count; + foreach (TreeNode child in node.Nodes) + GetNodeCountRecursive (child, ref count); + } + + internal bool NeedsWidth { + get { return width == -1; } + } + + internal void Invalidate () + { + // invalidate width first so Bounds retrieves + // the updated value (we don't use it here however) + width = -1; + + TreeView tv = TreeView; + if (tv == null) + return; + + tv.UpdateNode (this); + } + + internal void InvalidateWidth () + { + // bounds.Width = 0; + width = -1; + } + + internal void SetWidth (int width) + { + this.width = width; + } + + internal void SetParent (TreeNode parent) + { + this.parent = parent; + } + + private bool IsInClippingRect { + get { + if (TreeView == null) + return false; + Rectangle bounds = Bounds; + if (bounds.Y < 0 && bounds.Y > TreeView.ClientRectangle.Height) + return false; + return true; + } + } + + internal Image StateImage { + get { + if (TreeView != null) { + if (TreeView.StateImageList == null) + return null; + if (state_image_index >= 0) + return TreeView.StateImageList.Images[state_image_index]; + if (state_image_key != string.Empty) + return TreeView.StateImageList.Images[state_image_key]; + } + + return null; + } + } + + // Order of operation: + // 1) Node.Image[Key|Index] + // 2) TreeView.Image[Key|Index] + // 3) First image in TreeView.ImageList + internal int Image { + get { + if (TreeView == null || TreeView.ImageList == null) + return -1; + + if (IsSelected) { + if (selected_image_index >= 0) + return selected_image_index; + if (!string.IsNullOrEmpty (selected_image_key)) + return TreeView.ImageList.Images.IndexOfKey (selected_image_key); + if (!string.IsNullOrEmpty (TreeView.SelectedImageKey)) + return TreeView.ImageList.Images.IndexOfKey (TreeView.SelectedImageKey); + if (TreeView.SelectedImageIndex >= 0) + return TreeView.SelectedImageIndex; + } else { + if (image_index >= 0) + return image_index; + if (!string.IsNullOrEmpty (image_key)) + return TreeView.ImageList.Images.IndexOfKey (image_key); + if (!string.IsNullOrEmpty (TreeView.ImageKey)) + return TreeView.ImageList.Images.IndexOfKey (TreeView.ImageKey); + if (TreeView.ImageIndex >= 0) + return TreeView.ImageIndex; + } + + if (TreeView.ImageList.Images.Count > 0) + return 0; + + return -1; + } + } + #endregion // Internal & Private Methods and Properties + } +} diff --git a/source/ShiftUI/Widgets/TreeView.cs b/source/ShiftUI/Widgets/TreeView.cs new file mode 100644 index 0000000..0e08ece --- /dev/null +++ b/source/ShiftUI/Widgets/TreeView.cs @@ -0,0 +1,2438 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2004-2005 Novell, Inc. +// +// Authors: +// Jackson Harper ([email protected]) +// Kazuki Oikawa ([email protected]) + +using System; +using System.Collections; +using System.ComponentModel; +using System.ComponentModel.Design; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Runtime.InteropServices; + +namespace ShiftUI { + [ComVisible (true)] + [Docking (DockingBehavior.Ask)] + [ClassInterface (ClassInterfaceType.AutoDispatch)] + [DefaultProperty("Nodes")] + [DefaultEvent("AfterSelect")] + //[Designer("ShiftUI.Design.TreeViewDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")] + [ToolboxWidget] + public class TreeView : Widget { + #region Fields + private string path_separator = "\\"; + private int item_height = -1; + internal bool sorted; + internal TreeNode root_node; + internal bool nodes_added; + private TreeNodeCollection nodes; + + private TreeViewAction selection_action; + internal TreeNode selected_node; + private TreeNode pre_selected_node; + private TreeNode focused_node; + internal TreeNode highlighted_node; + private Rectangle mouse_rect; + private bool select_mmove; + + private ImageList image_list; + private int image_index = -1; + private int selected_image_index = -1; + + private string image_key; + private bool is_hovering; + private TreeNode mouse_click_node; + private bool right_to_left_layout; + private string selected_image_key; + private bool show_node_tool_tips; + private ImageList state_image_list; + private TreeNode tooltip_currently_showing; + private ToolTip tooltip_window; + private bool full_row_select; + private bool hot_tracking; + private int indent = 19; + + private NodeLabelEditEventArgs edit_args; + private LabelEditTextBox edit_text_box; + internal TreeNode edit_node; + + private bool checkboxes; + private bool label_edit; + private bool scrollable = true; + private bool show_lines = true; + private bool show_root_lines = true; + private bool show_plus_minus = true; + private bool hide_selection = true; + + private int max_visible_order = -1; + internal VScrollBar vbar; + internal HScrollBar hbar; + private bool vbar_bounds_set; + private bool hbar_bounds_set; + internal int skipped_nodes; + internal int hbar_offset; + + private int update_stack; + private bool update_needed; + + private Pen dash; + private Color line_color; + private StringFormat string_format; + + private int drag_begin_x = -1; + private int drag_begin_y = -1; + private long handle_count = 1; + + private TreeViewDrawMode draw_mode; + + IComparer tree_view_node_sorter; + #endregion // Fields + + #region Public Constructors + public TreeView () + { + vbar = new ImplicitVScrollBar (); + hbar = new ImplicitHScrollBar (); + + InternalBorderStyle = BorderStyle.Fixed3D; + base.background_color = ThemeEngine.Current.ColorWindow; + base.foreground_color = ThemeEngine.Current.ColorWindowText; + draw_mode = TreeViewDrawMode.Normal; + + root_node = new TreeNode (this); + root_node.Text = "ROOT NODE"; + nodes = new TreeNodeCollection (root_node); + root_node.SetNodes (nodes); + + MouseDown += new MouseEventHandler (MouseDownHandler); + MouseUp += new MouseEventHandler(MouseUpHandler); + MouseMove += new MouseEventHandler(MouseMoveHandler); + SizeChanged += new EventHandler (SizeChangedHandler); + FontChanged += new EventHandler (FontChangedHandler); + LostFocus += new EventHandler (LostFocusHandler); + GotFocus += new EventHandler (GotFocusHandler); + MouseWheel += new MouseEventHandler(MouseWheelHandler); + VisibleChanged += new EventHandler (VisibleChangedHandler); + + SetStyle (Widgetstyles.UserPaint | Widgetstyles.StandardClick | Widgetstyles.UseTextForAccessibility, false); + + string_format = new StringFormat (); + string_format.LineAlignment = StringAlignment.Center; + string_format.Alignment = StringAlignment.Center; + + vbar.Visible = false; + hbar.Visible = false; + vbar.ValueChanged += new EventHandler (VScrollBarValueChanged); + hbar.ValueChanged += new EventHandler (HScrollBarValueChanged); + + SuspendLayout (); + Widgets.AddImplicit (vbar); + Widgets.AddImplicit (hbar); + ResumeLayout (); + } + #endregion // Public Constructors + + #region Public Instance Properties + public override Color BackColor { + get { return base.BackColor;} + set { + base.BackColor = value; + + CreateDashPen (); + Invalidate (); + } + } + + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public override Image BackgroundImage { + get { return base.BackgroundImage; } + set { base.BackgroundImage = value; } + } + + [DefaultValue(BorderStyle.Fixed3D)] + [DispId(-504)] + public BorderStyle BorderStyle { + get { return InternalBorderStyle; } + set { InternalBorderStyle = value; } + } + + [DefaultValue(false)] + public bool CheckBoxes { + get { return checkboxes; } + set { + if (value == checkboxes) + return; + checkboxes = value; + + // Match a "bug" in the MS implementation where disabling checkboxes + // collapses the entire tree, but enabling them does not affect the + // state of the tree. + if (!checkboxes) + root_node.CollapseAllUncheck (); + + Invalidate (); + + // UIA Framework Event: CheckBoxes Changed + OnUIACheckBoxesChanged (EventArgs.Empty); + } + } + + public override Color ForeColor { + get { return base.ForeColor; } + set { base.ForeColor = value; } + } + [DefaultValue(false)] + public bool FullRowSelect { + get { return full_row_select; } + set { + if (value == full_row_select) + return; + full_row_select = value; + Invalidate (); + } + } + [DefaultValue(true)] + public bool HideSelection { + get { return hide_selection; } + set { + if (hide_selection == value) + return; + hide_selection = value; + Invalidate (); + } + } + + [DefaultValue(false)] + public bool HotTracking { + get { return hot_tracking; } + set { hot_tracking = value; } + } + + [DefaultValue (-1)] + [RelatedImageList ("ImageList")] + [RefreshProperties (RefreshProperties.Repaint)] + [TypeConverter (typeof (NoneExcludedImageIndexConverter))] + //[Editor("ShiftUI.Design.ImageIndexEditor, " + Consts.AssemblySystem_Design, typeof(System.Drawing.Design.UITypeEditor))] + [Localizable(true)] + public int ImageIndex { + get { return image_index; } + set { + if (value < -1) { + throw new ArgumentException ("'" + value + "' is not a valid value for 'value'. " + + "'value' must be greater than or equal to 0."); + } + if (image_index == value) + return; + image_index = value; + Invalidate (); + } + } + + [RefreshProperties (RefreshProperties.Repaint)] + [DefaultValue(null)] + public ImageList ImageList { + get { return image_list; } + set { + image_list = value; + Invalidate (); + } + } + + [Localizable(true)] + public int Indent { + get { return indent; } + set { + if (indent == value) + return; + if (value > 32000) { + throw new ArgumentException ("'" + value + "' is not a valid value for 'Indent'. " + + "'Indent' must be less than or equal to 32000"); + } + if (value < 0) { + throw new ArgumentException ("'" + value + "' is not a valid value for 'Indent'. " + + "'Indent' must be greater than or equal to 0."); + } + indent = value; + Invalidate (); + } + } + + public int ItemHeight { + get { + if (item_height == -1) + return FontHeight + 3; + return item_height; + } + set { + if (value == item_height) + return; + item_height = value; + Invalidate (); + } + } + + internal int ActualItemHeight { + get { + int res = ItemHeight; + if (ImageList != null && ImageList.ImageSize.Height > res) + res = ImageList.ImageSize.Height; + return res; + } + } + + [DefaultValue(false)] + public bool LabelEdit { + get { return label_edit; } + set { + label_edit = value; + + // UIA Framework Event: LabelEdit Changed + OnUIALabelEditChanged (EventArgs.Empty); + } + } + + //[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] + [MergableProperty(false)] + [Localizable(true)] + public TreeNodeCollection Nodes { + get { return nodes; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public new Padding Padding { + get { return base.Padding; } + set { base.Padding = value; } + } + + [DefaultValue("\\")] + public string PathSeparator { + get { return path_separator; } + set { path_separator = value; } + } + + [Localizable (true)] + [DefaultValue (false)] + public virtual bool RightToLeftLayout { + get { return right_to_left_layout; } + set { + if (right_to_left_layout != value) { + right_to_left_layout = value; + OnRightToLeftLayoutChanged (EventArgs.Empty); + } + } + } + + [DefaultValue(true)] + public bool Scrollable { + get { return scrollable; } + set { + if (scrollable == value) + return; + scrollable = value; + UpdateScrollBars (false); + } + } + + [DefaultValue (-1)] + [RelatedImageList ("ImageList")] + [TypeConverter (typeof (NoneExcludedImageIndexConverter))] + //[Editor("ShiftUI.Design.ImageIndexEditor, " + Consts.AssemblySystem_Design, typeof(System.Drawing.Design.UITypeEditor))] + [Localizable(true)] + public int SelectedImageIndex { + get { return selected_image_index; } + set { + if (value < -1) { + throw new ArgumentException ("'" + value + "' is not a valid value for 'value'. " + + "'value' must be greater than or equal to 0."); + } + UpdateNode (SelectedNode); + } + } + + [Browsable(false)] + //[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public TreeNode SelectedNode { + get { + if (!IsHandleCreated) + return pre_selected_node; + return selected_node; + } + set { + if (!IsHandleCreated) { + pre_selected_node = value; + return; + } + + if (selected_node == value) { + selection_action = TreeViewAction.Unknown; + return; + } + + if (value != null) { + TreeViewCancelEventArgs e = new TreeViewCancelEventArgs (value, false, selection_action); + OnBeforeSelect (e); + + if (e.Cancel) + return; + } + + Rectangle invalid = Rectangle.Empty; + + if (selected_node != null) { + invalid = Bloat (selected_node.Bounds); + } + if (focused_node != null) { + invalid = Rectangle.Union (invalid, + Bloat (focused_node.Bounds)); + } + + if (value != null) + invalid = Rectangle.Union (invalid, Bloat (value.Bounds)); + + highlighted_node = value; + selected_node = value; + focused_node = value; + + if (full_row_select || draw_mode != TreeViewDrawMode.Normal) { + invalid.X = 0; + invalid.Width = ViewportRectangle.Width; + } + + if (invalid != Rectangle.Empty) + Invalidate (invalid); + + // We ensure its visible after we update because + // scrolling is used for insure visible + if (selected_node != null) + selected_node.EnsureVisible (); + + if (value != null) { + OnAfterSelect (new TreeViewEventArgs (value, TreeViewAction.Unknown)); + } + selection_action = TreeViewAction.Unknown; + } + } + + private Rectangle Bloat (Rectangle rect) + { + rect.Y--; + rect.X--; + rect.Height += 2; + rect.Width += 2; + return rect; + } + + [DefaultValue(true)] + public bool ShowLines { + get { return show_lines; } + set { + if (show_lines == value) + return; + show_lines = value; + Invalidate (); + } + } + + [DefaultValue (false)] + public bool ShowNodeToolTips { + get { return show_node_tool_tips; } + set { show_node_tool_tips = value; } + } + + [DefaultValue(true)] + public bool ShowPlusMinus { + get { return show_plus_minus; } + set { + if (show_plus_minus == value) + return; + show_plus_minus = value; + Invalidate (); + } + } + + [DefaultValue(true)] + public bool ShowRootLines { + get { return show_root_lines; } + set { + if (show_root_lines == value) + return; + show_root_lines = value; + Invalidate (); + } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + [DefaultValue(false)] + public bool Sorted { + get { return sorted; } + set { + if (sorted == value) + return; + sorted = value; + //LAMESPEC: The documentation says that setting this to true should sort alphabetically if TreeViewNodeSorter is set. + // There seems to be a bug in the Microsoft implementation. + if (sorted && tree_view_node_sorter == null) { + Sort (null); + } + } + } + + [DefaultValue (null)] + public ImageList StateImageList { + get { return state_image_list; } + set { + state_image_list = value; + Invalidate (); + } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + [Bindable(false)] + public override string Text { + get { return base.Text; } + set { base.Text = value; } + } + + [Browsable(false)] + //[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public TreeNode TopNode { + get { + if (root_node.FirstNode == null) + return null; + OpenTreeNodeEnumerator one = new OpenTreeNodeEnumerator (root_node.FirstNode); + one.MoveNext (); + for (int i = 0; i < skipped_nodes; i++) + one.MoveNext (); + return one.CurrentNode; + } + set { + SetTop (value); + } + } + + [Browsable (false)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public IComparer TreeViewNodeSorter { + get { + return tree_view_node_sorter; + } + set { + tree_view_node_sorter = value; + if (tree_view_node_sorter != null) { + Sort(); + //LAMESPEC: The documentation says that setting this should set Sorted to false. + // There seems to be a bug in the Microsoft implementation. + sorted = true; + } + } + } + + [Browsable(false)] + //[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public int VisibleCount { + get { + return ViewportRectangle.Height / ActualItemHeight; + } + } + + /// According to MSDN this property has no effect on the treeview + //[EditorBrowsable (EditorBrowsableState.Never)] + protected override bool DoubleBuffered { + get { return base.DoubleBuffered; } + set { base.DoubleBuffered = value; } + } + + [DefaultValue ("Color [Black]")] + public Color LineColor { + get { + if (line_color == Color.Empty) { + Color res = WidgetPaint.Dark (BackColor); + if (res == BackColor) + res = WidgetPaint.Light (BackColor); + return res; + } + return line_color; + } + set { + line_color = value; + if (show_lines) { + CreateDashPen (); + Invalidate (); + } + } + } + + [Localizable (true)] + [DefaultValue ("")] + [RelatedImageList ("ImageList")] + [RefreshProperties (RefreshProperties.Repaint)] + [TypeConverter (typeof (ImageKeyConverter))] + //[Editor ("ShiftUI.Design.ImageIndexEditor, " + Consts.AssemblySystem_Design, typeof (System.Drawing.Design.UITypeEditor))] + public string ImageKey { + get { return image_key; } + set { + if (image_key == value) + return; + image_index = -1; + image_key = value; + Invalidate (); + } + } + + [Localizable (true)] + [DefaultValue ("")] + [RelatedImageList ("ImageList")] + [RefreshProperties (RefreshProperties.Repaint)] + [TypeConverter (typeof (ImageKeyConverter))] + //[Editor ("ShiftUI.Design.ImageIndexEditor, " + Consts.AssemblySystem_Design, typeof (System.Drawing.Design.UITypeEditor))] + public string SelectedImageKey { + get { return selected_image_key; } + set { + if (selected_image_key == value) + return; + selected_image_index = -1; + selected_image_key = value; + UpdateNode (SelectedNode); + } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public override ImageLayout BackgroundImageLayout { + get { return base.BackgroundImageLayout; } + set { base.BackgroundImageLayout = value; } + } + + [DefaultValue (TreeViewDrawMode.Normal)] + public TreeViewDrawMode DrawMode { + get { return draw_mode; } + set { draw_mode = value; } + } + #endregion // Public Instance Properties + + #region UIA Framework Properties + internal ScrollBar UIAHScrollBar { + get { return hbar; } + } + + internal ScrollBar UIAVScrollBar { + get { return vbar; } + } + #endregion // UIA Framework Properties + + #region Protected Instance Properties + protected override CreateParams CreateParams { + get { + CreateParams cp = base.CreateParams; + return cp; + } + } + + protected override Size DefaultSize { + get { return new Size (121, 97); } + } + + #endregion // Protected Instance Properties + + #region Public Instance Methods + public void BeginUpdate () + { + update_stack++; + } + + public void EndUpdate () + { + if (update_stack > 1) { + update_stack--; + } else { + update_stack = 0; + if (update_needed) { + RecalculateVisibleOrder (root_node); + UpdateScrollBars (false); + // if (SelectedNode != null) + // SelectedNode.EnsureVisible (); + Invalidate (ViewportRectangle); + update_needed = false; + } + } + } + + public void Sort () + { + Sort (tree_view_node_sorter); + } + + void Sort (IComparer sorter) + { + sorted = true; + Nodes.Sort (sorter); + RecalculateVisibleOrder (root_node); + UpdateScrollBars (false); + Invalidate (); + } + + void SetVScrollValue (int value) + { + if (value > vbar.Maximum) + value = vbar.Maximum; + else if (value < vbar.Minimum) + value = vbar.Minimum; + + vbar.Value = value; + } + + public void ExpandAll () + { + BeginUpdate (); + root_node.ExpandAll (); + + EndUpdate (); + + /// + /// Everything below this is basically an emulation of a strange bug on MS + /// where they don't always scroll to the last node on ExpandAll + /// + if (!IsHandleCreated) + return; + + bool found = false; + foreach (TreeNode child in Nodes) { + if (child.Nodes.Count > 0) + found = true; + } + + if (!found) + return; + + if (IsHandleCreated && vbar.VisibleInternal) { + SetVScrollValue (vbar.Maximum - VisibleCount + 1); + } else { + RecalculateVisibleOrder (root_node); + UpdateScrollBars (true); + + // Only move the top node if we now have a scrollbar + if (vbar.VisibleInternal) { + SetTop (Nodes [Nodes.Count - 1]); + SelectedNode = Nodes [Nodes.Count - 1]; + } + } + } + + public void CollapseAll () + { + BeginUpdate (); + root_node.CollapseAll (); + EndUpdate (); + + if (vbar.VisibleInternal) + SetVScrollValue (vbar.Maximum - VisibleCount + 1); + } + + public TreeNode GetNodeAt (Point pt) { + return GetNodeAt (pt.Y); + } + + public TreeNode GetNodeAt (int x, int y) + { + return GetNodeAt (y); + } + + private TreeNode GetNodeAtUseX (int x, int y) { + TreeNode node = GetNodeAt (y); + if (node == null || !(IsTextArea (node, x) || full_row_select)) + return null; + return node; + + } + + public int GetNodeCount (bool includeSubTrees) { + return root_node.GetNodeCount (includeSubTrees); + } + + public TreeViewHitTestInfo HitTest (Point pt) + { + return HitTest (pt.X, pt.Y); + } + + public TreeViewHitTestInfo HitTest (int x, int y) + { + TreeNode n = GetNodeAt (y); + + if (n == null) + return new TreeViewHitTestInfo (null, TreeViewHitTestLocations.None); + + if (IsTextArea (n, x)) + return new TreeViewHitTestInfo (n, TreeViewHitTestLocations.Label); + else if (IsPlusMinusArea (n, x)) + return new TreeViewHitTestInfo (n, TreeViewHitTestLocations.PlusMinus); + else if ((checkboxes || n.StateImage != null) && IsCheckboxArea (n, x)) + return new TreeViewHitTestInfo (n, TreeViewHitTestLocations.StateImage); + else if (x > n.Bounds.Right) + return new TreeViewHitTestInfo (n, TreeViewHitTestLocations.RightOfLabel); + else if (IsImage (n, x)) + return new TreeViewHitTestInfo (n, TreeViewHitTestLocations.Image); + else + return new TreeViewHitTestInfo (null, TreeViewHitTestLocations.Indent); + } + + public override string ToString () { + int count = Nodes.Count; + if (count <= 0) + return String.Concat (base.ToString (), ", Nodes.Count: 0"); + return String.Concat (base.ToString (), ", Nodes.Count: ", count, ", Nodes[0]: ", Nodes [0]); + + } + #endregion // Public Instance Methods + + #region Protected Instance Methods + protected override void CreateHandle () { + base.CreateHandle (); + RecalculateVisibleOrder (root_node); + UpdateScrollBars (false); + + if (pre_selected_node != null) + SelectedNode = pre_selected_node; + } + + protected override void Dispose (bool disposing) { + if (disposing) + image_list = null; + + base.Dispose (disposing); + } + + protected OwnerDrawPropertyBag GetItemRenderStyles (TreeNode node, int state) { + return node.prop_bag; + } + + protected override bool IsInputKey (Keys keyData) + { + if (IsHandleCreated && (keyData & Keys.Alt) == 0) { + switch (keyData & Keys.KeyCode) { + case Keys.Left: + case Keys.Up: + case Keys.Right: + case Keys.Down: + return true; + case Keys.Enter: + case Keys.Escape: + case Keys.Prior: + case Keys.Next: + case Keys.End: + case Keys.Home: + if (edit_node != null) + return true; + + break; + } + } + return base.IsInputKey (keyData); + } + + protected override void OnKeyDown (KeyEventArgs e) + { + OpenTreeNodeEnumerator ne; + + switch (e.KeyData & Keys.KeyCode) { + case Keys.Add: + if (selected_node != null && selected_node.IsExpanded) + selected_node.Expand (); + break; + case Keys.Subtract: + if (selected_node != null && selected_node.IsExpanded) + selected_node.Collapse (); + break; + case Keys.Left: + if (selected_node != null) { + if (selected_node.IsExpanded && selected_node.Nodes.Count > 0) + selected_node.Collapse (); + else { + TreeNode parent = selected_node.Parent; + if (parent != null) { + selection_action = TreeViewAction.ByKeyboard; + SelectedNode = parent; + } + } + } + break; + case Keys.Right: + if (selected_node != null) { + if (!selected_node.IsExpanded) + selected_node.Expand (); + else { + TreeNode child = selected_node.FirstNode; + if (child != null) + SelectedNode = child; + } + } + break; + case Keys.Up: + if (selected_node != null) { + ne = new OpenTreeNodeEnumerator (selected_node); + if (ne.MovePrevious () && ne.MovePrevious ()) { + selection_action = TreeViewAction.ByKeyboard; + SelectedNode = ne.CurrentNode; + } + } + break; + case Keys.Down: + if (selected_node != null) { + ne = new OpenTreeNodeEnumerator (selected_node); + if (ne.MoveNext () && ne.MoveNext ()) { + selection_action = TreeViewAction.ByKeyboard; + SelectedNode = ne.CurrentNode; + } + } + break; + case Keys.Home: + if (root_node.Nodes.Count > 0) { + ne = new OpenTreeNodeEnumerator (root_node.Nodes [0]); + if (ne.MoveNext ()) { + selection_action = TreeViewAction.ByKeyboard; + SelectedNode = ne.CurrentNode; + } + } + break; + case Keys.End: + if (root_node.Nodes.Count > 0) { + ne = new OpenTreeNodeEnumerator (root_node.Nodes [0]); + while (ne.MoveNext ()) + { } + selection_action = TreeViewAction.ByKeyboard; + SelectedNode = ne.CurrentNode; + } + break; + case Keys.PageDown: + if (selected_node != null) { + ne = new OpenTreeNodeEnumerator (selected_node); + int move = VisibleCount; + for (int i = 0; i < move && ne.MoveNext (); i++) { + + } + selection_action = TreeViewAction.ByKeyboard; + SelectedNode = ne.CurrentNode; + } + break; + case Keys.PageUp: + if (selected_node != null) { + ne = new OpenTreeNodeEnumerator (selected_node); + int move = VisibleCount; + for (int i = 0; i < move && ne.MovePrevious (); i++) + { } + selection_action = TreeViewAction.ByKeyboard; + SelectedNode = ne.CurrentNode; + } + break; + case Keys.Multiply: + if (selected_node != null) + selected_node.ExpandAll (); + break; + } + base.OnKeyDown (e); + + if (!e.Handled && checkboxes && + selected_node != null && + (e.KeyData & Keys.KeyCode) == Keys.Space) { + selected_node.check_reason = TreeViewAction.ByKeyboard; + selected_node.Checked = !selected_node.Checked; + e.Handled = true; + } + } + + protected override void OnKeyPress (KeyPressEventArgs e) + { + base.OnKeyPress (e); + if (e.KeyChar == ' ') + e.Handled = true; + } + + protected override void OnKeyUp (KeyEventArgs e) + { + base.OnKeyUp (e); + if ((e.KeyData & Keys.KeyCode) == Keys.Space) + e.Handled = true; + } + + protected override void OnMouseHover (EventArgs e) + { + base.OnMouseHover (e); + + is_hovering = true; + + TreeNode tn = GetNodeAt (PointToClient (MousePosition)); + + if (tn != null) + MouseEnteredItem (tn); + } + + protected override void OnMouseLeave (EventArgs e) + { + base.OnMouseLeave (e); + + is_hovering = false; + + if (tooltip_currently_showing != null) + MouseLeftItem (tooltip_currently_showing); + } + + protected virtual void OnNodeMouseClick (TreeNodeMouseClickEventArgs e) + { + TreeNodeMouseClickEventHandler eh = (TreeNodeMouseClickEventHandler)(Events[NodeMouseClickEvent]); + if (eh != null) + eh (this, e); + } + + protected virtual void OnNodeMouseDoubleClick (TreeNodeMouseClickEventArgs e) + { + TreeNodeMouseClickEventHandler eh = (TreeNodeMouseClickEventHandler)(Events[NodeMouseDoubleClickEvent]); + if (eh != null) + eh (this, e); + } + + protected virtual void OnNodeMouseHover (TreeNodeMouseHoverEventArgs e) + { + TreeNodeMouseHoverEventHandler eh = (TreeNodeMouseHoverEventHandler)(Events[NodeMouseHoverEvent]); + if (eh != null) + eh (this, e); + } + + protected virtual void OnItemDrag (ItemDragEventArgs e) + { + ItemDragEventHandler eh = (ItemDragEventHandler)(Events [ItemDragEvent]); + if (eh != null) + eh (this, e); + } + + protected virtual void OnDrawNode(DrawTreeNodeEventArgs e) { + DrawTreeNodeEventHandler eh = (DrawTreeNodeEventHandler)(Events[DrawNodeEvent]); + if (eh != null) + eh(this, e); + } + + //[EditorBrowsable (EditorBrowsableState.Advanced)] + protected virtual void OnRightToLeftLayoutChanged (EventArgs e) { + EventHandler eh = (EventHandler)(Events[RightToLeftLayoutChangedEvent]); + if (eh != null) + eh (this, e); + } + + protected internal virtual void OnAfterCheck (TreeViewEventArgs e) { + TreeViewEventHandler eh = (TreeViewEventHandler)(Events [AfterCheckEvent]); + if (eh != null) + eh (this, e); + } + + protected internal virtual void OnAfterCollapse (TreeViewEventArgs e) { + TreeViewEventHandler eh = (TreeViewEventHandler)(Events [AfterCollapseEvent]); + if (eh != null) + eh (this, e); + } + + protected internal virtual void OnAfterExpand (TreeViewEventArgs e) { + TreeViewEventHandler eh = (TreeViewEventHandler)(Events [AfterExpandEvent]); + if (eh != null) + eh (this, e); + } + + protected virtual void OnAfterLabelEdit (NodeLabelEditEventArgs e) { + NodeLabelEditEventHandler eh = (NodeLabelEditEventHandler)(Events [AfterLabelEditEvent]); + if (eh != null) + eh (this, e); + } + + protected virtual void OnAfterSelect (TreeViewEventArgs e) { + TreeViewEventHandler eh = (TreeViewEventHandler)(Events [AfterSelectEvent]); + if (eh != null) + eh (this, e); + } + + protected internal virtual void OnBeforeCheck (TreeViewCancelEventArgs e) { + TreeViewCancelEventHandler eh = (TreeViewCancelEventHandler)(Events [BeforeCheckEvent]); + if (eh != null) + eh (this, e); + } + + protected internal virtual void OnBeforeCollapse (TreeViewCancelEventArgs e) { + TreeViewCancelEventHandler eh = (TreeViewCancelEventHandler)(Events [BeforeCollapseEvent]); + if (eh != null) + eh (this, e); + } + + protected internal virtual void OnBeforeExpand (TreeViewCancelEventArgs e) { + TreeViewCancelEventHandler eh = (TreeViewCancelEventHandler)(Events [BeforeExpandEvent]); + if (eh != null) + eh (this, e); + } + + protected virtual void OnBeforeLabelEdit (NodeLabelEditEventArgs e) { + NodeLabelEditEventHandler eh = (NodeLabelEditEventHandler)(Events [BeforeLabelEditEvent]); + if (eh != null) + eh (this, e); + } + + protected virtual void OnBeforeSelect (TreeViewCancelEventArgs e) { + TreeViewCancelEventHandler eh = (TreeViewCancelEventHandler)(Events [BeforeSelectEvent]); + if (eh != null) + eh (this, e); + } + + protected override void OnHandleCreated (EventArgs e) { + base.OnHandleCreated (e); + } + + protected override void OnHandleDestroyed (EventArgs e) { + base.OnHandleDestroyed (e); + } + + protected override void WndProc(ref Message m) { + switch ((Msg) m.Msg) { + + case Msg.WM_LBUTTONDBLCLK: + int val = m.LParam.ToInt32(); + DoubleClickHandler (null, new + MouseEventArgs (MouseButtons.Left, + 2, val & 0xffff, + (val>>16) & 0xffff, 0)); + break; + case Msg.WM_CONTEXTMENU: + if (WmContextMenu (ref m)) + return; + + break; + } + base.WndProc (ref m); + } + + #endregion // Protected Instance Methods + + #region Internal & Private Methods and Properties + internal override bool ScaleChildrenInternal { + get { return false; } + } + + internal IntPtr CreateNodeHandle () + { + return (IntPtr) handle_count++; + } + + // According to MSDN docs, for these to be raised, + // the click must occur over a TreeNode + internal override void HandleClick (int clicks, MouseEventArgs me) + { + if (GetNodeAt (me.Location) != null) { + if ((clicks > 1) && GetStyle (Widgetstyles.StandardDoubleClick)) { + OnDoubleClick (me); + OnMouseDoubleClick (me); + } else { + OnClick (me); + OnMouseClick (me); + } + } + } + + internal override bool IsInputCharInternal (char charCode) + { + return true; + } + + internal TreeNode NodeFromHandle (IntPtr handle) + { + // This method is called rarely, so instead of maintaining a table + // we just walk the tree nodes to find the matching handle + return NodeFromHandleRecursive (root_node, handle); + } + + private TreeNode NodeFromHandleRecursive (TreeNode node, IntPtr handle) + { + if (node.handle == handle) + return node; + foreach (TreeNode child in node.Nodes) { + TreeNode match = NodeFromHandleRecursive (child, handle); + if (match != null) + return match; + } + return null; + } + + internal Rectangle ViewportRectangle { + get { + Rectangle res = ClientRectangle; + + if (vbar != null && vbar.Visible) + res.Width -= vbar.Width; + if (hbar != null && hbar.Visible) + res.Height -= hbar.Height; + return res; + } + } + + private TreeNode GetNodeAt (int y) + { + if (nodes.Count <= 0) + return null; + + OpenTreeNodeEnumerator o = new OpenTreeNodeEnumerator (TopNode); + int move = y / ActualItemHeight; + for (int i = -1; i < move; i++) { + if (!o.MoveNext ()) + return null; + } + + return o.CurrentNode; + } + + private bool IsTextArea (TreeNode node, int x) + { + return node != null && node.Bounds.Left <= x && node.Bounds.Right >= x; + } + + private bool IsSelectableArea (TreeNode node, int x) + { + if (node == null) + return false; + int l = node.Bounds.Left; + if (ImageList != null) + l -= ImageList.ImageSize.Width; + return l <= x && node.Bounds.Right >= x; + + } + + private bool IsPlusMinusArea (TreeNode node, int x) + { + if (node.Nodes.Count == 0 || (node.parent == root_node && !show_root_lines)) + return false; + + int l = node.Bounds.Left + 5; + + if (show_root_lines || node.Parent != null) + l -= indent; + if (ImageList != null) + l -= ImageList.ImageSize.Width + 3; + if (checkboxes) + l -= 19; + // StateImage is basically a custom checkbox + else if (node.StateImage != null) + l -= 19; + return (x > l && x < l + 8); + } + + private bool IsCheckboxArea (TreeNode node, int x) + { + int l = CheckBoxLeft (node); + return (x > l && x < l + 10); + } + + private bool IsImage (TreeNode node, int x) + { + if (ImageList == null) + return false; + + int l = node.Bounds.Left; + + l -= ImageList.ImageSize.Width + 5; + + if (x >= l && x <= (l + ImageList.ImageSize.Width + 5)) + return true; + + return false; + } + + private int CheckBoxLeft (TreeNode node) + { + int l = node.Bounds.Left + 5; + + if (show_root_lines || node.Parent != null) + l -= indent; + + if (!show_root_lines && node.Parent == null) + l -= indent; + + if (ImageList != null) + l -= ImageList.ImageSize.Width + 3; + + return l; + } + + internal void RecalculateVisibleOrder (TreeNode start) + { + if (update_stack > 0) + return; + + int order; + if (start == null) { + start = root_node; + order = 0; + } else + order = start.visible_order; + + + + OpenTreeNodeEnumerator walk = new OpenTreeNodeEnumerator (start); + while (walk.MoveNext ()) { + walk.CurrentNode.visible_order = order; + order++; + } + + max_visible_order = order; + } + + internal void SetTop (TreeNode node) + { + int order = 0; + if (node != null) + order = Math.Max (0, node.visible_order - 1); + + if (!vbar.is_visible) { + skipped_nodes = order; + return; + } + + SetVScrollValue (Math.Min (order, vbar.Maximum - VisibleCount + 1)); + } + + internal void SetBottom (TreeNode node) + { + if (!vbar.is_visible) + return; + + OpenTreeNodeEnumerator walk = new OpenTreeNodeEnumerator (node); + + int bottom = ViewportRectangle.Bottom; + int offset = 0; + while (walk.MovePrevious ()) { + if (walk.CurrentNode.Bounds.Bottom <= bottom) + break; + offset++; + } + + int nv = vbar.Value + offset; + if (vbar.Value + offset < vbar.Maximum) { + SetVScrollValue (nv); + } else { +#if DEBUG + Console.Error.WriteLine ("setting bottom to value greater then maximum ({0}, {1})", + nv, vbar.Maximum); +#endif + } + + } + + internal void UpdateBelow (TreeNode node) + { + if (update_stack > 0) { + update_needed = true; + return; + } + + if (node == root_node) { + Invalidate (ViewportRectangle); + return; + } + + // We need to update the current node so the plus/minus block gets update too + int top = Math.Max (node.Bounds.Top - 1, 0); + Rectangle invalid = new Rectangle (0, top, + Width, Height - top); + Invalidate (invalid); + } + + internal void UpdateNode (TreeNode node) + { + if (node == null) + return; + + if (update_stack > 0) { + update_needed = true; + return; + } + + if (node == root_node) { + Invalidate (); + return; + } + + Rectangle invalid = new Rectangle (0, node.Bounds.Top - 1, Width, + node.Bounds.Height + 1); + Invalidate (invalid); + } + + internal void UpdateNodePlusMinus (TreeNode node) + { + if (update_stack > 0) { + update_needed = true; + return; + } + + int l = node.Bounds.Left + 5; + + if (show_root_lines || node.Parent != null) + l -= indent; + if (ImageList != null) + l -= ImageList.ImageSize.Width + 3; + if (checkboxes) + l -= 19; + + Invalidate (new Rectangle (l, node.Bounds.Top, 8, node.Bounds.Height)); + } + + internal override void OnPaintInternal (PaintEventArgs pe) + { + Draw (pe.ClipRectangle, pe.Graphics); + } + + internal void CreateDashPen () + { + dash = new Pen (LineColor, 1); + dash.DashStyle = DashStyle.Dot; + } + + private void Draw (Rectangle clip, Graphics dc) + { + dc.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (BackColor), clip); + + if (dash == null) + CreateDashPen (); + + Rectangle viewport = ViewportRectangle; + Rectangle original_clip = clip; + if (clip.Bottom > viewport.Bottom) + clip.Height = viewport.Bottom - clip.Top; + + OpenTreeNodeEnumerator walk = new OpenTreeNodeEnumerator (TopNode); + while (walk.MoveNext ()) { + TreeNode current = walk.CurrentNode; + + // Haven't gotten to visible nodes yet + if (current.GetY () + ActualItemHeight < clip.Top) + continue; + + // Past the visible nodes + if (current.GetY () > clip.Bottom) + break; + + DrawTreeNode (current, dc, clip); + } + + if (hbar.Visible && vbar.Visible) { + Rectangle corner = new Rectangle (hbar.Right, vbar.Bottom, vbar.Width, hbar.Height); + if (original_clip.IntersectsWith (corner)) + dc.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (ThemeEngine.Current.ColorControl), + corner); + } + } + + private void DrawNodeState (TreeNode node, Graphics dc, int x, int y) + { + if (node.Checked) { + if (StateImageList.Images[1] != null) + dc.DrawImage (StateImageList.Images[1], new Rectangle (x, y, 16, 16)); + } else { + if (StateImageList.Images[0] != null) + dc.DrawImage (StateImageList.Images[0], new Rectangle (x, y, 16, 16)); + } + } + + private void DrawNodeCheckBox (TreeNode node, Graphics dc, int x, int middle) + { + Pen pen = ThemeEngine.Current.ResPool.GetSizedPen(Color.Black, 2); + dc.DrawRectangle (pen, x + 3, middle - 4, 11, 11); + + if (node.Checked) { + Pen check_pen = ThemeEngine.Current.ResPool.GetPen(Color.Black); + + int check_size = 5; + int lineWidth = 3; + + Rectangle rect = new Rectangle (x + 4, middle - 3, check_size, check_size); + + for (int i = 0; i < lineWidth; i++) { + dc.DrawLine (check_pen, rect.Left + 1, rect.Top + lineWidth + i, rect.Left + 3, rect.Top + 5 + i); + dc.DrawLine (check_pen, rect.Left + 3, rect.Top + 5 + i, rect.Left + 7, rect.Top + 1 + i); + } + } + } + + private void DrawNodeLines (TreeNode node, Graphics dc, Rectangle clip, Pen dash, int x, int y, int middle) + { + int ladjust = 9; + int radjust = 0; + + if (node.nodes.Count > 0 && show_plus_minus) + ladjust = 13; + if (checkboxes) + radjust = 3; + + if (show_root_lines || node.Parent != null) + dc.DrawLine (dash, x - indent + ladjust, middle, x + radjust, middle); + + if (node.PrevNode != null || node.Parent != null) { + ladjust = 9; + dc.DrawLine (dash, x - indent + ladjust, node.Bounds.Top, + x - indent + ladjust, middle - (show_plus_minus && node.Nodes.Count > 0 ? 4 : 0)); + } + + if (node.NextNode != null) { + ladjust = 9; + dc.DrawLine (dash, x - indent + ladjust, middle + (show_plus_minus && node.Nodes.Count > 0 ? 4 : 0), + x - indent + ladjust, node.Bounds.Bottom); + + } + + ladjust = 0; + if (show_plus_minus) + ladjust = 9; + TreeNode parent = node.Parent; + while (parent != null) { + if (parent.NextNode != null) { + int px = parent.GetLinesX () - indent + ladjust; + dc.DrawLine (dash, px, node.Bounds.Top, px, node.Bounds.Bottom); + } + parent = parent.Parent; + } + } + + private void DrawNodeImage (TreeNode node, Graphics dc, Rectangle clip, int x, int y) + { + if (!RectsIntersect (clip, x, y, ImageList.ImageSize.Width, ImageList.ImageSize.Height)) + return; + + int use_index = node.Image; + + if (use_index > -1 && use_index < ImageList.Images.Count) + ImageList.Draw (dc, x, y, ImageList.ImageSize.Width, ImageList.ImageSize.Height, use_index); + } + + private void LabelEditFinished (object sender, EventArgs e) + { + EndEdit (edit_node); + } + + internal void BeginEdit (TreeNode node) + { + if (edit_node != null) + EndEdit (edit_node); + + if (edit_text_box == null) { + edit_text_box = new LabelEditTextBox (); + edit_text_box.BorderStyle = BorderStyle.FixedSingle; + edit_text_box.Visible = false; + edit_text_box.EditingCancelled += new EventHandler (LabelEditCancelled); + edit_text_box.EditingFinished += new EventHandler (LabelEditFinished); + edit_text_box.TextChanged += new EventHandler (LabelTextChanged); + Widgets.Add (edit_text_box); + } + + node.EnsureVisible (); + + edit_text_box.Bounds = node.Bounds; + edit_text_box.Text = node.Text; + edit_text_box.Visible = true; + edit_text_box.Focus (); + edit_text_box.SelectAll (); + + edit_args = new NodeLabelEditEventArgs (node); + OnBeforeLabelEdit (edit_args); + + edit_node = node; + + if (edit_args.CancelEdit) { + edit_node = null; + EndEdit (node); + } + } + + private void LabelEditCancelled (object sender, EventArgs e) + { + edit_args.SetLabel (null); + EndEdit (edit_node); + } + + private void LabelTextChanged (object sender, EventArgs e) + { + int width = TextRenderer.MeasureTextInternal (edit_text_box.Text, edit_text_box.Font, false).Width + 4; + edit_text_box.Width = width; + + if (edit_args != null) + edit_args.SetLabel (edit_text_box.Text); + } + + internal void EndEdit (TreeNode node) + { + if (edit_text_box != null && edit_text_box.Visible) { + edit_text_box.Visible = false; + Focus (); + } + + // + // If we get a call to BeginEdit from any AfterLabelEdit handler, + // the focus seems to always remain in the TreeView. This call seems + // to synchronize the focus events - I don't like it but it works + // + Application.DoEvents (); + + if (edit_node != null && edit_node == node) { + edit_node = null; + + NodeLabelEditEventArgs e = new NodeLabelEditEventArgs (edit_args.Node, edit_args.Label); + + OnAfterLabelEdit (e); + + if (e.CancelEdit) + return; + + if (e.Label != null) + e.Node.Text = e.Label; + } + + // EndEdit ends editing even if not called on the editing node + edit_node = null; + UpdateNode (node); + } + + internal void CancelEdit (TreeNode node) + { + edit_args.SetLabel (null); + + if (edit_text_box != null && edit_text_box.Visible) { + edit_text_box.Visible = false; + Focus (); + } + + edit_node = null; + UpdateNode (node); + } + + internal int GetNodeWidth (TreeNode node) + { + Font font = node.NodeFont; + if (node.NodeFont == null) + font = Font; + return (int)TextRenderer.MeasureString (node.Text, font, 0, string_format).Width + 3; + } + + private void DrawSelectionAndFocus(TreeNode node, Graphics dc, Rectangle r) + { + if (Focused && focused_node == node && !full_row_select) { + WidgetPaint.DrawFocusRectangle (dc, r, ForeColor, BackColor); + } + if (draw_mode != TreeViewDrawMode.Normal) + return; + + r.Inflate (-1, -1); + + if (Focused && node == highlighted_node) { + // Use the node's BackColor if is not empty, and is not actually the selected one (yet) + Color back_color = node != selected_node && node.BackColor != Color.Empty ? node.BackColor : + ThemeEngine.Current.ColorHighlight; + dc.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (back_color), r); + + } else if (!hide_selection && node == highlighted_node) { + dc.FillRectangle (SystemBrushes.Control, r); + } else { + // If selected_node is not the current highlighted one, use the color of the TreeView + Color back_color = node == selected_node ? BackColor : node.BackColor; + dc.FillRectangle (ThemeEngine.Current.ResPool.GetSolidBrush (back_color), r); + } + } + + private void DrawStaticNode (TreeNode node, Graphics dc) + { + if (!full_row_select || show_lines) + DrawSelectionAndFocus(node, dc, node.Bounds); + + + Font font = node.NodeFont; + if (node.NodeFont == null) + font = Font; + Color text_color = (Focused && node == highlighted_node ? + ThemeEngine.Current.ColorHighlightText : node.ForeColor); + if (text_color.IsEmpty) + text_color = ForeColor; + dc.DrawString (node.Text, font, + ThemeEngine.Current.ResPool.GetSolidBrush (text_color), + node.Bounds, string_format); + } + + private void DrawTreeNode (TreeNode node, Graphics dc, Rectangle clip) + { + int child_count = node.nodes.Count; + int y = node.GetY (); + int middle = y + (ActualItemHeight / 2); + + if (full_row_select && !show_lines) { + Rectangle r = new Rectangle (1, y, ViewportRectangle.Width - 2, ActualItemHeight); + DrawSelectionAndFocus (node, dc, r); + } + + if (draw_mode == TreeViewDrawMode.Normal || draw_mode == TreeViewDrawMode.OwnerDrawText) { + if ((show_root_lines || node.Parent != null) && show_plus_minus && child_count > 0) + ThemeEngine.Current.TreeViewDrawNodePlusMinus (this, node, dc, node.GetLinesX () - Indent + 5, middle); + + if (checkboxes && state_image_list == null) + DrawNodeCheckBox (node, dc, CheckBoxLeft (node) - 3, middle); + + if (checkboxes && state_image_list != null) + DrawNodeState (node, dc, CheckBoxLeft (node) - 3, y); + + if (!checkboxes && node.StateImage != null) + dc.DrawImage (node.StateImage, new Rectangle (CheckBoxLeft (node) - 3, y, 16, 16)); + + if (show_lines) + DrawNodeLines (node, dc, clip, dash, node.GetLinesX (), y, middle); + + if (ImageList != null) + DrawNodeImage (node, dc, clip, node.GetImageX (), y); + } + + if (draw_mode != TreeViewDrawMode.Normal) { + dc.FillRectangle (Brushes.White, node.Bounds); + TreeNodeStates tree_node_state = TreeNodeStates.Default;; + if (node.IsSelected) + tree_node_state = TreeNodeStates.Selected; + if (node.Checked) + tree_node_state |= TreeNodeStates.Checked; + if (node == focused_node) + tree_node_state |= TreeNodeStates.Focused; + Rectangle node_bounds = node.Bounds; + if (draw_mode == TreeViewDrawMode.OwnerDrawText) { + node_bounds.X += 3; + node_bounds.Y += 1; + } else { + node_bounds.X = 0; + node_bounds.Width = Width; + } + + DrawTreeNodeEventArgs e = new DrawTreeNodeEventArgs (dc, node, node_bounds, tree_node_state); + + OnDrawNode (e); + if (!e.DrawDefault) + return; + } + + if (!node.IsEditing) + DrawStaticNode (node, dc); + } + + internal void UpdateScrollBars (bool force) + { + if (!force && (IsDisposed || update_stack > 0 || !IsHandleCreated || !Visible)) + return; + + bool vert = false; + bool horz = false; + int height = 0; + int width = -1; + + int item_height = ActualItemHeight; + if (scrollable) { + OpenTreeNodeEnumerator walk = new OpenTreeNodeEnumerator (root_node); + + while (walk.MoveNext ()) { + int r = walk.CurrentNode.Bounds.Right; + if (r > width) + width = r; + + height += item_height; + } + + height -= item_height; // root_node adjustment + width += hbar_offset; + + if (height > ClientRectangle.Height) { + vert = true; + + if (width > ClientRectangle.Width - SystemInformation.VerticalScrollBarWidth) + horz = true; + } else if (width > ClientRectangle.Width) { + horz = true; + } + + if (!vert && horz && height > ClientRectangle.Height - SystemInformation.HorizontalScrollBarHeight) + vert = true; + } + + if (vert) { + int visible_height = horz ? ClientRectangle.Height - hbar.Height : ClientRectangle.Height; + vbar.SetValues (Math.Max (0, max_visible_order - 2), visible_height / ActualItemHeight); + /* + vbar.Maximum = max_visible_order; + vbar.LargeChange = ClientRectangle.Height / ItemHeight; + */ + + if (!vbar_bounds_set) { + vbar.Bounds = new Rectangle (ClientRectangle.Width - vbar.Width, 0, vbar.Width, + ClientRectangle.Height - + (horz ? SystemInformation.VerticalScrollBarWidth : 0)); + vbar_bounds_set = true; + + // We need to recalc the hbar if the vbar is now visible + hbar_bounds_set = false; + } + + + vbar.Visible = true; + if (skipped_nodes > 0) { + int skip = Math.Min (skipped_nodes, vbar.Maximum - VisibleCount + 1); + skipped_nodes = 0; + vbar.SafeValueSet (skip); + skipped_nodes = skip; + } + } else { + skipped_nodes = 0; + RecalculateVisibleOrder (root_node); + vbar.Visible = false; + SetVScrollValue (0); + vbar_bounds_set = false; + } + + if (horz) { + hbar.SetValues (width + 1, ClientRectangle.Width - (vert ? SystemInformation.VerticalScrollBarWidth : 0)); + /* + hbar.LargeChange = ClientRectangle.Width; + hbar.Maximum = width + 1; + */ + + if (!hbar_bounds_set) { + hbar.Bounds = new Rectangle (0, ClientRectangle.Height - hbar.Height, + ClientRectangle.Width - (vert ? SystemInformation.VerticalScrollBarWidth : 0), + hbar.Height); + hbar_bounds_set = true; + } + hbar.Visible = true; + } else { + hbar_offset = 0; + hbar.Visible = false; + hbar_bounds_set = false; + } + } + + private void SizeChangedHandler (object sender, EventArgs e) + { + if (IsHandleCreated) { + if (max_visible_order == -1) + RecalculateVisibleOrder (root_node); + UpdateScrollBars (false); + } + + if (vbar.Visible) { + vbar.Bounds = new Rectangle (ClientRectangle.Width - vbar.Width, 0, vbar.Width, + ClientRectangle.Height - (hbar.Visible ? SystemInformation.HorizontalScrollBarHeight : 0)); + } + + if (hbar.Visible) { + hbar.Bounds = new Rectangle (0, ClientRectangle.Height - hbar.Height, + ClientRectangle.Width - (vbar.Visible ? SystemInformation.VerticalScrollBarWidth : 0), hbar.Height); + } + } + + private void VScrollBarValueChanged (object sender, EventArgs e) + { + if (edit_node != null) + EndEdit (edit_node); + + SetVScrollPos (vbar.Value, null); + } + + private void SetVScrollPos (int pos, TreeNode new_top) + { + if (!vbar.VisibleInternal) + return; + + if (pos < 0) + pos = 0; + + if (skipped_nodes == pos) + return; + + int diff = skipped_nodes - pos; + skipped_nodes = pos; + + if (!IsHandleCreated) + return; + + int y_move = diff * ActualItemHeight; + XplatUI.ScrollWindow (Handle, ViewportRectangle, 0, y_move, false); + } + + /*private void SetVScrollTop (TreeNode new_top) + { + vbar.Value = new_top.visible_order - VisibleCount; + }*/ + + private void HScrollBarValueChanged(object sender, EventArgs e) + { + if (edit_node != null) + EndEdit (edit_node); + + int old_offset = hbar_offset; + hbar_offset = hbar.Value; + + if (hbar_offset < 0) { + hbar_offset = 0; + } + + XplatUI.ScrollWindow (Handle, ViewportRectangle, old_offset - hbar_offset, 0, false); + } + + internal void ExpandBelow (TreeNode node, int count_to_next) + { + if (update_stack > 0) { + update_needed = true; + return; + } + + // If node Bottom is less than 0, the node is above and not visible, + // and we need to scroll the entire viewport + int node_bottom = node.Bounds.Bottom >= 0 ? node.Bounds.Bottom : 0; + Rectangle below = new Rectangle (0, node_bottom, ViewportRectangle.Width, + ViewportRectangle.Height - node_bottom); + + int amount = count_to_next * ActualItemHeight; + + if (amount > 0) + XplatUI.ScrollWindow (Handle, below, 0, amount, false); + + if (show_plus_minus) { + Invalidate (new Rectangle (0, node.GetY (), Width, ActualItemHeight)); + } + } + + internal void CollapseBelow (TreeNode node, int count_to_next) + { + if (update_stack > 0) { + update_needed = true; + return; + } + + Rectangle below = new Rectangle (0, node.Bounds.Bottom, ViewportRectangle.Width, + ViewportRectangle.Height - node.Bounds.Bottom); + + int amount = count_to_next * ActualItemHeight; + + if (amount > 0) + XplatUI.ScrollWindow (Handle, below, 0, -amount, false); + + if (show_plus_minus) { + Invalidate (new Rectangle (0, node.GetY (), Width, ActualItemHeight)); + } + } + + private void MouseWheelHandler(object sender, MouseEventArgs e) { + + if (vbar == null || !vbar.is_visible) { + return; + } + + if (e.Delta < 0) { + SetVScrollValue (Math.Min (vbar.Value + SystemInformation.MouseWheelScrollLines, vbar.Maximum - VisibleCount + 1)); + } else { + SetVScrollValue (Math.Max (0, vbar.Value - SystemInformation.MouseWheelScrollLines)); + } + } + + private void VisibleChangedHandler (object sender, EventArgs e) + { + if (Visible) { + UpdateScrollBars (false); + } + } + + private void FontChangedHandler (object sender, EventArgs e) + { + if (IsHandleCreated) { + TreeNode top = TopNode; + InvalidateNodeWidthRecursive (root_node); + + SetTop (top); + } + } + + private void InvalidateNodeWidthRecursive (TreeNode node) + { + node.InvalidateWidth (); + foreach (TreeNode child in node.Nodes) { + InvalidateNodeWidthRecursive (child); + } + } + + private void GotFocusHandler (object sender, EventArgs e) + { + if (selected_node == null) { + if (pre_selected_node != null) { + SelectedNode = pre_selected_node; + return; + } + + SelectedNode = TopNode; + + } else if (selected_node != null) + UpdateNode (selected_node); + } + + private void LostFocusHandler (object sender, EventArgs e) + { + UpdateNode (SelectedNode); + } + + private void MouseDownHandler (object sender, MouseEventArgs e) + { + if (e.Button == MouseButtons.Right) + Focus (); + + TreeNode node = GetNodeAt (e.Y); + if (node == null) + return; + + mouse_click_node = node; + + if (show_plus_minus && IsPlusMinusArea (node, e.X) && e.Button == MouseButtons.Left) { + node.Toggle (); + return; + } else if (checkboxes && IsCheckboxArea (node, e.X) && e.Button == MouseButtons.Left) { + node.check_reason = TreeViewAction.ByMouse; + node.Checked = !node.Checked; + UpdateNode(node); + return; + } else if (IsSelectableArea (node, e.X) || full_row_select) { + TreeNode old_highlighted = highlighted_node; + highlighted_node = node; + if (label_edit && e.Clicks == 1 && highlighted_node == old_highlighted && e.Button == MouseButtons.Left) { + BeginEdit (node); + } else if (highlighted_node != focused_node) { + Size ds = SystemInformation.DragSize; + mouse_rect.X = e.X - ds.Width; + mouse_rect.Y = e.Y - ds.Height; + mouse_rect.Width = ds.Width * 2; + mouse_rect.Height = ds.Height * 2; + + select_mmove = true; + } + + Invalidate (highlighted_node.Bounds); + if (old_highlighted != null) + Invalidate (Bloat (old_highlighted.Bounds)); + + drag_begin_x = e.X; + drag_begin_y = e.Y; + } + } + + private void MouseUpHandler (object sender, MouseEventArgs e) { + TreeNode node = GetNodeAt (e.Y); + + if (node != null && node == mouse_click_node) { + if (e.Clicks == 2) + OnNodeMouseDoubleClick (new TreeNodeMouseClickEventArgs (node, e.Button, e.Clicks, e.X, e.Y)); + else + OnNodeMouseClick (new TreeNodeMouseClickEventArgs (node, e.Button, e.Clicks, e.X, e.Y)); + } + + mouse_click_node = null; + + drag_begin_x = -1; + drag_begin_y = -1; + + if (!select_mmove) + return; + + select_mmove = false; + + if (e.Button == MouseButtons.Right && selected_node != null) { + Invalidate (highlighted_node.Bounds); + highlighted_node = selected_node; + Invalidate (selected_node.Bounds); + return; + } + + TreeViewCancelEventArgs ce = new TreeViewCancelEventArgs (highlighted_node, false, TreeViewAction.ByMouse); + OnBeforeSelect (ce); + + Rectangle invalid; + if (!ce.Cancel) { + TreeNode prev_focused_node = focused_node; + TreeNode prev_highlighted_node = highlighted_node; + + selected_node = highlighted_node; + focused_node = highlighted_node; + OnAfterSelect (new TreeViewEventArgs (selected_node, TreeViewAction.ByMouse)); + + if (prev_highlighted_node != null) { + if (prev_focused_node != null) { + invalid = Rectangle.Union (Bloat (prev_focused_node.Bounds), + Bloat (prev_highlighted_node.Bounds)); + } else { + invalid = Bloat (prev_highlighted_node.Bounds); + } + + invalid.X = 0; + invalid.Width = ViewportRectangle.Width; + + Invalidate (invalid); + } + + } else { + if (highlighted_node != null) + Invalidate (highlighted_node.Bounds); + + highlighted_node = focused_node; + selected_node = focused_node; + if (selected_node != null) + Invalidate (selected_node.Bounds); + } + } + + private void MouseMoveHandler (object sender, MouseEventArgs e) { + // XXX - This should use HitTest and only fire when we are over + // the important parts of a node, not things like gridlines or + // whitespace + TreeNode tn = GetNodeAt (e.Location); + + if (tn != tooltip_currently_showing) + MouseLeftItem (tooltip_currently_showing); + + if (tn != null && tn != tooltip_currently_showing) + MouseEnteredItem (tn); + + if (e.Button == MouseButtons.Left || e.Button == MouseButtons.Right) { + if (drag_begin_x != -1 && drag_begin_y != -1) { + double rise = Math.Pow (drag_begin_x - e.X, 2); + double run = Math.Pow (drag_begin_y - e.Y, 2); + double move = Math.Sqrt (rise + run); + if (move > 3) { + TreeNode drag = GetNodeAtUseX (e.X, e.Y); + + if (drag != null) { + OnItemDrag (new ItemDragEventArgs (e.Button, drag)); + } + drag_begin_x = -1; + drag_begin_y = -1; + } + } + + } + + // If there is enough movement before the mouse comes up, + // selection is reverted back to the originally selected node + if (!select_mmove || mouse_rect.Contains (e.X, e.Y)) + return; + + Invalidate (highlighted_node.Bounds); + if (selected_node != null) + Invalidate (selected_node.Bounds); + if (focused_node != null) + Invalidate (focused_node.Bounds); + + highlighted_node = selected_node; + focused_node = selected_node; + + select_mmove = false; + } + + private void DoubleClickHandler (object sender, MouseEventArgs e) { + TreeNode node = GetNodeAtUseX (e.X,e.Y); + if(node != null && node.Nodes.Count > 0) { + node.Toggle(); + } + } + + + private bool RectsIntersect (Rectangle r, int left, int top, int width, int height) + { + return !((r.Left > left + width) || (r.Right < left) || + (r.Top > top + height) || (r.Bottom < top)); + } + + // Return true if message was handled, false to send it to base + private bool WmContextMenu (ref Message m) + { + Point pt; + TreeNode tn; + + pt = new Point (LowOrder ((int)m.LParam.ToInt32 ()), HighOrder ((int)m.LParam.ToInt32 ())); + + // This means it's a keyboard menu request + if (pt.X == -1 || pt.Y == -1) { + tn = SelectedNode; + + if (tn == null) + return false; + + pt = new Point (tn.Bounds.Left, tn.Bounds.Top + (tn.Bounds.Height / 2)); + } else { + pt = PointToClient (pt); + + tn = GetNodeAt (pt); + + if (tn == null) + return false; + } + + if (tn.ContextMenuStrip != null) { + tn.ContextMenuStrip.Show (this, pt); + return true; + } + + // The node we found did not have its own menu, let the parent try to display its menu + return false; + } + + #region Stuff for ToolTips + private void MouseEnteredItem (TreeNode item) + { + tooltip_currently_showing = item; + + if (!is_hovering) + return; + + if (ShowNodeToolTips && !string.IsNullOrEmpty (tooltip_currently_showing.ToolTipText)) + ToolTipWindow.Present (this, tooltip_currently_showing.ToolTipText); + + OnNodeMouseHover (new TreeNodeMouseHoverEventArgs (tooltip_currently_showing)); + } + + private void MouseLeftItem (TreeNode item) + { + ToolTipWindow.Hide (this); + tooltip_currently_showing = null; + } + + private ToolTip ToolTipWindow { + get { + if (tooltip_window == null) + tooltip_window = new ToolTip (); + + return tooltip_window; + } + } + #endregion + + #endregion // Internal & Private Methods and Properties + + #region Events + static object ItemDragEvent = new object (); + static object AfterCheckEvent = new object (); + static object AfterCollapseEvent = new object (); + static object AfterExpandEvent = new object (); + static object AfterLabelEditEvent = new object (); + static object AfterSelectEvent = new object (); + static object BeforeCheckEvent = new object (); + static object BeforeCollapseEvent = new object (); + static object BeforeExpandEvent = new object (); + static object BeforeLabelEditEvent = new object (); + static object BeforeSelectEvent = new object (); + static object DrawNodeEvent = new object (); + static object NodeMouseClickEvent = new object (); + static object NodeMouseDoubleClickEvent = new object(); + static object NodeMouseHoverEvent = new object (); + static object RightToLeftLayoutChangedEvent = new object (); + + public event ItemDragEventHandler ItemDrag { + add { Events.AddHandler (ItemDragEvent, value); } + remove { Events.RemoveHandler (ItemDragEvent, value); } + } + + public event TreeViewEventHandler AfterCheck { + add { Events.AddHandler (AfterCheckEvent, value); } + remove { Events.RemoveHandler (AfterCheckEvent, value); } + } + + public event TreeViewEventHandler AfterCollapse { + add { Events.AddHandler (AfterCollapseEvent, value); } + remove { Events.RemoveHandler (AfterCollapseEvent, value); } + } + + public event TreeViewEventHandler AfterExpand { + add { Events.AddHandler (AfterExpandEvent, value); } + remove { Events.RemoveHandler (AfterExpandEvent, value); } + } + + public event NodeLabelEditEventHandler AfterLabelEdit { + add { Events.AddHandler (AfterLabelEditEvent, value); } + remove { Events.RemoveHandler (AfterLabelEditEvent, value); } + } + + public event TreeViewEventHandler AfterSelect { + add { Events.AddHandler (AfterSelectEvent, value); } + remove { Events.RemoveHandler (AfterSelectEvent, value); } + } + + public event TreeViewCancelEventHandler BeforeCheck { + add { Events.AddHandler (BeforeCheckEvent, value); } + remove { Events.RemoveHandler (BeforeCheckEvent, value); } + } + + public event TreeViewCancelEventHandler BeforeCollapse { + add { Events.AddHandler (BeforeCollapseEvent, value); } + remove { Events.RemoveHandler (BeforeCollapseEvent, value); } + } + + public event TreeViewCancelEventHandler BeforeExpand { + add { Events.AddHandler (BeforeExpandEvent, value); } + remove { Events.RemoveHandler (BeforeExpandEvent, value); } + } + + public event NodeLabelEditEventHandler BeforeLabelEdit { + add { Events.AddHandler (BeforeLabelEditEvent, value); } + remove { Events.RemoveHandler (BeforeLabelEditEvent, value); } + } + + public event TreeViewCancelEventHandler BeforeSelect { + add { Events.AddHandler (BeforeSelectEvent, value); } + remove { Events.RemoveHandler (BeforeSelectEvent, value); } + } + + public event DrawTreeNodeEventHandler DrawNode { + add { Events.AddHandler (DrawNodeEvent, value); } + remove { Events.RemoveHandler (DrawNodeEvent, value); } + } + + public event TreeNodeMouseClickEventHandler NodeMouseClick { + add { Events.AddHandler (NodeMouseClickEvent, value); } + remove { Events.RemoveHandler (NodeMouseClickEvent, value); } + } + + + public event TreeNodeMouseClickEventHandler NodeMouseDoubleClick { + add { Events.AddHandler (NodeMouseDoubleClickEvent, value); } + remove { Events.RemoveHandler (NodeMouseDoubleClickEvent, value); } + } + + public event TreeNodeMouseHoverEventHandler NodeMouseHover { + add { Events.AddHandler (NodeMouseHoverEvent, value); } + remove { Events.RemoveHandler (NodeMouseHoverEvent, value); } + } + + public event EventHandler RightToLeftLayoutChanged { + add { Events.AddHandler (RightToLeftLayoutChangedEvent, value); } + remove { Events.RemoveHandler (RightToLeftLayoutChangedEvent, value); } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler BackgroundImageChanged { + add { base.BackgroundImageChanged += value; } + remove { base.BackgroundImageChanged -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler BackgroundImageLayoutChanged { + add { base.BackgroundImageLayoutChanged += value; } + remove { base.BackgroundImageLayoutChanged -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler PaddingChanged { + add { base.PaddingChanged += value; } + remove { base.PaddingChanged -= value; } + } + + //[EditorBrowsable (EditorBrowsableState.Never)] + [Browsable (false)] + public new event PaintEventHandler Paint { + add { base.Paint += value; } + remove { base.Paint -= value; } + } + + //[EditorBrowsable (EditorBrowsableState.Never)] + [Browsable (false)] + public new event EventHandler TextChanged { + add { base.TextChanged += value; } + remove { base.TextChanged -= value; } + } + + #region UIA Framework Events + static object UIACheckBoxesChangedEvent = new object (); + + internal event EventHandler UIACheckBoxesChanged { + add { Events.AddHandler (UIACheckBoxesChangedEvent, value); } + remove { Events.RemoveHandler (UIACheckBoxesChangedEvent, value); } + } + + internal void OnUIACheckBoxesChanged (EventArgs e) + { + EventHandler eh = (EventHandler) Events [UIACheckBoxesChangedEvent]; + if (eh != null) + eh (this, e); + } + + static object UIALabelEditChangedEvent = new object (); + + internal event EventHandler UIALabelEditChanged { + add { Events.AddHandler (UIALabelEditChangedEvent, value); } + remove { Events.RemoveHandler (UIALabelEditChangedEvent, value); } + } + + internal void OnUIALabelEditChanged (EventArgs e) + { + EventHandler eh = (EventHandler) Events [UIALabelEditChangedEvent]; + if (eh != null) + eh (this, e); + } + + static object UIANodeTextChangedEvent = new object (); + + internal event TreeViewEventHandler UIANodeTextChanged { + add { Events.AddHandler (UIANodeTextChangedEvent, value); } + remove { Events.RemoveHandler (UIANodeTextChangedEvent, value); } + } + + internal void OnUIANodeTextChanged (TreeViewEventArgs e) + { + TreeViewEventHandler eh = + (TreeViewEventHandler) Events [UIANodeTextChangedEvent]; + if (eh != null) + eh (this, e); + } + + static object UIACollectionChangedEvent = new object (); + + internal event CollectionChangeEventHandler UIACollectionChanged { + add { Events.AddHandler (UIACollectionChangedEvent, value); } + remove { Events.RemoveHandler (UIACollectionChangedEvent, value); } + } + + internal void OnUIACollectionChanged (object sender, CollectionChangeEventArgs e) + { + CollectionChangeEventHandler eh = + (CollectionChangeEventHandler) Events [UIACollectionChangedEvent]; + if (eh != null) { + if (sender == root_node) + sender = this; + eh (sender, e); + } + } + #endregion // UIA Framework Events + #endregion // Events + } +} + + diff --git a/source/ShiftUI/Widgets/UserWidget.cs b/source/ShiftUI/Widgets/UserWidget.cs new file mode 100644 index 0000000..e0bb759 --- /dev/null +++ b/source/ShiftUI/Widgets/UserWidget.cs @@ -0,0 +1,236 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2004-2005 Novell, Inc. +// +// Authors: +// Peter Bartok [email protected] +// + +using System; +using System.ComponentModel; +using System.ComponentModel.Design; +using System.Drawing; +using System.Runtime.InteropServices; + +namespace ShiftUI { + [DefaultEvent("Load")] + [DesignerCategory("UserWidget")] + [Designer("ShiftUI.Design.WidgetDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")] + [Designer("ShiftUI.Design.UserWidgetDocumentDesigner, " + Consts.AssemblySystem_Design, typeof(IRootDesigner))] + [ClassInterface (ClassInterfaceType.AutoDispatch)] + [ComVisible (true)] + public class UserWidget : ContainerWidget { + #region Public Constructors + public UserWidget() { + SetStyle (Widgetstyles.SupportsTransparentBackColor, true); + } + #endregion // Public Constructors + + #region Public Instance Properties + [Browsable (true)] + [EditorBrowsable (EditorBrowsableState.Always)] + [DesignerSerializationVisibility (DesignerSerializationVisibility.Visible)] + public override bool AutoSize { + get { return base.AutoSize; } + set { base.AutoSize = value; } + } + + [Browsable (true)] + [LocalizableAttribute(true)] + [DefaultValue (AutoSizeMode.GrowOnly)] + public AutoSizeMode AutoSizeMode { + get { return base.GetAutoSizeMode (); } + set { + if (base.GetAutoSizeMode () != value) { + base.SetAutoSizeMode (value); + } + } + } + + [Browsable (true)] + [EditorBrowsable (EditorBrowsableState.Always)] + public override AutoValidate AutoValidate { + get { return base.AutoValidate; } + set { base.AutoValidate = value; } + } + + protected override Size DefaultSize { + get { + return new Size(150, 150); + } + } + + [Bindable(false)] + [Browsable(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + [EditorBrowsable(EditorBrowsableState.Never)] + public override string Text { + get { + return base.Text; + } + + set { + base.Text = value; + } + } + #endregion // Public Instance Properties + + #region Public Instance Methods + [Browsable (true)] + [EditorBrowsable (EditorBrowsableState.Always)] + public override bool ValidateChildren () + { + return base.ValidateChildren (); + } + + [Browsable (true)] + [EditorBrowsable (EditorBrowsableState.Always)] + public override bool ValidateChildren (ValidationConstraints validationConstraints) + { + return base.ValidateChildren (validationConstraints); + } + #endregion + + #region Protected Instance Methods + [EditorBrowsable(EditorBrowsableState.Advanced)] + protected override void OnCreateWidget() { + base.OnCreateWidget(); + + // The OnCreateWidget isn't neccessarily raised *before* it + // becomes first visible, but that's the best we've got + OnLoad(EventArgs.Empty); + } + + [EditorBrowsable(EditorBrowsableState.Advanced)] + protected virtual void OnLoad(EventArgs e) { + EventHandler eh = (EventHandler)(Events [LoadEvent]); + if (eh != null) + eh (this, e); + } + + [EditorBrowsable(EditorBrowsableState.Advanced)] + protected override void OnMouseDown(MouseEventArgs e) { + base.OnMouseDown(e); + } + + [EditorBrowsable(EditorBrowsableState.Advanced)] + protected override void WndProc(ref Message m) { + switch ((Msg) m.Msg) { + case Msg.WM_SETFOCUS: + if (ActiveWidget == null) + SelectNextWidget (null, true, true, true, false); + base.WndProc (ref m); + break; + default: + base.WndProc (ref m); + break; + } + } + #endregion // Protected Instance Methods + + #region Protected Properties + protected override CreateParams CreateParams { + get { + CreateParams cp = base.CreateParams; + cp.Style |= (int)WindowStyles.WS_TABSTOP; + cp.ExStyle |= (int)WindowExStyles.WS_EX_WidgetPARENT; + return cp; + } + } + #endregion + + #region Events + static object LoadEvent = new object (); + + [Browsable (true)] + [EditorBrowsable (EditorBrowsableState.Always)] + public new event EventHandler AutoSizeChanged { + add { base.AutoSizeChanged += value; } + remove { base.AutoSizeChanged -= value; } + } + + [Browsable (true)] + [EditorBrowsable (EditorBrowsableState.Always)] + public new event EventHandler AutoValidateChanged { + add { base.AutoValidateChanged += value; } + remove { base.AutoValidateChanged -= value; } + } + + public event EventHandler Load { + add { Events.AddHandler (LoadEvent, value); } + remove { Events.RemoveHandler (LoadEvent, value); } + } + + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler TextChanged { + add { base.TextChanged += value; } + remove { base.TextChanged -= value; } + } + #endregion // Events + + protected override void OnResize (EventArgs e) + { + base.OnResize (e); + } + + [Browsable (true)] + [DefaultValue (BorderStyle.None)] + [EditorBrowsable (EditorBrowsableState.Always)] + public BorderStyle BorderStyle { + get { return InternalBorderStyle; } + set { InternalBorderStyle = value; } + } + + internal override Size GetPreferredSizeCore (Size proposedSize) + { + Size retsize = Size.Empty; + + // Add up the requested sizes for Docked controls + foreach (Widget child in Widgets) { + if (!child.is_visible) + continue; + + if (child.Dock == DockStyle.Left || child.Dock == DockStyle.Right) + retsize.Width += child.PreferredSize.Width; + else if (child.Dock == DockStyle.Top || child.Dock == DockStyle.Bottom) + retsize.Height += child.PreferredSize.Height; + } + + // See if any non-Docked control is positioned lower or more right than our size + foreach (Widget child in Widgets) { + if (!child.is_visible) + continue; + + if (child.Dock != DockStyle.None) + continue; + + // If its anchored to the bottom or right, that doesn't really count + if ((child.Anchor & AnchorStyles.Bottom) == AnchorStyles.Bottom || (child.Anchor & AnchorStyles.Right) == AnchorStyles.Right) + continue; + + retsize.Width = Math.Max (retsize.Width, child.Bounds.Right + child.Margin.Right); + retsize.Height = Math.Max (retsize.Height, child.Bounds.Bottom + child.Margin.Bottom); + } + + return retsize; + } + } +} diff --git a/source/ShiftUI/Widgets/VScrollBar.cs b/source/ShiftUI/Widgets/VScrollBar.cs new file mode 100644 index 0000000..f458f66 --- /dev/null +++ b/source/ShiftUI/Widgets/VScrollBar.cs @@ -0,0 +1,79 @@ +// +// ShiftUI.HScrollBar.cs +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (C) 2004-2005, Novell, Inc. +// +// Authors: +// Jordi Mas i Hernandez [email protected] +// + + +using System.Drawing; +using System.ComponentModel; +using System.Runtime.InteropServices; +using System; + +namespace ShiftUI +{ + [ClassInterface (ClassInterfaceType.AutoDispatch)] + [ComVisible (true)] + public class VScrollBar : ScrollBar + { + #region events + + //[EditorBrowsable (EditorBrowsableState.Never)] + [Browsable (false)] + public new event EventHandler RightToLeftChanged { + add { base.RightToLeftChanged += value; } + remove { base.RightToLeftChanged -= value; } + } + + #endregion Events + + public VScrollBar() + { + vert = true; + } + + //[EditorBrowsable (EditorBrowsableState.Never)] + [Browsable (false)] + public override RightToLeft RightToLeft { + get { return base.RightToLeft; } + set { + if (RightToLeft == value) + return; + + base.RightToLeft = value; + + OnRightToLeftChanged (EventArgs.Empty); + } + } + + protected override Size DefaultSize { + get { return ThemeEngine.Current.VScrollBarDefaultSize; } + } + + protected override CreateParams CreateParams { + get { return base.CreateParams; } + } + } +} |
