You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
201 lines
8.2 KiB
201 lines
8.2 KiB
using System ;
|
|
using System.Text ;
|
|
using System.IO ;
|
|
using System.Collections.Generic ;
|
|
using System.Windows.Forms ;
|
|
using System.Drawing ;
|
|
|
|
using Manina.Windows.Forms ;
|
|
using Newtonsoft.Json.Linq ;
|
|
using log4net ;
|
|
|
|
// --------------------------------------------------------------------
|
|
|
|
public class ChartImage
|
|
{
|
|
// NOTE: This table maps shortcuts (e.g. Ctrl-Shift-C) to ChartImage's.
|
|
private static Dictionary< Tuple<Keys,Keys>, List<ChartImage> > mShortcuts = new Dictionary< Tuple<Keys,Keys>, List<ChartImage> >() ;
|
|
|
|
private string mFullPath ;
|
|
private JsonConfig mJsonConfig ;
|
|
private HashSet<string> mKeywords = new HashSet<string>() ;
|
|
private Image mImage ;
|
|
private ImageListViewItem mImageListViewItem ;
|
|
|
|
public ChartImage( string key, string fullPath, dynamic config )
|
|
{
|
|
// initialize the ChartImage
|
|
mFullPath = fullPath ;
|
|
mJsonConfig = new JsonConfig( config ) ;
|
|
mImage = Image.FromFile( fullPath ) ;
|
|
|
|
// parse the shortcut
|
|
ILog logger = LogManager.GetLogger( "shortcuts" ) ;
|
|
string val = mJsonConfig.getStringVal( new string[]{"shortcut"} ) ;
|
|
if ( val != "" ) {
|
|
Tuple<Keys,Keys> shortcut = parseShortcut( val ) ;
|
|
if ( shortcut == null )
|
|
Program.logStartupMsg( "bad-shortcut", $"{key}: {val}" ) ;
|
|
else {
|
|
if ( ! mShortcuts.ContainsKey( shortcut ) )
|
|
mShortcuts[ shortcut ] = new List<ChartImage>() ;
|
|
logger.Info( $"Registering shortcut: {shortcutString(shortcut.Item1,shortcut.Item2)} => ${caption()}" ) ;
|
|
mShortcuts[ shortcut ].Add( this ) ;
|
|
}
|
|
}
|
|
|
|
// prepare for search scoring
|
|
if ( config != null && config["keywords"] != null ) {
|
|
foreach( string kywd in config["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 ) ;
|
|
mImageListViewItem.Tag = this ;
|
|
mImageListViewItem.Text = caption() ;
|
|
}
|
|
|
|
public double getSearchScore( string searchQuery )
|
|
{
|
|
// initialize
|
|
searchQuery = searchQuery.ToUpper() ;
|
|
List< Tuple<string,double> > scores = new List<Tuple<string,double>>() ;
|
|
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
|
|
double exactKeywordMatchScore = Program.appConfig.getDoubleVal( new string[]{"search","exactKeywordMatchScore"}, 10 ) ;
|
|
double leadingPartialKeywordMatchScore = Program.appConfig.getDoubleVal( new string[]{"search","leadingPartialKeywordMatchScore"}, 2 ) ;
|
|
double internalPartialKeywordMatchScore = Program.appConfig.getDoubleVal( new string[]{"search","internalPartialKeywordMatchScore"}, 1.5 ) ;
|
|
double exactCaptionMatchScore = Program.appConfig.getDoubleVal( new string[]{"search","exactCaptionMatchScore"}, 5 ) ;
|
|
double leadingPartialCaptionMatchScore = Program.appConfig.getDoubleVal( new string[]{"search","leadingPartialCaptionMatchScore"}, 1 ) ;
|
|
double internalPartialCaptionMatchScore = Program.appConfig.getDoubleVal( new string[]{"search","internalPartialCaptionMatchScore"}, 0.5 ) ;
|
|
|
|
// look for keyword matches
|
|
foreach ( string kywd in mKeywords ) {
|
|
if ( kywd == searchQuery ) {
|
|
scores.Add( new Tuple<string,double>( "exactKeywordMatch", exactKeywordMatchScore ) ) ;
|
|
continue ;
|
|
}
|
|
int pos = kywd.IndexOf( searchQuery ) ;
|
|
if ( pos == 0 ) {
|
|
scores.Add( new Tuple<string,double>(
|
|
$"leadingPartialKeywordMatch[{kywd}]",
|
|
Math.Min( leadingPartialKeywordMatchScore * searchQuery.Length, exactKeywordMatchScore )
|
|
) ) ;
|
|
} else if ( pos > 0 ) {
|
|
scores.Add( new Tuple<string,double>(
|
|
$"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,double>( "exactCaptionMatch", exactCaptionMatchScore ) ) ;
|
|
else {
|
|
int pos = caption.IndexOf( searchQuery ) ;
|
|
if ( pos == 0 ) {
|
|
scores.Add( new Tuple<string,double>(
|
|
$"leadingPartialCaptionMatch[{caption}]",
|
|
Math.Min( leadingPartialCaptionMatchScore * searchQuery.Length, exactCaptionMatchScore )
|
|
) ) ;
|
|
} else if ( pos > 0 ) {
|
|
scores.Add( new Tuple<string,double>(
|
|
$"internalPartialCaptionMatch[{caption}]",
|
|
Math.Min( internalPartialCaptionMatchScore * searchQuery.Length, exactCaptionMatchScore )
|
|
) ) ;
|
|
}
|
|
}
|
|
|
|
// calculate the total score
|
|
double 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 ;
|
|
}
|
|
|
|
private Tuple<Keys,Keys> parseShortcut( string val )
|
|
{
|
|
// parse the shortcut
|
|
string[] parts = val.ToLower().Split( "-" ) ;
|
|
Keys modifiers = 0 ;
|
|
for ( int i=0 ; i < parts.Length-1 ; ++i ) {
|
|
if ( parts[i] == "ctrl" )
|
|
modifiers |= Keys.Control ;
|
|
else if ( parts[i] == "alt" )
|
|
modifiers |= Keys.Alt ;
|
|
else if ( parts[i] == "shift" )
|
|
modifiers |= Keys.Shift ;
|
|
else
|
|
return null ;
|
|
}
|
|
Keys key ;
|
|
bool rc = Enum.TryParse( parts[parts.Length-1], true, out key ) ;
|
|
if ( ! rc )
|
|
return null ;
|
|
return new Tuple<Keys,Keys>( modifiers, key ) ;
|
|
}
|
|
|
|
public static List<ChartImage> checkShortcut( Keys modifiers, Keys key )
|
|
{
|
|
// check if there are any ChartImage's associated with the specified shortcut
|
|
List<ChartImage> chartImages ;
|
|
if ( ! mShortcuts.TryGetValue( new Tuple<Keys,Keys>( modifiers, key ), out chartImages ) )
|
|
return null ;
|
|
return chartImages ;
|
|
}
|
|
|
|
public static string shortcutString( Keys modifiers, Keys key )
|
|
{
|
|
// return the shortcut as a string
|
|
StringBuilder buf = new StringBuilder() ;
|
|
if ( (modifiers & Keys.Control) != 0 )
|
|
buf.Append( "Ctrl-" ) ;
|
|
if ( (modifiers & Keys.Shift) != 0 )
|
|
buf.Append( "Shift-" ) ;
|
|
if ( (modifiers & Keys.Alt) != 0 )
|
|
buf.Append( "Alt-" ) ;
|
|
buf.Append( key ) ;
|
|
return buf.ToString() ;
|
|
}
|
|
|
|
public string caption()
|
|
{
|
|
// return the ChartImage's caption
|
|
return jsonConfig.getStringVal(
|
|
new string[]{ "caption" },
|
|
Path.GetFileNameWithoutExtension( mFullPath )
|
|
) ;
|
|
}
|
|
|
|
public JsonConfig jsonConfig { get { return mJsonConfig ; } }
|
|
public Image image { get { return mImage ; } }
|
|
public ImageListViewItem imageListViewItem { get { return mImageListViewItem ; } }
|
|
}
|
|
|