ToolStripSplitButton.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ FX-1434 / FX-1434 / 1.0 / untmp / whidbey / REDBITS / ndp / fx / src / WinForms / Managed / System / WinForms / ToolStripSplitButton.cs / 1 / ToolStripSplitButton.cs

                            //------------------------------------------------------------------------------ 
// 
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// 
//----------------------------------------------------------------------------- 

 
namespace System.Windows.Forms { 
    using System;
    using System.Security; 
    using System.Security.Permissions;
    using System.Drawing;
    using System.Windows.Forms;
    using System.Drawing.Imaging; 
    using System.ComponentModel;
    using System.Windows.Forms.Design; 
    using System.Diagnostics.CodeAnalysis; 
    using System.Globalization;
    using System.Diagnostics; 
    using System.Windows.Forms.Layout;

    /// 
    ///  
    [
    ToolStripItemDesignerAvailability(ToolStripItemDesignerAvailability.ToolStrip | ToolStripItemDesignerAvailability.StatusStrip), 
    DefaultEvent("ButtonClick") 
    ]
    public class ToolStripSplitButton : ToolStripDropDownItem { 

        private ToolStripItem                    defaultItem                =  null;
        private ToolStripSplitButtonButton       splitButtonButton          =  null;
        private Rectangle                     dropDownButtonBounds       =  Rectangle.Empty; 
        private ToolStripSplitButtonButtonLayout splitButtonButtonLayout    =  null;
        private int                           dropDownButtonWidth        =  0; 
        private int                           splitterWidth              =  1; 
        private Rectangle                     splitterBounds             =  Rectangle.Empty;
        private byte                          openMouseId                =  0; 
        private long lastClickTime = 0;

        private const int DEFAULT_DROPDOWN_WIDTH = 11;
 
        private static readonly object EventDefaultItemChanged                         = new object();
        private static readonly object EventButtonClick                                = new object(); 
        private static readonly object EventButtonDoubleClick                          = new object(); 
        private static readonly object EventDropDownOpened                             = new object();
        private static readonly object EventDropDownClosed                             = new object(); 

        /// 
        /// 
        /// Summary of ToolStripSplitButton. 
        /// 
        public ToolStripSplitButton() { 
            Initialize(); // all additional work should be done in Initialize 
        }
        public ToolStripSplitButton(string text):base(text,null,(EventHandler)null) { 
            Initialize();
        }
        public ToolStripSplitButton(Image image):base(null,image,(EventHandler)null) {
            Initialize(); 
        }
        public ToolStripSplitButton(string text, Image image):base(text,image,(EventHandler)null) { 
            Initialize(); 
        }
        public ToolStripSplitButton(string text, Image image, EventHandler onClick):base(text,image,onClick) { 
            Initialize();
        }
        public ToolStripSplitButton(string text, Image image, EventHandler onClick, string name) :base(text,image,onClick,name){
            Initialize(); 
        }
        public ToolStripSplitButton(string text, Image image, params ToolStripItem[] dropDownItems):base(text,image,dropDownItems) { 
            Initialize(); 
        }
 
        [DefaultValue(true)]
        public new bool AutoToolTip {
            get {
                return base.AutoToolTip; 
            }
            set { 
                base.AutoToolTip = value; 
            }
        } 


        /// 
        ///  
        /// Summary of ToolStripSplitButton.
        ///  
        [Browsable(false)] 
        public Rectangle ButtonBounds {
            get { 
                //Rectangle bounds = SplitButtonButton.Bounds;
                //bounds.Offset(this.Bounds.Location);
                return SplitButtonButton.Bounds;
            } 
        }
 
        ///  
        /// 
        /// Summary of ButtonPressed. 
        /// 
        [Browsable(false)]
        public bool ButtonPressed {
            get { 
                return SplitButtonButton.Pressed;
 
            } 
        }
 
        /// 
        /// 
        /// Summary of ButtonPressed.
        ///  
        [Browsable(false)]
        public bool ButtonSelected { 
            get { 
                return SplitButtonButton.Selected || DropDownButtonPressed;
            } 
        }

