/*	Filename:	centralwidget.cc
	Primary Qt interface class for QProg
	Interface for DIY PIC programmer hardware
	
	Created December 17, 2005 by Brandon Fosdick
	
	Copyright 2005 Brandon Fosdick (BSD License)

	$Id: centralwidget.cc,v 1.14 2007/06/17 05:03:19 bfoz Exp $
*/

#include <iostream>

#include <QFileDialog>
#include <QGridLayout>
#include <QLabel>
#include <QMessageBox>
#include <QSettings>
#include <QStringList>

#ifdef	Q_OS_DARWIN
#include <sstream>

#include <Carbon/Carbon.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/serial/IOSerialKeys.h>

#endif	//Q_OS_DARWIN

#include "chipinfo.h"
#include "intelhex.h"
#include "centralwidget.h"

#include "qextserialport.h"

CentralWidget::CentralWidget() : QWidget()
{
	QLabel	*ProgrammerDeviceNodeLabel = new QLabel("Programmer Port");
	QLabel	*TargetTypeLabel = new QLabel("Target Device");
	
	ProgrammerDeviceNode = new QComboBox();
	connect(ProgrammerDeviceNode, SIGNAL(activated(const QString&)), this, SLOT(onDeviceComboChange(const QString &)));

	TargetType = new QComboBox();
	connect(TargetType, SIGNAL(activated(const QString&)), this, SLOT(onTargetComboChange(const QString &)));

	EraseCheckBox = new QCheckBox("Erase before programming");
	VerifyCheckBox = new QCheckBox("Verify after programming");
	NewWindowOnReadCheckBox = new QCheckBox("Open new window on read");
	ProgramOnFileChangeCheckBox = new QCheckBox("Reprogram on file change");
	ProgramOnFileChangeCheckBox->setEnabled(false);

	//Connect the checkbox change signals so the state changes can be saved to settings
	connect(EraseCheckBox, SIGNAL(stateChanged(int)), this, SLOT(onEraseCheckBoxChange(int)));
	connect(VerifyCheckBox, SIGNAL(stateChanged(int)), this, SLOT(onVerifyCheckBoxChange(int)));
	connect(NewWindowOnReadCheckBox, SIGNAL(stateChanged(int)), this, SLOT(onNewWindowOnReadCheckBoxChange(int)));
	connect(ProgramOnFileChangeCheckBox, SIGNAL(stateChanged(int)), this, SLOT(onProgramOnFileChangeCheckBoxChange(int)));

	QPushButton	*ProgramButton = new QPushButton("Program");
	QPushButton	*ReadButton = new QPushButton("Read");
	QPushButton	*VerifyButton = new QPushButton("Verify");
	QPushButton	*EraseButton = new QPushButton("Erase");
	connect(ProgramButton, SIGNAL(clicked()), this, SLOT(program_all()));
	connect(ReadButton, SIGNAL(clicked()), this, SLOT(read()));
	connect(VerifyButton, SIGNAL(clicked()), this, SLOT(verify()));
	connect(EraseButton, SIGNAL(clicked()), this, SLOT(bulk_erase()));

	FileName = new QComboBox();
	FileName->setMaxCount(5);

	QPushButton	*BrowseButton = new QPushButton("Browse");
	connect(BrowseButton, SIGNAL(clicked()), this, SLOT(browse()));
	QLabel		*FileNameLabel = new QLabel("File");

	QGridLayout	*Layout0 = new QGridLayout;
	Layout0->addWidget(ProgrammerDeviceNodeLabel, 0, 0);
	Layout0->addWidget(TargetTypeLabel, 1, 0);
#ifdef	Q_OS_LINUX
	ProgrammerDeviceNode->setMaxCount(1);
	Layout0->addWidget(ProgrammerDeviceNode, 0, 1, 1, 2);
	QPushButton	*DeviceBrowseButton = new QPushButton("Browse");
	connect(DeviceBrowseButton, SIGNAL(clicked()), this, SLOT(device_browse()));
	Layout0->addWidget(DeviceBrowseButton, 0, 3);
#else	//Q_OS_LINUX
	Layout0->addWidget(ProgrammerDeviceNode, 0, 2, 1, 2);
#endif	//Q_OS_LINUX

	Layout0->addWidget(TargetType, 1, 2, 1, 2);

	Layout0->addWidget(FileNameLabel, 2, 0);
	Layout0->addWidget(FileName, 2, 1, 1, 2);
	Layout0->addWidget(BrowseButton, 2, 3);
	
	Layout0->addWidget(EraseCheckBox, 3, 0, 1, 2);
	Layout0->addWidget(VerifyCheckBox, 3, 2, 1, 2);

#ifdef	Q_OS_DARWIN
	Layout0->addWidget(NewWindowOnReadCheckBox, 4, 0, 1, 2);
#endif	//Q_OS_DARWIN
	Layout0->addWidget(ProgramOnFileChangeCheckBox, 4, 2, 1, 2);
	
	Layout0->addWidget(ProgramButton, 5, 0);
	Layout0->addWidget(ReadButton, 5, 1);
	Layout0->addWidget(VerifyButton, 5, 2);
	Layout0->addWidget(EraseButton, 5, 3);
	
	setLayout(Layout0);

	FillPortCombo();		//Fill the programmer dropdown with the available ports
	FillTargetCombo();		//Fill target device list from settings

	//Set the device combo to the last used port
	QString last_device = settings.value("CentralWidget/DeviceCombo/Last/Text").toString();
	int j=0;
	if( !last_device.isEmpty() )
		if( (j = ProgrammerDeviceNode->findText(last_device)) != -1 )
			ProgrammerDeviceNode->setCurrentIndex(j);
	
	//Restore the checkbox states from settings
	EraseCheckBox->setCheckState((Qt::CheckState)settings.value("CentralWidget/EraseBeforeProgrammingCheckBox/checkState").toInt());
	VerifyCheckBox->setCheckState((Qt::CheckState)settings.value("CentralWidget/VerifyAfterProgrammingCheckBox/checkState").toInt());
	NewWindowOnReadCheckBox->setCheckState((Qt::CheckState)settings.value("CentralWidget/NewWindowOnReadCheckBox/checkState").toInt());
	ProgramOnFileChangeCheckBox->setCheckState((Qt::CheckState)settings.value("CentralWidget/ProgramOnFileChangeCheckBox/checkState").toInt());

	//Restore the file list from settings
	j = settings.beginReadArray("CentralWidget/FileName/Last");
	for(int i = 0; i < j; ++i)
	{
		settings.setArrayIndex(i);
		FileName->addItem(settings.value("Name").toString(), settings.value("Path"));
	}
	settings.endArray();
	
	progressDialog = new QProgressDialog(this);
	progressDialog->setModal(true);
}

