using System ;
using System.Text ;
using System.Drawing ;
using System.IO ;
using System.Threading ;
using System.Runtime.InteropServices ;
using System.Collections.Generic ;
using System.Windows.Forms ;
using System.Diagnostics ;

using Manina.Windows.Forms ;
using Newtonsoft.Json.Linq ;
using log4net ;

// --------------------------------------------------------------------

public partial class MainForm : Form
{
    private void InitializeComponent()
    {
        // initialize the form
        this.Text = Program.APP_NAME ;
        this.MinimumSize = new Size( 800, 500 ) ;
        this.Icon = Icon.ExtractAssociatedIcon( // nb: doesn't work on Linux
            Process.GetCurrentProcess().MainModule.FileName
        ) ;

        // initialize the main splitter
        this.mSplitter.Orientation = Orientation.Horizontal ;
        this.mSplitter.Location = new Point( 0, 0 ) ;
        this.mSplitter.Size = new Size( this.Width, this.Height ) ;
        this.mSplitter.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Bottom | AnchorStyles.Right ;
        this.mSplitter.FixedPanel = FixedPanel.Panel1 ;
        this.mSplitter.Panel1MinSize = 140 ;
        this.mSplitter.Panel2MinSize = 300 ;
        this.mSplitter.SplitterDistance = this.mSplitter.Panel1MinSize ;
        this.mSplitter.Panel1.BackColor = Color.White ;
        this.mSplitter.Panel2.BackColor = Color.White ;
        this.mSplitter.BackColor = SystemColors.Control ;
        this.mSplitter.SplitterWidth = 2 ;
        this.mSplitter.IsSplitterFixed = true ;
        this.Controls.Add( mSplitter ) ;

        // initialize the top pane (search)
        int margin = 2 ;
        this.mSearchUserControl.Size = new Size( 100, mSplitter.Panel1.Size.Height ) ;
        this.mSearchUserControl.BackColor = Color.FromArgb( 255, 0xF8, 0xF8, 0xFF ) ;
        this.mSearchLabel.Location = new Point( margin, margin ) ;
        this.mSearchLabel.Size = new Size( mSearchUserControl.Width - 2*margin, 15 ) ;
        this.mSearchLabel.Font = new Font( mSearchLabel.Font, FontStyle.Bold ) ;
        this.mSearchLabel.Text = "Search for:" ;
        this.mSearchUserControl.Controls.Add( mSearchLabel ) ;
        this.mSearchQuery.Location = new Point( margin, mSearchLabel.Location.Y + mSearchLabel.Height ) ;
        this.mSearchQuery.Width = mSearchLabel.Width ;
        this.mSearchQuery.BorderStyle = BorderStyle.FixedSingle ;
        this.mSearchUserControl.Controls.Add( mSearchQuery ) ;
        LinkLabel showShortcutsLabel = new LinkLabel() ;
        showShortcutsLabel.Text = "Shortcuts" ;
        showShortcutsLabel.AutoSize = true ;
        showShortcutsLabel.Location = new Point( margin, mSearchUserControl.Height-margin-showShortcutsLabel.Height ) ;
        showShortcutsLabel.LinkClicked += delegate( object sender, LinkLabelLinkClickedEventArgs e ) { showShortcuts() ; } ;
        this.mSearchUserControl.Controls.Add( showShortcutsLabel ) ;
        this.mSplitter.Panel1.Controls.Add( mSearchUserControl ) ;
        this.mSearchResults.View = Manina.Windows.Forms.View.Gallery ;
        this.mSearchResults.SetRenderer( new AppImageListView.AppImageListViewRenderer() ) ;
        this.mSearchResults.CacheMode = CacheMode.Continuous ;
        this.mSearchResults.Location = new Point( mSearchUserControl.Location.X + mSearchUserControl.Width, 0 ) ;
        Size searchResultsSize = new Size( mSplitter.Width - (mSearchQuery.Location.X + mSearchQuery.Width) - margin - 7, mSplitter.Panel1MinSize ) ;
        this.mSearchResults.Size = searchResultsSize ;
        this.mSearchResults.BorderStyle = BorderStyle.None ;
        this.mSearchResults.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom ;
        this.mSearchResults.BackColor = mSearchUserControl.BackColor ;
        this.mSplitter.Panel1.Controls.Add( mSearchResults ) ;
        this.mSearchResults.Size = searchResultsSize ; // FUDGE! need to do this again :shrug:

        // initialize the bottom pane (image viewer)
        // FIXME! We will probably need something better to render the images, but this will do, for now...
        this.mChartImagePictureBox.SizeMode = PictureBoxSizeMode.Zoom ;
        // FUDGE! We need to put the PictureBox inside a Panel in order to get the h-scrollbar to appear :-/
        mChartImagePanel.Anchor = AnchorStyles.Top | AnchorStyles.Left ;
        mChartImagePanel.AutoScroll = true ;
        mChartImagePanel.Controls.Add( mChartImagePictureBox ) ;
        mSplitter.Panel2.Controls.Add( mChartImagePanel ) ;

        // initialize the bottom pane (startup messages)
        this.mWebBrowser.AutoSize = true ;
        this.mWebBrowser.Dock = DockStyle.Fill ;
        mSplitter.Panel2.Controls.Add( mWebBrowser ) ;

        // show the startup page
        string fname = Path.Combine( Program.resourcesDir, "startup.html" ) ;
        if ( File.Exists( fname ) ) {
            string buf = File.ReadAllText( fname ) ;
            fname = Path.Combine( Program.resourcesDir, "spinner.gif" ) ;
            mWebBrowser.DocumentText = buf.Replace( "{{SPINNER-URL}}", fname ) ;
        }

        // initialize handlers
        this.Load += new EventHandler( this.MainForm_Load ) ;
        this.FormClosing += new FormClosingEventHandler( this.MainForm_FormClosing ) ;
        this.Resize += new EventHandler( this.MainForm_Resize ) ;
        this.mSearchQuery.KeyPress += this.SearchQuery_KeyPress ;
        this.mSearchQuery.TextChanged += new EventHandler( this.SearchQuery_TextChanged ) ;
        this.mSearchResults.SelectionChanged += new EventHandler( this.SearchResults_SelectionChanged ) ;

        mChartImagePictureBox.MouseDown += new MouseEventHandler( this.ChartImagePictureBox_MouseDown ) ;
        mChartImagePictureBox.MouseMove += new MouseEventHandler( this.ChartImagePictureBox_MouseMove ) ;
        mChartImagePictureBox.MouseUp += new MouseEventHandler( this.ChartImagePictureBox_MouseUp ) ;
        // NOTE: We also register mouse event handlers for the panel, so that dragging will work if the user clicks
        // inside the image panel, but not on the image itself (i.e. when the image is smaller than the panel).
        mChartImagePanel.MouseDown += new MouseEventHandler( this.ChartImagePictureBox_MouseDown ) ;
        mChartImagePanel.MouseMove += new MouseEventHandler( this.ChartImagePictureBox_MouseMove ) ;
        mChartImagePanel.MouseUp += new MouseEventHandler( this.ChartImagePictureBox_MouseUp ) ;
    }