        /// 
        ///  
        /// Occurs when the button portion of a split button is clicked.
        ///  
        [ 
        SRCategory(SR.CatAction),
        SRDescription(SR.ToolStripSplitButtonOnButtonClickDescr) 
        ]
        public event EventHandler ButtonClick {
            add {
                Events.AddHandler(EventButtonClick, value); 
            }
            remove { 
                Events.RemoveHandler(EventButtonClick, value); 
            }
        } 
        /// 
        /// 
        /// Occurs when the utton portion of a split button  is double clicked.
        ///  
        [
        SRCategory(SR.CatAction), 
        SRDescription(SR.ToolStripSplitButtonOnButtonDoubleClickDescr) 
        ]
        public event EventHandler ButtonDoubleClick { 
            add {
                Events.AddHandler(EventButtonDoubleClick, value);
            }
            remove { 
                Events.RemoveHandler(EventButtonDoubleClick, value);
            } 
        } 

 
        protected override bool DefaultAutoToolTip {
            get {
               return true;
            } 
        }
 
        ///  
        /// 
        /// Summary of DefaultItem. 
        /// 
        [DefaultValue(null), Browsable(false)]
        public ToolStripItem DefaultItem {
            get { 
                return defaultItem;
            } 
            set { 
                if (defaultItem != value) {
                    OnDefaultItemChanged(new EventArgs()); 
                    defaultItem = value;
                }
            }
        } 

 
        ///  
        /// 
        /// Occurs when the default item has changed 
        /// 
        [
        SRCategory(SR.CatAction),
        SRDescription(SR.ToolStripSplitButtonOnDefaultItemChangedDescr) 
        ]
        public event EventHandler DefaultItemChanged { 
            add { 
                Events.AddHandler(EventDefaultItemChanged, value);
            } 
            remove {
                Events.RemoveHandler(EventDefaultItemChanged, value);
            }
        } 
 	
        ///  
        ///  
        /// specifies the default behavior of these items on ToolStripDropDowns when clicked.
        ///  
        internal protected override bool DismissWhenClicked {
            get {
                return DropDown.Visible != true;
            } 

        } 
 
        internal override Rectangle DropDownButtonArea {
               get { return this.DropDownButtonBounds; } 
        }

        /// 
        ///  
        /// The bounds of the DropDown in ToolStrip coordinates.
        ///  
        [Browsable(false)] 
        public Rectangle DropDownButtonBounds {
            get { 
                 return dropDownButtonBounds;
            }

        } 
        /// 
        ///  
        /// Summary of DropDownButtonBounds. 
        /// 
        [Browsable(false)] 
        public bool DropDownButtonPressed {
            get {
                //
                return DropDown.Visible; 
            }
        } 
        ///  
        /// 
        /// Summary of DropDownButtonSelected. 
        /// 
        [Browsable(false)]
        public bool DropDownButtonSelected{
            get { 
                return this.Selected;
            } 
        } 
        /// 
        ///  
        /// Summary of DropDownButtonWidth.
        /// 
        [
        SRCategory(SR.CatLayout), 
        SRDescription(SR.ToolStripSplitButtonDropDownButtonWidthDescr)
        ] 
        public int DropDownButtonWidth { 
            get{
                return dropDownButtonWidth; 
            }
            set {
                if (value < 0) {
                    // throw if less than 0. 
                    throw new ArgumentOutOfRangeException("DropDownButtonWidth", SR.GetString(SR.InvalidLowBoundArgumentEx, "DropDownButtonWidth", value.ToString(CultureInfo.CurrentCulture), (0).ToString(CultureInfo.CurrentCulture)));
                } 
 
                if (dropDownButtonWidth != value) {
                    dropDownButtonWidth = value; 
                    InvalidateSplitButtonLayout();
                    InvalidateItemLayout(PropertyNames.DropDownButtonWidth, true);
                }
             } 
        }
 
