#include "ImageCanvas.h"
#include "PlotCanvasImageAnalysis.h"
#include "PropViewFrame.h"
#include <limits>

using namespace std;

//=============================================================================
//============== Implementation helper functions ==============================
//=============================================================================
//-----------------------------------------------------------------------------
inline long GetNextLongParam( const wxString& token )
//-----------------------------------------------------------------------------
{
	long val = 0;
	token.ToLong( &val );
	return val;
}

//-----------------------------------------------------------------------------
inline double GetNextDoubleParam( const wxString& token )
//-----------------------------------------------------------------------------
{
	double val = 0;
	token.ToDouble( &val );
	return val;
}

//=============================================================================
//============== Implementation ImageCanvas ===================================
//=============================================================================
BEGIN_EVENT_TABLE(ImageCanvas, DrawingCanvas)
  EVT_PAINT(ImageCanvas::OnPaint)
  EVT_KEY_DOWN(ImageCanvas::OnKeyDown)
  EVT_LEFT_DCLICK(ImageCanvas::OnLeftDblClick)
  EVT_LEFT_DOWN(ImageCanvas::OnLeftDown)
  EVT_MOTION(ImageCanvas::OnMotion)
  EVT_MOUSEWHEEL(ImageCanvas::OnMouseWheel)
  EVT_RIGHT_DOWN(ImageCanvas::OnRightDown)
  EVT_RIGHT_UP(ImageCanvas::OnRightUp)
  EVT_MENU(miPopupFitToScreen, ImageCanvas::OnPopUpFitToScreen)
  EVT_MENU(miPopupFullScreen, ImageCanvas::OnPopUpFullScreen)
  EVT_MENU(miPopupShowPerformanceWarnings, ImageCanvas::OnPopUpShowPerformanceWarnings)
  EVT_MENU(miPopupShowRequestInfoOverlay, ImageCanvas::OnPopUpShowRequestInfoOverlay)
END_EVENT_TABLE()

//-----------------------------------------------------------------------------
void ImageCanvas::BlitAOIs( wxPaintDC& dc, double scaleFactor, int bmpXOff, int bmpYOff, int bmpW, int bmpH )
//-----------------------------------------------------------------------------
{
	dc.SetBrush( *wxTRANSPARENT_BRUSH );
	AOIContainer::iterator it = m_aoiContainer.begin();
	AOIContainer::const_iterator itEnd = m_aoiContainer.end();
	while( it != itEnd )
	{
		dc.SetPen( it->second->m_colour );
		int aoix = it->second->m_rect.GetX();
		int aoiy = it->second->m_rect.GetY();
		int aoiw = it->second->m_rect.GetWidth();
		int aoih = it->second->m_rect.GetHeight();
		dc.DrawRectangle( static_cast<int>(( aoix * scaleFactor ) + bmpXOff),
			static_cast<int>(( aoiy * scaleFactor ) + bmpYOff),
			( aoiw + aoix > bmpW ) ? static_cast<int>(( bmpW - aoix ) * scaleFactor) : static_cast<int>(aoiw * scaleFactor),
			( aoih + aoiy > bmpH ) ? static_cast<int>(( bmpH - aoiy ) * scaleFactor) : static_cast<int>(aoih * scaleFactor) );
		dc.SetTextForeground( it->second->m_colour );
		wxCoord textWidth, textHeight;
		dc.GetTextExtent( it->second->m_description, &textWidth, &textHeight );
		dc.DrawText( it->second->m_description,
			static_cast<wxCoord>(( aoix * scaleFactor ) + bmpXOff + ( ( static_cast<int>(aoiw * scaleFactor) - textWidth ) / 2 )),
			static_cast<wxCoord>(( aoiy * scaleFactor ) + bmpYOff + ( ( static_cast<int>(aoih * scaleFactor) - textHeight ) / 2 )) );
		dc.SetPen( wxNullPen );
		++it;
	}
}

