//
//  control_panel.cpp - control panel implementation file
//
//  $Id: control_panel.cpp 51 2014-02-07 02:46:11Z brian $ 
//
//  Copyright (c) 1999-2014 by Precision Micro Control Corp. All rights reserved.
//
#include "stdafx.h"

// Control ID's of zero buttons on control panel
int m_zero_buttonList[ZERO_BUTTON_CNT] = { IDC_XZERO, IDC_YZERO, IDC_ZZERO, IDC_SZERO };
								
// Control ID's of position displays on control panel
int PosDispList[POS_DISPLAY_CNT] = { IDC_POSDISPLAY1, IDC_POSDISPLAY2, IDC_POSDISPLAY3, IDC_POSDISPLAY4 };
								
// Control ID's of jog buttons on control panel
int m_jog_buttonList[JOG_BUTTON_CNT] = { IDC_XPLUS, IDC_XMINUS, IDC_YPLUS, IDC_YMINUS, IDC_ZPLUS, IDC_ZMINUS };
								
IMPLEMENT_DYNCREATE(ControlPanel, CFormView)

ControlPanel::ControlPanel() 
	: m_pos_disp_font(NULL), m_timer_update(false)
{
	TRACE("Constructor: ControlPanel\r\n");
	
	m_feed_override = "";
	m_rapid_override = "";
	m_spindle_override = "";
	m_pos_display1 = "";
	m_pos_display2 = "";
	m_pos_display3 = "";
	m_pos_display3 = "";
	
	m_run_status = 0;
	m_dio_channels = 0;

	ClearButtonState(ALL_BUTTONS);
	m_button_state_old = GetButtonState();
}

ControlPanel::~ControlPanel()
{
	TRACE("Destructor: ControlPanel\r\n");
	delete m_pos_disp_font;		// Delete Font used for position displays
}

void ControlPanel::DoDataExchange(CDataExchange* pDX)
{
//	TRACE("ControlPanel::DoDataExchange\r\n");
	
	CDialogBar::DoDataExchange(pDX);
	
	DDX_Control(pDX, IDC_STATUS_READY, m_status_ready);
	DDX_Control(pDX, IDC_STATUS_RUNNING, m_status_running);
	DDX_Control(pDX, IDC_STATUS_PAUSED, m_status_paused);
	DDX_Control(pDX, IDC_STATUS_FEEDHOLD, m_status_feedhold);
	DDX_Control(pDX, IDC_STATUS_FINISHED, m_status_finished);
	DDX_Control(pDX, IDC_STATUS_ANYERROR, m_status_error);
	
	DDX_Text(pDX, IDC_POSDISPLAY1, m_pos_display1);
	DDX_Text(pDX, IDC_POSDISPLAY2, m_pos_display2);
	DDX_Text(pDX, IDC_POSDISPLAY3, m_pos_display3);
	DDX_Text(pDX, IDC_POSDISPLAY4, m_pos_display4);

				// Exit here if performing timer update	
	if (m_timer_update)
	{
		m_timer_update = false;
		return;
	}
	
	//{{AFX_DATA_MAP(ControlPanel)
	DDX_Control(pDX, IDC_SPINDLE_SCROLL, m_spindle_scroll);
	DDX_Control(pDX, IDC_RAPID_SCROLL, m_rapid_scroll);
	DDX_Control(pDX, IDC_FEED_SCROLL, m_feed_scroll);
	DDX_Text(pDX, IDC_FEED_OVERRIDE, m_feed_override);
	DDX_Text(pDX, IDC_RAPID_OVERRIDE, m_rapid_override);
	DDX_Text(pDX, IDC_SPINDLE_OVERRIDE, m_spindle_override);
	//}}AFX_DATA_MAP
} 

BEGIN_MESSAGE_MAP(ControlPanel, CDialogBar)
	//{{AFX_MSG_MAP(ControlPanel)
	ON_WM_VSCROLL()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

