// 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  } }