//-----------------------------------------------------------------------------
/// Strings containing \a _PVDI_ (PropView Display Info) are treated differently.
///
/// These strings can be used to blit special information on top of the image.
/// Supported things are strings rectangles and ellipse overlays.
///
/// The string must have the following format:
///
/// token;token;token;token
///
/// one of these tokens must be \a _PVDI_ to inform the display that this string has to be treated
/// in a special way. Other valid tokens are:
///
/// - _RECT_=top,left,bottom,right,r,g,b
/// - _ELLI_=x,y,w,h,r,g,b
/// - _STR_=text,x,y,r,g,b
/// - _LN_=x1,y1,x2,y2,r,g,b (line)
///
/// _PVDI_;_STR_="text",x,y,r,g,b;_RECT_=top,left,bottom,right,r,g,b;_ELLI_=x,y,w,h,r,g,b
///
/// EXAMPLES:
///
/// _PVDI_;_STR_=dummy,100,100,0,255,0;_RECT_=50,50,200,200,255,0,0
///
/// This will draw 'dummy' at 100/100 in green on top of the image and a red rectangle from 50/50 to 200/200
///
/// _PVDI_;_STR_=dummy,100,100;_ELLI_=50,50,200,200,255,0,0
///
/// This will draw a green 'dummy' at 100/100(no colour info -> default colour) and a red circle (as w and h are equal)
/// at 50/50 with a radius of 200. Colour information can be omitted. Then 0/255/0 (green) will be used.
void ImageCanvas::BlitInfoStrings( wxPaintDC& dc, double scaleFactor, int bmpScaledViewXOff, int bmpScaledViewVOff, int bmpXOff, int bmpYOff, int bmpW, int bmpH )
//-----------------------------------------------------------------------------
{
	if( m_boShowInfoOverlay )
	{
		wxColour oldTextColour(dc.GetTextForeground());
		std::vector<wxString>::size_type vSize = m_infoStringsOverlay.size();
		for( std::vector<wxString>::size_type i=0; i<vSize; i++ )
		{
			dc.SetTextForeground( wxColour(128, 128, 0) );
			dc.SetPen( *wxGREEN );
			static const wxString SPECIAL_DISPLAY_INFO_TOKEN(wxT("_PVDI_"));
			int displayInfoPos = m_infoStringsOverlay[i].Find( SPECIAL_DISPLAY_INFO_TOKEN.c_str() );
			if( displayInfoPos >= 0 )
			{
				int pos = m_infoStringsOverlay[i].Find( wxT(":") );
				if( pos >= 0 )
				{
					vector<wxString> tokens;
					wxString tokenList(m_infoStringsOverlay[i].Mid( pos+1 ));
					tokenList.Trim().Trim( false );
					vector<wxString>::size_type tokenCnt = Split( tokenList, wxString(wxT(";")), tokens );
					for( vector<wxString>::size_type j=0; j<tokenCnt; j++ )
					{
						vector<wxString> params;
						vector<wxString>::size_type paramsCnt = Split( tokens[j], wxString(wxT("=")), params );
						if( ( ( paramsCnt == 1 ) && ( tokens[j] != SPECIAL_DISPLAY_INFO_TOKEN ) ) /*|| ( ( paramsCnt < 1 ) || ( paramsCnt > 2 ) )*/ )
						{
							dc.DrawText( wxString::Format( wxT("Invalid parameter string: %s"), m_infoStringsOverlay[i].c_str() ), static_cast<wxCoord>(bmpXOff), static_cast<wxCoord>(bmpYOff + 300 + (i*20)) );
							break;
						}

						if( params[0] == SPECIAL_DISPLAY_INFO_TOKEN )
						{
							continue;
						}

						dc.SetPen( *wxGREEN ); // the default pen
						dc.SetTextForeground( *wxGREEN ); // the default text colour
						if( params[0] == wxT("_STR_") )
						{
							vector<wxString> data;
							wxString message(params[1].BeforeLast( wxT('\"') ));
							if( message.empty() )
							{
								// old format: string message NOT encapsulated with "
								Split( params[1], wxString(wxT(",")), data );
							}
							else
							{
								// new format: _STR_="<message>",x,y,r,g,b
								wxString posAndColor(params[1].Mid( message.Len() + 2 ));
								Split( posAndColor, wxString(wxT(",")), data );
								message.Remove( 0, 1 );
								data.insert( data.begin(), message );
							}

							vector<wxString>::size_type dataCnt = data.size();
							if( params[1].StartsWith( wxT(",") ) )
							{
								// this is an empty string we don't need to draw
								continue;
							}
							if( dataCnt < 3 )
							{
								dc.SetTextForeground( *wxRED );
								dc.DrawText( wxString::Format( wxT("Invalid token: %s"), tokens[j].c_str() ), static_cast<wxCoord>(bmpXOff), static_cast<wxCoord>(bmpYOff + 320 + (i*20)) );
								continue;
							}
							if( dataCnt >= 6 )
							{
								dc.SetTextForeground( wxColour(GetNextLongParam( data[3] ), GetNextLongParam( data[4] ), GetNextLongParam( data[5] )) );
							}
							long x = GetNextLongParam( data[1] );
							long y = GetNextLongParam( data[2] );
							dc.DrawText( data[0], static_cast<wxCoord>(( x * scaleFactor ) + bmpScaledViewXOff ), static_cast<wxCoord>(( y * scaleFactor ) + bmpScaledViewVOff ));
						}
						else
						{
							vector<wxString> data;
							vector<wxString>::size_type dataCnt = Split( params[1], wxString(wxT(",")), data );
							if( params[0] == wxT("_RECT_") )
							{
								if( dataCnt < 4 )
								{
									dc.SetTextForeground( *wxRED );
									dc.DrawText( wxString::Format( wxT("Invalid token: %s"), tokens[j].c_str() ), static_cast<wxCoord>(bmpXOff), static_cast<wxCoord>(bmpYOff + 320 + (i*20)) );
									continue;
								}
								if( dataCnt >= 7 )
								{
									dc.SetPen( wxColour(GetNextLongParam( data[4] ), GetNextLongParam( data[5] ), GetNextLongParam( data[6] )) );
								}
								long y = GetNextLongParam( data[0] );
								long x = GetNextLongParam( data[1] );
								long bottom = GetNextLongParam( data[2] );
								long right = GetNextLongParam( data[3] );
								dc.DrawRectangle( static_cast<int>(( x * scaleFactor ) + bmpScaledViewXOff),
									static_cast<int>(( y * scaleFactor ) + bmpScaledViewVOff),
									( right > bmpW ) ? static_cast<int>(( bmpW - x ) * scaleFactor) : static_cast<int>(( right - x ) * scaleFactor),
									( bottom > bmpH ) ? static_cast<int>(( bmpH - y ) * scaleFactor) : static_cast<int>(( bottom - y ) * scaleFactor) );
							}
							else if( params[0] == wxT("_ELLI_") )
							{
								if( dataCnt < 4 )
								{
									dc.SetTextForeground( *wxRED );
									dc.DrawText( wxString::Format( wxT("Invalid token: %s"), tokens[j].c_str() ), static_cast<wxCoord>(bmpXOff), static_cast<wxCoord>(bmpYOff + 320 + (i*20)) );
									continue;
								}
								if( dataCnt >= 7 )
								{
									dc.SetPen( wxColour(GetNextLongParam( data[4] ), GetNextLongParam( data[5] ), GetNextLongParam( data[6] )) );
								}
								long x = GetNextLongParam( data[0] );
								long y = GetNextLongParam( data[1] );
								long w = GetNextLongParam( data[2] );
								long h = GetNextLongParam( data[3] );
								dc.DrawEllipse( static_cast<int>(( x * scaleFactor ) + bmpScaledViewXOff),
									static_cast<int>(( y * scaleFactor ) + bmpScaledViewVOff),
									( w + x > bmpW ) ? static_cast<int>(( bmpW - x ) * scaleFactor) : static_cast<int>(w * scaleFactor),
									( h + y > bmpH ) ? static_cast<int>(( bmpH - y ) * scaleFactor) : static_cast<int>(h * scaleFactor) );
							}
							else if( params[0] == wxT("_LN_") )
							{
								if( dataCnt < 4 )
								{
									dc.SetTextForeground( *wxRED );
									dc.DrawText( wxString::Format( wxT("Invalid token: %s"), tokens[j].c_str() ), static_cast<wxCoord>(bmpXOff), static_cast<wxCoord>(bmpYOff + 320 + (i*20)) );
									continue;
								}
								if( dataCnt >= 7 )
								{
									dc.SetPen( wxColour(GetNextLongParam( data[4] ), GetNextLongParam( data[5] ), GetNextLongParam( data[6] )) );
								}
								long x1 = GetNextLongParam( data[0] );
								long y1 = GetNextLongParam( data[1] );
								long x2 = GetNextLongParam( data[2] );
								long y2 = GetNextLongParam( data[3] );
								dc.DrawLine( static_cast<int>(( x1 * scaleFactor ) + bmpScaledViewXOff),
									static_cast<int>(( y1 * scaleFactor ) + bmpScaledViewVOff),
									static_cast<int>(( x2 * scaleFactor ) + bmpScaledViewXOff),
									static_cast<int>(( y2 * scaleFactor ) + bmpScaledViewVOff) );
							}
							else if( params[0] == wxT("_PT_") )
							{
								if( dataCnt < 2 )
								{
									dc.SetTextForeground( *wxRED );
									dc.DrawText( wxString::Format( wxT("Invalid token: %s"), tokens[j].c_str() ), static_cast<wxCoord>(bmpXOff), static_cast<wxCoord>(bmpYOff + 320 + (i*20)) );
									continue;
								}
								if( dataCnt >= 5 )
								{
									dc.SetPen( wxColour(GetNextLongParam( data[2] ), GetNextLongParam( data[3] ), GetNextLongParam( data[4] )) );
								}
								long x = GetNextLongParam( data[0] );
								long y = GetNextLongParam( data[1] );
								dc.DrawPoint( static_cast<int>(( x * scaleFactor ) + bmpScaledViewXOff),
									static_cast<int>(( y * scaleFactor ) + bmpScaledViewVOff) );
							}
							else if( params[0] == wxT("_DPT_") )
							{
								if( dataCnt < 2 )
								{
									dc.SetTextForeground( *wxRED );
									dc.DrawText( wxString::Format( wxT("Invalid token: %s"), tokens[j].c_str() ), static_cast<wxCoord>(bmpXOff), static_cast<wxCoord>(bmpYOff + 320 + (i*20)) );
									continue;
								}
								if( dataCnt >= 5 )
								{
									dc.SetPen( wxColour(GetNextLongParam( data[2] ), GetNextLongParam( data[3] ), GetNextLongParam( data[4] )) );
								}
								double x = GetNextDoubleParam( data[0] );
								double y = GetNextDoubleParam( data[1] );
								dc.DrawPoint( static_cast<int>(( x * scaleFactor ) + bmpScaledViewXOff),
									static_cast<int>(( y * scaleFactor ) + bmpScaledViewVOff) );
							}
							else if( params[0] == wxT("_3DPT_") )
							{
								if( dataCnt < 2 )
								{
									dc.SetTextForeground( *wxRED );
									dc.DrawText( wxString::Format( wxT("Invalid token: %s"), tokens[j].c_str() ), static_cast<wxCoord>(bmpXOff), static_cast<wxCoord>(bmpYOff + 320 + (i*20)) );
									continue;
								}
								if( dataCnt >= 6 )
								{
									dc.SetPen( wxColour(GetNextLongParam( data[3] ), GetNextLongParam( data[4] ), GetNextLongParam( data[5] )) );
								}
								double x = GetNextDoubleParam( data[0] );
								double y = GetNextDoubleParam( data[1] );
								dc.DrawPoint( static_cast<int>(( x * scaleFactor ) + bmpScaledViewXOff),
									static_cast<int>(( y * scaleFactor ) + bmpScaledViewVOff) );
							}
							else
							{
								dc.SetTextForeground( *wxRED );
								dc.DrawText( wxString::Format( wxT("Invalid token: %s"), tokens[j].c_str() ), static_cast<wxCoord>(bmpXOff), static_cast<wxCoord>(bmpYOff + 320 + (i*20)) );
							}
						}
					}
				}
				else
				{
					dc.DrawText( m_infoStringsOverlay[i], static_cast<wxCoord>(bmpXOff), static_cast<wxCoord>(bmpYOff + INFO_Y_OFFSET + (i*20)) );
				}
			}
			else
			{
				dc.DrawText( m_infoStringsOverlay[i], static_cast<wxCoord>(bmpXOff), static_cast<wxCoord>(bmpYOff + INFO_Y_OFFSET + (i*20)) );
			}
		}
		dc.SetPen( wxNullPen );
		dc.SetTextForeground( oldTextColour );
	}
}