    public bool preFilterMessage( ref Message msg )
    {
        // FUDGE! On Windows, when we handle Ctrl/Shift-ScrollWheel, Windows still wants to scroll the ChartImage.
        // To stop this, we disable mouse wheel messages and handle everything ourself :-/
        if ( msg.Msg == 0x020A ) { // nb: WM_MOUSEWHEEL
            // figure out how much to scroll
            ulong wParam = (ulong)msg.WParam & 0xFFFFFFFF ;
            int nLines = (int)( wParam >> 16 ) ;
            if ( (nLines & 0x8000) != 0 )
                nLines = nLines - 0x10000 ; // nb: negate the 32-bit value
            nLines = nLines * SystemInformation.MouseWheelScrollLines / 120 ;
            if ( (wParam & 0xFFFF) == 0 )
                nLines = - nLines ; // nb: no virtual keys are down
            // scroll the main form
            Point pos = Cursor.Position ;
            pos = mChartImagePanel.PointToClient( pos ) ;
            if ( pos.X >= 0 && pos.X < mChartImagePanel.Width && pos.Y >= 0 && pos.Y < mChartImagePanel.Height ) {
                if ( (Control.ModifierKeys & Keys.Shift) != 0 )
                    scrollMainForm( nLines, true ) ;
                else if ( (Control.ModifierKeys & Keys.Control) != 0 )
                    adjustZoom( (nLines > 0) ? +0.05 : -0.05, pos ) ;
                else
                    scrollMainForm( nLines, false ) ;
                return true ;
            }
        }
        return false ;
    }