void CentralWidget::onEraseCheckBoxChange(int state)
{
	settings.setValue("CentralWidget/EraseBeforeProgrammingCheckBox/checkState", state);
}

void CentralWidget::onVerifyCheckBoxChange(int state)
{
	settings.setValue("CentralWidget/VerifyAfterProgrammingCheckBox/checkState", state);
}

void CentralWidget::onNewWindowOnReadCheckBoxChange(int state)
{
	settings.setValue("CentralWidget/NewWindowOnReadCheckBox/checkState", state);
}

void CentralWidget::onProgramOnFileChangeCheckBoxChange(int state)
{
	settings.setValue("CentralWidget/ProgramOnFileChangeCheckBox/checkState", state);
}

bool CentralWidget::FillTargetCombo()
{
	TargetType->clear();
	
	settings.beginGroup("PartsDB");
	QStringList keys = settings.childGroups();
	settings.endGroup();
//	printf("Filling target combo with %d keys\n", keys.count());

	QStringListIterator i(keys);
	while(i.hasNext())
		TargetType->addItem(i.next());

	//Set the combo to the last used target
	QString last_target = settings.value("CentralWidget/TargetCombo/Last/Text").toString();
	int j=0;
	if( !last_target.isEmpty() )
		if( (j = TargetType->findText(last_target)) != -1 )
			TargetType->setCurrentIndex(j);

	return true;
}