        ///  
        /// This is here for serialization purposes.
        ///  
        private int DefaultDropDownButtonWidth {
            get {
                // lets start off with a size roughly equivilant to a combobox dropdown
                return DEFAULT_DROPDOWN_WIDTH; 
            }
        } 
 

 
        /// 
        /// Just used as a convenience to help manage layout
        /// 
        private ToolStripSplitButtonButton SplitButtonButton { 
            get {
                if (splitButtonButton == null) { 
                    splitButtonButton = new ToolStripSplitButtonButton(this); 
                }
                splitButtonButton.Image = this.Image; 
                splitButtonButton.Text = this.Text;
                splitButtonButton.BackColor = this.BackColor;
                splitButtonButton.ForeColor = this.ForeColor;
                splitButtonButton.Font = this.Font; 
                splitButtonButton.ImageAlign = this.ImageAlign;
                splitButtonButton.TextAlign = this.TextAlign; 
                splitButtonButton.TextImageRelation = this.TextImageRelation; 
                return splitButtonButton;
            } 
        }
        /// 
        /// Summary of SplitButtonButtonLayout.
        /// 	 
        internal ToolStripItemInternalLayout SplitButtonButtonLayout {
            get { 
                // For preferred size caching reasons, we need to keep our two 
                // internal layouts (button, dropdown button) in sync.
 
                if (InternalLayout != null /*if layout is invalid - calls CreateInternalLayout - which resets splitButtonButtonLayout to null*/
                    && splitButtonButtonLayout == null) {
                    splitButtonButtonLayout = new ToolStripSplitButtonButtonLayout(this);
                } 
                return splitButtonButtonLayout;
            } 
        } 

        ///  
        /// 
        /// the width of the separator between the default and drop down button
        /// 
        [ 
        SRDescription(SR.ToolStripSplitButtonSplitterWidthDescr),
        SRCategory(SR.CatLayout), 
        Browsable(false), 
        EditorBrowsable(EditorBrowsableState.Advanced)
        ] 
        internal int SplitterWidth {
            get {
                return splitterWidth;
            } 
            set {
                if (value < 0) { 
                    splitterWidth = 0; 
                }
                else { 
                    splitterWidth = value;
                }
                InvalidateSplitButtonLayout();
            } 
        }
        ///  
        ///  
        /// the boundaries of the separator between the default and drop down button, exposed for custom
        /// painting purposes. 
        /// 
        [Browsable(false)]
        public Rectangle SplitterBounds {
            get { 
                return splitterBounds;
            } 
        } 
        /// 
        /// Summary of CalculateLayout. 
        /// 	
        private void CalculateLayout() {

            // Figure out where the DropDown image goes. 
            Rectangle dropDownButtonBounds = new Rectangle(Point.Empty, this.Size);
            Rectangle splitButtonButtonBounds = Rectangle.Empty; 
			 

            dropDownButtonBounds = new Rectangle(Point.Empty, new Size(Math.Min(this.Width, DropDownButtonWidth), this.Height)); 
   			
            // Figure out the height and width of the selected item.
            int splitButtonButtonWidth = Math.Max(0, this.Width - dropDownButtonBounds.Width);
            int splitButtonButtonHeight = Math.Max(0, this.Height); 

            splitButtonButtonBounds = new Rectangle(Point.Empty, new Size(splitButtonButtonWidth, splitButtonButtonHeight)); 
 
            // grow the selected item by one since we're overlapping the borders.
            splitButtonButtonBounds.Width -= splitterWidth; 

            if (this.RightToLeft == RightToLeft.No) {
                // the dropdown button goes on the right
                dropDownButtonBounds.Offset(splitButtonButtonBounds.Right+splitterWidth, 0); 
                splitterBounds = new Rectangle(splitButtonButtonBounds.Right, splitButtonButtonBounds.Top, splitterWidth, splitButtonButtonBounds.Height);
            } 
            else { 
                // the split button goes on the right.
                splitButtonButtonBounds.Offset(DropDownButtonWidth+splitterWidth, 0); 
                splitterBounds = new Rectangle(dropDownButtonBounds.Right, dropDownButtonBounds.Top, splitterWidth, dropDownButtonBounds.Height);

            }
 
            this.SplitButtonButton.SetBounds(splitButtonButtonBounds);
            this.SetDropDownButtonBounds(dropDownButtonBounds); 
 
        }
 
        protected override AccessibleObject CreateAccessibilityInstance() {
           return new ToolStripSplitButtonAccessibleObject(this);
       }
 