//-----------------------------------------------------------------------------
void ImageCanvas::BlitPerformanceMessages( wxPaintDC& dc, int bmpXOff, int bmpYOff, TImageBufferPixelFormat pixelFormat )
//-----------------------------------------------------------------------------
{
	if( m_boShowPerformanceWarnings )
	{
		bool boFormatWarning = ( pixelFormat != ibpfMono8 ) && ( pixelFormat != ibpfRGBx888Packed );
		dc.SetTextForeground( *wxRED );
		wxString msg(wxT("Performance loss because of"));
		bool boMustShow = false;
		if( boFormatWarning )
		{
			msg.Append( wxT(" pixel format conversion for display") );
			boMustShow = true;
		}

		if( m_boScaleToClientSize )
		{
			if( boMustShow )
			{
				msg.Append( wxT(" and") );
			}
			msg.Append( wxT(" scaling to fit to client size") );
			boMustShow = true;
		}

		if( !m_boScaleToClientSize && ( m_currentZoomFactor != 1.0 ) )
		{
			if( boMustShow )
			{
				msg.Append( wxT(" and") );
			}
			msg.Append( wxString::Format( wxT(" scaling(approx. zoom factor: %.1f)"), m_currentZoomFactor ) );
			boMustShow = true;
		}

		if( boMustShow )
		{
			dc.DrawText( msg, bmpXOff, bmpYOff + PERFORMANCE_WARNINGS_Y_OFFSET );
		}

		if( ( m_skippedImages > 0 ) || ( m_skippedPaintEvents > 0 ) )
		{
			dc.DrawText( wxString::Format( wxT("%8lu image%s and %8lu paint event%s skipped as the display engine was still busy"),
										   static_cast<long unsigned int>(m_skippedImages),
										   ( m_skippedImages == 1 ) ? wxT("s") : wxT(""),
										   static_cast<long unsigned int>(m_skippedPaintEvents),
										   ( m_skippedPaintEvents == 1 ) ? wxT("s") : wxT("")),
						 bmpXOff,
						 bmpYOff + SKIPPED_IMAGE_MESSAGE_Y_OFFSET );
		}
	}
}

