From d40fed5ce2bc806a91245adb18039634eac13ed0 Mon Sep 17 00:00:00 2001 From: MichaelTheShifter Date: Wed, 20 Jul 2016 09:40:36 -0400 Subject: Move ShiftUI source code to ShiftOS This'll be a lot easier to work on. --- source/ShiftUI/Widgets/ContainerControl.cs | 850 +++++++++++++++++++++++++++++ 1 file changed, 850 insertions(+) create mode 100644 source/ShiftUI/Widgets/ContainerControl.cs (limited to 'source/ShiftUI/Widgets/ContainerControl.cs') 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 pbartok@novell.com +// +// + + +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); } + } + } +} -- cgit v1.2.3