void CentralWidget::onTargetComboChange(const QString &text)
{
	settings.setValue("CentralWidget/TargetCombo/Last/Text", text);
}

void CentralWidget::onDeviceComboChange(const QString &text)
{
	settings.setValue("CentralWidget/DeviceCombo/Last/Text", text);
}

void CentralWidget::browse()
{
	QString initdir;
	if( FileName->currentIndex() == -1 )
		initdir = QDir::currentPath();
	else
		initdir = (FileName->itemData(FileName->currentIndex())).toString();
			
	QString directory = QFileDialog::getOpenFileName(this, tr("Open File"), initdir);
	if( directory.size() != 0 )
	{
		QString str(directory);
		str.remove(0, str.lastIndexOf('/')+1);	//Don't display leading path info
		FileName->addItem(str, QVariant(directory));
		FileName->setCurrentIndex(FileName->count() - 1);

		//Save the new file list to settings
		settings.beginWriteArray("CentralWidget/FileName/Last");
		for(int i = 0; i < FileName->count(); ++i)
		{
			settings.setArrayIndex(i);
			settings.setValue("Name", FileName->itemText(i));
			settings.setValue("Path", FileName->itemData(i));
		}
		settings.endArray();
	}
}

#ifdef	Q_OS_LINUX

void CentralWidget::device_browse()
{
	QString initdir;
	if( ProgrammerDeviceNode->currentIndex() == -1 )
		initdir = "/dev";
	else
		initdir = (ProgrammerDeviceNode->itemData(ProgrammerDeviceNode->currentIndex())).toString();
	
	QString directory = QFileDialog::getOpenFileName(this, tr("Open File"), initdir);
	if( directory.size() != 0 )
	{
		QString str(directory);
		str.remove(0, str.lastIndexOf('/')+1);	//Don't display leading path info
		ProgrammerDeviceNode->clear();
		ProgrammerDeviceNode->addItem(str, QVariant(directory));

		settings.setValue("CentralWidget/DeviceCombo/Last/Text", directory);
	}	
}

#endif	//Q_OS_LINUX

//Load the chip info from the settings
void loadChipInfo(QString &part, chipinfo::chipinfo &chip_info)
{
	QSettings	settings;
	settings.beginGroup("PartsDB");
	settings.beginGroup(part);
	QStringList keys = settings.childKeys();
	
//	std::cout << "Loading chipinfo for " << part.toStdString() << std::endl;

	QStringListIterator i(keys);
	while(i.hasNext())
	{
		QString	key(i.next());
		QString	value(settings.value(key).toString());
		if( value.size() == 0 )	//Skip empty keys
			continue;
		chip_info.set(key.toStdString(), value.toStdString());
//		std::cout << key.toStdString() << " -> " << value.toStdString() << std::endl;
	}
}


bool do_reset(kitsrus::kitsrus_t &programmer)
{
	if(!programmer.hard_reset())
	{
//		std::cout << "Hard reset failed. Trying a K149 reset...\n";
		//Try assuming that the programmer is a Kit149
		programmer.set_149();
		if(!programmer.hard_reset())
		{
			QMessageBox::critical(0, "Error", "Could not reset programmer");
			return false;
		}
	}
	
	//Enter command mode
	if(!programmer.command_mode())
	{
		QMessageBox::critical(0, "Error", "Could not enter Command Mode");
		return false;
	}
	return true;
}

bool do_erase(kitsrus::kitsrus_t &programmer)
{
//	programmer.chip_power_on();		//Activate programming voltages
	if( !programmer.erase_chip() )	//Erase the chip first
	{
		QMessageBox::critical(0, "Error", "Could not erase part");
		return false;
	}
	programmer.chip_power_off();		//Turn the chip off
	return true;
}