        protected override ToolStripDropDown CreateDefaultDropDown() {
             // AutoGenerate a Winbar DropDown - set the property so we hook events 
              return new ToolStripDropDownMenu(this, /*isAutoGenerated=*/true); 
         }
 

        internal override ToolStripItemInternalLayout CreateInternalLayout() {
            // whenever the master layout is invalidated - invalidate the splitbuttonbutton layout.
            this.splitButtonButtonLayout = null; 
            return new ToolStripItemInternalLayout(this);
 
        } 

        ///  
        public override Size GetPreferredSize(Size constrainingSize) {
            Size preferredSize = SplitButtonButtonLayout.GetPreferredSize(constrainingSize);
            preferredSize.Width += DropDownButtonWidth + SplitterWidth + Padding.Horizontal;
            return preferredSize; 
        }
 
        ///  
        /// Summary of InvalidateSplitButtonLayout.
        /// 	 
        private void InvalidateSplitButtonLayout() {
            this.splitButtonButtonLayout = null;	
            CalculateLayout();
        } 

        private void Initialize() { 
            dropDownButtonWidth = DefaultDropDownButtonWidth; 
            SupportsSpaceKey = true;
        } 

        [UIPermission(SecurityAction.LinkDemand, Window=UIPermissionWindow.AllWindows)]
        protected internal override bool ProcessDialogKey(Keys keyData) {
            if (Enabled && (keyData == Keys.Enter || (SupportsSpaceKey && keyData == Keys.Space))) { 
               PerformButtonClick();
               return true; 
            } 

            return base.ProcessDialogKey(keyData); 
        }
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1720:AvoidTypeNamesInParameters")] // 'charCode' matches control.cs
        [UIPermission(SecurityAction.LinkDemand, Window=UIPermissionWindow.AllWindows)]
        protected internal override bool ProcessMnemonic(char charCode) { 
           // checking IsMnemonic is not necessary - toolstrip does this for us
           PerformButtonClick(); 
           return true; 
        }
 
        /// 
        /// 
        /// called when the button portion of a split button is clicked
        /// if there is a default item, this will route the click to the default item 
        /// 
        protected virtual void OnButtonClick(System.EventArgs e) { 
 
            if (DefaultItem != null) {
                DefaultItem.FireEvent(ToolStripItemEventType.Click); 
            }

            EventHandler handler = (EventHandler)Events[EventButtonClick];
            if (handler != null) handler(this, e); 

        } 
 
        /// 
        ///  
        /// called when the button portion of a split button is double clicked
        /// if there is a default item, this will route the double click to the default item
        /// 
        public virtual void OnButtonDoubleClick(System.EventArgs e) { 
            if (DefaultItem != null) {
                DefaultItem.FireEvent(ToolStripItemEventType.DoubleClick); 
            } 

            EventHandler handler = (EventHandler)Events[EventButtonDoubleClick]; 
            if (handler != null) handler(this,e);
        }

 
        /// 
        ///  
        /// Inheriting classes should override this method to handle this event. 
        /// 
        protected virtual void OnDefaultItemChanged(EventArgs e) { 
            InvalidateSplitButtonLayout();
            if (CanRaiseEvents) {
                EventHandler eh = Events[EventDefaultItemChanged] as EventHandler;
                if (eh != null) { 
                    eh(this, e);
                } 
            } 

        } 

