ShiftOS-C-/source/ShiftUI/Widgets/ContainerControl.cs

851 lines
23 KiB
C#
Raw Normal View History

// 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); }
}
}
}