// Copyright (c) 2007 Omer Rauchwerger (a.k.a rauchy) (omer@rauchy.net)
// All rights reserved.
//
// This file is part of Regionerate.
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License,
// or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
using System;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Rauchy.Regionerate.Shared.Components;
namespace Rauchy.Regionerate.Presentation.Components
{
///
/// Presents the user a selection of Code Layouts which can be selected using the keyboard shortcut.
///
public partial class CodeLayoutBrowser : Form
{
#region Fields (4)
private CloseMode _closeMode;
private readonly Timer _fadeInTimer = new Timer {Interval = 5, Enabled = true};
private readonly Keys _modifiers;
private readonly string _shortcutKey;
#endregion Fields
#region Enums (1)
private enum CloseMode
{
KeyUp,
DoubleClick
}
#endregion Enums
#region Constructors (3)
///
/// Initializes a new instance of the class in "Key Up" mode.
///
/// "Key Up" mode means that the Code Layout Browser will close and return the selected
/// Code Layout when the user releases the key indicated by shortcutKey.
/// The set of modifiers the user has selected to launch by (usually Keys.Control)
/// The shortcut key the user has selected to launch by (usually "R")
/// The primary code layout.
public CodeLayoutBrowser( Keys modifiers, string shortcutKey, CodeLayout primaryCodeLayout )
: this( CloseMode.KeyUp, primaryCodeLayout )
{
_modifiers = modifiers;
_shortcutKey = shortcutKey;
}
///
/// Initializes a new instance of the .
///
/// The closing mode for this Code Layout Browser.
/// The primary code layout.
private CodeLayoutBrowser( CloseMode closeMode, CodeLayout primaryCodeLayout )
{
InitializeComponent();
SetMode( closeMode );
codeLayouts.Items[ 0 ].Tag = primaryCodeLayout;
// Set fade-in animation
Opacity = 0;
_fadeInTimer.Tick += delegate
{
Opacity += .09;
if ( Opacity >= 80 )
{
_fadeInTimer.Stop();
}
};
// Select the primary Code Layout.
codeLayouts.Focus();
codeLayouts.Items[ 0 ].Selected = true;
AddItems();
UpdateLabel();
// Make this window and the blue panel rounded.
Roundify( this );
Roundify( bluePanel );
}
///
/// Initializes a new instance of the class in "Double Click" mode.
///
/// "Double Click" mode means that the Code Layout Browser will close and return the selected
/// Code Layout when the user double clicks a Code Layout.
/// The primary code layout.
public CodeLayoutBrowser( CodeLayout primaryCodeLayout ) : this( CloseMode.DoubleClick, primaryCodeLayout ) {}
#endregion Constructors
#region Properties (1)
///
/// Gets the selected code layout.
///
/// The selected code layout.
public CodeLayout SelectedCodeLayout
{
get
{
object tag;
if ( codeLayouts.SelectedItems.Count == 0 )
{
// No Code Layout was selected, use the primary Code Layout item's tag.
tag = codeLayouts.Items[ 0 ].Tag;
}
else
{
tag = codeLayouts.SelectedItems[ 0 ].Tag;
}
if ( tag is string )
{
// To prevent loading all Code Layouts every time the browser is loaded, only the
// file name is tagged and when an item is selected, its Code Layout is loaded.
string codeLayoutFileName = ( string )tag;
return CodeLayout.Load( codeLayoutFileName );
}
else
{
// An exception to this is the primary Code Layout which is already loaded.
return ( CodeLayout )tag;
}
}
}
#endregion Properties
#region Methods (21)
// Private Methods (21)
private void AddItems()
{
// Create items for all .xml files inside the My Code Layouts directory.
string myCodeLayoutsDirectory = GetMyCodeLayoutsDirectory();
if ( Directory.Exists( myCodeLayoutsDirectory ) )
{
// Handle files on the main directory.
foreach (
string file in Directory.GetFiles( myCodeLayoutsDirectory, "*.xml", SearchOption.TopDirectoryOnly )
)
{
ListViewItem item = CreateListViewItem( file );
codeLayouts.Items.Add( item );
Width += 120;
}
// Handle inner directories.
foreach ( string directory in Directory.GetDirectories( myCodeLayoutsDirectory ) )
{
// Create a group named after the directory.
DirectoryInfo directoryInfo = new DirectoryInfo( directory );
string directoryTitle = directoryInfo.Name;
ListViewGroup group = new ListViewGroup( directoryTitle );
codeLayouts.Groups.Add( group );
string[] files = Directory.GetFiles( directory, "*.xml", SearchOption.TopDirectoryOnly );
// If there are files inside the directory, they will be displayed in a new row, so make sure there is enough space.
Height += 160;
// Handle files inside inner directories.
foreach ( string file in files )
{
ListViewItem item = CreateListViewItem( file );
group.Items.Add( item );
codeLayouts.Items.Add( item );
//this.Width += 108;
}
}
}
}
///
/// Closes this and returns a specific to the caller.
///
private void Close( DialogResult dialogResult )
{
DialogResult = dialogResult;
Close();
}
///
/// Handles the KeyDown event for the double click mode.
///
///
/// This is overriden to allow users to hit Enter in order to select a Code Layout while in double click mode.
///
private void CodeLayoutBrowser_DoubleClickMode_KeyPress( object sender, KeyPressEventArgs e )
{
switch ( e.KeyChar )
{
case ( char )Keys.Escape:
Close( DialogResult.Cancel );
break;
case ( char )Keys.Enter:
Close( DialogResult.OK );
break;
}
}
private void CodeLayoutBrowser_KeyDown( object sender, KeyEventArgs e )
{
char key = Convert.ToChar( e.KeyValue );
if ( char.IsNumber( key ) )
{
int digit = int.Parse( key.ToString() );
bool betweenOneAndNine = digit >= 1 && digit <= 9;
bool notBeyondLastItem = digit < ( codeLayouts.Items.Count );
bool inRange = betweenOneAndNine && notBeyondLastItem;
if ( inRange )
{
SelectListViewItem( digit );
}
}
else if ( key.ToString().ToLower() ==
_shortcutKey.ToLower() )
{
int selectedIndex = GetSelectedIndex();
// Find the next item's index.
if ( selectedIndex == codeLayouts.Items.Count - 1 )
{
// The selected item is the last one, so return to the first.
selectedIndex = 0;
}
else
{
selectedIndex++;
}
// Select the new item.
SelectListViewItem( selectedIndex );
}
}
private void CodeLayoutBrowser_KeyUp( object sender, KeyEventArgs e )
{
if ( e.Modifiers != _modifiers )
{
// Modifiers changed, the user is no longer keeping the
// modifiers pressed - meaning that he chose his Code Layout.
Close( DialogResult = DialogResult.OK );
}
}
private void codeLayouts_DoubleClick( object sender, EventArgs e )
{
Close( DialogResult.OK );
}
private void codeLayouts_SelectedIndexChanged( object sender, EventArgs e )
{
UpdateLabel();
}
private ListViewItem CreateListViewItem( string file )
{
string title = Path.GetFileNameWithoutExtension( file );
ListViewItem item = new ListViewItem( title )
{Tag = file, ImageKey = GetImageKey( file ), ToolTipText = title};
return item;
}
[ DllImport( "Gdi32.dll", EntryPoint = "CreateRoundRectRgn" ) ]
private static extern IntPtr CreateRoundRectRgn( int nLeftRect,
// x-coordinate of upper-left corner
int nTopRect,
// y-coordinate of upper-left corner
int nRightRect,
// x-coordinate of lower-right corner
int nBottomRect,
// y-coordinate of lower-right corner
int nWidthEllipse,
// height of ellipse
int nHeightEllipse // width of ellipse
);
private void donate_LinkClicked( object sender, LinkLabelLinkClickedEventArgs e )
{
Process.Start( Urls.Donations );
Close( DialogResult.Cancel ); // Do NOT select a Code Layout.
}
///
/// Modifies an by adding a small number sign in its bottom right corner.
///
///
///
private static void DrawNumber( Image image, int number )
{
Graphics graphics = Graphics.FromImage( image );
Brush backgroundBrush = Brushes.BlanchedAlmond;
Brush foregroundBrush = Brushes.Black;
Pen pen = new Pen( foregroundBrush, 2 ) {DashStyle = DashStyle.Dot};
const int size = 24;
Rectangle rect = new Rectangle( image.Width - size - 10, image.Height - size - 10, size, size );
graphics.FillRectangle( backgroundBrush, rect );
graphics.DrawRectangle( pen, rect );
Font font = new Font( "Arial", 13, FontStyle.Bold );
graphics.DrawString( number.ToString(), font, foregroundBrush, rect.Left + 4, rect.Top + 2 );
}
///
/// Gets the image key for a specified Code Layout file.
///
///
/// If a file with a similar name and a ".png" extension co-exists in the same directory as the Code Layout file,
/// the image is loaded and its key is returned.
/// If no such file exists, the primary key is returned.
///
private string GetImageKey( string file )
{
string imageFile = Path.ChangeExtension( file, ".png" );
if ( File.Exists( imageFile ) )
{
string key = Path.GetFileNameWithoutExtension( imageFile );
Image image = LoadImage( imageFile, imageList.Images.Count );
imageList.Images.Add( key, image );
return key;
}
return codeLayouts.Items[ 0 ].ImageKey;
}
private static string GetMyCodeLayoutsDirectory()
{
Assembly executingAssembly = Assembly.GetExecutingAssembly();
string assemblyPath = executingAssembly.Location;
string assemblyDirectory = Path.GetDirectoryName( assemblyPath );
return Path.Combine( assemblyDirectory, "My Code Layouts" );
}
private int GetSelectedIndex()
{
if ( codeLayouts.SelectedIndices.Count == 1 )
{
return codeLayouts.SelectedIndices[ 0 ];
}
return 0;
}
private Image LoadImage( string imageFile, int number )
{
Image image = Image.FromFile( imageFile );
if ( ( number <= 9 ) &&
( _closeMode == CloseMode.KeyUp ) )
{
DrawNumber( image, number );
}
return image;
}
private void manage_LinkClicked( object sender, LinkLabelLinkClickedEventArgs e )
{
Close( DialogResult.Cancel ); // Do NOT select a Code Layout.
Process.Start( GetMyCodeLayoutsDirectory() );
}
///
/// Rounds the edges of a .
///
private static void Roundify( Control control )
{
control.Region =
Region.FromHrgn( CreateRoundRectRgn( control.Left, control.Top, control.Width - 10, control.Height - 10,
20, 20 ) );
}
private void SelectListViewItem( int index )
{
int currentlySelectedIndex = GetSelectedIndex();
// Deselect the current item.
codeLayouts.Items[ currentlySelectedIndex ].Selected = false;
// Select & focus on the new item.
codeLayouts.Items[ index ].Selected = true;
codeLayouts.Items[ index ].EnsureVisible();
}
private void SetMode( CloseMode closeMode )
{
_closeMode = closeMode;
switch ( _closeMode )
{
case CloseMode.KeyUp:
codeLayouts.DoubleClick -= codeLayouts_DoubleClick;
break;
case CloseMode.DoubleClick:
KeyDown -= CodeLayoutBrowser_KeyDown;
KeyUp -= CodeLayoutBrowser_KeyUp;
KeyPress += CodeLayoutBrowser_DoubleClickMode_KeyPress;
codeLayouts.KeyDown -= CodeLayoutBrowser_KeyDown;
codeLayouts.KeyUp -= CodeLayoutBrowser_KeyUp;
codeLayouts.KeyPress += CodeLayoutBrowser_DoubleClickMode_KeyPress;
break;
default:
throw new ArgumentOutOfRangeException();
}
}
private void settings_LinkClicked( object sender, LinkLabelLinkClickedEventArgs e )
{
Hide();
SettingsForm settingsForm = new SettingsForm();
settingsForm.ShowDialog();
Show();
}
private void UpdateLabel()
{
switch ( _closeMode )
{
case CloseMode.KeyUp:
string selectedItemDescription = "the selected Code Layout";
if ( codeLayouts.SelectedItems.Count == 1 )
{
selectedItemDescription = codeLayouts.SelectedItems[ 0 ].ToolTipText;
}
label.Text = string.Format( "Press {0} to browse. Release {1} to apply {2}.", _shortcutKey,
_modifiers, selectedItemDescription );
break;
case CloseMode.DoubleClick:
label.Text = "Double click any Code Layout to apply it on the active document.";
break;
default:
throw new ArgumentOutOfRangeException();
}
}
#endregion Methods
}
}