///
/// 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.Drawing;
using System.Windows.Forms;
namespace JetBrains.UI.Components.TreeSearchWindow
{
///
/// Represents a small search window which is a simple interface for different run-time searching features
///
public class SearchWindow : System.Windows.Forms.Control
{
/// Stores the text to search
private string mySearchText = "";
/// The tree view to attach search window to
private TreeView myTreeView;
/// Contains node filter
private INodeFilter myNodeFilter;
/// Stores the parent form's "Cancel" button reference when a search window
/// is visible to intercept "Escape" key pressing. Restores button reference back
/// when the window is hidded.
private IButtonControl _ownerCancelBtn;
///
/// Gets the text being searched
///
public string SearchText
{
get { return mySearchText; }
set
{
mySearchText = value;
if( Handle != (IntPtr) 0 )
Size = DefaultSize;
}
}
///
/// Gets or sets node filter
///
public INodeFilter NodeFilter
{
get { return myNodeFilter; }
set
{
if( value == null )
throw new ArgumentNullException( "value" );
myNodeFilter = value;
}
}
///
/// Gets or sets an attached tree view
///
public TreeView TreeView
{
get { return myTreeView; }
set
{
if( myTreeView != null )
{
myTreeView.KeyDown -= OnKeyDown;
myTreeView.KeyPress -= OnKeyPress;
VisibleChanged -= SearchWindow_VisibleChanged;
}
myTreeView = value;
if( myTreeView != null )
{
myTreeView.KeyDown += OnKeyDown;
myTreeView.KeyPress += OnKeyPress;
VisibleChanged += SearchWindow_VisibleChanged;
}
}
}
///
/// Creates a new search window for a tree view
///
public SearchWindow()
{
if( Handle == (IntPtr) 0 )
HandleCreated += OnHandleCreated;
else
Size = DefaultSize;
BackColor = SystemColors.Info;
ForeColor = SystemColors.InfoText;
myNodeFilter = new StartingFilter();
Visible = false;
}
///
/// When a SearchWindow is made visible (activated) it prohibits a standard
/// processing of "Escape" key via submitting a "Cancel" button. This makes
/// possible to process "Escape" key locally to hide the search window. When
/// a SearchWindow is not visible, "Cancel" key is processed as usual.
///
private void SearchWindow_VisibleChanged( object sender, EventArgs e )
{
Form owner = myTreeView.FindForm();
if( owner != null )
{
if( Visible )
{
_ownerCancelBtn = owner.CancelButton;
owner.CancelButton = null;
}
else
{
owner.CancelButton = _ownerCancelBtn;
}
}
}
///
/// Gets default size for this control
///
protected override Size DefaultSize
{
get
{
try
{
Size defaultSize = Graphics.FromHwnd( Handle ).MeasureString( "Search for: " + mySearchText, Font ).ToSize();
defaultSize.Width += 4;
defaultSize.Height += 4;
return defaultSize;
}
catch( Exception ex )
{
System.Diagnostics.Trace.WriteLine( "SearchWindow.DefaultSize failed : " + ex, "UI" );
}
return new Size();
}
}
protected override void OnPaint( PaintEventArgs pe )
{
try
{
Graphics g = pe.Graphics;
Rectangle rect = pe.ClipRectangle;
if( rect.Width == 0 )
return;
g.FillRectangle( new SolidBrush( BackColor ), rect );
Rectangle textRect = pe.ClipRectangle;
textRect.Height -= 4;
textRect.Y += 2;
g.DrawString( "Search for: " + mySearchText, Font, new SolidBrush( ForeColor ), textRect );
rect.Width--;
rect.Height--;
g.DrawRectangle( new Pen( new SolidBrush( ForeColor ), 1 ), rect );
}
catch( Exception ex )
{
System.Diagnostics.Trace.WriteLine( "SearchWindow.OnPaint failed : " + ex, "UI" );
}
// Calling the base class OnPaint
base.OnPaint( pe );
}
///
/// Handles the HandleCreated event
///
///
///
private void OnHandleCreated( object sender, EventArgs e )
{
Size = DefaultSize;
}
#region Keys processing logic
///
/// Handles the key down event of the attached tree view
///
private void OnKeyDown( object sender, KeyEventArgs e )
{
try
{
if( Visible && Enabled && myTreeView != null )
{
TreeNode node = null;
switch( e.KeyCode )
{
case Keys.Back:
if( SearchText.Length > 0 )
{
if( e.Control )
{
CancelSearch();
}
else
{
SearchText = SearchText.Substring( 0, SearchText.Length - 1 );
Invalidate();
Update();
node = GetFirstMatchingNode( SearchText );
}
e.Handled = true;
}
break;
case Keys.Enter:
CancelSearch();
e.Handled = true;
break;
case Keys.Up:
node = GetPrevMatchingNode( myTreeView.SelectedNode, SearchText );
e.Handled = true;
break;
case Keys.Down:
node = GetNextMatchingNode( myTreeView.SelectedNode, SearchText );
e.Handled = true;
break;
}
if( node != null )
myTreeView.SelectedNode = node;
}
}
catch( Exception ex )
{
System.Diagnostics.Trace.WriteLine( "SearchWindow.OnKeyDown failed : " + ex, "UI" );
}
}
///
/// Handles the key press event of the attached tree view
///
private void OnKeyPress( object sender, KeyPressEventArgs e )
{
try
{
if( !Enabled || myTreeView == null )
return;
if( Char.IsLetterOrDigit( e.KeyChar ) || Char.IsPunctuation( e.KeyChar ) )
{
SearchText += e.KeyChar;
if( !Visible )
Show();
Invalidate();
Update();
TreeNode matchingNode = GetFirstMatchingNode( SearchText );
if( matchingNode != null )
myTreeView.SelectedNode = matchingNode;
e.Handled = true;
}
else
if( e.KeyChar == 27 )
{
if( Visible )
{
CancelSearch();
e.Handled = true;
}
}
}
catch( Exception ex )
{
System.Diagnostics.Trace.WriteLine( "SearchWindow.OnKeyPress failed : " + ex, "UI" );
}
}
///
/// Cancels the search
///
public void CancelSearch()
{
if( Visible )
Hide();
SearchText = "";
}
#endregion
#region Node tranversal methods
///
/// Gets the first node that matches the given text
///
/// The search text to match
private TreeNode GetFirstMatchingNode( string searchText )
{
TreeNode node = myTreeView.TopNode;
while( node != null )
{
if( myNodeFilter.Matches( node, searchText ) )
break;
node = GetNextNode( node );
}
return node;
}
///
/// Gets the first matching node which follows the given node
///
private TreeNode GetNextMatchingNode( TreeNode node, string searchText )
{
node = GetNextNode( node );
while( node != null )
{
if( myNodeFilter.Matches( node, searchText ) )
break;
node = GetNextNode( node );
}
return node;
}
///
/// Gets the first matching node which goes before the given node
///
private TreeNode GetPrevMatchingNode( TreeNode node, string searchText )
{
node = GetPrevNode( node );
while( node != null )
{
if( myNodeFilter.Matches( node, searchText ) )
break;
node = GetPrevNode( node );
}
return node;
}
///
/// Gets node which follows the given one
///
private TreeNode GetNextNode( TreeNode node )
{
if( node.Nodes.Count > 0 )
return node.Nodes[ 0 ];
while( node.NextNode == null && node.Parent != null )
node = node.Parent;
return node.NextNode;
}
///
/// Gets node which is followed by the given one
///
private TreeNode GetPrevNode( TreeNode node )
{
if( node.PrevNode != null )
{
node = node.PrevNode;
while( node.Nodes.Count > 0 )
node = node.Nodes[ node.Nodes.Count - 1 ];
return node;
}
return node.Parent;
}
#endregion
}
}