        /// 
        /// 
        /// Summary of OnMouseDown. 
        /// 
        protected override void OnMouseDown(MouseEventArgs e) { 
		 
            if (DropDownButtonBounds.Contains(e.Location)) {
                if (e.Button == MouseButtons.Left) { 

                    if (!DropDown.Visible) {
                        Debug.Assert(ParentInternal != null, "Parent is null here, not going to get accurate ID");
                        openMouseId = (ParentInternal == null) ? (byte)0: ParentInternal.GetMouseId(); 
                        this.ShowDropDown(/*mousePress = */true);
                    } 
                } 
            }
            else { 
                SplitButtonButton.Push(true);
            }

        } 

 
        ///  
        /// 
        /// Summary of OnMouseUp. 
        /// 
        protected override void OnMouseUp(MouseEventArgs e) {
            if (!Enabled) {
                return; 
            }
 
 
            SplitButtonButton.Push(false);
 
            if (DropDownButtonBounds.Contains(e.Location)) {
                if (e.Button == MouseButtons.Left) {
                    if (DropDown.Visible) {
                        Debug.Assert(ParentInternal != null, "Parent is null here, not going to get accurate ID"); 
                        byte closeMouseId = (ParentInternal == null) ? (byte)0: ParentInternal.GetMouseId();
                        if (closeMouseId != openMouseId) { 
                            openMouseId = 0;  // reset the mouse id, we should never get this value from toolstrip. 
                            ToolStripManager.ModalMenuFilter.CloseActiveDropDown(DropDown, ToolStripDropDownCloseReason.AppClicked);
                            Select(); 
                       }
                    }
                }
            } 
            Point clickPoint = new Point(e.X, e.Y);
            if ((e.Button == MouseButtons.Left) && this.SplitButtonButton.Bounds.Contains(clickPoint)) { 
                bool shouldFireDoubleClick = false; 
                if (DoubleClickEnabled) {
                    long newTime = DateTime.Now.Ticks; 
                    long deltaTicks = newTime - lastClickTime;
                    lastClickTime = newTime;
                    // use >= for cases where the succession of click events is so fast it's not picked up by
                    // DateTime resolution. 
                    Debug.Assert(deltaTicks >= 0, "why are deltaticks less than zero? thats some mighty fast clicking");
                    // if we've seen a mouse up less than the double click time ago, we should fire. 
                    if (deltaTicks >= 0 && deltaTicks < DoubleClickTicks) { 
                        shouldFireDoubleClick = true;
                    } 
                }
                if (shouldFireDoubleClick) {
                    OnButtonDoubleClick(new System.EventArgs());
                    // VSWhidbey 486983: if we actually fired DoubleClick - reset the lastClickTime. 
                    lastClickTime = 0;
                } 
                else { 
                    OnButtonClick(new System.EventArgs());
                } 
            }

        }
        ///  
        protected override void OnMouseLeave(EventArgs e) {
            openMouseId = 0;  // reset the mouse id, we should never get this value from toolstrip. 
            SplitButtonButton.Push(false); 
            base.OnMouseLeave(e);
        } 
      	
        /// 
        /// 
        /// Summary of OnRightToLeftChanged. 
        /// 
        protected override void OnRightToLeftChanged(EventArgs e) { 
            base.OnRightToLeftChanged(e); 
            InvalidateSplitButtonLayout();			
        } 
        /// 
        /// 
        /// Summary of OnPaint.
        ///  
        /// 
        protected override void OnPaint(System.Windows.Forms.PaintEventArgs e) { 
 
             ToolStripRenderer renderer = this.Renderer;
             if (renderer != null) { 
                 InvalidateSplitButtonLayout();
                 Graphics g = e.Graphics;

                 renderer.DrawSplitButton(new ToolStripItemRenderEventArgs(g, this)); 

                 if ((DisplayStyle & ToolStripItemDisplayStyle.Image) != ToolStripItemDisplayStyle.None)  { 
                     renderer.DrawItemImage(new ToolStripItemImageRenderEventArgs(g, this, SplitButtonButtonLayout.ImageRectangle)); 
                 }
 
                 if ((DisplayStyle & ToolStripItemDisplayStyle.Text) != ToolStripItemDisplayStyle.None) {
                      renderer.DrawItemText(new ToolStripItemTextRenderEventArgs(g, this, SplitButtonButton.Text, SplitButtonButtonLayout.TextRectangle, this.ForeColor, this.Font, SplitButtonButtonLayout.TextFormat));
                 }
             } 
        }
 
        public void PerformButtonClick() { 
            if (Enabled && Available) {
                PerformClick(); 
                OnButtonClick(EventArgs.Empty);
            }
        }
 
        /// 
        ///  
        /// Resets the RightToLeft to be the default. 
        /// 
        [EditorBrowsable(EditorBrowsableState.Never)] 
        public virtual void ResetDropDownButtonWidth() {
            DropDownButtonWidth = DefaultDropDownButtonWidth;
        }
 
