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 { private string mFullPath ; private JsonConfig mJsonConfig ; private HashSet mKeywords = new HashSet() ; private Image mImage ; private ImageListViewItem mImageListViewItem ; public ChartImage( string key, string fullPath, dynamic config ) { // initialize the ChartImage mFullPath = fullPath ; mJsonConfig = new JsonConfig( config ) ; mImage = null ; // nb: we load this on demand // parse the shortcut ILog logger = LogManager.GetLogger( "shortcuts" ) ; string val = mJsonConfig.getStringVal( new string[]{"shortcut"} ) ; if ( val != "" ) { Keys? keys = Shortcut.parseShortcutString( val ) ; if ( keys == null ) Program.logStartupMsg( "bad-shortcut", $"{key}: {val}" ) ; else { Shortcut shortcut = Shortcut.findRegisteredShortcut( keys.Value ) ; if ( shortcut == null ) { shortcut = new ChartImageShortcut( keys.Value ) ; Shortcut.registerShortcut( shortcut ) ; } if ( shortcut is ChartImageShortcut ) { logger.Info( $"Registering image shortcut: {shortcut} => {caption()}" ) ; ((ChartImageShortcut)shortcut).addChartImage( this ) ; } else Program.logStartupMsg( "bad-shortcut", $"Found duplicate: {shortcut}" ) ; } } // 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 > scores = new List>() ; 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( "exactKeywordMatch", exactKeywordMatchScore ) ) ; continue ; } int pos = kywd.IndexOf( searchQuery ) ; if ( pos == 0 ) { scores.Add( new Tuple( $"leadingPartialKeywordMatch[{kywd}]", Math.Min( leadingPartialKeywordMatchScore * searchQuery.Length, exactKeywordMatchScore ) ) ) ; } else if ( pos > 0 ) { scores.Add( new Tuple( $"internalPartialKeywordMatch[{kywd}]", Math.Min( internalPartialKeywordMatchScore * searchQuery.Length, exactKeywordMatchScore ) ) ) ; } } // look for caption matches string caption = this.caption().ToUpper() ; if ( searchQuery == caption ) scores.Add( new Tuple( "exactCaptionMatch", exactCaptionMatchScore ) ) ; else { int pos = caption.IndexOf( searchQuery ) ; if ( pos == 0 ) { scores.Add( new Tuple( $"leadingPartialCaptionMatch[{caption}]", Math.Min( leadingPartialCaptionMatchScore * searchQuery.Length, exactCaptionMatchScore ) ) ) ; } else if ( pos > 0 ) { scores.Add( new Tuple( $"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 ; } public string caption() { // return the ChartImage's caption return jsonConfig.getStringVal( new string[]{ "caption" }, Path.GetFileNameWithoutExtension( mFullPath ) ) ; } public Image image() { // return the image data // NOTE: It would be nice to reuse the ImageListView cache, but it only seems to cache thumbnails. if ( mImage == null ) { try { mImage = Image.FromFile( mFullPath ) ; } catch( Exception ex ) { // NOTE: This crashes on Linux, but we can live with that - ChartImage's are created by iterating // over files in the data directory, so if we can't load the file, it'll be because the file was deleted // after startup, or a permissions problem i.e. things we don't really need to worry about. Program.showErrorMsg( $"Can't load image file:\n {mFullPath}\n\n{ex}" ) ; } } return mImage ; } public JsonConfig jsonConfig { get { return mJsonConfig ; } } public ImageListViewItem imageListViewItem { get { return mImageListViewItem ; } } }