Manage your ASL charts, play aids and other documents.
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.
 
 
 
 
asl-charts/src/MainForm.cs

281 lines
12 KiB

using System ;
using System.Text ;
using System.IO ;
using System.Threading ;
using System.Drawing ;
using System.Security ;
using System.Collections.Generic ;
using System.Windows.Forms ;
using Manina.Windows.Forms ;
using log4net ;
// --------------------------------------------------------------------
public partial class MainForm : Form
{
private readonly HashSet<string> mValidImageExtensions = new HashSet<string>{ ".png", ".jpg", ".gif" } ;
private Dictionary<string,ChartImage> mChartImages = new Dictionary<string,ChartImage>() ;
private SplitContainer mSplitter = new SplitContainer() ;
private UserControl mSearchUserControl = new UserControl() ;
private Label mSearchLabel = new Label() ;
private TextBox mSearchQuery = new TextBox() ;
private ImageListView mSearchResults = new ImageListView() ;
private WebBrowser mWebBrowser = new WebBrowser() ;
private Panel mChartImagePanel = new Panel() ;
private PictureBox mChartImagePictureBox = new PictureBox() ;
private string mSearchResultsKey = null ;
private Point? mMouseDragAnchor = null ;
private Tuple<int,int> mScrollDragAnchor ;
private double mCurrZoom, mMinZoom, mMaxZoom ;
private DateTime mLastKeyPressTimeStamp = DateTime.Now ;
private bool mDisableProcessCmdKey = false ;
private bool mSearchQueryTextChangedDisabled = false ;
public MainForm()
{
// initialize the form
InitializeComponent() ;
}
private void loadChartImages()
{
// initialize
ILog logger = LogManager.GetLogger( "startup" ) ;
// build an index of the config entries
HashSet<string> configIndex = new HashSet<string>(
Program.dataConfig.data.ToObject< Dictionary<string,object> >().Keys
) ;
// locate the chart images
string dataDir = Path.GetFullPath( Program.dataDir ) ;
IEnumerable<string> files = Directory.EnumerateFiles(
dataDir, "*.*", SearchOption.AllDirectories
) ;
foreach( string fname in files ) {
// load the next image
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 ) ;
key = key.Replace( "\\", "/" ) ;
if ( 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 ;
}
logger.Debug( $"Loading image: {key}" ) ;
dynamic config = Program.dataConfig.data[ key ] ;
if ( config == null )
Program.logStartupMsg( "unconfigured-image", fname.Substring(dataDir.Length+1) ) ;
else
configIndex.Remove( key ) ;
ChartImage chartImage = new ChartImage( key, fullPath, config ) ;
mChartImages[ key ] = chartImage ;
// add the image to the list
ImageListViewItem item = chartImage.imageListViewItem ;
mSearchResults.Invoke( (MethodInvoker) ( () => mSearchResults.Items.Add( item ) ) ) ;
}
// log any config entries that were unused
foreach( string key in configIndex ) {
if ( key[0] == '_' )
continue ;
Program.logStartupMsg( "unused-config", key ) ;
}
// everything has been loaded, update the UI
this.Invoke( (MethodInvoker) onStartupCompleted ) ;
}
private void onStartupCompleted()
{
// show any startup messages
string unusedConfigs = Program.getStartupMsgs( "unused-config", "WARNING: Found unused configuration entries:" ) ;
string unconfiguredImages = Program.getStartupMsgs( "unconfigured-image", "WARNING: Found image files with no configuration:" ) ;
string badShortcuts = Program.getStartupMsgs( "bad-shortcut", "WARNING: Couldn't parse shortcuts:" ) ;
string otherMsgs = Program.getStartupMsgs( "", "" ) ;
string buf = "" ;
string fname = Path.Combine( Program.resourcesDir, "startup-msgs.html" ) ;
if ( File.Exists( fname ) ) {
buf = File.ReadAllText( fname ) ;
buf = buf.Replace( "{{UNUSED-CONFIGS}}", unusedConfigs ) ;
buf = buf.Replace( "{{UNCONFIGURED-IMAGES}}", unconfiguredImages ) ;
buf = buf.Replace( "{{BAD-SHORTCUTS}}", badShortcuts ) ;
buf = buf.Replace( "{{OTHER-MESSAGES}}", otherMsgs ) ;
}
mWebBrowser.DocumentText = buf ;
// allow searches
mSearchLabel.Enabled = true ;
mSearchQuery.Enabled = true ;
mSearchQuery.Focus() ;
}
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< Tuple<ChartImage,double> > results = new List<Tuple<ChartImage,double>>() ;
foreach( ChartImage chartImage in mChartImages.Values ) {
double score ;
if ( searchQuery == "" )
score = 0 ;
else {
score = chartImage.getSearchScore( searchQuery ) ;
if ( score <= 0 )
continue ;
}
results.Add( new Tuple<ChartImage,double>( chartImage, score ) ) ;
}
// sort the search results
results.Sort( (lhs, rhs) => {
if ( lhs.Item2 < rhs.Item2 )
return +1 ;
else if ( lhs.Item2 > rhs.Item2 )
return -1 ;
// NOTE: We also sort by caption/filename to handle things like "foo-1" vs. "foo-2".
return String.Compare( lhs.Item1.caption(), rhs.Item1.caption() ) ;
} ) ;
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, searchQuery, false ) ;
}
public void loadSearchResults( IEnumerable<ChartImage> chartImages, string key, bool clearSearchQuery )
{
// check if we need to reload the search results
if ( key == mSearchResultsKey )
return ;
// clear the search results
mSearchResults.SuspendLayout() ;
mSearchResults.Items.Clear() ;
mSearchResults.ResumeLayout( true ) ;
// load the search results
mSearchResults.SuspendLayout() ;
int nItems = 0 ;
foreach( ChartImage chartImage in chartImages ) {
ImageListViewItem item = chartImage.imageListViewItem ;
mSearchResults.Items.Add( item ) ;
if ( nItems++ == 0 && key != "" )
item.Selected = true ;
}
mSearchResults.ResumeLayout( true ) ;
mSearchResultsKey = key ;
// clear the search query
if ( clearSearchQuery ) {
mSearchQueryTextChangedDisabled = true ;
mSearchQuery.Text = "" ;
mSearchQueryTextChangedDisabled = false ;
}
}
public void showShortcuts()
{
// load the report template
string fname = Path.Combine( Program.resourcesDir, "shortcuts.html" ) ;
if ( ! File.Exists( fname ) ) {
Program.showErrorMsg( "Can't find the shortcuts report template." ) ;
return ;
}
string htmlBuf = File.ReadAllText( fname ) ;
// FUDGE! We also log the shortcuts, in case WebBrowser is not working e.g. on Mono :-/
ILog logger = LogManager.GetLogger( "shortcuts" ) ;
// generate HTML fragments for each type of shortcut
Dictionary<Type,List<Shortcut>> registeredShortcuts = Shortcut.getRegisteredShortcuts() ;
string makeShortcutsHtml( string caption, Type shortcutType ) {
logger.Info( $"SHORTCUTS: {caption}" ) ;
List<Shortcut> shortcuts ;
if ( ! registeredShortcuts.TryGetValue( shortcutType, out shortcuts ) )
return "" ;
shortcuts.Sort( (lhs, rhs) => {
int rc = String.Compare( lhs.getKeyCode().ToString().ToLower(), rhs.getKeyCode().ToString().ToLower() ) ;
if ( rc != 0 )
return rc ;
int nLhsModifiers = lhs.countModifiers() ;
int nRhsModifiers = rhs.countModifiers() ;
if ( nLhsModifiers < nRhsModifiers )
return -1 ;
else if ( nLhsModifiers > nRhsModifiers )
return +1 ;
else
return 0 ;
} ) ;
StringBuilder buf = new StringBuilder() ;
buf.Append( $"<div class='header'> {caption} </div>" ) ;
buf.Append( "<table class='shortcuts'>" ) ;
foreach( Shortcut shortcut in shortcuts ) {
logger.Info( $"- {shortcut} => {shortcut.textDescription()}" ) ;
buf.Append( "<tr>" ) ;
buf.Append( "<td class='key'>" + SecurityElement.Escape(shortcut.ToString()) ) ;
buf.Append( "<td class='val'>" + shortcut.htmlDescription() ) ;
}
buf.Append( "</table>" ) ;
return buf.ToString() ;
}
string htmlBuf2a = makeShortcutsHtml( "Search queries", typeof(SearchQueryShortcut) ) ;
string htmlBuf2b = makeShortcutsHtml( "Images", typeof(ChartImageShortcut) ) ;
// generate the shortcuts report
if ( htmlBuf2a != "" && htmlBuf2b != "" ) {
mWebBrowser.DocumentText = htmlBuf.Replace( "{{SHORTCUTS}}",
"<table><tr><td style='padding-right:2em;'>" + htmlBuf2a + "<td>" + htmlBuf2b + "</table>"
) ;
} else if ( htmlBuf2a != "" )
mWebBrowser.DocumentText = htmlBuf.Replace( "{{SHORTCUTS}}", htmlBuf2a ) ;
else if ( htmlBuf2b != "" )
mWebBrowser.DocumentText = htmlBuf.Replace( "{{SHORTCUTS}}", htmlBuf2b ) ;
else
mWebBrowser.DocumentText = htmlBuf.Replace( "{{SHORTCUTS}}", "There are no shortcuts defined." ) ;
mWebBrowser.BringToFront() ;
}
public void setSearchQuery( string s ) { mSearchQuery.Text = s ; }
private void setChartImagePanelScrollPos( int? hscrollPos, int? vscrollPos )
{
// FUDGE! These need to be set twice to take effect on Windows :-/
if ( hscrollPos != null ) {
mChartImagePanel.HorizontalScroll.Value = hscrollPos.Value ;
mChartImagePanel.HorizontalScroll.Value = hscrollPos.Value ;
}
if ( vscrollPos != null ) {
mChartImagePanel.VerticalScroll.Value = vscrollPos.Value ;
mChartImagePanel.VerticalScroll.Value = vscrollPos.Value ;
}
}
}