    protected override bool ProcessCmdKey( ref Message msg, Keys keyData )
    {
        // prevent recursive calls
        if ( mDisableProcessCmdKey )
            return false ;

        // initialize
        Keys modifierKeys = Control.ModifierKeys & (Keys.Control | Keys.Shift | Keys.Alt) ;
        Keys keyCode = keyData & ~(Keys.Control | Keys.Shift | Keys.Alt) ;

        // handle search result selection
        if ( (keyData & Keys.Control) == 0 && (keyCode == Keys.Left || keyCode == Keys.Right) ) {
            // send the keypress to the search results
            // NOTE: We could also respond to Up/Down and scroll the ChartImage vertically,
            // but that would be confusing, given that Left/Right selects a search result.
            if ( mSearchResults.Items.Count > 1 && mSearchResults.SelectedItems.Count > 0 ) {
                // check for wrap-around
                ImageListViewItem selItem = mSearchResults.SelectedItems[0] ;
                if ( keyCode == Keys.Left && Object.ReferenceEquals( selItem, mSearchResults.Items[0] ) ) {
                    mSearchResults.setSelection( mSearchResults.Items.Count-1 ) ;
                    return true ;
                }
                if ( keyCode == Keys.Right && Object.ReferenceEquals( selItem, mSearchResults.Items[mSearchResults.Items.Count-1] ) ) {
                    mSearchResults.setSelection( 0 ) ;
                    return true ;
                }
            }
            mSearchResults.Focus() ;
            mDisableProcessCmdKey = true ;
            SendKeys.SendWait( "{" + keyCode.ToString() + "}" ) ;
            mDisableProcessCmdKey = false ;
            return true ;
        }

        // handle ChartImage zoom and panning
        if ( (keyData & Keys.Control) == 0 && (keyCode == Keys.Up || keyCode == Keys.Down) ) {
            // FUDGE! Windows seems to be mapping Ctrl-Up/Down to PageUp/Down before even preFilterMessage()
            //  sees the message. It would nice to be able to scroll the ChartImage using Ctrl-Left/Right/Up/Down,
            //  but since Ctrl-Left/Right will be rarely used, we can live with doing things like this :-/
            if ( keyCode == Keys.Up ) {
                scrollMainForm( -3, false ) ;
                return true ;
            }
            if ( keyCode == Keys.Down ) {
                scrollMainForm( +3, false ) ;
                return true ;
            }
        }
        if ( (keyData & Keys.Control) != 0 ) {
            if ( keyCode == Keys.Add || keyCode == Keys.Oemplus ) {
                adjustZoom( +0.05, null ) ;
                return true ;
            }
            if ( keyCode == Keys.Subtract || keyCode == Keys.OemMinus ) {
                adjustZoom( -0.05, null ) ;
                return true ;
            }
            if ( keyCode == Keys.D0 ) {
                adjustZoom( null, null ) ;
                return true ;
            }
            if ( keyCode == Keys.Left ) {
                scrollMainForm( -3, true ) ;
                return true ;
            }
            if ( keyCode == Keys.Right ) {
                scrollMainForm( +3, true ) ;
                return true ;
            }
        }
        if ( keyCode == Keys.PageUp || keyCode == Keys.PageDown ) {
            if ( mChartImagePictureBox.Image == null )
                return false ;
            double ratio = (double)mChartImagePanel.Height / (double)mChartImagePictureBox.Height ;
            VScrollProperties vscroll = mChartImagePanel.VerticalScroll ;
            int scrollRange = vscroll.Maximum - vscroll.Minimum ;
            int pageSize = (int)( ratio * scrollRange * 0.8 ) ;
            if ( keyCode == Keys.PageUp )
                pageSize = - pageSize ;
            int scrollY = vscroll.Value + pageSize ;
            scrollChartImagePanel( null, scrollY ) ;
            return true ;
        }
        if ( keyCode == Keys.Home ) {
            int? scrollX=null, scrollY=null ;
            if ( (modifierKeys & Keys.Control) != 0 )
                scrollX = scrollY = 0 ;
            if ( (modifierKeys & Keys.Shift) != 0 )
                scrollX = 0 ;
            else
                scrollY = 0 ;
            setChartImagePanelScrollPos( scrollX, scrollY ) ;
            return true ;
        }
        if ( keyCode == Keys.End ) {
            int? scrollX=null, scrollY=null ;
            if ( (modifierKeys & Keys.Control) != 0 ) {
                scrollX = 0 ;
                scrollY = ChartImagePanel_MaxScrollY() ;
            }
            if ( (modifierKeys & Keys.Shift) != 0 )
                scrollX = ChartImagePanel_MaxScrollX() ;
            else
                scrollY = ChartImagePanel_MaxScrollY() ;
            setChartImagePanelScrollPos( scrollX, scrollY ) ;
            return true ;
        }

        // check if the user wants to clear the search query
        if ( keyCode == Keys.Escape ) {
            loadSearchResults( null, "", true ) ;
            return true ;
        }

        // FUDGE! Stop Windows from beeping :-/
        if ( keyCode == Keys.Return )
            return true ;

        // NOTE: The remaining keypress handling is only done after we've finished initialization.
        if ( ! mIsReady )
            return false ;

        // handle shortcuts
        if ( Shortcut.handleShortcut( keyData ) )
            return true ;

        // check if we should update the search query
        // FUDGE! This is incredibly annoying :-/ We get a *key code*, which is not the same as an ASCII character,
        // so we only recognize a very limited set of characters here. Other characters will still be recognized
        // and added to the search query *if* the textbox has focus (since the code here won't fire, we end up
        // flagging the message as "not processed", and the textbox will receive the message normally), so it's
        // only an issue when the user is typing a search query and something else has focus. We could perhaps
        // translate things like Keys.OemXXX key codes here, but it's not worth the effort, since search queries
        // will almost always consist of only letters and/or numbers.
        int ch = (int) keyCode ;
        string sendKey = "" ;
        if ( (ch >= 65 && ch <= 90) || ch == 32 )
            sendKey = ((char)ch).ToString().ToLower() ;
        else if ( ch >= 48 && ch <= 57 )
            sendKey = keyCode.ToString().Substring( 1 ) ; // nb: this will also detect Shift 0-9 :-/
        else if ( keyCode == Keys.Back )
            sendKey = "{BKSP}" ;
        if ( sendKey != "" ) {
            // send the keypress to the search query textbox
            mSearchQuery.Focus() ;
            mDisableProcessCmdKey = true ;
            SendKeys.SendWait( sendKey ) ;
            mDisableProcessCmdKey = false ;
            return true ;
        }

        return false ;
    }