void ControlPanel::InitialUpdate(void)
{
	CMachine *pTheMachine;
	char szEntry[DOUBLESTRINGLENGTH];

	// The control panel needs a larger font for the position
	// display readouts. Use information about the current
	// font to calculate the size of the new font.
	CFont* pOrgFont = this->GetFont();						// Get the dialog bar current font
	LOGFONT LFont;
	if(!pOrgFont->GetObject(sizeof(LOGFONT), &LFont))		// Get information about the current font
		TRACE("Failed to retrieve font information\r\n");

	// Increase the size of the Font	
	LFont.lfHeight = (int)(LFont.lfHeight * 2.5);
	LFont.lfWidth = (int)(LFont.lfWidth * 2.5);
	m_pos_disp_font = new CFont;							// Create a new font using the modified information
	if (!m_pos_disp_font->CreateFontIndirect(&LFont))
		TRACE("Failed to create new font for position display");
		
	// Subclass Start Button windows
	if (!m_start_button.SubclassDlgItem(IDC_START,this))
			TRACE("Failed to subclass Start button\r");
			
	if (!m_block_button.SubclassDlgItem(IDC_BLOCK,this))
			TRACE("Failed to subclass Block button\r");
			
	if (!m_feed_hold_button.SubclassDlgItem(IDC_FHOLD,this))
			TRACE("Failed to subclass FHold button\r");
			
   // Setup the zero buttons
	for (int i = 0; i < ZERO_BUTTON_CNT; i++)
	{
		// Subclass Zero Button windows
		if (!m_zero_button[i].SubclassDlgItem(m_zero_buttonList[i], this))
			TRACE("Failed to subclass zero button\r");
			
		// Get a pointer to the Zero Button window	
		CWnd* pWnd = this->GetDlgItem(m_zero_buttonList[i]);
		if (pWnd == NULL)
			TRACE("Unable to find Zero Button Window\r\n");
			
		// Set new font for zero button
		pWnd->SetFont(m_pos_disp_font, true);
	}

	// Setup jog buttons
	for (int i = 0; i < JOG_BUTTON_CNT; i++)
	{
		// Subclass Jog Button windows
		if (!m_jog_button[i].SubclassDlgItem(m_jog_buttonList[i], this))
			TRACE("Failed to subclass jog button\r");
	}

	if (!m_cut_enable_button.SubclassDlgItem(IDC_CUT_ENABLE, this))
			TRACE("Failed to subclass Cut Enable check box\r");
	
	pTheMachine = theApp.GetMachine();
	
	UpdateData(false);		// Call UpdateData function to initialize Data Exchange
							//  variables (ie. connect them to window controls)
	
	// Initialize override edit controls on panel
	double over = pTheMachine->GetFeedRateOverride();
	sprintf(szEntry, "%3.0f", over * 100);
	m_feed_override = szEntry;
	
	over = pTheMachine->GetRapidOverride();
	sprintf(szEntry, "%3.0f", over * 100);
	m_rapid_override = szEntry;
	
	over = pTheMachine->GetSpindleOverride();
	sprintf(szEntry, "%3.0f", over * 100);
	m_spindle_override = szEntry;
	
	m_timer_update = false;
	UpdateData(false);		// Update controls with member variable data
}