bool do_rom_write(kitsrus::kitsrus_t &programmer, intelhex::hex_data &HexData)
{
	const intelhex::hex_data::size_type num_rom_bytes = HexData.size_below_addr(programmer.get_rom_size());
	
	if( num_rom_bytes > 0 )
	{
		programmer.chip_power_on();		//Activate programming voltages
//		std::cout << "Programming " << num_rom_bytes << " ROM words for " << PartName << std::endl;
		if( !programmer.write_rom(HexData) )
		{
			std::cerr << "Error programming ROM\n";
			programmer.hard_reset();		//Do a hard reset to clear the error and turn power off
			return false;								// and then bail out
		}
		programmer.chip_power_off();		//Turn the chip off
	}
	else
		std::cout << "No ROM words in file\n";
	return true;
}

bool do_config_write(kitsrus::kitsrus_t &programmer, intelhex::hex_data &HexData)
{
	programmer.chip_power_on();		//Activate programming voltages
//	std::cout << "Programming Config for " << PartName << std::endl;
	if( !programmer.write_config(HexData) )
	{
		programmer.hard_reset();		//Do a hard reset to clear the error and turn power off
		return false;
	}
	programmer.chip_power_off();		//Turn the chip off
	return true;
}

bool do_eeprom_write(kitsrus::kitsrus_t &programmer, intelhex::hex_data &HexData)
{
	const intelhex::hex_data::size_type num_eeprom_bytes = HexData.size_in_range(programmer.get_eeprom_start(), programmer.get_eeprom_start() + programmer.get_eeprom_size());
	
	if(num_eeprom_bytes > 0)
	{
		programmer.chip_power_on();		//Activate programming voltages
//		std::cout << "Programming " << num_eeprom_bytes << " EEPROM bytes for " << PartName << std::endl;
		if( !programmer.write_eeprom(HexData) )
		{
			programmer.hard_reset();		//Do a hard reset to clear the error and turn power off
			return false;
		}
		programmer.chip_power_off();		//Turn the chip off
	}
	else
		std::cout << "No EEPROM bytes in file\n";
	return true;
}

bool do_rom_read(kitsrus::kitsrus_t &programmer, intelhex::hex_data &HexData)
{
	programmer.chip_power_on();		//Activate programming voltages
	if( !programmer.read_rom(HexData) )	//Read ROM
	{
		programmer.hard_reset();		//Do a hard reset to clear the error and turn power off
		return false;
	}
	programmer.chip_power_off();	//Turn the chip off
	return true;
}

bool do_config_read(kitsrus::kitsrus_t &programmer, intelhex::hex_data &HexData)
{
	programmer.chip_power_on();			//Activate programming voltages
	if( !programmer.read_config(HexData) )	//Read Config
	{
		programmer.hard_reset();		//Do a hard reset to clear the error and turn power off
		return false;
	}
	programmer.chip_power_off();		//Turn the chip off
	return true;
}

bool do_eeprom_read(kitsrus::kitsrus_t &programmer, intelhex::hex_data &HexData)
{
	programmer.chip_power_on();		//Activate programming voltages
	if( !programmer.read_eeprom(HexData) )	//Read EEPROM
	{
		programmer.hard_reset();		//Do a hard reset to clear the error and turn power off
		return false;
	}
	programmer.chip_power_off();		//Turn the chip off
	return true;
}

bool handle_progress(void* p, int i, int max_i)
{
	return static_cast<CentralWidget*>(p)->handleProgress(i, max_i);
}