//-----------------------------------------------------------------------------
void ImageCanvas::ClipAOI( wxRect& rect, bool boForDragging ) const
//-----------------------------------------------------------------------------
{
	if( rect.GetX() < 0 )
	{
		rect.SetX( 0 );
	}
	if( rect.GetY() < 0 )
	{
		rect.SetY( 0 );
	}
	if( m_pIB && m_pIB->vpData )
	{
		if( boForDragging )
		{
			if( ( rect.GetX() + rect.GetWidth() ) > m_pIB->iWidth )
			{
				rect.SetX( m_pIB->iWidth - rect.GetWidth() );
			}
			if( ( rect.GetY() + rect.GetHeight() ) > m_pIB->iHeight )
			{
				rect.SetY( m_pIB->iHeight - rect.GetHeight() );
			}
		}
		if( ( rect.GetWidth() + rect.GetX() ) > m_pIB->iWidth )
		{
			rect.SetWidth( m_pIB->iWidth - rect.GetX() );
		}
		if( ( rect.GetHeight() + rect.GetY() ) > m_pIB->iHeight )
		{
			rect.SetHeight( m_pIB->iHeight - rect.GetY() );
		}
	}
	else
	{
		rect.SetWidth( 1 );
		rect.SetHeight( 1 );
	}
}