void ControlPanel::OnTimer(void)
{
	char display_buffer[40];
	char sLineBuf[PARTMAXLINELENGTH];

	CMachine* machine = theApp.GetMachine();
	if (machine == NULL)
		return;				// Skip timer update if machine doesn't exist

	int mode = machine->GetMode();

	m_dio_channels = machine->MtnCtrlr.GetAllDigitalIO();	// Get current digital I/O reading
	
	if (mode == RUN_MODE)
	{	
		m_run_status = machine->MtnCtrlr.GetRunStatus();
		
		m_status_ready.SetCheck((m_run_status & READY) != 0);
		m_status_running.SetCheck((m_run_status & RUNNING) != 0);
		m_status_paused.SetCheck((m_run_status & PAUSED) != 0);
		m_status_feedhold.SetCheck((m_run_status & FEEDHOLD) != 0);
		m_status_finished.SetCheck((m_run_status & FINISHED) != 0);
	
		m_status_error.SetCheck((m_run_status & (PROGRAM_ERROR | E_STOP | EE_STOP | DRIVE_FAULT | MOTOR_ERROR))!=0);
	}
	else if ((mode == JOG_MODE) || (mode == HOME_MODE))
	{
		// Check for motor errors during jogging and homing
		int motor_error = 0;
		if (machine->XAxis.GetAxis())
			motor_error |= machine->XAxis.DecodeStatus(machine->XAxis.GetStatus(), MC_STAT_ERROR);
		if (machine->YAxis.GetAxis())
			motor_error |= machine->YAxis.DecodeStatus(machine->YAxis.GetStatus(), MC_STAT_ERROR);
		if (machine->ZAxis.GetAxis())
			motor_error |= machine->ZAxis.DecodeStatus(machine->ZAxis.GetStatus(), MC_STAT_ERROR);
		if (machine->m_cutter.GetAxis())
			motor_error |= machine->m_cutter.DecodeStatus(machine->m_cutter.GetStatus(), MC_STAT_ERROR);
			
		m_status_ready.SetCheck(0);
		m_status_running.SetCheck(0);
		m_status_paused.SetCheck(0);
		m_status_feedhold.SetCheck(0);
		m_status_finished.SetCheck(0);
	
		m_status_error.SetCheck(motor_error);
		
		if (mode == JOG_MODE)
			machine->JogUpdate();		// JogUpdate member function of machine implements jogging 
	}
	else
	{
		m_status_ready.SetCheck(0);
		m_status_running.SetCheck(0);
		m_status_paused.SetCheck(0);
		m_status_feedhold.SetCheck(0);
		m_status_finished.SetCheck(0);
		m_status_error.SetCheck(0);
	}
	
	// Generate X axis position readout
	int readout_mode = machine->GetPositionReadoutMode();
	if (machine->XAxis.GetAxis())
	{
		double value = 0.0;
		if (readout_mode == SHOW_CURRENT)
			value = machine->XAxis.GetPosition();
		else if (readout_mode == SHOW_OPTIMAL)
			value = machine->XAxis.GetOptimal();
		sprintf(display_buffer, machine->XAxis.GetReadoutFormat(), value);
	}
	else
		display_buffer[0] = '\0';
	m_pos_display1 = display_buffer;
	
	// Generate Y axis position readout
	if (machine->YAxis.GetAxis())
	{
		double value = 0.0;
		if (readout_mode == SHOW_CURRENT)
			value = machine->YAxis.GetPosition();
		else if (readout_mode == SHOW_OPTIMAL)
			value = machine->YAxis.GetOptimal();
		sprintf(display_buffer, machine->YAxis.GetReadoutFormat(), value);
	}
	else
		display_buffer[0] = '\0';
	m_pos_display2 = display_buffer;

	// Generate Z axis or cutter position readout
	if (machine->ZAxis.GetAxis())
	{
		double value = 0.0;
		if (readout_mode == SHOW_CURRENT)
			value = machine->ZAxis.GetPosition();
		else if (readout_mode == SHOW_OPTIMAL)
			value = machine->ZAxis.GetOptimal();
		sprintf(display_buffer, machine->ZAxis.GetReadoutFormat(), value);
	}
	else if (machine->m_cutter.GetAxis())
	{
		double value = 0.0;
		if (readout_mode == SHOW_CURRENT)
			value = machine->m_cutter.GetPosition();
		else if (readout_mode == SHOW_OPTIMAL)
			value = machine->m_cutter.GetOptimal();
					// Convert cutter from units of rotation to degrees
		value -= floor(value);
		value *= 360.0;
		sprintf(display_buffer, machine->m_cutter.GetReadoutFormat(), value);
	}
	else
		display_buffer[0] = '\0';
	m_pos_display3 = display_buffer;
	
	// Generate Spindle speed readout
	if (machine->m_spindle.CtrlrAxis)
	{
		double value = machine->m_spindle.GetSpeed();
		sprintf(display_buffer, machine->m_spindle.GetReadoutFormat(), value);
	}
	else
		display_buffer[0] = '\0';
	m_pos_display4 = display_buffer;

	m_timer_update = true;
	UpdateData(false);		// Update controls with member variable data
	
	// Test whether motion controller has output at its' ASCII mailbox	
	//machine->MtnCtrlr.GetRamEx(0xA0020104, &cMBXChar, 1);	// For MFX-PCI1000
	sLineBuf[0] = static_cast<char>(machine->MtnCtrlr.GetC());
	if (sLineBuf[0] != 0)
	{
		// Retieve rest of string from motion controller and pass it to machine object for display
		machine->MtnCtrlr.GetS(&sLineBuf[1], sizeof(sLineBuf) - 1);
		machine->OutputText(sLineBuf);
		// Set flag to cause on idle processing to get additional output from motion controller
		theApp.OutputOnIdle(true);
	}
	
	// Service external controls

	// Check for external buttons activated
	SetExternalButtonState((int)machine->StartButtonChannel, START_DOWN);
	SetExternalButtonState((int)machine->BlockButtonChannel, BLOCK_DOWN);
	SetExternalButtonState((int)machine->FHoldButtonChannel, FHOLD_DOWN);

	// Update external indicators
	EnableExternalIndicator((int)machine->StartIndicatorChannel, StartEnabled());
	EnableExternalIndicator((int)machine->BlockIndicatorChannel, BlockEnabled());
	EnableExternalIndicator((int)machine->FHoldIndicatorChannel, FholdEnabled());
	
	// Check external Block Skip option switch
	int channel;
	if ((channel = static_cast<int>(machine->BlockSkipSwitchChannel)) != 0)
	{
		if (machine->GetBlockSkipStatus())
		{
			if (!(m_dio_channels & (1 << (channel - 1))))
				machine->DisableBlockSkip();
		}
		else
		{
			if (m_dio_channels & (1 << (channel - 1)))
				machine->EnableBlockSkip();
		}
	}
	
	// Check external Dryrun option switch
	if ((channel = static_cast<int>(machine->DryrunSwitchChannel)) != 0)
	{
		if (machine->GetDryrunStatus())
		{
			if (!(m_dio_channels & (1 << (channel - 1))))
				machine->DisableDryrun();
		}
		else
		{
			if (m_dio_channels & (1 << (channel - 1)))
				machine->EnableDryrun();
		}
	}
	
	int new_button_state = GetButtonState();			// Get current button state
	if (new_button_state ^ m_button_state_old)
	{
		if ((new_button_state & START_DOWN) && !(m_button_state_old & START_DOWN))
		{
			machine->Start();
			TRACE("START BUTTON ACTIVATE\r\n");
		}
		if ((new_button_state & BLOCK_DOWN) && !(m_button_state_old & BLOCK_DOWN))
			machine->Block();
		if ((new_button_state & FHOLD_DOWN) && !(m_button_state_old & FHOLD_DOWN))
			machine->FHold();
	}
	m_button_state_old = new_button_state;	// Record old button state so that transitions can be detected
}

