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/ out/
_work_ _work_/
.vscode/ .vscode/
*.swp *.swp

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

Binary file not shown.

@ -1,27 +1,33 @@
{ {
"IIFTMQRDCc/leader-creation.png": { "IIFTMQRDCc/leader-creation.png": {
"caption": "Leader Creation" "caption": "Leader Creation",
"keywords": [ "LC", "leader" ]
}, },
"IIFTMQRDCc/close-combat.png": { "IIFTMQRDCc/close-combat.png": {
"caption": "Close Combat" "caption": "Close Combat",
"keywords": [ "CC" ]
}, },
"IIFTMQRDCc/heat-to-kill.png": { "IIFTMQRDCc/heat-to-kill.png": {
"caption": "HEAT To Kill" "caption": "HEAT To Kill",
"keywords": [ "HEAT", "TK" ]
}, },
"IIFTMQRDCc/iift.png": { "IIFTMQRDCc/iift.png": {
"caption": "Incremental IFT" "caption": "Incremental IFT",
"keywords": [ "IFT", "IIFT" ]
}, },
"IIFTMQRDCc/ap-to-kill.png": { "IIFTMQRDCc/ap-to-kill.png": {
"caption": "AP To Kill" "caption": "AP To Kill",
"keywords": [ "AP", "TK" ]
}, },
"IIFTMQRDCc/afv-destruction.png": { "IIFTMQRDCc/afv-destruction.png": {
"caption": "AFV Destruction" "caption": "AFV Destruction",
"keywords": [ "AFV" ]
}, },
"IIFTMQRDCc/ambush.png": { "IIFTMQRDCc/ambush.png": {
@ -29,15 +35,18 @@
}, },
"IIFTMQRDCc/heat-of-battle.png": { "IIFTMQRDCc/heat-of-battle.png": {
"caption": "Heat Of Battle" "caption": "Heat Of Battle",
"keywords": [ "HOB" ]
}, },
"IIFTMQRDCc/apcr-apds-to-kill.png": { "IIFTMQRDCc/apcr-apds-to-kill.png": {
"caption": "APCR/APDS To Kill" "caption": "APCR/APDS To Kill",
"keywords": [ "APCR", "APDS", "TK" ]
}, },
"IIFTMQRDCc/to-hit.png": { "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.IO ;
using System.Collections.Generic ;
using System.Drawing ; using System.Drawing ;
using Manina.Windows.Forms ; using Manina.Windows.Forms ;
using Newtonsoft.Json.Linq ;
using log4net ;
// -------------------------------------------------------------------- // --------------------------------------------------------------------
@ -9,15 +14,25 @@ public class ChartImage
{ {
private string mFullPath ; private string mFullPath ;
private dynamic mConfig ; private dynamic mConfig ;
private HashSet<string> mKeywords = new HashSet<string>() ;
private Image mImage ; private Image mImage ;
private ImageListViewItem mImageListViewItem ; private ImageListViewItem mImageListViewItem ;
public ChartImage( string key, string fullPath ) public ChartImage( string key, string fullPath )
{ {
// initialize the ChartImage
mFullPath = fullPath ; mFullPath = fullPath ;
mConfig = Program.dataConfig.data[ key ] ; mConfig = Program.dataConfig.data[ key ] ;
if ( mConfig == null )
mConfig = new JObject() ;
mImage = Image.FromFile( fullPath ) ; 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, // NOTE: Thumbnails are cached by ImageListViewItem GUID, so we reuse these objects,
// instead of creating new ones every time we reload the search results. // instead of creating new ones every time we reload the search results.
mImageListViewItem = new ImageListViewItem( fullPath ) ; mImageListViewItem = new ImageListViewItem( fullPath ) ;
@ -25,6 +40,90 @@ public class ChartImage
mImageListViewItem.Text = caption() ; 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() public string caption()
{ {
string caption = (mConfig != null) ? mConfig["caption"] : null ; string caption = (mConfig != null) ? mConfig["caption"] : null ;

@ -2,6 +2,8 @@ using System ;
using System.IO ; using System.IO ;
using Newtonsoft.Json ; using Newtonsoft.Json ;
using Newtonsoft.Json.Linq ;
using log4net ;
// -------------------------------------------------------------------- // --------------------------------------------------------------------
@ -11,14 +13,52 @@ public class JsonConfig
public JsonConfig( string caption, string fname ) public JsonConfig( string caption, string fname )
{ {
// initialize
ILog logger = LogManager.GetLogger( "startup" ) ;
// load the JSON config // load the JSON config
string data ; string data ;
if ( File.Exists( fname ) ) { 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 ) ; data = File.ReadAllText( fname ) ;
} else } else
data ="{}" ; data ="{}" ;
mData = JsonConvert.DeserializeObject( 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 ; } } public dynamic data { get { return mData ; } }

@ -4,6 +4,7 @@ using System.Collections.Generic ;
using System.Windows.Forms ; using System.Windows.Forms ;
using Manina.Windows.Forms ; using Manina.Windows.Forms ;
using log4net ;
// -------------------------------------------------------------------- // --------------------------------------------------------------------
@ -28,6 +29,9 @@ public partial class MainForm : Form
private void loadChartImages() private void loadChartImages()
{ {
// initialize
ILog logger = LogManager.GetLogger( "startup" ) ;
// locate the chart images // locate the chart images
string dataDir = Path.GetFullPath( Program.dataDir ) ; string dataDir = Path.GetFullPath( Program.dataDir ) ;
IEnumerable<string> files = Directory.EnumerateFiles( 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... // their configuration using full paths for the image files, but at least we will still run...
key = fullPath ; key = fullPath ;
} }
Program.logTraceMsg( String.Format( "Loading image: {0}", key ) ) ; logger.Debug( $"Loading image: {key}" ) ;
mChartImages[ key ] = new ChartImage( key, fullPath ) ; mChartImages[ key ] = new ChartImage( key, fullPath ) ;
} }
} }
private void updateSearchResults( string searchQuery ) 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 // search for matching chart images
List<ChartImage> results = new List<ChartImage>() ; List< Tuple<ChartImage,float> > results = new List<Tuple<ChartImage,float>>() ;
searchQuery = searchQuery.ToLower() ; foreach( ChartImage chartImage in mChartImages.Values ) {
foreach ( ChartImage chartImage in mChartImages.Values ) { float score ;
if ( chartImage.caption().ToLower().IndexOf( searchQuery ) >= 0 ) if ( searchQuery == "" )
results.Add( chartImage ) ; 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 ) private void loadSearchResults( IEnumerable<ChartImage> chartImages )

@ -3,6 +3,8 @@ using System.Windows.Forms ;
using System.IO ; using System.IO ;
using System.Diagnostics ; using System.Diagnostics ;
using log4net.Config ;
// -------------------------------------------------------------------- // --------------------------------------------------------------------
public static class Program public static class Program
@ -11,6 +13,7 @@ public static class Program
private static string mBaseDir ; private static string mBaseDir ;
private static string mDataDir ; private static string mDataDir ;
private static JsonConfig mAppConfig = null ;
private static JsonConfig mDataConfig = null ; private static JsonConfig mDataConfig = null ;
private static JsonConfig mDebugConfig = null ; private static JsonConfig mDebugConfig = null ;
private static MainForm mMainForm = null ; private static MainForm mMainForm = null ;
@ -21,10 +24,17 @@ public static class Program
// initialize // initialize
mBaseDir = Application.StartupPath ; 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 // locate the data directory
mDataDir = System.IO.Path.Combine( mBaseDir , "data" ) ; mDataDir = Path.Combine( mBaseDir , "data" ) ;
if ( ! Directory.Exists( mDataDir ) ) { if ( ! Directory.Exists( mDataDir ) ) {
mDataDir = System.IO.Path.Combine( mBaseDir , "../data" ) ; mDataDir = Path.Combine( mBaseDir , "../data" ) ;
if ( ! Directory.Exists( mDataDir ) ) if ( ! Directory.Exists( mDataDir ) )
mDataDir = mBaseDir ; mDataDir = mBaseDir ;
} }
@ -32,8 +42,16 @@ public static class Program
try { try {
// load the app config // load the app config
// NOTE: It would be nice to be able to load the app config from another location, but it seems string fname0 = Path.GetFileNameWithoutExtension( System.Reflection.Assembly.GetEntryAssembly().Location ) + ".json" ;
// we need to create a new AppDomain to do this, and it doesn't work with Mono :-/ 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" ) ) ; mDataConfig = new JsonConfig( "data config", Path.Combine( mDataDir, "config.json" ) ) ;
// load the debug config // 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 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 showErrorMsg( string msg ) { MessageBox.Show(msg,APP_NAME,MessageBoxButtons.OK,MessageBoxIcon.Error) ; }
public static void logTraceMsg( string msg ) public static JsonConfig appConfig { get { return mAppConfig ; } }
{
// log a trace message
Trace.WriteLine( DateTime.Now.ToString("HH:mm:ss") + " | " + msg ) ;
}
public static JsonConfig dataConfig { get { return mDataConfig ; } } public static JsonConfig dataConfig { get { return mDataConfig ; } }
public static JsonConfig debugConfig { get { return mDebugConfig ; } } public static JsonConfig debugConfig { get { return mDebugConfig ; } }
public static string dataDir { get { return mDataDir ; } } public static string dataDir { get { return mDataDir ; } }

Loading…
Cancel
Save