        /// 
        /// Summary of SetDropDownBounds. 
        ///  
        private void SetDropDownButtonBounds(Rectangle rect) {
            dropDownButtonBounds = rect; 
        }
        /// 
        /// Determines if the  property needs to be persisted.
        ///  
        [EditorBrowsable(EditorBrowsableState.Never)]
        internal virtual bool ShouldSerializeDropDownButtonWidth() { 
            return  (DropDownButtonWidth != DefaultDropDownButtonWidth); 
        }
 
        /// 
        ///  This class represents the item to the left of the dropdown [ A |v]  (e.g the "A")
        ///  It exists so that we can use our existing methods for text and image layout
        ///  and have a place to stick certain state information like pushed and selected 
        ///  Note since this is NOT an actual item hosted on the Winbar - it wont get things
        ///  like MouseOver, wont be laid out by the ToolStrip, etc etc.  This is purely internal 
        ///  convenience. 
        /// 
        private class ToolStripSplitButtonButton : ToolStripButton { 

            private ToolStripSplitButton owner = null;

            public ToolStripSplitButtonButton(ToolStripSplitButton owner) { 
                   this.owner = owner;
            } 
 
            public override bool Enabled {
                get { 
                    return owner.Enabled;
                }
                set {
                    // do nothing 
                }
            } 
 

            public override ToolStripItemDisplayStyle DisplayStyle { 
                get {
                    return owner.DisplayStyle;
                }
                set { 
                    // do nothing
                } 
            } 

            public override Padding Padding { 
                get {
                    return this.owner.Padding;
                }
                set { 
                    // do nothing
                } 
            } 

 
            public override ToolStripTextDirection TextDirection {
                get {
                    return owner.TextDirection;
                } 
            }
 
 
            public override Image Image {
                get { 
                    if ((owner.DisplayStyle & ToolStripItemDisplayStyle.Image) == ToolStripItemDisplayStyle.Image) {
                        return owner.Image;
                    }
                    else { 
                        return null;
                    } 
                } 
                set {
                    // do nothing 
                }
            }

            public override bool Selected { 
                get {
 
                    if (owner != null) { 
                        return owner.Selected;
                    } 
                    return base.Selected;
                }
            }
 
            public override string Text {
                get { 
                    if ((owner.DisplayStyle & ToolStripItemDisplayStyle.Text) == ToolStripItemDisplayStyle.Text) { 
                        return owner.Text;
                    } 
                    else {
                        return null;
                    }
                } 
                set {
                    // do nothing 
                } 
            }
 
        }

        /// 
        ///  This class performs internal layout for the "split button button" portion of a split button. 
        ///  Its main job is to make sure the inner button has the same parent as the split button, so
        ///  that layout can be performed using the correct graphics context. 
        ///  
        private class ToolStripSplitButtonButtonLayout : ToolStripItemInternalLayout {
 
            ToolStripSplitButton owner;

            public ToolStripSplitButtonButtonLayout(ToolStripSplitButton owner) : base(owner.SplitButtonButton) {
                this.owner = owner; 
            }
 
            protected override ToolStripItem Owner { 
                get { return owner; }
            } 

            protected override ToolStrip ParentInternal {
                get {
                    return owner.ParentInternal; 
                }
            } 
            public override Rectangle ImageRectangle { 
                get {
                    Rectangle imageRect = base.ImageRectangle; 
                    // translate to ToolStripItem coordinates
                    imageRect.Offset(owner.SplitButtonButton.Bounds.Location);
                    return imageRect;
                } 
            }
 
            public override Rectangle TextRectangle { 
                get {
                    Rectangle textRect = base.TextRectangle; 
                    // translate to ToolStripItem coordinates
                    textRect.Offset(owner.SplitButtonButton.Bounds.Location);
                    return textRect;
                } 
            }
        } 
 
        /// 
        [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")] 
        public class ToolStripSplitButtonAccessibleObject : ToolStripItem.ToolStripItemAccessibleObject {
            private ToolStripSplitButton owner;

            public ToolStripSplitButtonAccessibleObject(ToolStripSplitButton item) : base(item) { 
                owner = item;
            } 
 
            /// 
            [SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)] 
            public override void DoDefaultAction() {
                owner.PerformButtonClick();
            }
 
        }
 
 
    }
 
}


 

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK