///
/// Copyright © 2003-2008 JetBrains s.r.o.
/// You may distribute under the terms of the GNU General Public License, as published by the Free Software Foundation, version 2 (see License.txt in the repository root folder).
///
using System;
using System.Collections;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Reflection;
using System.Windows.Forms;
using JetBrains.DataStructures;
using JetBrains.Omea.GUIControls.CommandBar;
using JetBrains.Omea.OpenAPI;
using JetBrains.UI.Components.ImageListButton;
namespace JetBrains.Omea.GUIControls
{
///
/// A bar that displays a sequence of controls (as many as fit in the available
/// horizontal space) and a chevron button under which the controls which do not fit
/// are hidden.
///
public class ChevronBar : UserControl, ICommandBar
{
#region Data
private IContainer components;
///
/// The chevron button that draws the context menu upon a click.
/// Visisble only if there are hidden or unfit controls.
///
private ImageListButton _btnChevron;
///
/// If this flag is raisen, then some shit has happened. Yuck.
///
private bool _isLayoutBroken = false;
///
/// All the normal controls of the chevron bar (ones that potentially can be visible on the toolbar).
/// Defines the order of controls on the toolbar.
/// The unfit controls are not excluded from this list.
/// The full set of toolbar's controls can be gotten by taking and .
///
private ArrayList _controls = new ArrayList();
///
/// Controls that could have been visible on the bar if there were a little bit more space.
/// Note that these controls are not excluded from the list.
/// Should be displayed under the chevron menu, as well as the natively-hidden controls.
/// This set is recalculated each time by the function.
///
private HashSet _hashDropped = new HashSet();
///
/// Controls that are never allowed to come visible into the bar and permanenly stick to the chevron's drop down list.
/// Have nothing to do with the array.
/// The full set of toolbar's controls can be gotten by taking and .
///
private ArrayList _hiddenControls = new ArrayList();
///
/// A delegate that allows to assign some text to the chevron menu item that
/// is different from its 's property, which is used by default.
///
private GetChevronMenuTextDelegate _getChevronMenuText;
///
/// Separator between the hidden and unfit items in the chevron's popup menu.
///
private bool _separateHiddenControls = true;
///
/// Left margin of the toolbar (spacing between the control's left side and the first child control).
///
private int _nLeftMargin = 0;
///
/// Right margin of the toolbar (spacing between the control's right side and the last child control, or the chevron, if visible).
///
private int _nRightMargin = 0;
///
/// Context menu of the chevron.
///
private ContextMenu _menuOnChevron;
///
/// Gap between the adjacent controls. Does not apply to the gap between the first/last control and this control's edges.
///
private int _nGap = 4;
///
/// Tells whether the toolbar may be sized above the total maximum size of its controls and spacings.
///
protected bool _bAllowOversizing = false;
///
/// The command bar site of this control.
///
private ICommandBarSite _site;
#endregion
#region Construction
public ChevronBar()
{
// This call is required by the Windows.Forms Form Designer.
InitializeComponentSelf();
}
///
/// Clean up any resources being used.
///
protected override void Dispose( bool disposing )
{
if( disposing )
{
if( components != null )
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#endregion
#region Visual Init
private void InitializeComponentSelf()
{
components = new Container();
using( new LayoutSuspender( this ) )
{
//
// _chevronToolbar
//
_btnChevron = new ImageListButton();
_btnChevron.Location = new Point( 124, 2 );
_btnChevron.Name = "_btnChevron";
_btnChevron.TabStop = false;
_btnChevron.Visible = false;
_btnChevron.Click += new EventHandler( OnChevronClick );
_btnChevron.AddIcon(LoadChevronIcon( false ), ImageListButton.ButtonState.Normal);
_btnChevron.AddIcon(LoadChevronIcon( true ), ImageListButton.ButtonState.Hot);
_btnChevron.BackColor = BackColor;
//
// _menuOnChevron
//
_menuOnChevron = new ContextMenu();
//
// ChevronBar
//
Controls.Add( _btnChevron );
Name = "ChevronBar";
Size = OptimalSize;
SetStyle( ControlStyles.AllPaintingInWmPaint
| ControlStyles.CacheText
| ControlStyles.ContainerControl
| ControlStyles.UserPaint
| ControlStyles.Opaque
, true );
SetStyle( ControlStyles.DoubleBuffer
| ControlStyles.ResizeRedraw
| ControlStyles.Selectable
, false );
}
}
#endregion
#region Events
#region ChevronMenuItemClick Event
///
/// Fires when the chevron
///
public event ChevronMenuItemClickEventHandler ChevronMenuItemClick;
///
/// Delegate type for the event.
///
public delegate void ChevronMenuItemClickEventHandler( object sender, ChevronMenuItemClickEventArgs args );
///
/// Arguments for the the event.
///
public class ChevronMenuItemClickEventArgs
{
private Control _clicked;
public ChevronMenuItemClickEventArgs( Control clicked )
{
_clicked = clicked;
}
///
/// A control that has been clicked within the Chevron menu.
///
public Control ClickedControl
{
get { return _clicked; }
}
}
///
/// Fires the event.
///
protected void FireChevronMenuItemClick( ChevronMenuItemClickEventArgs args )
{
try
{
if( ChevronMenuItemClick != null )
ChevronMenuItemClick( this, args );
}
catch( Exception ex )
{
Core.ReportException( ex, ExceptionReportFlags.AttachLog );
}
}
///
/// A delegate for the function.
///
internal delegate void FireChevronMenuItemClickDelegate( ChevronMenuItemClickEventArgs args );
#endregion
#endregion
#region Operations
///
/// Removes all the controls from the bar.
/// Warning! The controls get disposed when they are removed!
///
public void ClearControls()
{
using( new LayoutSuspender( this ) )
{
foreach( Control control in _controls )
Controls.Remove( control );
_controls.Clear();
_hiddenControls.Clear();
}
// Note: there's no use of clearing the _hashUnfit, it will be updated by the panding Layout request
_isLayoutBroken = false; // The layout is broken no more if there are no more controls
if( _site != null )
_site.PerformLayout( this );
}
///
/// Adds a control to the bar.
///
public void AddControl( Control ctl )
{
_controls.Add( ctl );
Controls.Add( ctl );
if( _site != null )
_site.PerformLayout( this );
}
///
/// Adds a control to the list of controls which are only visible when the chevron menu is dropped down.
///
public void AddHiddenControl( Control ctl )
{
_hiddenControls.Add( ctl );
if( _site != null )
_site.PerformLayout( this );
PerformLayout(); // This is needed in case the chevron gets visible
}
///
/// Retrieves the chevron's icon for either of its states.
///
/// True for the hot (hovered by mouse) chevron state, and False for the normal state.
///
public static Icon LoadChevronIcon( bool hot )
{
return new Icon( Assembly.GetExecutingAssembly().GetManifestResourceStream( String.Format( "GUIControls.Icons.Chevron.{0}.ico", (hot ? "Hot" : "Normal") ) ) );
}
#endregion
#region Implementation
///
/// When the control is resized, if there is not enough space to show all buttons or if there is space to show the next button, recreates the bar.
///
protected override void OnLayout( LayoutEventArgs levent )
{
if( _isLayoutBroken )
return;
try
{
// Start with an assumption that all the controls will fit
int nWidth = OptimalSize.Width; // MaxSize may be euqal to +inf
Size sizeMin = MinSize;
_hashDropped.Clear();
Rectangle client = ClientRectangle; // The client rectangle within which the controls should be layouted
// If there's not enough space for an adequate layouting, hide all the controls and do nothing to the return arrays
// Also, cancel layouting if not initialized yet
if( (client.Width <= 0) || (client.Height <= 0) || (client.Width < sizeMin.Width) || (client.Height < sizeMin.Height) )
{
foreach( Control control in Controls )
control.Visible = false;
return; // Layout failed
}
// Indicates presence of the chevron button in the current vision of the layout.
// As we start from the MaxSize version, we add a chevron only if there are hidden controls to be shown under it.
// In this case its width (along with gap) is already encountered in the MaxSize.
bool bChevronPresent = _hiddenControls.Count != 0;
// Start dropping off the controls that do not fit, from right to the left
int nDropPosition = _controls.Count - 1; // Points to the control that should be dropped next
for(; (nWidth > client.Width) && (nDropPosition >= 0); nDropPosition-- )
{
// Check if we have to add a chevron (with one more separator) — this works on the first step only
if( !bChevronPresent )
{
nWidth += _btnChevron.Width; // Chevron
nWidth += _nGap; // Separator before the chevron
bChevronPresent = true;
}
// Exclude the rightmost not excluded yet control, as well as one separator along with it
Control controlDrop = (Control) _controls[ nDropPosition ];
nWidth -= controlDrop.Width;
nWidth -= _nGap;
_hashDropped.Add( controlDrop );
}
// Could not fit … not enough controls to drop
if( nDropPosition < 0 )
Trace.WriteLine( "Not enought space to layout the controls properly.", "[CB]" );
// Apply the layout
int nCurPos = client.Left + _nLeftMargin;
// Add the fitting controls
foreach( Control control in _controls )
{
// Hide the dropped controls
if( _hashDropped.Contains( control ) )
{
control.Visible = false;
continue;
}
////
// If the control is not dropped, show it
// Add the control itself
control.Location = new Point( nCurPos, client.Top + (client.Height - control.Height) / 2 ); // Hor-layout, ver-center
control.Visible = true;
nCurPos += control.Width;
// Gap after the control
nCurPos += _nGap;
}
// Add the chevron (if it's been chosen to be visible)
if( bChevronPresent )
{
// Rihgt-align the chevron within the bar's bounds
_btnChevron.Location = new Point( client.Right - _nRightMargin - _btnChevron.Width, client.Top + (client.Height - _btnChevron.Height) / 2 );
_btnChevron.Visible = true;
}
else
_btnChevron.Visible = false;
}
catch( Exception e )
{
Core.ReportBackgroundException( e );
_isLayoutBroken = true;
}
}
protected override void OnPaint(PaintEventArgs e)
{
if( BackColor != Color.Transparent )
{
using( Brush brush = new SolidBrush( BackColor ) )
e.Graphics.FillRectangle( brush, ClientRectangle );
}
}
///
/// Shows the popup menu with controls that didn't fit into the chevron bar and
/// controls which were explicitly hidden.
///
private void OnChevronClick( object sender, EventArgs e )
{
_menuOnChevron.MenuItems.Clear();
FillChevronContextMenu( _menuOnChevron ); // Extracted for the tests
// Show
_menuOnChevron.Show( this, new Point( _btnChevron.Right, _btnChevron.Bottom ) );
}
///
/// Fills in the chevron context menu.
/// Extracted for the sake of the tests.
///
public void FillChevronContextMenu( ContextMenu menu ) // public for unit tests
{
bool bSeparator = false; // Indicates whether to add a separator between the menu groups
// Add the unfit, but visible-wannabe controls
foreach( Control control in _controls )
{
if( _hashDropped.Contains( control ) )
{
menu.MenuItems.Add( new ChevronMenuItem( control, _getChevronMenuText, new FireChevronMenuItemClickDelegate( FireChevronMenuItemClick ) ) );
bSeparator = bSeparator | _separateHiddenControls; // Needed, if allowed
}
}
// Add the forever-concealed controls
foreach( Control control in _hiddenControls )
{
// Add the separator if both groups are non-empty
if( bSeparator )
{
bSeparator = false;
menu.MenuItems.Add( new MenuItem( "-" ) );
}
menu.MenuItems.Add( new ChevronMenuItem( control, _getChevronMenuText, new FireChevronMenuItemClickDelegate( FireChevronMenuItemClick ) ) );
}
}
#endregion
#region ICommandBar Members
public void SetSite( ICommandBarSite site )
{
_site = site;
}
public Size MinSize
{
get
{
// Is the chevron mandatory in the minimum toolbar size (if there's a control of any kind)?
if( (_controls.Count != 0) || (_hiddenControls.Count != 0) )
return new Size( _btnChevron.Size.Width + _nLeftMargin + _nRightMargin, _btnChevron.Size.Height );
else
return new Size( _nLeftMargin + _nRightMargin, 0 );
}
}
///
/// Size that is needed to house all the controls.
/// Width is the sum of all the controls' widths, plus the chevron if there are mandatory hidden controls under it.
/// Height is the maximum control height encountered.
///
public Size OptimalSize
{
get
{
// All the controls to be measured
ArrayList controlsAll = new ArrayList( _controls );
// Is the chevron mandatory?
if( _hiddenControls.Count > 0 )
controlsAll.Add( _btnChevron ); // This is just a chevron button, not the toolbar :)
// Add all the controls
int nTotalWidth = 0;
int nMaxHeight = 0;
ICommandBar cmdbar = null;
Size size;
foreach( Control control in controlsAll )
{
//cmdbar = control as ICommandBar;
size = cmdbar != null ? cmdbar.OptimalSize : control.Size; // If a control knows its optimal size, use it
nTotalWidth += size.Width;
nMaxHeight = nMaxHeight > size.Height ? nMaxHeight : size.Height;
}
// Add the gaps between the controls
nTotalWidth += _nGap;
// Add the left-right margins
nTotalWidth += _nLeftMargin + _nRightMargin;
return new Size( nTotalWidth, nMaxHeight );
}
}
public Size MaxSize
{
get
{
// If oversizing is allowed, report no limit on the toolbar's size; otherwise, calculate it from the sizes of the controls
if( _bAllowOversizing )
return new Size( int.MaxValue, int.MaxValue );
else
{
// All the controls to be measured
ArrayList controlsAll = new ArrayList( _controls );
// Is the chevron mandatory?
if( _hiddenControls.Count > 0 )
controlsAll.Add( _btnChevron ); // This is just a chevron button, not the toolbar :)
// Add all the controls
int nTotalWidth = 0;
int nMaxHeight = 0;
ICommandBar cmdbar = null;
Size size;
foreach( Control control in controlsAll )
{
//cmdbar = control as ICommandBar;
size = cmdbar != null ? cmdbar.MaxSize : control.Size; // If a control knows its optimal size, use it
nTotalWidth += size.Width;
nMaxHeight = nMaxHeight > size.Height ? nMaxHeight : size.Height;
}
// Add the gaps between the controls
nTotalWidth += _nGap * (controlsAll.Count - 1);
// Add the left-right margins
nTotalWidth += _nLeftMargin + _nRightMargin;
return new Size( nTotalWidth, nMaxHeight );
}
}
}
public Size Integral
{
get { return new Size( 1, 1 ); }
}
#endregion
#region Attributes
///
/// Tells whether the chevron bar's chevron control should be currently visible.
/// Unlike checking the control's visiiblity state, it indicates the desired chevron state, not the current one.
///
public bool IsChevronVisible
{
get { return _hiddenControls.Count + _hashDropped.Count != 0; }
}
///
/// Gets or sets whether a separator between the hidden and unfit items in the chevron's popup menu is displayed.
///
[DefaultValue( true )]
public bool SeparateHiddenControls
{
get { return _separateHiddenControls; }
set { _separateHiddenControls = value; }
}
///
/// Gets or sets a delegate that allows to assign some text to the chevron menu item that
/// is different from its 's property, which is used by default.
/// Null is a valid value which defaults to taking the property.
///
public GetChevronMenuTextDelegate GetChevronMenuText
{
get { return _getChevronMenuText; }
set { _getChevronMenuText = value; }
}
///
/// Tells whether the toolbar may be sized above the total maximum size of its controls and spacings.
///
public bool AllowOversizing
{
get { return _bAllowOversizing; }
set
{
if( _bAllowOversizing != value )
{
_bAllowOversizing = value;
if( _site != null )
_site.PerformLayout( this );
}
}
}
///
/// Background color of the toolbar.
///
public override Color BackColor
{
get
{
return base.BackColor;
}
set
{
base.BackColor = value;
if(_btnChevron != null)
_btnChevron.BackColor = value;
}
}
#endregion
#region Class ChevronMenuItem — Represents an item of the chevron context menu.
///
/// Represents an item of the chevron context menu.
///
internal class ChevronMenuItem : MenuItem
{
///
/// Control represented by the item.
///
private Control _control;
///
/// If an item gets clicked, fires the corresponding notification event.
///
private FireChevronMenuItemClickDelegate _delegateEventFirer;
///
/// Constructs the menu item.
///
/// Control represented by the item.
/// Gets the item text (may be Null).
/// If an item gets clicked, fires the corresponding notification event.
internal ChevronMenuItem( Control control, GetChevronMenuTextDelegate delegateTextGetter, FireChevronMenuItemClickDelegate delegateEventFirer )
{
_control = control;
_delegateEventFirer = delegateEventFirer;
Text = delegateTextGetter != null ? delegateTextGetter( _control ) : _control.Text; // Retrieve the menu item text
}
protected override void OnClick( EventArgs e )
{
_delegateEventFirer( new ChevronMenuItemClickEventArgs( _control ) );
}
}
#endregion
}
public delegate string GetChevronMenuTextDelegate( Control ctl );
}