//-----------------------------------------------------------------------------
void ImageCanvas::DeleteAOIs( void )
//-----------------------------------------------------------------------------
{
	AOIContainer::iterator it = m_aoiContainer.begin();
	AOIContainer::iterator itEND = m_aoiContainer.end();
	while( it != itEND )
	{
		delete it->second;
		++it;
	}
}

//-----------------------------------------------------------------------------
void ImageCanvas::GetCurrentPixel( double* pScale, wxPoint* pPoint )
//-----------------------------------------------------------------------------
{
	if( pScale )
	{
		*pScale = m_lastScaleFactor;
	}
	if( pPoint )
	{
		*pPoint = m_lastMousePos;
	}
}

//-----------------------------------------------------------------------------
void ImageCanvas::HandleMouseAndKeyboardEvents( bool boHandle )
//-----------------------------------------------------------------------------
{
	wxCriticalSectionLocker locker(m_critSect);
	m_boHandleMouseAndKeyboardEvents = boHandle;
}

//-----------------------------------------------------------------------------
void ImageCanvas::IncreaseSkippedCounter( size_t count )
//-----------------------------------------------------------------------------
{
	m_skippedImages += count;
}

//-----------------------------------------------------------------------------
void ImageCanvas::Init( const wxWindow* const pApp )
//-----------------------------------------------------------------------------
{
	m_boRefreshInProgress = false;
	m_pActiveAnalysisPlot = 0;
	m_boAOIDragInProgress = false;
	m_boHandleMouseAndKeyboardEvents = true;
	m_boScaleToClientSize = false;
	m_boShowPerformanceWarnings = false;
	m_pApp = const_cast<wxWindow* const>(pApp);
	m_pIB = 0;
	m_currentZoomFactor = 1.0;
	m_zoomFactor_Max = 1.0;
	m_skippedImages = 0;
	m_skippedPaintEvents = 0;
	m_pMonitorDisplay = 0;
	m_pVisiblePartOfImage = 0;
	m_userData = -1;
	SetScrollRate( 1, 1 );
}

//-----------------------------------------------------------------------------
void ImageCanvas::OnKeyDown( wxKeyEvent& e )
//-----------------------------------------------------------------------------
{
	if( m_boHandleMouseAndKeyboardEvents )
	{
		if( ( e.GetKeyCode() == wxT('+') ) || ( e.GetKeyCode() == WXK_ADD ) || ( e.GetKeyCode() == WXK_NUMPAD_ADD ) )
		{
			UpdateZoomFactor( zimMultiply, 2.0 );
		}
		else if( ( e.GetKeyCode() == wxT('-') ) || ( e.GetKeyCode() == WXK_SUBTRACT ) || ( e.GetKeyCode() == WXK_NUMPAD_SUBTRACT ) )
		{
			UpdateZoomFactor( zimDivide, 2.0 );
		}
	}
	e.Skip();
}

//-----------------------------------------------------------------------------
void ImageCanvas::OnLeftDblClick( wxMouseEvent& e )
//-----------------------------------------------------------------------------
{
	if( m_boHandleMouseAndKeyboardEvents )
	{
		wxCommandEvent e(toggleDisplayArea, m_pApp->GetId());
		::wxPostEvent( m_pApp->GetEventHandler(), e );
	}
	else
	{
		e.Skip();
	}
}

//-----------------------------------------------------------------------------
void ImageCanvas::OnLeftDown( wxMouseEvent& e )
//-----------------------------------------------------------------------------
{
	if( m_boHandleMouseAndKeyboardEvents )
	{
		m_AOIAtLeftMouseDown = AOI();
		if( IsActive() )
		{
			m_lastLeftMouseDownPos = GetScaledMousePos( e.GetX(), e.GetY() );
			wxCriticalSectionLocker locker(m_critSect);
			if( m_pActiveAnalysisPlot && m_pActiveAnalysisPlot->IsActive() && !m_pActiveAnalysisPlot->IsAOIFullModeActive() )
			{
				AOIContainer::const_iterator it = m_aoiContainer.find( m_pActiveAnalysisPlot );
				if( it != m_aoiContainer.end() )
				{
					m_AOIAtLeftMouseDown = *it->second;
#if wxCHECK_VERSION(2, 8, 0)
					m_boAOIDragInProgress = ( it->second->m_rect.Contains( m_lastLeftMouseDownPos ) ) ? true : false;
#else
					m_boAOIDragInProgress = ( it->second->m_rect.Inside( m_lastLeftMouseDownPos ) ) ? true : false;
#endif // #if wxCHECK_VERSION(2, 8, 0)
				}
			}
		}
	}
	e.Skip();
	wxCommandEvent eDisplaySelection(imageCanvasSelected, m_pApp->GetId());
	eDisplaySelection.SetInt( m_userData );
	::wxPostEvent( m_pApp->GetEventHandler(), eDisplaySelection );
}

//-----------------------------------------------------------------------------
void ImageCanvas::OnMotion( wxMouseEvent& e )
//-----------------------------------------------------------------------------
{
	if( m_boHandleMouseAndKeyboardEvents && IsActive() && m_pIB && m_pIB->vpData )
	{
		m_lastMousePos = GetScaledMousePos( e.GetX(), e.GetY() );
		bool boInformApplication = false;
		{
			wxCriticalSectionLocker locker(m_critSect);
			AOI* pAOI = e.LeftIsDown() ? &m_AOIAtLeftMouseDown : ( e.RightIsDown() ? &m_AOIAtRightMouseDown : 0 );
			const PlotCanvasImageAnalysis* pAnalysisCanvas = pAOI ? dynamic_cast<const PlotCanvasImageAnalysis*>(pAOI->m_pOwner) : 0;
			bool boAOIFullModeActive = ( pAnalysisCanvas && pAnalysisCanvas->IsAOIFullModeActive() ) ? true : false;
			if( pAOI && pAOI->m_pOwner && pAOI->m_pOwner->IsActive() && !boAOIFullModeActive )
			{
				AOIContainer::iterator it = m_aoiContainer.find( pAOI->m_pOwner );
				if( it != m_aoiContainer.end() )
				{
					if( m_boAOIDragInProgress && e.LeftIsDown() )
					{
						wxPoint moved = m_lastMousePos - m_lastLeftMouseDownPos;
						wxRect aoi(pAOI->m_rect);
						aoi.Offset( moved );
						ClipAOI( aoi, true );
						it->second->m_rect = aoi;
						boInformApplication = true;
					}
					else if( e.RightIsDown() )
					{
						wxPoint upperLeft;
						wxPoint lowerRight;
						upperLeft.x = ( m_lastRightMouseDownPos.x < m_lastMousePos.x ) ? m_lastRightMouseDownPos.x : m_lastMousePos.x;
						upperLeft.y = ( m_lastRightMouseDownPos.y < m_lastMousePos.y ) ? m_lastRightMouseDownPos.y : m_lastMousePos.y;
						lowerRight.x = ( m_lastRightMouseDownPos.x > m_lastMousePos.x ) ? m_lastRightMouseDownPos.x : m_lastMousePos.x;
						lowerRight.y = ( m_lastRightMouseDownPos.y > m_lastMousePos.y ) ? m_lastRightMouseDownPos.y : m_lastMousePos.y;
						if( upperLeft.x < 0 )
						{
							upperLeft.x = 0;
						}
						if( upperLeft.y < 0 )
						{
							upperLeft.y = 0;
						}
						wxRect aoi(upperLeft, lowerRight);
						ClipAOI( aoi, false );
						it->second->m_rect = aoi;
						boInformApplication = true;
					}
				}
			}
		}
		if( boInformApplication )
		{
			wxCommandEvent e(refreshAOIControls, m_pApp->GetId());
			e.SetInt( m_userData );
			::wxPostEvent( m_pApp->GetEventHandler(), e );
		}
		wxCommandEvent e(refreshCurrentPixelData, m_pApp->GetId());
		e.SetInt( m_userData );
		::wxPostEvent( m_pApp->GetEventHandler(), e );
	}
}

//-----------------------------------------------------------------------------
void ImageCanvas::OnMouseWheel( wxMouseEvent& e )
//-----------------------------------------------------------------------------
{
	if( m_boHandleMouseAndKeyboardEvents )
	{
		if( e.GetWheelRotation() > 0 )
		{
			UpdateZoomFactor( zimMultiply, 2.0 );
		}
		else if( e.GetWheelRotation() < 0 )
		{
			UpdateZoomFactor( zimDivide, 2.0 );
		}
	}
	else
	{
		e.Skip();
	}
}

//-----------------------------------------------------------------------------
void ImageCanvas::OnPopUpFitToScreen( wxCommandEvent& e )
//-----------------------------------------------------------------------------
{
	SetScaling( e.IsChecked() );
}

//-----------------------------------------------------------------------------
void ImageCanvas::OnPopUpShowPerformanceWarnings( wxCommandEvent& e )
//-----------------------------------------------------------------------------
{
	SetPerformanceWarningOutput( e.IsChecked() );
}

//-----------------------------------------------------------------------------
void ImageCanvas::OnPopUpShowRequestInfoOverlay( wxCommandEvent& e )
//-----------------------------------------------------------------------------
{
	SetInfoOverlayMode( e.IsChecked() );
}