void ControlPanel::SetExternalButtonState(int channel_num, int state)
{
	// Test for non-zero external channel number and input channel true
	if (channel_num && (m_dio_channels & (1 << (channel_num - 1))))
		SetButtonState(state);
	else
		ClearButtonState(state);
}

void ControlPanel::EnableExternalIndicator(int channel_num, bool state)
{
	// Test for non-zero external channel number
	if (channel_num)
	{
		// If current state of output doesn't match desired state, issue digital IO command to controller
		if ((state && !(m_dio_channels & (1 << (channel_num - 1)))) ||
				(!state && (m_dio_channels & (1 << (channel_num - 1)))))
			theApp.GetMachine()->MtnCtrlr.EnableDigitalIO(channel_num, state != 0);
	}
}

bool ControlPanel::ZeroEnabled(void)
{
	if (theApp.GetMachine())
		return theApp.GetMachine()->GetMode() == JOG_MODE;
	else
		return false;
}

bool ControlPanel::JogEnabled(void)
{
	if (theApp.GetMachine())
		return theApp.GetMachine()->GetMode() == JOG_MODE;
	else
		return false;
}

bool ControlPanel::StartEnabled(void)
{
	bool enabled = false;
	
	if (theApp.GetMachine())
	{
		switch (theApp.GetMachine()->GetMode()) 
		{
			case RUN_MODE:
					enabled = ((m_run_status & (READY | RUNNING)) != 0);
				break;

			case HOME_MODE:
					enabled = true;
				break;
		}
	}
	return enabled;
}

