///
/// 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.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using JetBrains.Interop.WinApi;
using JetBrains.Omea.OpenAPI;
using JetBrains.UI.Interop;
using JetBrains.UI.RichText;
namespace JetBrains.Omea.GUIControls
{
///
/// A link label which draws its text through the Windows API, draws highlighting
/// on mouse over and executes the link click action only on single click.
///
public class JetLinkLabel : UserControl
{
///
/// Required designer variable.
///
private System.ComponentModel.Container components = null;
private static readonly FontCache _fontCache = new FontCache();
private static readonly Color _linkColor = Color.FromArgb( 70, 70, 211 );
private Font _underlineFont;
private bool _underline;
private bool _autoSize = false;
private bool _wordWrap;
private bool _clickableLink = true;
private bool _useMnemonic = false;
private bool _endEllipsis = false;
private string _postfixText = ""; // the text which is drawn after the link
private ContentAlignment _textAlign = ContentAlignment.TopLeft;
public JetLinkLabel()
{
// This call is required by the Windows.Forms Form Designer.
InitializeComponent();
SetStyle( ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | ControlStyles.SupportsTransparentBackColor |
ControlStyles.ResizeRedraw | ControlStyles.CacheText, true );
SetStyle( ControlStyles.StandardClick | ControlStyles.Selectable, false );
ForeColor = _linkColor;
Cursor = Cursors.Hand;
_underlineFont = new Font( Font, FontStyle.Underline );
_autoSize = true;
}
///
/// Clean up any resources being used.
///
protected override void Dispose( bool disposing )
{
if( disposing )
{
if(components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#region Component Designer generated code
///
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
///
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
}
#endregion
protected override void ScaleCore( float dx, float dy )
{
base.ScaleCore( dx, dy );
if ( _underlineFont != null )
{
_underlineFont.Dispose();
}
_underlineFont = new Font( Font, FontStyle.Underline );
if ( _autoSize )
{
Size = PreferredSize;
}
}
[DefaultValue(true)]
public bool AutoSize
{
get { return _autoSize; }
set { _autoSize = value; }
}
[DefaultValue(false)]
public bool WordWrap
{
get { return _wordWrap; }
set
{
if ( _wordWrap != value )
{
_wordWrap = value;
Invalidate();
}
}
}
[DefaultValue(true)]
public bool ClickableLink
{
get { return _clickableLink; }
set
{
_clickableLink = value;
ForeColor = _clickableLink ? _linkColor : SystemColors.ControlText;
Cursor = _clickableLink ? Cursors.Hand : Cursors.Default;
}
}
[DefaultValue(false)]
public bool UseMnemonic
{
get { return _useMnemonic; }
set
{
if ( _useMnemonic != value )
{
_useMnemonic = value;
Invalidate();
}
}
}
[DefaultValue("")]
public string PostfixText
{
get { return _postfixText; }
set
{
if ( _postfixText != value )
{
_postfixText = value;
if ( _autoSize )
{
Size = PreferredSize;
}
Invalidate();
}
}
}
[DefaultValue(ContentAlignment.TopLeft)]
public ContentAlignment TextAlign
{
get { return _textAlign; }
set
{
if ( _textAlign != value )
{
_textAlign = value;
Invalidate();
}
}
}
[DefaultValue(false)]
public bool EndEllipsis
{
get { return _endEllipsis; }
set
{
if ( _endEllipsis != value )
{
_endEllipsis = value;
Invalidate();
}
}
}
protected override void OnFontChanged( EventArgs e )
{
base.OnFontChanged( e );
_underlineFont = new Font( Font, FontStyle.Underline );
if ( IsHandleCreated && _autoSize )
{
Size = PreferredSize;
}
}
protected override void OnHandleCreated( EventArgs e )
{
base.OnHandleCreated( e );
if ( _autoSize )
{
Size = PreferredSize;
}
}
protected override void OnTextChanged( EventArgs e )
{
base.OnTextChanged( e );
if ( IsHandleCreated && !IsDisposed )
{
if ( _autoSize )
{
Size = PreferredSize;
}
Invalidate();
}
}
///
/// Size of the link label text. Note: it is recalculated using the device context every time you request it.
///
public Size PreferredSize
{
get
{
Size prefSize = GetTextSize( this, Text + _postfixText, Font, GetTextFormatFlags() );
if ( Core.ScaleFactor.Height >= 1.01f )
{
prefSize.Height += 1;
}
return prefSize;
}
}
#region Generic Helper Functions
///
/// Calculates the rectangle needed for rendering the text string.
///
/// Control to which the text will be rendered.
/// It's used for getting the device context and setting the initial text bounds
/// (the latter is ignored in the single-line case).
/// Text to be rendered.
/// Font in which the text will be rendered.
/// Initial size that should be expanded to fit the text.
/// Additional parameters that control rendering of the text.
/// Size of the text's bounding rectangle.
public static Size GetTextSize( Control control, string text, Font font, Size bounds, DrawTextFormatFlags dtf )
{
using( Graphics g = control.CreateGraphics() )
{
IntPtr hdc = g.GetHdc();
try
{
IntPtr hFont = _fontCache.GetHFont( font );
IntPtr oldFont = Win32Declarations.SelectObject( hdc, hFont );
RECT rc = new RECT( 0, 0, bounds.Width, bounds.Height );
Win32Declarations.DrawText( hdc, text, text.Length, ref rc,
dtf | DrawTextFormatFlags.DT_CALCRECT );
int height = rc.bottom - rc.top;
//height += 1;
Size sz = new Size( rc.right - rc.left, height );
Win32Declarations.SelectObject( hdc, oldFont );
return sz;
}
finally
{
g.ReleaseHdc( hdc );
}
}
}
///
/// Calculates the rectangle needed for rendering the text string.
///
/// Control to which the text will be rendered.
/// It's used for getting the device context and setting the initial text bounds
/// (the latter is ignored in the single-line case).
/// Text to be rendered.
/// Font in which the text will be rendered.
/// Additional parameters that control rendering of the text
/// Size of the text's bounding rectangle.
/// The initial size is the control size.
public static Size GetTextSize( Control control, string text, Font font, DrawTextFormatFlags dtf )
{
return GetTextSize( control, text, font, new Size( Int32.MaxValue, control.Height ), dtf );
}
///
/// Calculates the rectangle needed for rendering the text string.
///
/// Control to which the text will be rendered.
/// It's used for getting the device context and setting the initial text bounds
/// (the latter is ignored in the single-line case).
/// Text to be rendered.
/// Font in which the text will be rendered.
/// Size of the text's bounding rectangle.
/// Uses and
/// as the text rendering flags for calling the main overload.
public static Size GetTextSize( Control control, string text, Font font )
{
return GetTextSize(control, text, font, new Size(control.Width, control.Height), DrawTextFormatFlags.DT_NOPREFIX | DrawTextFormatFlags.DT_SINGLELINE);
}
///
/// Renders some text to a graphics device.
///
/// Graphics device to render the text into.
/// Text to render.
/// Bounding rectangle for the text to fit into.
/// Font in which the text is rendered.
/// Text color.
/// Formatting flags.
public static void DrawText(Graphics graphics, string text, Rectangle rect, Font font, Color color, DrawTextFormatFlags dtf)
{
IntPtr hdc = graphics.GetHdc();
try
{
// Font
IntPtr hFont = _fontCache.GetHFont( font );
IntPtr oldFont = Win32Declarations.SelectObject( hdc, hFont );
// Bounding rectangle
RECT rc = new RECT(rect.Left, rect.Top, rect.Right, rect.Bottom);
// Color
int textColor = Win32Declarations.ColorToRGB( color );
int oldColor = Win32Declarations.SetTextColor( hdc, textColor );
BackgroundMode oldMode = Win32Declarations.SetBkMode( hdc, BackgroundMode.TRANSPARENT );
// Render the text
Win32Declarations.DrawText( hdc, text, text.Length, ref rc, dtf );
// Do deinit
Win32Declarations.SetBkMode( hdc, oldMode );
Win32Declarations.SetTextColor( hdc, oldColor );
Win32Declarations.SelectObject( hdc, oldFont );
}
finally
{
graphics.ReleaseHdc( hdc );
}
}
#endregion
///
/// Width of the link label text. Note: it is recalculated using the device context every time you request it.
///
public int PreferredWidth
{
get { return PreferredSize.Width; }
}
private DrawTextFormatFlags GetTextFormatFlags()
{
DrawTextFormatFlags flags = 0;
if ( !_useMnemonic )
{
flags |= DrawTextFormatFlags.DT_NOPREFIX;
}
if ( _wordWrap )
{
flags |= DrawTextFormatFlags.DT_WORDBREAK;
}
if ( _endEllipsis )
{
flags |= DrawTextFormatFlags.DT_END_ELLIPSIS | DrawTextFormatFlags.DT_SINGLELINE;
}
if ( _textAlign != ContentAlignment.TopLeft )
{
flags |= DrawTextFormatFlags.DT_SINGLELINE;
}
switch( _textAlign )
{
case ContentAlignment.TopCenter:
flags |= DrawTextFormatFlags.DT_CENTER; break;
case ContentAlignment.TopRight:
flags |= DrawTextFormatFlags.DT_RIGHT; break;
case ContentAlignment.MiddleLeft:
flags |= DrawTextFormatFlags.DT_VCENTER; break;
case ContentAlignment.MiddleCenter:
flags |= DrawTextFormatFlags.DT_CENTER | DrawTextFormatFlags.DT_VCENTER; break;
case ContentAlignment.MiddleRight:
flags |= DrawTextFormatFlags.DT_VCENTER | DrawTextFormatFlags.DT_RIGHT; break;
case ContentAlignment.BottomLeft:
flags |= DrawTextFormatFlags.DT_BOTTOM; break;
case ContentAlignment.BottomCenter:
flags |= DrawTextFormatFlags.DT_BOTTOM | DrawTextFormatFlags.DT_CENTER; break;
case ContentAlignment.BottomRight:
flags |= DrawTextFormatFlags.DT_BOTTOM | DrawTextFormatFlags.DT_RIGHT; break;
}
return flags;
}
protected override void OnPaint( PaintEventArgs e )
{
// Paint background
if(BackColor != Color.Transparent)
{
using(Brush brush = new SolidBrush(BackColor))
e.Graphics.FillRectangle( brush, ClientRectangle );
}
base.OnPaint( e );
// Paint foreground
IntPtr hdc = e.Graphics.GetHdc();
try
{
IntPtr hFont = _fontCache.GetHFont( _underline ? _underlineFont : Font );
IntPtr oldFont = Win32Declarations.SelectObject( hdc, hFont );
RECT rc = new RECT( 0, 0, Bounds.Width, Bounds.Height );
int textColor = Enabled
? Win32Declarations.ColorToRGB( ForeColor )
: Win32Declarations.ColorToRGB( SystemColors.GrayText );
int oldColor = Win32Declarations.SetTextColor( hdc, textColor );
BackgroundMode oldMode = Win32Declarations.SetBkMode( hdc, BackgroundMode.TRANSPARENT );
int postfixLeft = 0;
if ( _postfixText.Length > 0 )
{
Win32Declarations.DrawText( hdc, Text, Text.Length, ref rc,
GetTextFormatFlags() | DrawTextFormatFlags.DT_CALCRECT );
postfixLeft = rc.right;
}
Win32Declarations.DrawText( hdc, Text, Text.Length, ref rc, GetTextFormatFlags() );
if ( _postfixText.Length > 0 )
{
Win32Declarations.SetTextColor( hdc, ColorTranslator.ToWin32( Color.Black ) );
if ( _underline )
{
Win32Declarations.SelectObject( hdc, _fontCache.GetHFont( Font ) );
}
rc.left = postfixLeft;
rc.right = Bounds.Width;
Win32Declarations.DrawText( hdc, _postfixText, _postfixText.Length, ref rc, GetTextFormatFlags() );
}
Win32Declarations.SetBkMode( hdc, oldMode );
Win32Declarations.SetTextColor( hdc, oldColor );
Win32Declarations.SelectObject( hdc, oldFont );
}
finally
{
e.Graphics.ReleaseHdc( hdc );
}
}
protected override void OnMouseDown( MouseEventArgs e )
{
// UserControl.OnMouseDown sets focus to the control, which is wrong if
// the link is not clickable
if ( _clickableLink )
{
base.OnMouseDown (e);
}
}
protected override void OnMouseEnter( EventArgs e )
{
base.OnMouseEnter( e );
if ( Enabled && ClickableLink )
{
_underline = true;
Invalidate();
}
}
protected override void OnMouseLeave( EventArgs e )
{
base.OnMouseLeave( e );
if ( Enabled && ClickableLink )
{
_underline = false;
Invalidate();
}
}
protected override void OnMouseUp( MouseEventArgs e )
{
base.OnMouseUp( e );
if ( Enabled && ClickableLink && e.Button == MouseButtons.Left && ClientRectangle.Contains( e.X, e.Y ) )
{
OnClick( EventArgs.Empty );
}
}
}
}