//Handle the actual write sequence
bool do_write_all(kitsrus::kitsrus_t& prog, intelhex::hex_data &HexData, bool erase_first, QProgressDialog *progressDialog)
{
	//If erase before programming...
	if( erase_first )
		do_erase(prog);
	
	//Do the programming sequence
	//	For some reason config has to be written first or the programmer locks up
	progressDialog->setLabelText("Writing Config");	//Set the progress dialog label
	if( !do_config_write(prog, HexData) )
		return false;
	
	progressDialog->setLabelText("Writing EEPROM");	//Set the progress dialog label
	if( !do_eeprom_write(prog, HexData) )
		return false;
	
	progressDialog->setLabelText("Writing ROM");	//Set the progress dialog label
	if( !do_rom_write(prog, HexData) )					//Write the ROM words
		return false;

	return true;
}

//Handle the actual write sequence
bool do_read_all(kitsrus::kitsrus_t& prog, intelhex::hex_data &HexData, QProgressDialog *progressDialog)
{
	//		std::cout << "Reading " << prog.get_rom_size() << " ROM words\n";
	progressDialog->setLabelText("Reading ROM");	//Set the progress dialog label
	if( !do_rom_read(prog, HexData) )
		return false;
	
	progressDialog->setLabelText("Reading Config");	//Set the progress dialog label
	if( !do_config_read(prog, HexData) )
		return false;
	
	//		std::cout << "Reading " << prog.get_eeprom_size() << " EEPROM bytes\n";
	progressDialog->setLabelText("Reading EEPROM");	//Set the progress dialog label
	if( !do_eeprom_read(prog, HexData) )
		return false;
	
	return true;
}

bool CentralWidget::doProgrammerInit(kitsrus::kitsrus_t& prog)
{
	if( !prog.open() )			//Open the port
	{
		QMessageBox::critical(this, "Error", "Could not open serial port");
		return false;
	}
	
	if( !do_reset(prog) )			//Reset the programmer
		return false;
	
	//Check the protocol version
	std::string protocol = prog.get_protocol();
	if( protocol != "P018" )
	{
		QMessageBox::critical(this, "Error", tr("Wrong protocol version ( %1 )").arg(QString(protocol.c_str())));
		return false;
	}
	
	prog.init_program_vars();	//Initialize programming variables
	
	prog.set_callback(&handle_progress, this);	//Set the progress callback
	
	return true;
}

void CentralWidget::program_all()
{
	chipinfo::chipinfo	chip_info;
	QString	target(TargetType->itemText(TargetType->currentIndex()));

	loadChipInfo(target, chip_info);	//Load the chip info from the settings
	
	//Grab the currently selected file name
	if( FileName->currentIndex() == -1 )
	{
		browse();	//Open a file dialog if a file hasn't been selected yet
		if( FileName->currentIndex() == -1 )
		{
			QMessageBox::critical(this, "Error", "You must select a file to program");
			return;
		}
	}
	QString file_name = (FileName->itemData(FileName->currentIndex())).toString();

	//Put this in a block to close the serial port early
	{
		QString	path(currentPath());
		kitsrus::kitsrus_t	prog(path, chip_info);	//Programmer interface
		intelhex::hex_data HexData(file_name.toStdString());	//Load the hex file

		if( !doProgrammerInit(prog) )
			return;
		
		if( !do_write_all(prog, HexData, EraseCheckBox->isChecked(), progressDialog) )
		{
			progressDialog->reset();
			QMessageBox::critical(this, "Error", tr("Error writing to chip"));
			return;
		}
		
		if( VerifyCheckBox->isChecked() )
		{
			if( !doProgrammerInit(prog) )
				return;

			intelhex::hex_data VerifyData;
			if( !do_read_all(prog, VerifyData, progressDialog) )
			{
				progressDialog->reset();
				QMessageBox::critical(this, "Error", tr("Error reading chip"));
			}
			
			if(intelhex::compare(HexData, VerifyData) )
				QMessageBox::information(this, "Verify Results", "Pass");
			else
				QMessageBox::information(this, "Verify Results", "Fail");
		}
	}
}