bool ControlPanel::BlockEnabled(void)
{
	CMachine* pTheMachine = theApp.GetMachine();
	bool enabled = false;
	
	if (pTheMachine)
	{
		switch (pTheMachine->GetMode()) 
		{
			case RUN_MODE:
					enabled = ((m_run_status & (READY | RUNNING)) != 0);
				break;
		}
	}
	return enabled;
}

bool ControlPanel::FholdEnabled()
{
	CMachine* pTheMachine = theApp.GetMachine();
	bool enabled = false;

	if (pTheMachine)
	{
		switch (pTheMachine->GetMode()) 
		{
			case RUN_MODE:
					enabled = ((m_run_status & (READY | RUNNING)) != 0);
				break;
		}
	}
	return enabled;
}

void ControlPanel::OnKillfocusFeedOverride(void)
{
	CMachine* pTheMachine = theApp.GetMachine();
	ASSERT(pTheMachine);
	
	UpdateData(true);			// Update member variables with control data
	
	double over = atof((const char *)m_feed_override) / 100;
	over = __min(1.5, over);
	over = __max(0.0, over);
	
	char buffer[DOUBLESTRINGLENGTH];
	_snprintf(buffer, sizeof(buffer), "%3.0f", over * 100);
	m_feed_override = buffer;
	pTheMachine->SetFeedRateOverride(over);
	UpdateData(false);			// Update control with range checked value
}

void ControlPanel::OnKillfocusRapidOverride(void)
{
	CMachine* pTheMachine = theApp.GetMachine();
	ASSERT(pTheMachine);

	UpdateData(true);			// Update member variables with control data		
	
	double over = atof((const char *)m_rapid_override) / 100;
	over = __min(1.0, over);
	over = __max(0.0, over);
	
	char buffer[DOUBLESTRINGLENGTH];
	_snprintf(buffer, sizeof(buffer), "%3.0f", over * 100);
	m_rapid_override = buffer;
	pTheMachine->SetRapidOverride(over);
	UpdateData(false);			// Update control with range checked value
}

void ControlPanel::OnKillfocusSpindleOverride(void)
{
	CMachine* pTheMachine = theApp.GetMachine();
	ASSERT(pTheMachine);
	
	UpdateData(true);			// Update member variables with control data
	
	double over = atof((const char *)m_spindle_override) / 100;
	
	over = __min(1.5, over);
	over = __max(0.0, over);
	
	char buffer[DOUBLESTRINGLENGTH];
	_snprintf(buffer, sizeof(buffer), "%3.0f", over * 100);
	m_spindle_override = buffer;
	pTheMachine->SetSpindleOverride(over);
	UpdateData(false);			// Update control with range checked value
}