//-----------------------------------------------------------------------------
void ImageCanvas::OnRightDown( wxMouseEvent& e )
//-----------------------------------------------------------------------------
{
	if( m_boHandleMouseAndKeyboardEvents )
	{
		m_AOIAtRightMouseDown = AOI();
		m_lastRightMouseDownPosRaw = wxPoint(e.GetX(), e.GetY());
		if( IsActive() )
		{
			m_lastRightMouseDownPos = GetScaledMousePos( e.GetX(), e.GetY() );
			wxCriticalSectionLocker locker(m_critSect);
			if( m_pActiveAnalysisPlot && m_pActiveAnalysisPlot->IsActive() && !m_pActiveAnalysisPlot->IsAOIFullModeActive() )
			{
				AOIContainer::const_iterator it = m_aoiContainer.find( m_pActiveAnalysisPlot );
				if( it != m_aoiContainer.end() )
				{
					m_AOIAtRightMouseDown = *it->second;
				}
			}
		}
	}
	else
	{
		e.Skip();
	}
}

//-----------------------------------------------------------------------------
void ImageCanvas::OnRightUp( wxMouseEvent& e )
//-----------------------------------------------------------------------------
{
	if( m_boHandleMouseAndKeyboardEvents && !IsFullScreen() )
	{
		if( m_lastRightMouseDownPosRaw == wxPoint(e.GetX(), e.GetY()) )
		{
			wxMenu menu(wxT(""));
			menu.Append( miPopupFitToScreen, wxT("Fit To Screen"), wxT("If active the captured image will be scaled to fit into the main display area(slower)"), wxITEM_CHECK )->Check( IsScaled() );
			if( SupportsFullScreenMode() )
			{
				menu.Append( miPopupFullScreen, wxT("Full Screen(Press 'ESC' to exit full screen mode afterwards)"), wxT(""), wxITEM_CHECK );
			}
			menu.Append( miPopupShowPerformanceWarnings, wxT("Performance Warning Overlay"), wxT("If active the various informations will be blitted on top of the image in the main display area to inform e.g. about possible performance losses"), wxITEM_CHECK )->Check( GetPerformanceWarningOutput() );
			menu.Append( miPopupShowRequestInfoOverlay, wxT("Request Info Overlay"), wxT("If active the various informations returned together with the image will be displayed as an overlay in the main display area(This will cost some additional CPU time)"), wxITEM_CHECK )->Check( InfoOverlayActive() );
			PopupMenu( &menu );
		}
	}
	else
	{
		e.Skip();
	}
}

//-----------------------------------------------------------------------------
AOI* ImageCanvas::RegisterAOI( const wxRect& r, const wxColour& c, const DrawingCanvas* const pOwner )
//-----------------------------------------------------------------------------
{
	wxCriticalSectionLocker locker(m_critSect);
	AOI* pAOI = new AOI(r, c, pOwner->GetName(), pOwner);
	m_aoiContainer.insert( make_pair( pOwner, pAOI ) );
	return pAOI;
}

//-----------------------------------------------------------------------------
bool ImageCanvas::RegisterMonitorDisplay( ImageCanvas* pMonitorDisplay )
//-----------------------------------------------------------------------------
{
	wxCriticalSectionLocker locker(m_critSect);
	if( pMonitorDisplay && m_pMonitorDisplay )
	{
		return false;
	}

	if( pMonitorDisplay && !m_pMonitorDisplay )
	{
		wxRect rect(0, 0, numeric_limits<int>::max(), numeric_limits<int>::max());
		m_pVisiblePartOfImage = pMonitorDisplay->RegisterAOI( rect, *wxRED, this );
		Refresh( false );
	}
	else if( !pMonitorDisplay && m_pMonitorDisplay )
	{
		m_pMonitorDisplay->RemoveAOI( this );
		m_pVisiblePartOfImage = 0;
		m_pMonitorDisplay = 0;
	}
	else
	{
		return false;
	}
	m_pMonitorDisplay = pMonitorDisplay;
	return true;
}

//-----------------------------------------------------------------------------
bool ImageCanvas::RemoveAOI( const DrawingCanvas* const pOwner )
//-----------------------------------------------------------------------------
{
	wxCriticalSectionLocker locker(m_critSect);
	AOIContainer::iterator it = m_aoiContainer.find( pOwner );
	if( it != m_aoiContainer.end() )
	{
		delete it->second;
		m_aoiContainer.erase( it );
		return true;
	}
	return false;
}

//-----------------------------------------------------------------------------
void ImageCanvas::ResetRequestInProgressFlag( void )
//-----------------------------------------------------------------------------
{
	wxCriticalSectionLocker locker(m_critSect);
	m_boRefreshInProgress = false;
}