#ifdef	Q_OS_DARWIN
void handle_open_new_text(intelhex::hex_data& HexData)
{
	OSErr err = noErr;
	FSRef	ref;
	LSApplicationParameters	app_parms = {0, kLSLaunchNoParams, &ref, NULL, NULL, NULL, NULL};
	ProcessSerialNumber	psn;

	//Find the preferred application for text files
	if( LSGetApplicationForInfo('TEXT', kLSUnknownCreator, NULL, kLSRolesAll, &ref, NULL) != noErr )
	{
		QMessageBox::critical(NULL, "Error", "Could not find preferred text editor");
		return;
	}
	
	if( LSOpenApplication(&app_parms, &psn) != noErr )	//Launch the application
	{
		QMessageBox::critical(NULL, "Error", "Could not open preferred text editor");
		return;
	}
	
	AppleEvent createEvent;
	//Build an event to open a new document
	if( AEBuildAppleEvent(kAECoreSuite, kAECreateElement, typeProcessSerialNumber, &psn, sizeof(psn), kAutoGenerateReturnID, kAnyTransactionID, &createEvent, NULL, "kocl:type(docu)") != noErr )
	{
		AppleEvent replyEvent;

/*		Handle pretty_handle;
		err = AEPrintDescToHandle(&createEvent, &pretty_handle);
		if( err == noErr )
			printf("event: %s\n", *pretty_handle);
		else
			printf("bad pretty print %d\n", err);
*/
		//Send an Apple Event to open a new document
		if( AESendMessage(&createEvent, &replyEvent, kAEWaitReply, kAEDefaultTimeout) != noErr )
		{
			AppleEvent openEvent;
			AEDesc	docSpec, insertLoc;
			std::ostringstream text;

			//Get the doc spec from the reply event
			AEGetParamDesc(&replyEvent, keyDirectObject, typeWildCard, &docSpec);
			AEDisposeDesc(&createEvent);	//The create event is no longer needed

			//Build an Apple Event to send the text to the editor
			HexData.write(text);
			AEBuildAppleEvent(kAECoreSuite, kAECreateElement, typeProcessSerialNumber, &psn, sizeof(psn), kAutoGenerateReturnID, kAnyTransactionID, &openEvent, NULL, "kocl:type(cpar), data:TEXT(@)", text.str().c_str());
//			AEPutAttributeDesc(&openEvent, keySubjectAttr, &docSpec);	//Add the doc spec as an attribute
			AEBuildDesc(&insertLoc, NULL, "insl{kobj:@, kpos:end}", &docSpec);	//Make an insert location
			AEPutParamDesc(&openEvent, keyAEInsertHere, &insertLoc);	//Insert the insert location

			//Send an Apple Event to append the new text
			AESendMessage(&openEvent, NULL, kAENoReply, kAEDefaultTimeout);
			
			//Cleanup
			AEDisposeDesc(&docSpec);
			AEDisposeDesc(&insertLoc);
			AEDisposeDesc(&openEvent);
			AEDisposeDesc(&replyEvent);
		}
	}
}
#endif	//Q_OS_DARWIN

void CentralWidget::read()
{
	chipinfo::chipinfo	chip_info;
	intelhex::hex_data	HexData;

	QString	target(TargetType->itemText(TargetType->currentIndex()));

	loadChipInfo(target, chip_info);	//Load the chip info from the settings

	QString	path(ProgrammerDeviceNode->itemData(ProgrammerDeviceNode->currentIndex()).toString());

	//Put this in a block to close the serial port early
	{
		kitsrus::kitsrus_t	prog(path, chip_info);	//Programmer interface
		
		if( !doProgrammerInit(prog) )
			return;

		if( !do_read_all(prog, HexData, progressDialog) )
		{
			progressDialog->reset();
			QMessageBox::critical(this, "Error", tr("Error reading chip"));
		}
	}


#ifdef	Q_OS_DARWIN
	if( NewWindowOnReadCheckBox->isChecked() )
		handle_open_new_text(HexData);
	else
	{
#endif	//Q_OS_DARWIN
		QString out_file(QFileDialog::getSaveFileName(this));
		if( !out_file.isEmpty() )
			HexData.write(out_file.toStdString().c_str());
#ifdef	Q_OS_DARWIN
	}
#endif	//Q_OS_DARWIN
}