void ControlPanel::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
	CMachine* pTheMachine = theApp.GetMachine();
	ASSERT(pTheMachine);
	
	// Check for click on frame window's scroll bar, pointer will be null	
	if (pScrollBar == NULL)
	{
		//Call frame window's OnVScroll function
		//CFormView::OnVScroll(nSBCode, nPos, pScrollBar);
	}
	// Check for "spin control" scroll messages	
	else if (pScrollBar->GetDlgCtrlID() == IDC_FEED_SCROLL)
	{
		if ((nSBCode == SB_LINEUP) || (nSBCode == SB_LINEDOWN))
		{
			double over = pTheMachine->GetFeedRateOverride();
			if (nSBCode == SB_LINEUP)
				over = __min(1.5, over + 0.01);
			else
				over = __max(0.0, over - 0.01);
				
			char buffer[DOUBLESTRINGLENGTH];
			_snprintf(buffer, sizeof(buffer), "%3.0f", over * 100);
			m_feed_override = buffer;
			pTheMachine->SetFeedRateOverride(over);
			
			UpdateData(false);		// Update control with new value
		}
	}
	else if (pScrollBar->GetDlgCtrlID() == IDC_RAPID_SCROLL)
	{
		if ((nSBCode == SB_LINEUP) || (nSBCode == SB_LINEDOWN))
		{
			double over = pTheMachine->GetRapidOverride();
			if (nSBCode == SB_LINEUP)
				over = __min(1.0, over + 0.01);
			else
				over = __max(0.0, over - 0.01);
				
			char buffer[DOUBLESTRINGLENGTH];
			_snprintf(buffer, sizeof(buffer), "%3.0f", over * 100);
			m_rapid_override = buffer;
			pTheMachine->SetRapidOverride(over);
			
			UpdateData(false);		// Update control with new value
		}
	}
	else if (pScrollBar->GetDlgCtrlID() == IDC_SPINDLE_SCROLL)
	{
		if ((nSBCode == SB_LINEUP) || (nSBCode == SB_LINEDOWN))
		{
			double over = pTheMachine->GetSpindleOverride();
			if (nSBCode == SB_LINEUP)
				over = __min(1.5, over + 0.01);
			else
				over = __max(0.0, over - 0.01);
				
			char buffer[DOUBLESTRINGLENGTH];
			_snprintf(buffer, sizeof(buffer), "%3.0f", over * 100);
			m_spindle_override = buffer;
			pTheMachine->SetSpindleOverride(over);
			
			UpdateData(false);		// Update control with new value
		}
	}
}

void ControlPanel::LeftButtonDown(int id)
{
	CMachine* pTheMachine = theApp.GetMachine();
	ASSERT(pTheMachine);
	
//	TRACE("Left Button Down: %d\r",iControlID);

	switch (id) 
	{
		case IDC_XPLUS:
			SetButtonState(JOG_XPLUS_DOWN);
			break;
		case IDC_XMINUS:
			SetButtonState(JOG_XMINUS_DOWN);
			break;
		case IDC_YPLUS:
			SetButtonState(JOG_YPLUS_DOWN);
			break;
		case IDC_YMINUS:
			SetButtonState(JOG_YMINUS_DOWN);
			break;
		case IDC_ZPLUS:
			SetButtonState(JOG_ZPLUS_DOWN);
			break;
		case IDC_ZMINUS:
			SetButtonState(JOG_ZMINUS_DOWN);
			break;
		case IDC_START:
			pTheMachine->Start();
			break;
		case IDC_BLOCK:
			pTheMachine->Block();
			break;
		case IDC_FHOLD:
			pTheMachine->FHold();
			break;
		case IDC_XZERO:
			pTheMachine->XZero();
			break;
		case IDC_YZERO:
			pTheMachine->YZero();
			break;
		case IDC_ZZERO:
			pTheMachine->ZZero();
			break;
	}
}

void ControlPanel::LeftButtonUp(int id)
{
	CMachine* pTheMachine = theApp.GetMachine();
	ASSERT(pTheMachine);
	
//	TRACE("Left Button Up: %d\r",iControlID);
	
	switch (id) 
	{
		case IDC_XPLUS:
			ClearButtonState(JOG_XPLUS_DOWN);
			break;
		case IDC_XMINUS:
			ClearButtonState(JOG_XMINUS_DOWN);
			break;
		case IDC_YPLUS:
			ClearButtonState(JOG_YPLUS_DOWN);
			break;
		case IDC_YMINUS:
			ClearButtonState(JOG_YMINUS_DOWN);
			break;
		case IDC_ZPLUS:
			ClearButtonState(JOG_ZPLUS_DOWN);
			break;
		case IDC_ZMINUS:
			ClearButtonState(JOG_ZMINUS_DOWN);
			break;
		case IDC_CUT_ENABLE:
			if (m_cut_enable_button.GetCheck())					// Retrieve previous state of check box
			{
				pTheMachine->DisableCutterWhileJogging();		// If it was checked, disable cutter
				if (pTheMachine->GetMode()==JOG_MODE)			// If in Jog mode, raise cutter
					pTheMachine->m_cutter.Raise();
			}
			else
				pTheMachine->EnableCutterWhileJogging();
			break;
	}
}