//-----------------------------------------------------------------------------
void ImageCanvas::SetActiveAnalysisPlot( const PlotCanvasImageAnalysis* pPlot )
//-----------------------------------------------------------------------------
{
	{
		wxCriticalSectionLocker locker(m_critSect);
		m_pActiveAnalysisPlot = pPlot;
	}
	Refresh( false );
}

//-----------------------------------------------------------------------------
bool ImageCanvas::SetAOI( const PlotCanvasImageAnalysis* pPlot, int x, int y, int w, int h )
//-----------------------------------------------------------------------------
{
	wxCriticalSectionLocker locker(m_critSect);
	AOIContainer::iterator it = m_aoiContainer.find( pPlot );
	if( it != m_aoiContainer.end() )
	{
		AOI* pAOI = it->second;
		if( x >= 0 )
		{
			pAOI->m_rect.SetX( x );
		}
		if( y >= 0 )
		{
			pAOI->m_rect.SetY( y );
		}
		if( w >= 0 )
		{
			pAOI->m_rect.SetWidth( w );
		}
		if( h >= 0 )
		{
			pAOI->m_rect.SetHeight( h );
		}
		Refresh( false );
		return true;
	}
	return false;
}

//-----------------------------------------------------------------------------
void ImageCanvas::SetMaxZoomFactor( const mvIMPACT::acquire::ImageBuffer* pIB, int oldMaxDim )
//-----------------------------------------------------------------------------
{
	if( !pIB )
	{
		m_zoomFactor_Max = 1.0;
		return;
	}

	int newMaxDim = std::max( pIB->iWidth, pIB->iHeight );
	if( oldMaxDim != newMaxDim )
	{
		m_zoomFactor_Max = 1.0;
		while( ( ( newMaxDim = newMaxDim >> 1 ) != 0 ) && ( m_currentZoomFactor <= 512.0 ) )
		{
			m_zoomFactor_Max *= 2;
		}
		if( m_currentZoomFactor > m_zoomFactor_Max )
		{
			m_currentZoomFactor = m_zoomFactor_Max;
		}
	}
}

//-----------------------------------------------------------------------------
void ImageCanvas::SetScaling( bool boOn )
//-----------------------------------------------------------------------------
{
	if( m_boScaleToClientSize != boOn )
	{
		m_boScaleToClientSize = boOn;
		RefreshScrollbars();
		Refresh( true );
	}
}

//-----------------------------------------------------------------------------
void ImageCanvas::SetInfoOverlay( const std::vector<wxString>& infoStrings )
//-----------------------------------------------------------------------------
{
	if( m_infoStringsOverlay != infoStrings )
	{
		m_infoStringsOverlay = infoStrings;
	}
}

//-----------------------------------------------------------------------------
void ImageCanvas::SetInfoOverlayMode( bool boOn )
//-----------------------------------------------------------------------------
{
	if( m_boShowInfoOverlay != boOn )
	{
		m_boShowInfoOverlay = boOn;
		Refresh( true );
	}
}

//-----------------------------------------------------------------------------
void ImageCanvas::SetPerformanceWarningOutput( bool boOn )
//-----------------------------------------------------------------------------
{
	if( m_boShowPerformanceWarnings != boOn )
	{
		m_boShowPerformanceWarnings = boOn;
		Refresh( true );
	}
}

//-----------------------------------------------------------------------------
ImageCanvas::TSaveResult ImageCanvas::StoreImage( const wxImage& img, const wxString& filenameAndPath, const wxString& extension ) const
//-----------------------------------------------------------------------------
{
	if( extension == wxT("bmp") )
	{
		if( img.SaveFile( filenameAndPath, wxBITMAP_TYPE_BMP ) )
		{
			return srOK;
		}
	}
	else if( extension == wxT("png") )
	{
		if( img.SaveFile( filenameAndPath, wxBITMAP_TYPE_PNG ) )
		{
			return srOK;
		}
	}
	return srFailedToSave;
}

//-----------------------------------------------------------------------------
void ImageCanvas::UpdateZoomFactor( TZoomIncrementMode zim, double value )
//-----------------------------------------------------------------------------
{
	double zoomFactor = m_currentZoomFactor;
	switch( zim )
	{
	case zimMultiply:
		if( m_currentZoomFactor < m_zoomFactor_Max )
		{
			m_currentZoomFactor *= value;
		}
		break;
	case zimDivide:
		if( m_currentZoomFactor >= 2.0 )
		{
			m_currentZoomFactor /= value;
		}
		break;
	default:
		return;
	}

	RefreshScrollbars( true );
	if( !m_boScaleToClientSize )
	{
		Refresh( true );
	}

	if( m_boScaleToClientSize && ( zoomFactor != m_currentZoomFactor ) )
	{
		wxMessageBox(wxString::Format( wxT("Current zoom factor set to %.2f, but this is currently shadowed by the 'fit to screen' mode thus won't have visual effect."), m_currentZoomFactor ), wxString(wxT("Zoom factor changed")), wxOK | wxICON_INFORMATION, this);
	}
}
