diff --git a/Makefile b/Makefile index a632092..fcfcf58 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,7 @@ init: compile: csc $(SRC) /nologo /warnaserror \ + /r:lib/Newtonsoft.Json.dll \ /r:lib/ImageListView.dll \ /d:TRACE \ /target:winexe /out:$(OUTPUT)/asl-charts.exe diff --git a/_archive_/Json120r2.zip b/_archive_/Json120r2.zip new file mode 100755 index 0000000..ed77935 Binary files /dev/null and b/_archive_/Json120r2.zip differ diff --git a/data/config.json b/data/config.json index 0967ef4..5a6ba3f 100644 --- a/data/config.json +++ b/data/config.json @@ -1 +1,43 @@ -{} +{ + +"IIFTMQRDCc/leader-creation.png": { + "caption": "Leader Creation" +}, + +"IIFTMQRDCc/close-combat.png": { + "caption": "Close Combat" +}, + +"IIFTMQRDCc/heat-to-kill.png": { + "caption": "HEAT To Kill" +}, + +"IIFTMQRDCc/iift.png": { + "caption": "Incremental IFT" +}, + +"IIFTMQRDCc/ap-to-kill.png": { + "caption": "AP To Kill" +}, + +"IIFTMQRDCc/afv-destruction.png": { + "caption": "AFV Destruction" +}, + +"IIFTMQRDCc/ambush.png": { + "caption": "Ambush" +}, + +"IIFTMQRDCc/heat-of-battle.png": { + "caption": "Heat Of Battle" +}, + +"IIFTMQRDCc/apcr-apds-to-kill.png": { + "caption": "APCR/APDS To Kill" +}, + +"IIFTMQRDCc/to-hit.png": { + "caption": "To Hit" +} + +} diff --git a/lib/Newtonsoft.Json.dll b/lib/Newtonsoft.Json.dll new file mode 100644 index 0000000..a04a6ca Binary files /dev/null and b/lib/Newtonsoft.Json.dll differ diff --git a/src/ChartImage.cs b/src/ChartImage.cs new file mode 100644 index 0000000..2ace334 --- /dev/null +++ b/src/ChartImage.cs @@ -0,0 +1,38 @@ +using System.IO ; +using System.Drawing ; + +using Manina.Windows.Forms ; + +// -------------------------------------------------------------------- + +public class ChartImage +{ + private string mFullPath ; + private dynamic mConfig ; + private Image mImage ; + private ImageListViewItem mImageListViewItem ; + + public ChartImage( string key, string fullPath ) + { + mFullPath = fullPath ; + mConfig = Program.dataConfig.data[ key ] ; + mImage = Image.FromFile( fullPath ) ; + + // NOTE: Thumbnails are cached by ImageListViewItem GUID, so we reuse these objects, + // instead of creating new ones every time we reload the search results. + mImageListViewItem = new ImageListViewItem( fullPath ) ; + mImageListViewItem.Tag = this ; + mImageListViewItem.Text = caption() ; + } + + public string caption() + { + string caption = (mConfig != null) ? mConfig["caption"] : null ; + if ( caption == null ) + caption = Path.GetFileNameWithoutExtension( mFullPath ) ; + return caption ; + } + + public Image image { get { return mImage ; } } + public ImageListViewItem imageListViewItem { get { return mImageListViewItem ; } } +} diff --git a/src/JsonConfig.cs b/src/JsonConfig.cs index bb09e64..599bda9 100644 --- a/src/JsonConfig.cs +++ b/src/JsonConfig.cs @@ -1,16 +1,13 @@ using System ; -using System.Text ; using System.IO ; -using System.Runtime.Serialization.Json ; -using System.Xml ; -using System.Xml.Linq ; -using System.Xml.XPath ; + +using Newtonsoft.Json ; // -------------------------------------------------------------------- public class JsonConfig { - private XElement mRootElem ; + private dynamic mData ; public JsonConfig( string caption, string fname ) { @@ -21,29 +18,8 @@ public class JsonConfig data = File.ReadAllText( fname ) ; } else data ="{}" ; - var jsonReader = JsonReaderWriterFactory.CreateJsonReader( - Encoding.UTF8.GetBytes( data ), - new XmlDictionaryReaderQuotas() - ) ; - mRootElem = XElement.Load( jsonReader ) ; - } - - public string getStringVal( string xpath, string defaultVal="" ) - { - // return the specified value - var elem = mRootElem.XPathSelectElement( xpath ) ; - if ( elem == null ) - return defaultVal ; - return elem.Value ; + mData = JsonConvert.DeserializeObject( data ) ; } - public int getIntVal( string xpath, int defaultVal=0 ) - { - // return the specified value - try { - return Int32.Parse( getStringVal( xpath ) ) ; - } catch( System.FormatException ) { - return defaultVal ; - } - } + public dynamic data { get { return mData ; } } } diff --git a/src/MainForm.cs b/src/MainForm.cs index 9a50587..7906347 100644 --- a/src/MainForm.cs +++ b/src/MainForm.cs @@ -1,5 +1,5 @@ +using System ; using System.IO ; -using System.Drawing ; using System.Collections.Generic ; using System.Windows.Forms ; @@ -11,10 +11,6 @@ public partial class MainForm : Form { private readonly HashSet mValidImageExtensions = new HashSet{ ".png", ".jpg", ".gif" } ; - private struct ChartImage { - public string mFullPath ; - public Image mImage ; - } private Dictionary mChartImages = new Dictionary() ; private SplitContainer mSplitter = new SplitContainer() ; @@ -30,20 +26,57 @@ public partial class MainForm : Form InitializeComponent() ; } - private void loadSearchResults( Dictionary.Enumerator iter ) + private void loadChartImages() + { + // locate the chart images + string dataDir = Path.GetFullPath( Program.dataDir ) ; + IEnumerable files = Directory.EnumerateFiles( + dataDir, "*.*", SearchOption.AllDirectories + ) ; + foreach( string fname in files ) { + string extn = Path.GetExtension( fname ).ToLower() ; + if ( ! mValidImageExtensions.Contains( extn ) ) + continue ; + string key ; + string fullPath = Path.GetFullPath( fname ) ; + if ( fullPath.StartsWith( dataDir ) ) { + key = fname.Substring( dataDir.Length ) ; + if ( key.StartsWith( "/" ) || key.StartsWith( "\\" ) ) + key = key.Substring( 1 ) ; + } else { + // NOTE: I don't think we should ever get here :-/ If we do, the user will have to manage + // their configuration using full paths for the image files, but at least we will still run... + key = fullPath ; + } + Program.logTraceMsg( String.Format( "Loading image: {0}", key ) ) ; + mChartImages[ key ] = new ChartImage( key, fullPath ) ; + } + } + + private void updateSearchResults( string searchQuery ) + { + // search for matching chart images + List results = new List() ; + searchQuery = searchQuery.ToLower() ; + foreach ( ChartImage chartImage in mChartImages.Values ) { + if ( chartImage.caption().ToLower().IndexOf( searchQuery ) >= 0 ) + results.Add( chartImage ) ; + } + loadSearchResults( results ) ; + } + + private void loadSearchResults( IEnumerable chartImages ) { // clear the search results mSearchResults.SuspendLayout() ; - foreach( var item in mSearchResults.SelectedItems ) - mSearchResults.Items.Remove( item ) ; + mSearchResults.Items.Clear() ; + mSearchResults.ResumeLayout( true ) ; // load the search results + mSearchResults.SuspendLayout() ; int nItems = 0 ; - while ( iter.MoveNext() ) { - ChartImage chartImage = iter.Current.Value ; - ImageListViewItem item = new ImageListViewItem( chartImage.mFullPath ) ; - item.Tag = chartImage ; - item.Text = Path.GetFileNameWithoutExtension( chartImage.mFullPath ) ; + foreach( ChartImage chartImage in chartImages ) { + ImageListViewItem item = chartImage.imageListViewItem ; mSearchResults.Items.Add( item ) ; if ( nItems++ == 0 ) item.Selected = true ; diff --git a/src/MainForm.ui.cs b/src/MainForm.ui.cs index 3e20628..05d1827 100644 --- a/src/MainForm.ui.cs +++ b/src/MainForm.ui.cs @@ -49,6 +49,7 @@ public partial class MainForm : Form this.mSplitter.Panel1.Controls.Add( mSearchUserControl ) ; this.mSearchResults.View = Manina.Windows.Forms.View.Gallery ; this.mSearchResults.SetRenderer( new AppImageListView.AppImageListViewRenderer() ) ; + this.mSearchResults.CacheMode = CacheMode.Continuous ; this.mSearchResults.Location = new Point( mSearchUserControl.Location.X + mSearchUserControl.Width, margin ) ; Size searchResultsSize = new Size( mSplitter.Width - (mSearchQuery.Location.X + mSearchQuery.Width) - margin, mSplitter.Panel1MinSize ) ; this.mSearchResults.Size = searchResultsSize ; @@ -65,53 +66,43 @@ public partial class MainForm : Form // initialize handlers this.Load += new EventHandler( this.MainForm_Load ) ; - this.mSearchQuery.KeyDown += new System.Windows.Forms.KeyEventHandler( this.SearchQuery_KeyDown ) ; - this.mSearchResults.KeyPress += new System.Windows.Forms.KeyPressEventHandler( this.SearchResults_KeyPress ) ; + this.mSearchQuery.TextChanged += new EventHandler( this.SearchQuery_TextChanged ) ; + this.mSearchQuery.KeyDown += new KeyEventHandler( this.SearchQuery_KeyDown ) ; + this.mSearchResults.KeyPress += new KeyPressEventHandler( this.SearchResults_KeyPress ) ; this.mSearchResults.SelectionChanged += new EventHandler( this.SearchResults_SelectionChanged ) ; } private void MainForm_Load( object sender, EventArgs e ) { - // locate the image files - string dataDir = Path.GetFullPath( Program.dataDir ) ; - IEnumerable files = Directory.EnumerateFiles( - dataDir, "*.*", SearchOption.AllDirectories - ) ; - foreach( string fname in files ) { - string extn = Path.GetExtension( fname ).ToLower() ; - if ( ! mValidImageExtensions.Contains( extn ) ) - continue ; - string key ; - string fullPath = Path.GetFullPath( fname ) ; - if ( fullPath.StartsWith( dataDir ) ) { - key = fname.Substring( dataDir.Length ) ; - if ( key.StartsWith( "/" ) || key.StartsWith( "\\" ) ) - key = key.Substring( 1 ) ; - } else { - // NOTE: I don't think we should ever get here :-/ If we do, the user will have to manage - // their configuration using full paths for the image files, but at least we will still run... - key = fullPath ; - } - Program.logTraceMsg( String.Format( "Loading image: {0}", key ) ) ; - mChartImages[ key ] = new ChartImage{ mFullPath=fullPath, mImage=Image.FromFile(fname) } ; - } - - // load the images into the search results - loadSearchResults( mChartImages.GetEnumerator() ) ; + // load the chart images + loadChartImages() ; + loadSearchResults( mChartImages.Values ) ; } private void SearchResults_SelectionChanged( object sender, EventArgs e ) { // show the selected chart image Debug.Assert( mSearchResults.SelectedItems.Count == 1 ) ; - ChartImage chartImage = (ChartImage) mSearchResults.SelectedItems[0].Tag ; - mImagePictureBox.Image = chartImage.mImage ; + ChartImage chartImage ; + if ( mSearchResults.SelectedItems.Count > 0 ) { + chartImage = (ChartImage) mSearchResults.SelectedItems[0].Tag ; + mImagePictureBox.Image = chartImage.image ; + } else + mImagePictureBox.Image = null ; + } + + private void SearchQuery_TextChanged( object sender, EventArgs e ) + { + // update the search results + updateSearchResults( mSearchQuery.Text.Trim() ) ; } private void SearchQuery_KeyDown( object sender, KeyEventArgs e ) { // check if we should apply the keypress to the search results - if ( e.KeyCode == Keys.Left || e.KeyCode == Keys.Right ) { + if ( e.KeyCode == Keys.Escape ) + mSearchQuery.Text = "" ; + else if ( e.KeyCode == Keys.Left || e.KeyCode == Keys.Right ) { mSearchResults.Focus() ; SendKeys.SendWait( "{" + e.KeyCode.ToString() + "}" ) ; mSearchQuery.Focus() ; @@ -123,7 +114,9 @@ public partial class MainForm : Form { // check if we should apply the keypress to the search query int ch = (int) e.KeyChar ; - if ( ch >= 32 && ch < 127 ) { + if ( ch == 27 ) + mSearchQuery.Text = "" ; + else if ( ch >= 32 && ch < 127 ) { mSearchQuery.AppendText( e.KeyChar.ToString() ) ; mSearchQuery.Focus() ; } diff --git a/src/Program.cs b/src/Program.cs index de760df..73fee2d 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -11,7 +11,7 @@ public static class Program private static string mBaseDir ; private static string mDataDir ; - private static JsonConfig mAppConfig = null ; + private static JsonConfig mDataConfig = null ; private static JsonConfig mDebugConfig = null ; private static MainForm mMainForm = null ; @@ -34,7 +34,7 @@ public static class Program // load the app config // NOTE: It would be nice to be able to load the app config from another location, but it seems // we need to create a new AppDomain to do this, and it doesn't work with Mono :-/ - mAppConfig = new JsonConfig( "app config", Path.Combine( mDataDir, "config.json" ) ) ; + mDataConfig = new JsonConfig( "data config", Path.Combine( mDataDir, "config.json" ) ) ; // load the debug config mDebugConfig = new JsonConfig( "debug config", Path.Combine( mBaseDir, "debug.json" ) ) ; @@ -61,7 +61,7 @@ public static class Program Trace.WriteLine( DateTime.Now.ToString("HH:mm:ss") + " | " + msg ) ; } - public static JsonConfig appConfig { get { return mAppConfig ; } } + public static JsonConfig dataConfig { get { return mDataConfig ; } } public static JsonConfig debugConfig { get { return mDebugConfig ; } } public static string dataDir { get { return mDataDir ; } } }