    private int ChartImagePanel_MaxScrollX() { return Math.Max( 0, mChartImagePanel.HorizontalScroll.Maximum-mChartImagePanel.Width) + 17 ; }
    private int ChartImagePanel_MaxScrollY() { return Math.Max( 0, mChartImagePanel.VerticalScroll.Maximum-mChartImagePanel.Height) + 17 ; }

    private void MainForm_Load( object sender, EventArgs e )
    {
        // restore the window state
        string windowStateStr = Program.appConfig.getStringVal( new string[]{"MainWindow","WindowState"} ) ;
        if ( windowStateStr != "" ) {
            FormWindowState windowState ;
            if ( Enum.TryParse( windowStateStr, out windowState ) ) {
                DesktopBounds = new Rectangle(
                    Program.appConfig.getIntVal( new string[]{"MainWindow","X"}, 50 ),
                    Program.appConfig.getIntVal( new string[]{"MainWindow","Y"}, 50 ),
                    Program.appConfig.getIntVal( new string[]{"MainWindow","Width"}, MinimumSize.Width ),
                    Program.appConfig.getIntVal( new string[]{"MainWindow","Height"}, MinimumSize.Height )
                ) ;
                if ( windowState == FormWindowState.Maximized )
                    WindowState = windowState ;
            }
        }

        // initialize
        doMainFormResize( null ) ;

        // load the chart images
        // NOTE: This can take some time, so we update the UI as they are loaded.
        mSearchLabel.Enabled = false ;
        mSearchQuery.Enabled = false ;
        Thread thread = new Thread( () => loadChartImages() ) ;
        thread.Start() ;
    }

    private void MainForm_FormClosing( object sender, FormClosingEventArgs e )
    {
        // save the window state
        // NOTE: RestoreBounds doesn't work properly under Mono :-/
        Rectangle rc = (WindowState == FormWindowState.Maximized) ? RestoreBounds : DesktopBounds ;
        Program.appConfig.setIntVal( new string[]{"MainWindow","X"}, rc.X, false ) ;
        Program.appConfig.setIntVal( new string[]{"MainWindow","Y"}, rc.Y, false ) ;
        Program.appConfig.setIntVal( new string[]{"MainWindow","Width"}, rc.Width, false ) ;
        Program.appConfig.setIntVal( new string[]{"MainWindow","Height"}, rc.Height, false ) ;
        Program.appConfig.setStringVal( new string[]{"MainWindow","WindowState"}, WindowState.ToString() ) ;
    }