void CentralWidget::onVerify()
{
	chipinfo::chipinfo	chip_info;
	QString	target(TargetType->itemText(TargetType->currentIndex()));
	
	loadChipInfo(target, chip_info);	//Load the chip info from the settings
	
	//Grab the currently selected file name
	if( FileName->currentIndex() == -1 )
	{
		browse();	//Open a file dialog if a file hasn't been selected yet
		if( FileName->currentIndex() == -1 )
		{
			QMessageBox::critical(this, "Error", "You must select a file to verify against");
			return;
		}
	}
	QString file_name = (FileName->itemData(FileName->currentIndex())).toString();
	
	//Put this in a block to close the serial port early
	{
		QString	path(currentPath());
		kitsrus::kitsrus_t	prog(path, chip_info);	//Programmer interface
		intelhex::hex_data HexData(file_name.toStdString());	//Load the hex file
		
		if( !doProgrammerInit(prog) )
			return;
		
		intelhex::hex_data VerifyData;
		if( !do_read_all(prog, VerifyData, progressDialog) )
		{
			progressDialog->reset();
			QMessageBox::critical(this, "Error", tr("Error reading chip"));
		}
		
		if(intelhex::compare(HexData, VerifyData) )
			QMessageBox::information(this, "Verify Results", "Pass");
		else
			QMessageBox::information(this, "Verify Results", "Fail");
	}
}

void CentralWidget::bulk_erase()
{
	chipinfo::chipinfo	chip_info;
	QString	target(TargetType->itemData(TargetType->currentIndex()).toString());
	
	loadChipInfo(target, chip_info);	//Load the chip info from the settings

	QString	path(ProgrammerDeviceNode->itemData(ProgrammerDeviceNode->currentIndex()).toString());

	//Put this in a block to close the serial port early
	{
		kitsrus::kitsrus_t	prog(path, chip_info);	//Programmer interface
		
		if( !prog.open() )			//Open the port
		{
			QMessageBox::critical(this, "Error", "Could not open serial port");
			return;
		}
		if( !do_reset(prog) )			//Reset the programmer
			return;
		
		//Check the protocol version
		std::string protocol = prog.get_protocol();
		if( protocol != "P018" )
		{
			QMessageBox::critical(this, "Error", tr("Wrong protocol version ( %1 )").arg(QString(protocol.c_str())));
			return;
		}
		
		do_erase(prog);		//Do the erase	
	}

	QMessageBox::information(this, "Bulk Erase", "Successfully Erased");
}

#ifdef	Q_OS_DARWIN

kern_return_t FindPorts(io_iterator_t *matchingServices)
{
    kern_return_t       kernResult;
    mach_port_t         masterPort;
    CFMutableDictionaryRef  classesToMatch;

    kernResult = IOMasterPort(MACH_PORT_NULL, &masterPort);	
    if (KERN_SUCCESS != kernResult)
    {
		QMessageBox::critical(NULL, "Error", "IOMasterPort returned error");
        printf("IOMasterPort returned %d\n", kernResult);
		return kernResult;
    }

    // Serial devices are instances of class IOSerialBSDClient.	
    classesToMatch = IOServiceMatching(kIOSerialBSDServiceValue);
    if (classesToMatch == NULL)
    {
        printf("IOServiceMatching returned a NULL dictionary.\n");
    }
    else
	{
		CFDictionarySetValue(classesToMatch, CFSTR(kIOSerialBSDTypeKey), CFSTR(kIOSerialBSDRS232Type));
		// Each serial device object has a property with key
        // kIOSerialBSDTypeKey and a value that is one of
        // kIOSerialBSDAllTypes, kIOSerialBSDModemType, 
        // or kIOSerialBSDRS232Type. You can change the
        // matching dictionary to find other types of serial
        // devices by changing the last parameter in the above call
        // to CFDictionarySetValue.
    }

    kernResult = IOServiceGetMatchingServices(masterPort, classesToMatch, matchingServices);
    if (KERN_SUCCESS != kernResult)
    {
		QMessageBox::critical(NULL, "Error", "IOServiceGetMatchingServices returned error");
        printf("IOServiceGetMatchingServices returned %d\n", kernResult);
    }
	return kernResult;
}

