Added the ability to search keywords.

master
Pacman Ghost 5 years ago
parent 7483504e6d
commit 5a284e8414
  1. 4
      .gitignore
  2. 2
      Makefile
  3. 0
      _archive_/Json120r2.zip
  4. BIN
      _archive_/log4net-2.0.8-bin-newkey.zip
  5. BIN
      _archive_/log4net-2.0.8-src.zip
  6. 27
      data/config.json
  7. BIN
      lib/log4net.dll
  8. 99
      src/ChartImage.cs
  9. 42
      src/JsonConfig.cs
  10. 48
      src/MainForm.cs
  11. 33
      src/Program.cs

4
.gitignore vendored

@ -1,6 +1,6 @@
app.config
log4net.xml
out/
_work_
_work_/
.vscode/
*.swp

@ -10,9 +10,9 @@ compile:
csc $(SRC) /nologo /warnaserror \
/r:lib/Newtonsoft.Json.dll \
/r:lib/ImageListView.dll \
/r:lib/log4net.dll \
/d:TRACE \
/target:winexe /out:$(OUTPUT)/asl-charts.exe
cp app.config $(OUTPUT)/asl-charts.exe.config
cp lib/*.dll $(OUTPUT)
clean:

Binary file not shown.

@ -1,27 +1,33 @@
{
"IIFTMQRDCc/leader-creation.png": {
"caption": "Leader Creation"
"caption": "Leader Creation",
"keywords": [ "LC", "leader" ]
},
"IIFTMQRDCc/close-combat.png": {
"caption": "Close Combat"
"caption": "Close Combat",
"keywords": [ "CC" ]
},
"IIFTMQRDCc/heat-to-kill.png": {
"caption": "HEAT To Kill"
"caption": "HEAT To Kill",
"keywords": [ "HEAT", "TK" ]
},
"IIFTMQRDCc/iift.png": {
"caption": "Incremental IFT"
"caption": "Incremental IFT",
"keywords": [ "IFT", "IIFT" ]
},
"IIFTMQRDCc/ap-to-kill.png": {
"caption": "AP To Kill"
"caption": "AP To Kill",
"keywords": [ "AP", "TK" ]
},
"IIFTMQRDCc/afv-destruction.png": {
"caption": "AFV Destruction"
"caption": "AFV Destruction",
"keywords": [ "AFV" ]
},
"IIFTMQRDCc/ambush.png": {
@ -29,15 +35,18 @@
},
"IIFTMQRDCc/heat-of-battle.png": {
"caption": "Heat Of Battle"
"caption": "Heat Of Battle",
"keywords": [ "HOB" ]
},
"IIFTMQRDCc/apcr-apds-to-kill.png": {
"caption": "APCR/APDS To Kill"
"caption": "APCR/APDS To Kill",
"keywords": [ "APCR", "APDS", "TK" ]
},
"IIFTMQRDCc/to-hit.png": {
"caption": "To Hit"
"caption": "To Hit",
"keywords": [ "TH" ]
}
}

Binary file not shown.

@ -1,7 +1,12 @@
using System ;
using System.Text ;
using System.IO ;
using System.Collections.Generic ;
using System.Drawing ;
using Manina.Windows.Forms ;
using Newtonsoft.Json.Linq ;
using log4net ;
// --------------------------------------------------------------------
@ -9,15 +14,25 @@ public class ChartImage
{
private string mFullPath ;
private dynamic mConfig ;
private HashSet<string> mKeywords = new HashSet<string>() ;
private Image mImage ;
private ImageListViewItem mImageListViewItem ;
public ChartImage( string key, string fullPath )
{
// initialize the ChartImage
mFullPath = fullPath ;
mConfig = Program.dataConfig.data[ key ] ;
if ( mConfig == null )
mConfig = new JObject() ;
mImage = Image.FromFile( fullPath ) ;
// prepare for search scoring
if ( mConfig["keywords"] != null ) {
foreach( string kywd in mConfig["keywords"] )
mKeywords.Add( kywd.ToUpper() ) ;
}
// 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 ) ;
@ -25,6 +40,90 @@ public class ChartImage
mImageListViewItem.Text = caption() ;
}
public float getSearchScore( string searchQuery )
{
// initialize
searchQuery = searchQuery.ToUpper() ;
List< Tuple<string,float> > scores = new List<Tuple<string,float>>() ;
ILog logger = LogManager.GetLogger( "search" ) ;
if ( logger.IsDebugEnabled ) {
if ( mKeywords.Count == 0 )
logger.Debug( $"- \"{this.caption()}\": (no keywords)" ) ;
else {
StringBuilder buf2 = new StringBuilder() ;
foreach( string kywd in mKeywords )
buf2.Append( $"{kywd} ; " ) ;
string val = buf2.ToString() ;
logger.Debug( $"- \"{this.caption()}\": [ {val.Substring(0,val.Length-3)} ]" ) ;
}
}
// initialize the search score weights
float exactKeywordMatchScore = Program.appConfig.getFloatVal( new string[]{"search","exactKeywordMatchScore"}, 10f ) ;
float leadingPartialKeywordMatchScore = Program.appConfig.getFloatVal( new string[]{"search","leadingPartialKeywordMatchScore"}, 2f ) ;
float internalPartialKeywordMatchScore = Program.appConfig.getFloatVal( new string[]{"search","internalPartialKeywordMatchScore"}, 1.5f ) ;
float exactCaptionMatchScore = Program.appConfig.getFloatVal( new string[]{"search","exactCaptionMatchScore"}, 5f ) ;
float leadingPartialCaptionMatchScore = Program.appConfig.getFloatVal( new string[]{"search","leadingPartialCaptionMatchScore"}, 1f ) ;
float internalPartialCaptionMatchScore = Program.appConfig.getFloatVal( new string[]{"search","internalPartialCaptionMatchScore"}, 0.5f ) ;
// look for keyword matches
foreach ( string kywd in mKeywords ) {
if ( kywd == searchQuery ) {
scores.Add( new Tuple<string,float>( "exactKeywordMatch", exactKeywordMatchScore ) ) ;
continue ;
}
int pos = kywd.IndexOf( searchQuery ) ;
if ( pos == 0 ) {
scores.Add( new Tuple<string,float>(
$"leadingPartialKeywordMatch[{kywd}]",
Math.Min( leadingPartialKeywordMatchScore * searchQuery.Length, exactKeywordMatchScore )
) ) ;
} else if ( pos > 0 ) {
scores.Add( new Tuple<string,float>(
$"internalPartialKeywordMatch[{kywd}]",
Math.Min( internalPartialKeywordMatchScore * searchQuery.Length, exactKeywordMatchScore )
) ) ;
}
}
// look for caption matches
string caption = this.caption().ToUpper() ;
if ( searchQuery == caption )
scores.Add( new Tuple<string,float>( "exactCaptionMatch", exactCaptionMatchScore ) ) ;
else {
int pos = caption.IndexOf( searchQuery ) ;
if ( pos == 0 ) {
scores.Add( new Tuple<string,float>(
$"leadingPartialCaptionMatch[{caption}]",
Math.Min( leadingPartialCaptionMatchScore * searchQuery.Length, exactCaptionMatchScore )
) ) ;
} else if ( pos > 0 ) {
scores.Add( new Tuple<string,float>(
$"internalPartialCaptionMatch[{caption}]",
Math.Min( internalPartialCaptionMatchScore * searchQuery.Length, exactCaptionMatchScore )
) ) ;
}
}
// calculate the total score
float totalScore = 0 ;
StringBuilder buf = logger.IsDebugEnabled ? new StringBuilder() : null ;
for ( int i=0 ; i < scores.Count ; ++i ) {
totalScore += scores[i].Item2 ;
if ( buf != null ) {
if ( i > 0 )
buf.Append( " ; " ) ;
buf.Append( $"{scores[i].Item1}={scores[i].Item2:F1}" ) ;
}
}
if ( totalScore > 0 ) {
logger.Debug( $" - {buf}" ) ;
logger.Debug( $" - totalScore = {totalScore:F1}" ) ;
}
return totalScore ;
}
public string caption()
{
string caption = (mConfig != null) ? mConfig["caption"] : null ;

@ -2,6 +2,8 @@ using System ;
using System.IO ;
using Newtonsoft.Json ;
using Newtonsoft.Json.Linq ;
using log4net ;
// --------------------------------------------------------------------
@ -11,14 +13,52 @@ public class JsonConfig
public JsonConfig( string caption, string fname )
{
// initialize
ILog logger = LogManager.GetLogger( "startup" ) ;
// load the JSON config
string data ;
if ( File.Exists( fname ) ) {
Program.logTraceMsg( String.Format( "Loading {0}: {1}", caption, Path.GetFullPath(fname) ) ) ;
logger.Info( $"Loading {caption}: {Path.GetFullPath(fname)}" ) ;
data = File.ReadAllText( fname ) ;
} else
data ="{}" ;
mData = JsonConvert.DeserializeObject( data ) ;
logger.Debug( mData.ToString() ) ;
}
public string getStringVal( string[] keys, string defaultVal="" )
{
// get the specified value
JToken curr = mData ;
foreach( string key in keys ) {
curr = curr[ key ] ;
if ( curr == null )
return defaultVal ;
}
return (string) curr ;
}
public int getIntVal( string[] keys, int defaultVal=0 )
{
// get the specified value
string val = getStringVal( keys ) ;
try {
return Int32.Parse( val ) ;
} catch( FormatException ) {
return defaultVal ;
}
}
public float getFloatVal( string[] keys, float defaultVal=0 )
{
// get the specified value
string val = getStringVal( keys ) ;
try {
return float.Parse( val ) ;
} catch( FormatException ) {
return defaultVal ;
}
}
public dynamic data { get { return mData ; } }

@ -4,6 +4,7 @@ using System.Collections.Generic ;
using System.Windows.Forms ;
using Manina.Windows.Forms ;
using log4net ;
// --------------------------------------------------------------------
@ -28,6 +29,9 @@ public partial class MainForm : Form
private void loadChartImages()
{
// initialize
ILog logger = LogManager.GetLogger( "startup" ) ;
// locate the chart images
string dataDir = Path.GetFullPath( Program.dataDir ) ;
IEnumerable<string> files = Directory.EnumerateFiles(
@ -48,21 +52,51 @@ public partial class MainForm : Form
// 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 ) ) ;
logger.Debug( $"Loading image: {key}" ) ;
mChartImages[ key ] = new ChartImage( key, fullPath ) ;
}
}
private void updateSearchResults( string searchQuery )
{
// initialize
searchQuery = searchQuery.Trim() ;
ILog logger = LogManager.GetLogger( "search" ) ;
logger.Info( $"Updating search results: query=\"{searchQuery}\"" ) ;
// search for matching chart images
List<ChartImage> results = new List<ChartImage>() ;
searchQuery = searchQuery.ToLower() ;
foreach ( ChartImage chartImage in mChartImages.Values ) {
if ( chartImage.caption().ToLower().IndexOf( searchQuery ) >= 0 )
results.Add( chartImage ) ;
List< Tuple<ChartImage,float> > results = new List<Tuple<ChartImage,float>>() ;
foreach( ChartImage chartImage in mChartImages.Values ) {
float score ;
if ( searchQuery == "" )
score = 0 ;
else {
score = chartImage.getSearchScore( searchQuery ) ;
if ( score <= 0 )
continue ;
}
results.Add( new Tuple<ChartImage,float>( chartImage, score ) ) ;
}
loadSearchResults( results ) ;
// sort the search results
results.Sort( (lhs, rhs) => {
return (lhs.Item2 == rhs.Item2) ? 0 : (lhs.Item2 > rhs.Item2) ? -1 : +1 ;
} ) ;
if ( searchQuery != "" && logger.IsInfoEnabled ) {
if ( results.Count > 0 ) {
logger.Info( "- Sorted results:" ) ;
foreach ( var val in results )
logger.Info( $" - \"{val.Item1.caption()}\" = {val.Item2:F1}" ) ;
} else
logger.Info( "- No results." ) ;
} else
logger.Info( "- No search query, showing all chart images." ) ;
// show the search results
List<ChartImage> results2 = new List<ChartImage>() ;
foreach ( var r in results )
results2.Add( r.Item1 ) ;
loadSearchResults( results2 ) ;
}
private void loadSearchResults( IEnumerable<ChartImage> chartImages )

@ -3,6 +3,8 @@ using System.Windows.Forms ;
using System.IO ;
using System.Diagnostics ;
using log4net.Config ;
// --------------------------------------------------------------------
public static class Program
@ -11,6 +13,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 ;
@ -21,10 +24,17 @@ public static class Program
// initialize
mBaseDir = Application.StartupPath ;
// configure logging
string fname = Path.Combine( mBaseDir, "log4net.xml" ) ;
if ( ! File.Exists( fname ) )
fname = Path.Combine( mBaseDir, "../log4net.xml" ) ;
if ( File.Exists( fname ) )
XmlConfigurator.Configure( new FileInfo( fname ) ) ;
// locate the data directory
mDataDir = System.IO.Path.Combine( mBaseDir , "data" ) ;
mDataDir = Path.Combine( mBaseDir , "data" ) ;
if ( ! Directory.Exists( mDataDir ) ) {
mDataDir = System.IO.Path.Combine( mBaseDir , "../data" ) ;
mDataDir = Path.Combine( mBaseDir , "../data" ) ;
if ( ! Directory.Exists( mDataDir ) )
mDataDir = mBaseDir ;
}
@ -32,8 +42,16 @@ public static class Program
try {
// 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 :-/
string fname0 = Path.GetFileNameWithoutExtension( System.Reflection.Assembly.GetEntryAssembly().Location ) + ".json" ;
fname = Path.Combine( mBaseDir, fname0 ) ;
if ( ! File.Exists( fname ) ) {
fname = Path.Combine( mDataDir, fname0 ) ;
if ( ! File.Exists( fname ) )
fname = Path.Combine( mBaseDir, "../"+fname0 ) ;
}
mAppConfig = new JsonConfig( "app config", fname ) ;
// load the data config
mDataConfig = new JsonConfig( "data config", Path.Combine( mDataDir, "config.json" ) ) ;
// load the debug config
@ -55,12 +73,7 @@ public static class Program
public static void showWarningMsg( string msg ) { MessageBox.Show(msg,APP_NAME,MessageBoxButtons.OK,MessageBoxIcon.Warning) ; }
public static void showErrorMsg( string msg ) { MessageBox.Show(msg,APP_NAME,MessageBoxButtons.OK,MessageBoxIcon.Error) ; }
public static void logTraceMsg( string msg )
{
// log a trace message
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 ; } }

Loading…
Cancel
Save