    private void MainForm_Resize( object sender, EventArgs e ) { doMainFormResize( null ) ; }

    private void doMainFormResize( Point? clientMousePos )
    {
        // check if we are showing a ChartImage
        Image img = mChartImagePictureBox.Image ;
        if ( img == null ) {
            mChartImagePanel.Visible = false ;
            return ;
        }
        mChartImagePanel.Visible = true ;

        // resize the ChartImage panel to fill the available space
        mChartImagePanel.Size = new Size(
            mSplitter.Panel2.Width - (Program.isMono?8:17),
            mSplitter.Panel2.Height - (Program.isMono?28:40)
        ) ;

        // figure out how large to show the ChartImage
        int width, height ;
        int margin = 5 ;
        int availableWidth = mChartImagePanel.Width - 2*margin ;
        double zoom = Math.Max( Math.Min( mCurrZoom, mMaxZoom ), mMinZoom ) ;
        width = (int)( img.Width * zoom + 0.5 ) ;
        height = (int)( img.Height * zoom + 0.5 ) ;

        // check if the v-scrollbar is showing
        if ( height >= mChartImagePanel.Height ) {
            // yup - adjust the width
            int sbWidth = SystemInformation.VerticalScrollBarWidth ;
            availableWidth -= sbWidth ;
            width -= sbWidth ;
        }

        // remember the current scroll positions
        HScrollProperties hscroll = mChartImagePanel.HorizontalScroll ;
        int prevScrollPosX = hscroll.Value ;
        int prevMaxScrollX = ChartImagePanel_MaxScrollX() ;
        double prevRelScrollPosX = (double)prevScrollPosX / (double)prevMaxScrollX ;
        VScrollProperties vscroll = mChartImagePanel.VerticalScroll ;
        int prevScrollPosY = vscroll.Value ;
        int prevMaxScrollY = ChartImagePanel_MaxScrollY() ;
        double prevRelScrollPosY = (double)prevScrollPosY / (double)prevMaxScrollY ;

        // resize and position the ChartImage
        setChartImagePanelScrollPos( 0, 0 ) ;
        Size prevChartImagePictureBoxSize = new Size( mChartImagePictureBox.Size.Width, mChartImagePictureBox.Size.Height ) ;
        mChartImagePictureBox.Size = new Size( width, height ) ;
        if ( width < availableWidth )
            mChartImagePictureBox.Location = new Point( margin + (availableWidth - width)/2, margin ) ;
        else
            mChartImagePictureBox.Location = new Point( margin, margin ) ;

        // restore the scroll positions
        int newScrollPosX, newScrollPosY ;
        int newMaxScrollX = ChartImagePanel_MaxScrollX() ;
        int newMaxScrollY = ChartImagePanel_MaxScrollY() ;
        if ( clientMousePos == null ) {
            // maintain the relative scroll positions
            newScrollPosX = (int)( prevRelScrollPosX * newMaxScrollX + 0.5 ) ;
            newScrollPosY = (int)( prevRelScrollPosY * newMaxScrollY + 0.5 ) ;
        } else {
            // keep whatever's under the mouse in the same place
            int absX = prevScrollPosX + clientMousePos.Value.X ;
            double scaling = (double)mChartImagePictureBox.Size.Width / (double)prevChartImagePictureBoxSize.Width ;
            newScrollPosX = (int)( absX * scaling + 0.5) - clientMousePos.Value.X ;
            int absY = prevScrollPosY + clientMousePos.Value.Y ;
            scaling = (double)mChartImagePictureBox.Size.Height / (double)prevChartImagePictureBoxSize.Height ;
            newScrollPosY = (int)( absY * scaling + 0.5) - clientMousePos.Value.Y ;
        }
        scrollChartImagePanel( newScrollPosX, newScrollPosY ) ;
    }