//Enumerate the available ports and fill up the relevant combo box
//	Returns true if a port is found, false otherwise
bool CentralWidget::FillPortCombo()
{
	io_object_t     Service;
	bool   result = false;
	char PortPath[256];
	CFIndex maxPathSize = 256;
	io_iterator_t   serialPortIterator;

	FindPorts(&serialPortIterator);		//Get an iterator to a list of ports

	*PortPath = '\0';	//Initialize the string

	// Iterate across all devices found
	while( (Service = IOIteratorNext(serialPortIterator)) )
	{
		// Get the callout device's path (/dev/cu.xxxxx). 
		// The callout device should almost always be
		// used. You would use the dialin device (/dev/tty.xxxxx) when 
		// monitoring a serial port for
		// incoming calls, for example, a fax listener.
		CFTypeRef deviceAsCFString = IORegistryEntryCreateCFProperty(Service, CFSTR(kIOCalloutDeviceKey), kCFAllocatorDefault, 0);
		if(deviceAsCFString)
		{
			// Convert the path from a CFString to a NULL-terminated C string
			Boolean result = CFStringGetCString((const __CFString*)deviceAsCFString, (char *)(&PortPath), maxPathSize, kCFStringEncodingASCII);
			CFRelease(deviceAsCFString);
			if(result)
			{
				QString str(PortPath);
				str.remove(0, str.lastIndexOf('/')+1);	//Don't display leading path info
				ProgrammerDeviceNode->addItem(str, QVariant(PortPath));
				result = true;
			}
		}
		IOObjectRelease(Service);  //Release the io_service_t now that we are done with it.
	}

	IOObjectRelease(serialPortIterator);    // Release the iterator
	return result;
}
#endif	//Q_OS_DARWIN

#ifdef	Q_WS_X11
#	ifdef	Q_OS_LINUX
bool CentralWidget::FillPortCombo()
{
}

#	else	//Q_OS_LINUX

//Enumerate the available ports and fill up the relevant combo box
//	Returns true if a port is found, false otherwise
bool CentralWidget::FillPortCombo()
{
	QDir	dir("/dev");
	if( !dir.exists() )
		return false;

	dir.setFilter(QDir::System);
	QStringList 	names("cu*");
	dir.setNameFilters(names);
	
	QStringList devs = dir.entryList();
	devs = devs.filter(QRegExp("\\d\\s*$"));	//Filter the .init and .lock nodes on FreeBSD
	QStringListIterator i(devs);
	while(i.hasNext())
	{
		QString path(i.next());
		ProgrammerDeviceNode->addItem(i.peekPrevious(), QVariant(path.prepend("/dev/")));
	}
	
	return true;
}	

#endif	//Q_OS_LINUX

#endif	//Q_WS_X11

#ifdef	Q_WS_WIN

//Enumerate the available ports and fill up the relevant combo box
//	Returns true if a port is found, false otherwise
bool CentralWidget::FillPortCombo()
{
	ProgrammerDeviceNode->addItem("COM 1", QVariant("COM1"));
	ProgrammerDeviceNode->addItem("COM 2", QVariant("COM2"));
	ProgrammerDeviceNode->addItem("COM 3", QVariant("COM3"));
	ProgrammerDeviceNode->addItem("COM 4", QVariant("COM4"));

	return true;
}	

#endif	//Q_WS_WIN


syntax highlighted by Code2HTML, v. 0.9.1