// ********************************* // Message from Original Author: // // 2008 Jose Menendez Poo // Please give me credit if you use this code. It's all I ask. // Contact me for more info: menendezpoo@gmail.com // ********************************* // // Original project from http://ribbon.codeplex.com/ // Continue to support and maintain by http://officeribbon.codeplex.com/ using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Drawing.Drawing2D; namespace System.Windows.Forms { public class RibbonOrbDropDown : RibbonPopup { #region const private const bool DefaultAutoSizeContentButtons = true; private const int DefaultContentButtonsMinWidth = 150; private const int DefaultContentRecentItemsMinWidth = 150; #endregion #region Fields internal RibbonOrbMenuItem LastPoppedMenuItem; private Rectangle designerSelectedBounds; private readonly int glyphGap = 3; private Padding _contentMargin; private DateTime OpenedTime; //Steve - capture time popup was shown private string _recentItemsCaption; //private GlobalHook _keyboardHook; private int _contentButtonsWidth = DefaultContentButtonsMinWidth; #endregion #region Ctor internal RibbonOrbDropDown(Ribbon ribbon) { DoubleBuffered = true; Ribbon = ribbon; MenuItems = new RibbonOrbMenuItemCollection(); RecentItems = new RibbonOrbRecentItemCollection(); OptionItems = new RibbonOrbOptionButtonCollection(); MenuItems.SetOwner(Ribbon); RecentItems.SetOwner(Ribbon); OptionItems.SetOwner(Ribbon); OptionItemsPadding = 6; Size = new Size(527, 447); BorderRoundness = 8; //if (!(Site != null && Site.DesignMode)) //{ // _keyboardHook = new GlobalHook(GlobalHook.HookTypes.Keyboard); // _keyboardHook.KeyUp += new KeyEventHandler(_keyboardHook_KeyUp); //} } /// /// Clean up any resources being used. /// /// true if managed resources should be disposed; otherwise, false. protected override void Dispose(bool disposing) { base.Dispose(disposing); if (Sensor != null && !Sensor.Disposed) { Sensor.Dispose(); } //if (_keyboardHook != null) //{ // _keyboardHook.Dispose(); //} } //Finalize is called by base class "System.ComponentModel.Component" #endregion #region Props /// /// Gets all items involved in the dropdown /// internal List AllItems { get { List lst = new List(); lst.AddRange(MenuItems); lst.AddRange(RecentItems); lst.AddRange(OptionItems); return lst; } } /// /// Gets the margin of the content bounds /// [Browsable(false)] public Padding ContentMargin { get { if (_contentMargin.Size.IsEmpty) { _contentMargin = new Padding(6, 17, 6, 29); } return _contentMargin; } } /// /// Gets the bounds of the content (where menu buttons are) /// [Browsable(false)] public Rectangle ContentBounds => Rectangle.FromLTRB(ContentMargin.Left, ContentMargin.Top, ClientRectangle.Right - ContentMargin.Right, ClientRectangle.Bottom - ContentMargin.Bottom); /// /// Gets the bounds of the content part that contains the buttons on the left /// [Browsable(false)] public Rectangle ContentButtonsBounds { get { Rectangle r = ContentBounds; r.Width = _contentButtonsWidth; if (Ribbon.RightToLeft == RightToLeft.Yes) r.X = ContentBounds.Right - _contentButtonsWidth; return r; } } /// /// Gets or sets the minimum width for the content buttons. /// [DefaultValue(DefaultContentButtonsMinWidth)] public int ContentButtonsMinWidth { get; set; } = DefaultContentButtonsMinWidth; /// /// Gets the bounds fo the content part that contains the recent-item list /// [Browsable(false)] public Rectangle ContentRecentItemsBounds { get { Rectangle r = ContentBounds; r.Width -= _contentButtonsWidth; //Steve - Recent Items Caption r.Height -= ContentRecentItemsCaptionBounds.Height; r.Y += ContentRecentItemsCaptionBounds.Height; if (Ribbon.RightToLeft == RightToLeft.No) r.X += _contentButtonsWidth; return r; } } /// /// Gets the bounds of the caption area on the content part of the recent-item list /// [Browsable(false)] public Rectangle ContentRecentItemsCaptionBounds { get { if (RecentItemsCaption != null) { //Lets measure the height of the text so we take into account the font and its size SizeF cs; using (Graphics g = CreateGraphics()) { cs = g.MeasureString(RecentItemsCaption, Ribbon.RibbonTabFont); } Rectangle r = ContentBounds; r.Width -= _contentButtonsWidth; r.Height = Convert.ToInt32(cs.Height) + Ribbon.ItemMargin.Top + Ribbon.ItemMargin.Bottom; //padding r.Height += RecentItemsCaptionLineSpacing; //Spacing for the divider line if (Ribbon.RightToLeft == RightToLeft.No) r.X += _contentButtonsWidth; return r; } return Rectangle.Empty; } } /// /// Gets the bounds of the caption area on the content part of the recent-item list /// [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public int RecentItemsCaptionLineSpacing { get; } = 8; /// /// Gets or sets the minimum width for the recent items. /// [DefaultValue(DefaultContentRecentItemsMinWidth)] public int ContentRecentItemsMinWidth { get; set; } = DefaultContentRecentItemsMinWidth; /// /// Gets if currently on design mode /// private bool RibbonInDesignMode => RibbonDesigner.Current != null; /// /// Gets the collection of items shown in the menu area /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] public RibbonOrbMenuItemCollection MenuItems { get; } /// /// Gets the collection of items shown in the options area (bottom) /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] public RibbonOrbOptionButtonCollection OptionItems { get; } [DefaultValue(6)] [Description("Spacing between option buttons (those on the bottom)")] public int OptionItemsPadding { get; set; } /// /// Gets the collection of items shown in the recent items area /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] public RibbonOrbRecentItemCollection RecentItems { get; } /// /// Gets or Sets the caption for the Recent Items area /// [DefaultValue(null)] public string RecentItemsCaption { get => _recentItemsCaption; set { _recentItemsCaption = value; Invalidate(); } } /// /// Gets the ribbon that owns this dropdown /// [Browsable(false)] public Ribbon Ribbon { get; } /// /// Gets the sensor of the dropdown /// [Browsable(false)] public RibbonMouseSensor Sensor { get; private set; } /// /// Gets the bounds of the glyph /// internal Rectangle ButtonsGlyphBounds { get { Size s = new Size(50, 18); Rectangle rf = ContentButtonsBounds; Rectangle r = new Rectangle(rf.Left + (rf.Width - s.Width * 2) / 2, rf.Top + glyphGap, s.Width, s.Height); if (MenuItems.Count > 0) { r.Y = MenuItems[MenuItems.Count - 1].Bounds.Bottom + glyphGap; } return r; } } /// /// Gets the bounds of the glyph /// internal Rectangle ButtonsSeparatorGlyphBounds { get { Size s = new Size(18, 18); Rectangle r = ButtonsGlyphBounds; r.X = r.Right + glyphGap; return r; } } /// /// Gets the bounds of the recent items add glyph /// internal Rectangle RecentGlyphBounds { get { Size s = new Size(50, 18); Rectangle rf = ContentRecentItemsBounds; Rectangle r = new Rectangle(rf.Left + glyphGap, rf.Top + glyphGap, s.Width, s.Height); if (RecentItems.Count > 0) { r.Y = RecentItems[RecentItems.Count - 1].Bounds.Bottom + glyphGap; } return r; } } /// /// Gets the bounds of the option items add glyph /// internal Rectangle OptionGlyphBounds { get { Size s = new Size(50, 18); Rectangle rf = ContentBounds; Rectangle r = new Rectangle(rf.Right - s.Width, rf.Bottom + glyphGap, s.Width, s.Height); if (OptionItems.Count > 0) { r.X = OptionItems[OptionItems.Count - 1].Bounds.Left - s.Width - glyphGap; } return r; } } [DefaultValue(DefaultAutoSizeContentButtons)] public bool AutoSizeContentButtons { get; set; } = DefaultAutoSizeContentButtons; #endregion #region Methods internal void HandleDesignerItemRemoved(RibbonItem item) { if (MenuItems.Contains(item)) { MenuItems.Remove(item); } else if (RecentItems.Contains(item)) { RecentItems.Remove(item); } else if (OptionItems.Contains(item)) { OptionItems.Remove(item); } OnRegionsChanged(); } /// /// Gets the height that a separator should be on the DropDown /// /// /// private int SeparatorHeight(RibbonSeparator s) { if (!string.IsNullOrEmpty(s.Text)) { return 20; } return 3; } /// /// Updates the regions and bounds of items /// private void UpdateRegions() { int curtop = 0; int curright = 0; int menuItemHeight = 44; int recentHeight = 22; int mbuttons = 1; //margin int mrecent = 1; //margin int buttonsHeight = 0; int recentsHeight = 0; if (AutoSizeContentButtons) { #region important to do the item max width check before the ContentBounds and other stuff is used (internal Property stuff) int itemMaxWidth = 0; using (Graphics g = CreateGraphics()) { foreach (RibbonItem item in MenuItems) { int width = item.MeasureSize(this, new RibbonElementMeasureSizeEventArgs(g, RibbonElementSizeMode.DropDown)).Width; if (width > itemMaxWidth) itemMaxWidth = width; } } itemMaxWidth = Math.Min(itemMaxWidth, ContentBounds.Width - ContentRecentItemsMinWidth); itemMaxWidth = Math.Max(itemMaxWidth, ContentButtonsMinWidth); _contentButtonsWidth = itemMaxWidth; #endregion } Rectangle rcontent = ContentBounds; Rectangle rbuttons = ContentButtonsBounds; Rectangle rrecent = ContentRecentItemsBounds; foreach (RibbonItem item in AllItems) { item.SetSizeMode(RibbonElementSizeMode.DropDown); item.SetCanvas(this); } #region Menu Items curtop = rcontent.Top + 1; foreach (RibbonItem item in MenuItems) { Rectangle ritem = new Rectangle(rbuttons.Left + mbuttons, curtop, rbuttons.Width - mbuttons * 2, menuItemHeight); if (item is RibbonSeparator) ritem.Height = SeparatorHeight(item as RibbonSeparator); item.SetBounds(ritem); curtop += ritem.Height; } buttonsHeight = curtop - rcontent.Top + 1; #endregion #region Recent List //curtop = rbuttons.Top; //Steve - for recent documents curtop = rrecent.Top; //Steve - for recent documents foreach (RibbonItem item in RecentItems) { Rectangle ritem = new Rectangle(rrecent.Left + mrecent, curtop, rrecent.Width - mrecent * 2, recentHeight); if (item is RibbonSeparator) ritem.Height = SeparatorHeight(item as RibbonSeparator); item.SetBounds(ritem); curtop += ritem.Height; } recentsHeight = curtop - rbuttons.Top; #endregion #region Set size int actualHeight = Math.Max(buttonsHeight, recentsHeight); if (RibbonDesigner.Current != null) { actualHeight += ButtonsGlyphBounds.Height + glyphGap * 2; } Height = actualHeight + ContentMargin.Vertical; rcontent = ContentBounds; #endregion #region Option buttons curright = ClientSize.Width - ContentMargin.Right; using (Graphics g = CreateGraphics()) { foreach (RibbonItem item in OptionItems) { Size s = item.MeasureSize(this, new RibbonElementMeasureSizeEventArgs(g, RibbonElementSizeMode.DropDown)); curtop = rcontent.Bottom + (ContentMargin.Bottom - s.Height) / 2; item.SetBounds(new Rectangle(new Point(curright - s.Width, curtop), s)); curright = item.Bounds.Left - OptionItemsPadding; } } #endregion } /// /// Refreshes the sensor /// private void UpdateSensor() { if (Sensor != null && !Sensor.Disposed) { Sensor.Dispose(); } Sensor = new RibbonMouseSensor(this, Ribbon, AllItems); } /// /// Updates all areas and bounds of items /// internal void OnRegionsChanged() { UpdateRegions(); UpdateSensor(); UpdateDesignerSelectedBounds(); Invalidate(); } /// /// Selects the specified item on the designer /// /// internal void SelectOnDesigner(RibbonItem item) { if (RibbonDesigner.Current != null) { RibbonDesigner.Current.SelectedElement = item; UpdateDesignerSelectedBounds(); Invalidate(); } } /// /// Updates the selection bounds on the designer /// internal void UpdateDesignerSelectedBounds() { designerSelectedBounds = Rectangle.Empty; if (RibbonInDesignMode) { RibbonItem item = RibbonDesigner.Current.SelectedElement as RibbonItem; if (item != null && AllItems.Contains(item)) { designerSelectedBounds = item.Bounds; } } } protected override void OnMouseDown(MouseEventArgs e) { base.OnMouseDown(e); if (RibbonInDesignMode) { #region DesignMode clicks if (ContentBounds.Contains(e.Location)) { if (ContentButtonsBounds.Contains(e.Location)) { foreach (RibbonItem item in MenuItems) { if (item.Bounds.Contains(e.Location)) { SelectOnDesigner(item); break; } } } else if (ContentRecentItemsBounds.Contains(e.Location)) { foreach (RibbonItem item in RecentItems) { if (item.Bounds.Contains(e.Location)) { SelectOnDesigner(item); break; } } } } if (ButtonsGlyphBounds.Contains(e.Location)) { RibbonDesigner.Current.CreateOrbMenuItem(typeof(RibbonOrbMenuItem)); } else if (ButtonsSeparatorGlyphBounds.Contains(e.Location)) { RibbonDesigner.Current.CreateOrbMenuItem(typeof(RibbonSeparator)); } else if (RecentGlyphBounds.Contains(e.Location)) { RibbonDesigner.Current.CreateOrbRecentItem(typeof(RibbonOrbRecentItem)); } else if (OptionGlyphBounds.Contains(e.Location)) { RibbonDesigner.Current.CreateOrbOptionItem(typeof(RibbonOrbOptionButton)); } else { foreach (RibbonItem item in OptionItems) { if (item.Bounds.Contains(e.Location)) { SelectOnDesigner(item); break; } } } #endregion } } protected override void OnOpening(CancelEventArgs e) { base.OnOpening(e); UpdateRegions(); } protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); Ribbon.Renderer.OnRenderOrbDropDownBackground( new RibbonOrbDropDownEventArgs(Ribbon, this, e.Graphics, e.ClipRectangle)); foreach (RibbonItem item in AllItems) { item.OnPaint(this, new RibbonElementPaintEventArgs(e.ClipRectangle, e.Graphics, RibbonElementSizeMode.DropDown)); } if (RibbonInDesignMode) { using (SolidBrush b = new SolidBrush(Color.FromArgb(50, Color.Blue))) { e.Graphics.FillRectangle(b, ButtonsGlyphBounds); e.Graphics.FillRectangle(b, RecentGlyphBounds); e.Graphics.FillRectangle(b, OptionGlyphBounds); e.Graphics.FillRectangle(b, ButtonsSeparatorGlyphBounds); } using (StringFormat sf = StringFormatFactory.Center(StringTrimming.None)) { e.Graphics.DrawString("+", Font, Brushes.White, ButtonsGlyphBounds, sf); e.Graphics.DrawString("+", Font, Brushes.White, RecentGlyphBounds, sf); e.Graphics.DrawString("+", Font, Brushes.White, OptionGlyphBounds, sf); e.Graphics.DrawString("---", Font, Brushes.White, ButtonsSeparatorGlyphBounds, sf); } using (Pen p = new Pen(Color.Black)) { p.DashStyle = DashStyle.Dot; e.Graphics.DrawRectangle(p, designerSelectedBounds); } //e.Graphics.DrawString("Press ESC to Hide", Font, Brushes.Black, Width - 100f, 2f); } } protected override void OnClosed(EventArgs e) { Ribbon.OrbPressed = false; Ribbon.OrbSelected = false; LastPoppedMenuItem = null; foreach (RibbonItem item in AllItems) { item.SetSelected(false); item.SetPressed(false); } base.OnClosed(e); } protected override void OnShowed(EventArgs e) { base.OnShowed(e); OpenedTime = DateTime.Now; UpdateSensor(); } protected override void OnMouseClick(MouseEventArgs e) { if (Ribbon.RectangleToScreen(Ribbon.OrbBounds).Contains(PointToScreen(e.Location))) { Ribbon.OnOrbClicked(EventArgs.Empty); //Steve - if click time is within the double click time after the drop down was shown, then this is a double click if (DateTime.Compare(DateTime.Now, OpenedTime.AddMilliseconds(SystemInformation.DoubleClickTime)) < 0) Ribbon.OnOrbDoubleClicked(EventArgs.Empty); } base.OnMouseClick(e); } protected override void OnMouseDoubleClick(MouseEventArgs e) { base.OnMouseDoubleClick(e); if (Ribbon.RectangleToScreen(Ribbon.OrbBounds).Contains(PointToScreen(e.Location))) { Ribbon.OnOrbDoubleClicked(EventArgs.Empty); } } private void _keyboardHook_KeyUp(object sender, KeyEventArgs e) { //base.OnKeyUp(e); if (e.KeyCode == Keys.Down) { RibbonItem NextItem = null; RibbonItem SelectedItem = null; foreach (RibbonItem itm in MenuItems) { if (itm.Selected) { SelectedItem = itm; break; } } if (SelectedItem != null) { //get the next item in the chain int Index = MenuItems.IndexOf(SelectedItem); NextItem = GetNextSelectableMenuItem(Index + 1); } else { //nothing found so lets search through the recent buttons foreach (RibbonItem itm in RecentItems) { if (itm.Selected) { SelectedItem = itm; itm.SetSelected(false); itm.RedrawItem(); break; } } if (SelectedItem != null) { //get the next item in the chain int Index = RecentItems.IndexOf(SelectedItem); NextItem = GetNextSelectableRecentItem(Index + 1); } else { //nothing found so lets search through the option buttons foreach (RibbonItem itm in OptionItems) { if (itm.Selected) { SelectedItem = itm; itm.SetSelected(false); itm.RedrawItem(); break; } } if (SelectedItem != null) { //get the next item in the chain int Index = OptionItems.IndexOf(SelectedItem); NextItem = GetNextSelectableOptionItem(Index + 1); } } } //last check to make sure we found a selected item if (SelectedItem == null) { //we should have the right item by now so lets select it NextItem = GetNextSelectableMenuItem(0); if (NextItem != null) { NextItem.SetSelected(true); NextItem.RedrawItem(); } } else { SelectedItem.SetSelected(false); SelectedItem.RedrawItem(); NextItem.SetSelected(true); NextItem.RedrawItem(); } //_sensor.SelectedItem = NextItem; //_sensor.HittedItem = NextItem; } else if (e.KeyCode == Keys.Up) { } } private RibbonItem GetNextSelectableMenuItem(int StartIndex) { for (int idx = StartIndex; idx < MenuItems.Count; idx++) { RibbonButton btn = MenuItems[idx] as RibbonButton; if (btn != null) return btn; } //nothing found so lets move on to the recent items RibbonItem NextItem = GetNextSelectableRecentItem(0); if (NextItem == null) { //nothing found so lets try the option items NextItem = GetNextSelectableOptionItem(0); if (NextItem == null) { //nothing again so go back to the top of the menu items NextItem = GetNextSelectableMenuItem(0); } } return NextItem; } private RibbonItem GetNextSelectableRecentItem(int StartIndex) { for (int idx = StartIndex; idx < RecentItems.Count; idx++) { RibbonButton btn = RecentItems[idx] as RibbonButton; if (btn != null) return btn; } //nothing found so lets move on to the option items RibbonItem NextItem = GetNextSelectableOptionItem(0); if (NextItem == null) { //nothing found so lets try the menu items NextItem = GetNextSelectableMenuItem(0); if (NextItem == null) { //nothing again so go back to the top of the recent items NextItem = GetNextSelectableRecentItem(0); } } return NextItem; } private RibbonItem GetNextSelectableOptionItem(int StartIndex) { for (int idx = StartIndex; idx < OptionItems.Count; idx++) { RibbonButton btn = OptionItems[idx] as RibbonButton; if (btn != null) return btn; } //nothing found so lets move on to the menu items RibbonItem NextItem = GetNextSelectableMenuItem(0); if (NextItem == null) { //nothing found so lets try the recent items NextItem = GetNextSelectableRecentItem(0); if (NextItem == null) { //nothing again so go back to the top of the option items NextItem = GetNextSelectableOptionItem(0); } } return NextItem; } #endregion } }