    private void ChartImagePictureBox_MouseDown( object sender, MouseEventArgs e )
    {
        // flag that the user has started to drag the ChartImage
        // FUDGE! The mouse position as reported by the MouseEventArgs bounces around weirdly during mouse move,
        // so we read the mouse position directly :shrug:
        mMouseDragAnchor = Cursor.Position ;
        mScrollDragAnchor = new Tuple<int,int>( mChartImagePanel.HorizontalScroll.Value, mChartImagePanel.VerticalScroll.Value ) ;
        Cursor.Current = Cursors.Hand ;
    }

    private void ChartImagePictureBox_MouseMove( object sender, MouseEventArgs e )
    {
        // check if a drag is in progress
        if ( mMouseDragAnchor == null )
            return ;
        Point pos = Cursor.Position ;
        int deltaX = pos.X - mMouseDragAnchor.Value.X ;
        int deltaY = pos.Y - mMouseDragAnchor.Value.Y ;
        int scrollX = mScrollDragAnchor.Item1 - deltaX ;
        int scrollY = mScrollDragAnchor.Item2 - deltaY ;
        scrollChartImagePanel( scrollX, scrollY ) ;
    }

    private void ChartImagePictureBox_MouseUp( object sender, MouseEventArgs e )
    {
        // end the drag
        mMouseDragAnchor = null ;
        mScrollDragAnchor = null ;
        Cursor.Current = Cursors.Default ;
    }

    private void scrollChartImagePanel( int? scrollX, int? scrollY )
    {
        // update the scroll position
        if ( scrollX != null ) {
            int maxScrollX = ChartImagePanel_MaxScrollX() ;
            int scrollPos = Math.Max( 0, Math.Min( maxScrollX, scrollX.Value ) ) ;
            setChartImagePanelScrollPos( scrollPos, null ) ;
        }
        if ( scrollY != null ) {
            int maxScrollY = ChartImagePanel_MaxScrollY() ;
            int scrollPos = Math.Max( 0, Math.Min( maxScrollY, scrollY.Value ) ) ;
            setChartImagePanelScrollPos( null, scrollPos ) ;
        }
    }

    private void SearchResults_SelectionChanged( object sender, EventArgs e )
    {
        // figure out the initial zoom
        Debug.Assert( mSearchResults.SelectedItems.Count == 1 ) ;
        double userZoom = Program.dataConfig.getDoubleVal( new string[]{"_DefaultZoom"}, 1.0 ) ;
        double minZoom = Program.dataConfig.getDoubleVal( new string[]{"_MinZoom"}, 0.2*userZoom ) ;
        double maxZoom = Program.dataConfig.getDoubleVal( new string[]{"_MaxZoom"}, 2*userZoom ) ;
        if (  mSearchResults.SelectedItems.Count > 0 ) {
            ChartImage chartImage = (ChartImage) mSearchResults.SelectedItems[0].Tag ;
            mChartImagePictureBox.Image = chartImage.image() ;
            userZoom = chartImage.jsonConfig.getDoubleVal( new string[]{"defaultZoom"}, userZoom ) ;
            minZoom = chartImage.jsonConfig.getDoubleVal( new string[]{"minZoom"}, minZoom ) ;
            maxZoom = chartImage.jsonConfig.getDoubleVal( new string[]{"maxZoom"}, maxZoom ) ;
        } else {
            mChartImagePictureBox.Image = null ;
        }
        ILog logger = LogManager.GetLogger( "zoom" ) ;
        logger.Info( $"Setting initial image zoom: {userZoom:0.00} (min={minZoom:0.00}, max={maxZoom:0.00})" ) ;
        mCurrZoom = mDefaultZoom = userZoom ;
        mMinZoom = minZoom ;
        mMaxZoom = maxZoom ;

        // show the selected chart image
        mChartImagePanel.BringToFront() ;
        setChartImagePanelScrollPos( 0, 0 ) ;
        doMainFormResize( null ) ;
    }

    private void SearchQuery_KeyPress( object sender, KeyPressEventArgs e )
    {
        // check if it's time to start a new search query
        int ttl = Program.appConfig.getIntVal( new string[]{"SearchQueryTTL"}, 5 ) ;
        if ( (DateTime.Now - mLastKeyPressTimeStamp).TotalSeconds > ttl ) {
            setSearchQuery( "", false ) ;
        }
        mLastKeyPressTimeStamp = DateTime.Now ;
    }

    private void SearchQuery_TextChanged( object sender, EventArgs e )
    {
        // update the search results
        if ( mSearchQueryTextChangedDisabled )
            return ;
        updateSearchResults( mSearchQuery.Text.Trim() ) ;
    }

}
