summaryrefslogblamecommitdiffstats
path: root/heimdall-frontend/Source/mainwindow.cpp
blob: 6949e0cc602c3670fc27eeee91b6445c73997bf0 (plain) (tree)
1
                                                          


















                                                                               








                            
                     
                    


                        

                            

                                  

                                                             
                                       

                                                                  
         

                                                      


















                                                                                              

                                    












                                                                                                  

                                                                                






                                                                          
                                                                




                                                       
                                                
  






                                                                                   
                                                                                                                                                    
                                                                              






                                                                                         

  
                                      
  

                                             
 
                                                                 
 









                                                                    

  
                                                  
  



                                             
          


                                               
 


                                                 

                                                           
                                                        

             
          





                                                                                              
                  


                                                                                                                 
                  





                                                                                                                 
                                                                                                        
                  
                                                                                                                
                                                                                                                                                         
                  
 
                                                                                                      
                  

                                                                                                          

                  
                                                                                                          
                                                                                                    
          

                                                  
  
 

                                         
                                                                                                     


                                                                                                                                                           
 
                                                                                       
  
                                                                                           
 

                                                                      
 

                       
 
                                                                                      
  
                                                                                           
 

                                                                      
 

                       
 
                                                              
  



                                                            
 







                                                                             
 



                                                                                   
 
                                                                                         




                                                        
                                                     
          
                                                                                            
 

                                                                                                 
 







                                                                 
 



                                                                                       
                                                
                                                                  

                                                                  



                                                          
 

                                                    
                                                 
          

  
                                                                
  
                                                     
          
                                                                                         
 













                                                                                                                                                                       
 



                                                                   
          

             





                                                            
                                                     
          



                                                       
                                                                                          












                                                                     
 








                                                                                              
 

                                                       
          







                                                    
 
                                                     

                                       
 

                                                                     
 


                                                                                                                               
          


                                                                     
                  



                                                                            
                  
          

  
                                                     
  
                                         
 




                                                                          
          






                                                                                                                                         
          



                                            





                                                              
                                                


                                                                                      

                                          

                               
                                                      

                                                                                               


                                                                                                               
                                                                                                
                                                                                                          
                                                                                                      

                                                                                             
                            








                                                                                                                  
                     


                                                                                                                         
                                                                                       
 
                                                                                                           
 
                                                                                                     
                                                                                                 


                                                                                         
                              





















                                                                                                                                                  

                                                                                             
 
                                                                                               



                                                                                                                        
                                                                                     
 



                                                                                                  


                                                                                                                                                        










                                                                                                     




                                                




                                 





                                                      
                                             
  


                                      
                                                                          
                                                
 


                                                                                                    
                                                      
                     
                                                   
          

  
                                             
  

                                                                                                                                   

  
                                                    
  

                                                                                                                                         

  
                                           
  


                                    

                                                                            
 
                                                                                                     
 

                                                            

                                        





                                                                                                                                                                              
 
                                                  


                                       


                                                                                                                                   
          
 



                                                                         
 


                                                                                                                                 
 

                                            

                                                                                  

                                                              
 



                                                            
 


                               
 

                                                                                                                   
                                                                                                             
 


                                          
 
                                       
 



                                                                                                        
 
                                                                                                     
 





                                                                                     

                                                                                                  


                                                                     
 


                                                       
          
 



                                                      
 

                                                                                                


                                                                                          


                                                                                            
 


                                                                                     
 
                                       

  
                                                
  
                                                                                              
          


                                                                            



                                                                                                                              








                                                                                     

                                                       
                                                                                  











                                                                                                                                                                                             






                                                                                                                               
          

  
                                           
  
                                              
 
                        
          


                                                                                                                              
                                                                          












                                                                                                                                                                                     



                                                        
                                               


                                                              
          

  
                                          
  
                      
          








                                                                                                          








                                                                                               


             






                                                              

                                                      
          

  
                                    
  


                                                                                   
 





                                                                                                                     
 
                                       

  
                                       
  

                                                                                                          
 






                                                                     
                                       

  
                                 
  
                                                                   

                                    



                                                                                                                                  
 



                                                                                                                         
 
                                        
 














                                                                                                                              
                                                                                                                   











                                                                                                     
 

                                                                                   
                  

















                                                                                                              
                  
 
                                            
 
                                          
 
                                                                                             
 


                                                            
 

                                                                                                        
 
                                               
          

  
 
                                             
  
                                                                      

                                                  
  
 


                                                                   














                                                     
  


                                  

                                      
                                                 
                                


                                                                                 



                                   
                                           

                                                   









                                                                     

          

                                                 
 


                                              




                                               
                                  

  


                                                            
                                       




                                                               
                                       




                                                                              
                                       




                                                                                 
                                       













                                                                 


                                                    



                                          


                                                    







                                                                                                          

                                                    










                                                                                                                 

                                       



                                                        

                      















                                                                                                                                                 

                                                                                                                                                       
 
                                                                                                                                                            


                                             

                                       










                                                                                                               

                                       



                                    
                                                                              
 
                                    
          




















                                                                                            
                                                        




                                    


                                               








                                             
                                                        



                                             


                                              
 


                                               






                                             
                                                                








                                                       
          





                                                      
 
                                                       







                                                          

                                         


                                              


                                               




                                             


































                                                                      



                                             
                                                    




                                       





                                                                 
                                             
                                         


                                              
 


                                               
                                  

  

                                            
                                                    













                                                                                                             

                                                    
 
                                                      








                                                                     



                                                                                       

                                
                                                                 
          





                                                                                                                                                                   
                  
                                                                              
                  
                                                                          
                  
                                                                     
                  


             
                                                              
                  
                                                                                
 
                                                                       
 



                                                                                       
 

                                                    
                                                                          


                                                                      
          
 
                                                

                                             





                                                                             
                                                              








                                                                                                                 
                                       
                                                        
                                               


                                             
                                                              







                                                                                                            
                 
                                                        
                                               


             
                                                              







                                                                                                                              
                 
                                                        
                                               

          
/* Copyright (c) 2010-2012 Benjamin Dobell, Glass Echidna
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:
 
 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.
 
 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.*/

// Qt
#include <QCoreApplication>
#include <QDesktopServices>
#include <QDir>
#include <QFileDialog>
#include <QProcess>
#include <QRegExp>
#include <QUrl>

// Heimdall Frontend
#include "Alerts.h"
#include "mainwindow.h"
#include "Packaging.h"

#define UNUSED(x) (void)(x)

using namespace HeimdallFrontend;

void MainWindow::StartHeimdall(const QStringList& arguments)
{
	UpdateInterfaceAvailability();

	heimdallProcess.setReadChannel(QProcess::StandardOutput);
	
	heimdallProcess.start("heimdall", arguments);
	heimdallProcess.waitForStarted(3000);
	
	// OS X was playing up and not finding heimdall, so we're manually checking the PATH.
	if (heimdallFailed)
	{
		QStringList environment = QProcess::systemEnvironment();
		
		QStringList paths;

		// Ensure /usr/bin is in PATH
		for (int i = 0; i < environment.length(); i++)
		{
			if (environment[i].left(5) == "PATH=")
			{
				paths = environment[i].mid(5).split(':');
				paths.prepend("/usr/bin");
				break;
			}
		}
		
		int pathIndex = -1;

		while (heimdallFailed && ++pathIndex < paths.length())
		{
			QString heimdallPath = paths[pathIndex];
			
			if (heimdallPath.length() > 0)
			{
				heimdallFailed = false;
				
				if (heimdallPath[heimdallPath.length() - 1] != QDir::separator())
					heimdallPath += QDir::separator();
				
				heimdallPath += "heimdall";
				
				heimdallProcess.start(heimdallPath, arguments);
				heimdallProcess.waitForStarted(3000);
			}
		}
		
		if (heimdallFailed)
		{
			flashLabel->setText("Failed to start Heimdall!");
			
			heimdallState = HeimdallState::Stopped;
			UpdateInterfaceAvailability();
		}
	}
}

void MainWindow::UpdateUnusedPartitionIds(void)
{
	unusedPartitionIds.clear();

	// Initially populate unusedPartitionIds with all possible partition IDs. 
	for (unsigned int i = 0; i < currentPitData.GetEntryCount(); i++)
	{
		const PitEntry *pitEntry = currentPitData.GetEntry(i);

		if (pitEntry->IsFlashable() && strcmp(pitEntry->GetPartitionName(), "PIT") != 0 && strcmp(pitEntry->GetPartitionName(), "PT") != 0)
			unusedPartitionIds.append(pitEntry->GetIdentifier());
	}

	// Remove any used partition IDs from unusedPartitionIds
	QList<FileInfo>& fileList = workingPackageData.GetFirmwareInfo().GetFileInfos();

	for (int i = 0; i < fileList.length(); i++)
		unusedPartitionIds.removeOne(fileList[i].GetPartitionId());
}

bool MainWindow::ReadPit(QFile *file)
{
	if(!file->open(QIODevice::ReadOnly))
		return (false);

	unsigned char *buffer = new unsigned char[file->size()];

	file->read(reinterpret_cast<char *>(buffer), file->size());
	file->close();

	bool success = currentPitData.Unpack(buffer);
	delete buffer;

	if (!success)
		currentPitData.Clear();

	return (success);
}

void MainWindow::UpdatePackageUserInterface(void)
{
	supportedDevicesListWidget->clear();
	includedFilesListWidget->clear();

	if (loadedPackageData.IsCleared())
	{
		// Package Interface
		firmwareNameLineEdit->clear();
		versionLineEdit->clear();

		developerNamesLineEdit->clear();

		platformLineEdit->clear();
		
		repartitionRadioButton->setChecked(false);
		noRebootRadioButton->setChecked(false);
	}
	else
	{
		firmwareNameLineEdit->setText(loadedPackageData.GetFirmwareInfo().GetName());
		versionLineEdit->setText(loadedPackageData.GetFirmwareInfo().GetVersion());

		QString developerNames;

		if (!loadedPackageData.GetFirmwareInfo().GetDevelopers().isEmpty())
		{
			developerNames = loadedPackageData.GetFirmwareInfo().GetDevelopers()[0];
			for (int i = 1; i < loadedPackageData.GetFirmwareInfo().GetDevelopers().length(); i++)
				developerNames += ", " + loadedPackageData.GetFirmwareInfo().GetDevelopers()[i];
		}

		developerNamesLineEdit->setText(developerNames);

		platformLineEdit->setText(loadedPackageData.GetFirmwareInfo().GetPlatformInfo().GetName() + " ("
			+ loadedPackageData.GetFirmwareInfo().GetPlatformInfo().GetVersion() + ")");

		for (int i = 0; i < loadedPackageData.GetFirmwareInfo().GetDeviceInfos().length(); i++)
		{
			const DeviceInfo& deviceInfo = loadedPackageData.GetFirmwareInfo().GetDeviceInfos()[i];
			supportedDevicesListWidget->addItem(deviceInfo.GetManufacturer() + " " + deviceInfo.GetName() + ": " + deviceInfo.GetProduct());
		}

		for (int i = 0; i < loadedPackageData.GetFirmwareInfo().GetFileInfos().length(); i++)
		{
			const FileInfo& fileInfo = loadedPackageData.GetFirmwareInfo().GetFileInfos()[i];
			includedFilesListWidget->addItem(fileInfo.GetFilename());
		}

		repartitionRadioButton->setChecked(loadedPackageData.GetFirmwareInfo().GetRepartition());
		noRebootRadioButton->setChecked(loadedPackageData.GetFirmwareInfo().GetNoReboot());
	}

	UpdateLoadPackageInterfaceAvailability();
}

bool MainWindow::IsArchive(QString path)
{
	// Not a real check but hopefully it gets the message across, don't directly flash archives!
	return (path.endsWith(".tar", Qt::CaseInsensitive) || path.endsWith(".gz", Qt::CaseInsensitive) || path.endsWith(".zip", Qt::CaseInsensitive)
		|| path.endsWith(".bz2", Qt::CaseInsensitive) || path.endsWith(".7z", Qt::CaseInsensitive) || path.endsWith(".rar", Qt::CaseInsensitive));
}

QString MainWindow::PromptFileSelection(const QString& caption, const QString& filter)
{
	QString path = QFileDialog::getOpenFileName(this, caption, lastDirectory, filter);

	if (path != "")
		lastDirectory = path.left(path.lastIndexOf('/') + 1);

	return (path);
}

QString MainWindow::PromptFileCreation(const QString& caption, const QString& filter)
{
	QString path = QFileDialog::getSaveFileName(this, caption, lastDirectory, filter);

	if (path != "")
		lastDirectory = path.left(path.lastIndexOf('/') + 1);

	return (path);
}

void MainWindow::UpdateLoadPackageInterfaceAvailability(void)
{
	if (loadedPackageData.IsCleared())
	{
		developerHomepageButton->setEnabled(false);
		developerDonateButton->setEnabled(false);

		loadFirmwareButton->setEnabled(false);
	}
	else
	{
		if (!loadedPackageData.GetFirmwareInfo().GetUrl().isEmpty())
			developerHomepageButton->setEnabled(true);
		else
			developerHomepageButton->setEnabled(false);

		if (!loadedPackageData.GetFirmwareInfo().GetDonateUrl().isEmpty())
			developerDonateButton->setEnabled(true);
		else
			developerDonateButton->setEnabled(false);

		loadFirmwareButton->setEnabled(heimdallState == HeimdallState::Stopped);
	}
}

void MainWindow::UpdateFlashInterfaceAvailability(void)
{
	if (heimdallState == HeimdallState::Stopped)
	{
		partitionNameComboBox->setEnabled(partitionsListWidget->currentRow() >= 0);

		bool allPartitionsValid = true;
		QList<FileInfo>& fileList = workingPackageData.GetFirmwareInfo().GetFileInfos();

		for (int i = 0; i < fileList.length(); i++)
		{
			if (fileList[i].GetFilename().isEmpty())
			{
				allPartitionsValid = false;
				break;
			}
		}

		bool validFlashSettings = allPartitionsValid && fileList.length() > 0;
		
		flashProgressBar->setEnabled(false);
		optionsGroup->setEnabled(true);
		sessionGroup->setEnabled(true);
		startFlashButton->setEnabled(validFlashSettings);
		noRebootCheckBox->setEnabled(validFlashSettings);
		resumeCheckbox->setEnabled(validFlashSettings);
	}
	else
	{
		partitionNameComboBox->setEnabled(false);

		flashProgressBar->setEnabled(true);
		optionsGroup->setEnabled(false);
		sessionGroup->setEnabled(false);
	}
}

void MainWindow::UpdateCreatePackageInterfaceAvailability(void)
{
	if (heimdallState == HeimdallState::Stopped)
	{
		const FirmwareInfo& firmwareInfo = workingPackageData.GetFirmwareInfo();

		if (firmwareInfo.GetName().isEmpty() || firmwareInfo.GetVersion().isEmpty() || firmwareInfo.GetPlatformInfo().GetName().isEmpty()
			|| firmwareInfo.GetPlatformInfo().GetVersion().isEmpty() || firmwareInfo.GetDevelopers().isEmpty() || firmwareInfo.GetDeviceInfos().isEmpty())
		{
			buildPackageButton->setEnabled(false);
		}
		else
		{
			buildPackageButton->setEnabled(true);
		}

		if (addDeveloperButton->text().isEmpty())
			addDeveloperButton->setEnabled(false);
		else
			addDeveloperButton->setEnabled(true);

		if (createDevelopersListWidget->currentRow() >= 0)
			removeDeveloperButton->setEnabled(true);
		else
			removeDeveloperButton->setEnabled(false);
	}
	else
	{
		buildPackageButton->setEnabled(false);
	}
}

void MainWindow::UpdateUtilitiesInterfaceAvailability(void)
{
	if (heimdallState == HeimdallState::Stopped)
	{
		detectDeviceButton->setEnabled(true);
		closePcScreenButton->setEnabled(true);
		pitSaveAsButton->setEnabled(true);

		downloadPitButton->setEnabled(!pitDestinationLineEdit->text().isEmpty());
		
		if (printPitDeviceRadioBox->isChecked())
		{
			// Device
			printLocalPitGroup->setEnabled(false);
			printPitButton->setEnabled(true);
		}
		else
		{
			// Local File
			printLocalPitGroup->setEnabled(true);
			printLocalPitLineEdit->setEnabled(true);
			printLocalPitBrowseButton->setEnabled(true);

			printPitButton->setEnabled(!printLocalPitLineEdit->text().isEmpty());
		}
	}
	else
	{
		detectDeviceButton->setEnabled(false);
		closePcScreenButton->setEnabled(false);
		pitSaveAsButton->setEnabled(false);
		downloadPitButton->setEnabled(false);

		printLocalPitGroup->setEnabled(false);
		printPitButton->setEnabled(false);
	}
}

void MainWindow::UpdateInterfaceAvailability(void)
{
	UpdateLoadPackageInterfaceAvailability();
	UpdateFlashInterfaceAvailability();
	UpdateCreatePackageInterfaceAvailability();
	UpdateUtilitiesInterfaceAvailability();

	if (heimdallState == HeimdallState::Stopped)
	{		
		// Enable/disable tabs

		for (int i = 0; i < functionTabWidget->count(); i++)
			functionTabWidget->setTabEnabled(i, true);

		functionTabWidget->setTabEnabled(functionTabWidget->indexOf(createPackageTab), startFlashButton->isEnabled());
	}
	else
	{
		// Disable non-current tabs

		for (int i = 0; i < functionTabWidget->count(); i++)
		{
			if (i == functionTabWidget->currentIndex())
				functionTabWidget->setTabEnabled(i, true);
			else
				functionTabWidget->setTabEnabled(i, false);
		}
	}
}

void MainWindow::UpdatePartitionNamesInterface(void)
{
	populatingPartitionNames = true;

	partitionNameComboBox->clear();

	int partitionsListWidgetRow = partitionsListWidget->currentRow();

	if (partitionsListWidgetRow >= 0)
	{
		const FileInfo& partitionInfo = workingPackageData.GetFirmwareInfo().GetFileInfos()[partitionsListWidget->currentRow()];

		for (int i = 0; i < unusedPartitionIds.length(); i++)
			partitionNameComboBox->addItem(currentPitData.FindEntry(unusedPartitionIds[i])->GetPartitionName());

		partitionNameComboBox->addItem(currentPitData.FindEntry(partitionInfo.GetPartitionId())->GetPartitionName());
		partitionNameComboBox->setCurrentIndex(unusedPartitionIds.length());
	}

	populatingPartitionNames = false;

	UpdateFlashInterfaceAvailability();
}

MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
{
    setupUi(this);

	heimdallState = HeimdallState::Stopped;

	lastDirectory = QDir::toNativeSeparators(QApplication::applicationDirPath());

	populatingPartitionNames = false;

	verboseOutput = false;

	tabIndex = functionTabWidget->currentIndex();
	functionTabWidget->setTabEnabled(functionTabWidget->indexOf(createPackageTab), false);

	QObject::connect(functionTabWidget, SIGNAL(currentChanged(int)), this, SLOT(FunctionTabChanged(int)));
	
	// Menu
	QObject::connect(actionDonate, SIGNAL(triggered()), this, SLOT(OpenDonationWebpage()));
	QObject::connect(actionVerboseOutput, SIGNAL(toggled(bool)), this, SLOT(SetVerboseOutput(bool)));
	QObject::connect(actionResumeConnection, SIGNAL(toggled(bool)), this, SLOT(SetResume(bool)));
	QObject::connect(actionAboutHeimdall, SIGNAL(triggered()), this, SLOT(ShowAbout()));

	// Load Package Tab
	QObject::connect(browseFirmwarePackageButton, SIGNAL(clicked()), this, SLOT(SelectFirmwarePackage()));
	QObject::connect(developerHomepageButton, SIGNAL(clicked()), this, SLOT(OpenDeveloperHomepage()));
	QObject::connect(developerDonateButton, SIGNAL(clicked()), this, SLOT(OpenDeveloperDonationWebpage()));
	QObject::connect(loadFirmwareButton, SIGNAL(clicked()), this, SLOT(LoadFirmwarePackage()));

	QObject::connect(partitionsListWidget, SIGNAL(currentRowChanged(int)), this, SLOT(SelectPartition(int)));
	QObject::connect(addPartitionButton, SIGNAL(clicked()), this, SLOT(AddPartition()));
	QObject::connect(removePartitionButton, SIGNAL(clicked()), this, SLOT(RemovePartition()));

	// Flash Tab
	QObject::connect(partitionNameComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(SelectPartitionName(int)));
	QObject::connect(partitionFileBrowseButton, SIGNAL(clicked()), this, SLOT(SelectPartitionFile()));

	QObject::connect(pitBrowseButton, SIGNAL(clicked()), this, SLOT(SelectPit()));

	QObject::connect(repartitionCheckBox, SIGNAL(stateChanged(int)), this, SLOT(SetRepartition(int)));

	QObject::connect(noRebootCheckBox, SIGNAL(stateChanged(int)), this, SLOT(SetNoReboot(int)));
	QObject::connect(resumeCheckbox, SIGNAL(stateChanged(int)), this, SLOT(SetResume(int)));
	
	QObject::connect(startFlashButton, SIGNAL(clicked()), this, SLOT(StartFlash()));

	// Create Package Tab
	QObject::connect(createFirmwareNameLineEdit, SIGNAL(textChanged(const QString&)), this, SLOT(FirmwareNameChanged(const QString&)));
	QObject::connect(createFirmwareVersionLineEdit, SIGNAL(textChanged(const QString&)), this, SLOT(FirmwareVersionChanged(const QString&)));
	QObject::connect(createPlatformNameLineEdit, SIGNAL(textChanged(const QString&)), this, SLOT(PlatformNameChanged(const QString&)));
	QObject::connect(createPlatformVersionLineEdit, SIGNAL(textChanged(const QString&)), this, SLOT(PlatformVersionChanged(const QString&)));

	QObject::connect(createHomepageLineEdit, SIGNAL(textChanged(const QString&)), this, SLOT(HomepageUrlChanged(const QString&)));
	QObject::connect(createDonateLineEdit, SIGNAL(textChanged(const QString&)), this, SLOT(DonateUrlChanged(const QString&)));

	QObject::connect(createDevelopersListWidget, SIGNAL(currentRowChanged(int)), this, SLOT(SelectDeveloper(int)));
	QObject::connect(createDeveloperNameLineEdit, SIGNAL(textChanged(const QString&)), this, SLOT(DeveloperNameChanged(const QString&)));
	QObject::connect(addDeveloperButton, SIGNAL(clicked()), this, SLOT(AddDeveloper()));
	QObject::connect(removeDeveloperButton, SIGNAL(clicked()), this, SLOT(RemoveDeveloper()));

	QObject::connect(createDevicesListWidget, SIGNAL(currentRowChanged(int)), this, SLOT(SelectDevice(int)));
	QObject::connect(deviceManufacturerLineEdit, SIGNAL(textChanged(const QString&)), this, SLOT(DeviceInfoChanged(const QString&)));
	QObject::connect(deviceNameLineEdit, SIGNAL(textChanged(const QString&)), this, SLOT(DeviceInfoChanged(const QString&)));
	QObject::connect(deviceProductCodeLineEdit, SIGNAL(textChanged(const QString&)), this, SLOT(DeviceInfoChanged(const QString&)));
	QObject::connect(addDeviceButton, SIGNAL(clicked()), this, SLOT(AddDevice()));
	QObject::connect(removeDeviceButton, SIGNAL(clicked()), this, SLOT(RemoveDevice()));
			
	QObject::connect(buildPackageButton, SIGNAL(clicked()), this, SLOT(BuildPackage()));

	// Utilities Tab
	QObject::connect(detectDeviceButton, SIGNAL(clicked()), this, SLOT(DetectDevice()));

	QObject::connect(closePcScreenButton, SIGNAL(clicked()), this, SLOT(ClosePcScreen()));

	QObject::connect(printPitDeviceRadioBox, SIGNAL(toggled(bool)), this, SLOT(DevicePrintPitToggled(bool)));
	QObject::connect(printPitLocalFileRadioBox, SIGNAL(toggled(bool)), this, SLOT(LocalFilePrintPitToggled(bool)));
	QObject::connect(printLocalPitBrowseButton, SIGNAL(clicked()), this, SLOT(SelectPrintPitFile()));
	QObject::connect(printPitButton, SIGNAL(clicked()), this, SLOT(PrintPit()));

	QObject::connect(pitSaveAsButton, SIGNAL(clicked()), this, SLOT(SelectPitDestination()));
	QObject::connect(downloadPitButton, SIGNAL(clicked()), this, SLOT(DownloadPit()));

	// Heimdall Command Line
	QObject::connect(&heimdallProcess, SIGNAL(readyRead()), this, SLOT(HandleHeimdallStdout()));
	QObject::connect(&heimdallProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(HandleHeimdallReturned(int, QProcess::ExitStatus)));
	QObject::connect(&heimdallProcess, SIGNAL(error(QProcess::ProcessError)), this, SLOT(HandleHeimdallError(QProcess::ProcessError)));
}

MainWindow::~MainWindow()
{
}

void MainWindow::OpenDonationWebpage(void)
{
	QDesktopServices::openUrl(QUrl("http://www.glassechidna.com.au/donate/", QUrl::StrictMode));
}

void MainWindow::SetVerboseOutput(bool enabled)
{
	verboseOutput = enabled;
}

void MainWindow::ShowAbout(void)
{
	aboutForm.show();
}

void MainWindow::FunctionTabChanged(int index)
{
	tabIndex = index;
	deviceDetectedRadioButton->setChecked(false);
}

void MainWindow::SelectFirmwarePackage(void)
{
	loadedPackageData.Clear();
	UpdatePackageUserInterface();

	QString path = PromptFileSelection("Select Package", "*.tar.gz");
	firmwarePackageLineEdit->setText(path);

	if (firmwarePackageLineEdit->text() != "")
	{
		if (Packaging::ExtractPackage(firmwarePackageLineEdit->text(), &loadedPackageData))
			UpdatePackageUserInterface();
		else
			loadedPackageData.Clear();
	}
}

void MainWindow::OpenDeveloperHomepage(void)
{
	if(!QDesktopServices::openUrl(QUrl(loadedPackageData.GetFirmwareInfo().GetUrl(), QUrl::TolerantMode)))
		Alerts::DisplayWarning(QString("Cannot open invalid URL:\n%1").arg(loadedPackageData.GetFirmwareInfo().GetUrl()));
}

void MainWindow::OpenDeveloperDonationWebpage(void)
{
	if (!QDesktopServices::openUrl(QUrl(loadedPackageData.GetFirmwareInfo().GetDonateUrl(), QUrl::TolerantMode)))
		Alerts::DisplayWarning(QString("Cannot open invalid URL:\n%1").arg(loadedPackageData.GetFirmwareInfo().GetDonateUrl()));
}

void MainWindow::LoadFirmwarePackage(void)
{
	workingPackageData.Clear();
	currentPitData.Clear();
	
	workingPackageData.GetFiles().append(loadedPackageData.GetFiles());
	loadedPackageData.RemoveAllFiles();

	const QList<FileInfo> packageFileInfos = loadedPackageData.GetFirmwareInfo().GetFileInfos();

	for (int i = 0; i < packageFileInfos.length(); i++)
	{
		bool fileFound = false;

		for (int j = 0; j < workingPackageData.GetFiles().length(); j++)
		{
			if (workingPackageData.GetFiles()[j]->fileTemplate() == ("XXXXXX-" + packageFileInfos[i].GetFilename()))
			{
				FileInfo partitionInfo(packageFileInfos[i].GetPartitionId(), QDir::current().absoluteFilePath(workingPackageData.GetFiles()[j]->fileName()));
				workingPackageData.GetFirmwareInfo().GetFileInfos().append(partitionInfo);

				fileFound = true;
				break;
			}
		}

		if (!fileFound)
			Alerts::DisplayWarning(QString("%1 is missing from the package.").arg(packageFileInfos[i].GetFilename()));
	}

	// Find the PIT file and read it
	for (int i = 0; i < workingPackageData.GetFiles().length(); i++)
	{
		QTemporaryFile *file = workingPackageData.GetFiles()[i];

		if (file->fileTemplate() == ("XXXXXX-" + loadedPackageData.GetFirmwareInfo().GetPitFilename()))
		{
			workingPackageData.GetFirmwareInfo().SetPitFilename(QDir::current().absoluteFilePath(file->fileName()));

			if (!ReadPit(file))
			{
				Alerts::DisplayError("Failed to read PIT file.");

				loadedPackageData.Clear();
				UpdatePackageUserInterface();

				workingPackageData.Clear();
				UpdateUnusedPartitionIds();
				return;
			}

			break;
		}
	}

	UpdateUnusedPartitionIds();
	workingPackageData.GetFirmwareInfo().SetRepartition(loadedPackageData.GetFirmwareInfo().GetRepartition());
	workingPackageData.GetFirmwareInfo().SetNoReboot(loadedPackageData.GetFirmwareInfo().GetNoReboot());

	loadedPackageData.Clear();
	UpdatePackageUserInterface();
	firmwarePackageLineEdit->clear();

	partitionsListWidget->clear();

	// Populate partitionsListWidget with partition names (from the PIT file)
	for (int i = 0; i < workingPackageData.GetFirmwareInfo().GetFileInfos().length(); i++)
	{
		const FileInfo& partitionInfo = workingPackageData.GetFirmwareInfo().GetFileInfos()[i];

		const PitEntry *pitEntry = currentPitData.FindEntry(partitionInfo.GetPartitionId());

		if (pitEntry)
		{
			partitionsListWidget->addItem(pitEntry->GetPartitionName());
		}
		else
		{
			Alerts::DisplayError("Firmware package includes invalid partition IDs.");

			loadedPackageData.GetFirmwareInfo().Clear();
			currentPitData.Clear();
			UpdateUnusedPartitionIds();

			partitionsListWidget->clear();
			return;
		}
	}

	partitionNameComboBox->clear();
	partitionIdLineEdit->clear();
	partitionFileLineEdit->clear();
	partitionFileBrowseButton->setEnabled(false);

	repartitionCheckBox->setEnabled(true);
	repartitionCheckBox->setChecked(workingPackageData.GetFirmwareInfo().GetRepartition());
	noRebootCheckBox->setEnabled(true);
	noRebootCheckBox->setChecked(workingPackageData.GetFirmwareInfo().GetNoReboot());

	partitionsListWidget->setEnabled(true);
	addPartitionButton->setEnabled(true);
	removePartitionButton->setEnabled(true && partitionsListWidget->currentRow() >= 0);

	pitLineEdit->setText(workingPackageData.GetFirmwareInfo().GetPitFilename());

	functionTabWidget->setCurrentWidget(flashTab);

	UpdateInterfaceAvailability();
}

void MainWindow::SelectPartitionName(int index)
{
	if (!populatingPartitionNames && index != -1 && index != unusedPartitionIds.length())
	{
		unsigned int newPartitionIndex = unusedPartitionIds[index];
		unusedPartitionIds.removeAt(index);

		FileInfo& fileInfo = workingPackageData.GetFirmwareInfo().GetFileInfos()[partitionsListWidget->currentRow()];
		unusedPartitionIds.append(fileInfo.GetPartitionId());
		fileInfo.SetPartitionId(newPartitionIndex);

		PitEntry *pitEntry = currentPitData.FindEntry(newPartitionIndex);

		QString title("File");

		if (pitEntry && strlen(pitEntry->GetFlashFilename()) > 0)
			title += " (" + QString(pitEntry->GetFlashFilename()) + ")";

		partitionFileGroup->setTitle(title);

		if (!fileInfo.GetFilename().isEmpty())
		{
			QString partitionFilename = pitEntry->GetFlashFilename();
			int lastPeriod = partitionFilename.lastIndexOf(QChar('.'));

			if (lastPeriod >= 0)
			{
				QString partitionFileExtension = partitionFilename.mid(lastPeriod + 1);

				lastPeriod = fileInfo.GetFilename().lastIndexOf(QChar('.'));

				if (lastPeriod < 0 || fileInfo.GetFilename().mid(lastPeriod + 1) != partitionFileExtension)
					Alerts::DisplayWarning(QString("%1 partition expects files with file extension \"%2\".").arg(pitEntry->GetPartitionName(), partitionFileExtension));
			}
		}

		partitionNameComboBox->clear();

		// Update interface
		UpdatePartitionNamesInterface();
		partitionIdLineEdit->setText(QString::number(newPartitionIndex));
		partitionsListWidget->currentItem()->setText(currentPitData.FindEntry(newPartitionIndex)->GetPartitionName());
	}
}

void MainWindow::SelectPartitionFile(void)
{
	QString path = PromptFileSelection();

	if (path != "")
	{
		FileInfo& fileInfo = workingPackageData.GetFirmwareInfo().GetFileInfos()[partitionsListWidget->currentRow()];
		PitEntry *pitEntry = currentPitData.FindEntry(fileInfo.GetPartitionId());

		QString partitionFilename = pitEntry->GetFlashFilename();
		int lastPeriod = partitionFilename.lastIndexOf(QChar('.'));

		if (lastPeriod >= 0)
		{
			QString partitionFileExtension = partitionFilename.mid(lastPeriod + 1);

			lastPeriod = path.lastIndexOf(QChar('.'));

			if (lastPeriod < 0 || path.mid(lastPeriod + 1) != partitionFileExtension)
				Alerts::DisplayWarning(QString("%1 partition expects files with file extension \"%2\".").arg(pitEntry->GetPartitionName(), partitionFileExtension));
		}

		fileInfo.SetFilename(path);
		partitionFileLineEdit->setText(path);

		pitBrowseButton->setEnabled(true);
		partitionsListWidget->setEnabled(true);
		UpdateInterfaceAvailability();

		if (unusedPartitionIds.length() > 0)
			addPartitionButton->setEnabled(true);
	}
}

void MainWindow::SelectPartition(int row)
{
	if (row >= 0)
	{
		const FileInfo& partitionInfo = workingPackageData.GetFirmwareInfo().GetFileInfos()[row];

		UpdatePartitionNamesInterface();

		partitionIdLineEdit->setText(QString::number(partitionInfo.GetPartitionId()));
		partitionFileLineEdit->setText(partitionInfo.GetFilename());
		partitionFileBrowseButton->setEnabled(true);

		removePartitionButton->setEnabled(true);

		QString title("File");

		PitEntry *pitEntry = currentPitData.FindEntry(partitionInfo.GetPartitionId());

		if (pitEntry && strlen(pitEntry->GetFlashFilename()) > 0)
			title += " (" + QString(pitEntry->GetFlashFilename()) + ")";

		partitionFileGroup->setTitle(title);
	}
	else
	{
		UpdatePartitionNamesInterface();

		partitionIdLineEdit->clear();
		partitionFileLineEdit->clear();
		partitionFileBrowseButton->setEnabled(false);

		removePartitionButton->setEnabled(false);

		partitionFileGroup->setTitle("File");
	}
}

void MainWindow::AddPartition(void)
{
	FileInfo partitionInfo(unusedPartitionIds.first(), "");
	workingPackageData.GetFirmwareInfo().GetFileInfos().append(partitionInfo);
	UpdateUnusedPartitionIds();

	pitBrowseButton->setEnabled(false);
	addPartitionButton->setEnabled(false);

	partitionsListWidget->addItem(currentPitData.FindEntry(partitionInfo.GetPartitionId())->GetPartitionName());
	partitionsListWidget->setCurrentRow(partitionsListWidget->count() - 1);
	partitionsListWidget->setEnabled(false);

	UpdateInterfaceAvailability();
}

void MainWindow::RemovePartition(void)
{
	workingPackageData.GetFirmwareInfo().GetFileInfos().removeAt(partitionsListWidget->currentRow());
	UpdateUnusedPartitionIds();

	QListWidgetItem *item = partitionsListWidget->currentItem();
	partitionsListWidget->setCurrentRow(-1);
	delete item;

	pitBrowseButton->setEnabled(true);
	addPartitionButton->setEnabled(true);
	partitionsListWidget->setEnabled(true);
	UpdateInterfaceAvailability();
}

void MainWindow::SelectPit(void)
{
	QString path = PromptFileSelection("Select PIT", "*.pit");
	bool validPit = path != "";

	if (validPit)
	{
		// In order to map files in the old PIT to file in the new one, we first must use partition names instead of IDs.
		QList<FileInfo> fileInfos = workingPackageData.GetFirmwareInfo().GetFileInfos();

		int partitionNamesCount = fileInfos.length();
		QString *partitionNames = new QString[fileInfos.length()];
		for (int i = 0; i < fileInfos.length(); i++)
			partitionNames[i] = currentPitData.FindEntry(fileInfos[i].GetPartitionId())->GetPartitionName();

		currentPitData.Clear();

		QFile pitFile(path);

		if (ReadPit(&pitFile))
		{
			workingPackageData.GetFirmwareInfo().SetPitFilename(path);

			partitionsListWidget->clear();
			int partitionInfoIndex = 0;

			for (int i = 0; i < partitionNamesCount; i++)
			{
				const PitEntry *pitEntry = currentPitData.FindEntry(partitionNames[i].toAscii().constData());
				
				if (pitEntry)
				{
					fileInfos[partitionInfoIndex++].SetPartitionId(pitEntry->GetIdentifier());
					partitionsListWidget->addItem(pitEntry->GetPartitionName());
				}
				else
				{
					fileInfos.removeAt(partitionInfoIndex);
				}
			}
		}
		else
		{
			validPit = false;
		}

		// If the selected PIT was invalid, attempt to reload the old one.
		if (!validPit)
		{
			Alerts::DisplayError("The file selected was not a valid PIT file.");

			if (!workingPackageData.GetFirmwareInfo().GetPitFilename().isEmpty())
			{
				QFile originalPitFile(workingPackageData.GetFirmwareInfo().GetPitFilename());

				if (ReadPit(&originalPitFile))
				{
					validPit = true;
				}
				else
				{
					Alerts::DisplayError("Failed to reload working PIT data.");

					workingPackageData.Clear();
					partitionsListWidget->clear();
				}
			}
		}

		UpdateUnusedPartitionIds();

		delete [] partitionNames;

		pitLineEdit->setText(workingPackageData.GetFirmwareInfo().GetPitFilename());

		repartitionCheckBox->setEnabled(validPit);
		noRebootCheckBox->setEnabled(validPit);
		partitionsListWidget->setEnabled(validPit);

		addPartitionButton->setEnabled(validPit);
		removePartitionButton->setEnabled(validPit && partitionsListWidget->currentRow() >= 0);

		UpdateInterfaceAvailability();
	}
}


void MainWindow::SetRepartition(int enabled)
{
	workingPackageData.GetFirmwareInfo().SetRepartition(enabled);

	repartitionCheckBox->setChecked(enabled);
}

void MainWindow::SetNoReboot(int enabled)
{
	workingPackageData.GetFirmwareInfo().SetNoReboot(enabled);

	noRebootCheckBox->setChecked(enabled);
}

void MainWindow::SetResume(bool enabled)
{
	resume = enabled;

	actionResumeConnection->setChecked(enabled);
	resumeCheckbox->setChecked(enabled);
}

void MainWindow::SetResume(int enabled)
{
	SetResume(enabled != 0);
}

void MainWindow::StartFlash(void)
{
	outputPlainTextEdit->clear();

	heimdallState = HeimdallState::Flashing;
	heimdallFailed = false;

	const FirmwareInfo& firmwareInfo = workingPackageData.GetFirmwareInfo();
	const QList<FileInfo>& fileInfos = firmwareInfo.GetFileInfos();
	
	QStringList arguments;
	arguments.append("flash");

	if (firmwareInfo.GetRepartition())
		arguments.append("--repartition");

	arguments.append("--pit");
	arguments.append(firmwareInfo.GetPitFilename());

	for (int i = 0; i < fileInfos.length(); i++)
	{
		QString flag;
		flag.sprintf("--%u", fileInfos[i].GetPartitionId());

		arguments.append(flag);
		arguments.append(fileInfos[i].GetFilename());
	}

	if (firmwareInfo.GetNoReboot())
		arguments.append("--no-reboot");

	if (resume)
		arguments.append("--resume");

	if (verboseOutput)
		arguments.append("--verbose");

	arguments.append("--stdout-errors");

	StartHeimdall(arguments);
}

void MainWindow::FirmwareNameChanged(const QString& text)
{
	workingPackageData.GetFirmwareInfo().SetName(text);
	UpdateInterfaceAvailability();
}

void MainWindow::FirmwareVersionChanged(const QString& text)
{
	workingPackageData.GetFirmwareInfo().SetVersion(text);
	UpdateInterfaceAvailability();
}

void MainWindow::PlatformNameChanged(const QString& text)
{
	workingPackageData.GetFirmwareInfo().GetPlatformInfo().SetName(text);
	UpdateInterfaceAvailability();
}

void MainWindow::PlatformVersionChanged(const QString& text)
{
	workingPackageData.GetFirmwareInfo().GetPlatformInfo().SetVersion(text);
	UpdateInterfaceAvailability();
}

void MainWindow::HomepageUrlChanged(const QString& text)
{
	workingPackageData.GetFirmwareInfo().SetUrl(text);
}

void MainWindow::DonateUrlChanged(const QString& text)
{
	workingPackageData.GetFirmwareInfo().SetDonateUrl(text);
}

void MainWindow::DeveloperNameChanged(const QString& text)
{
	UNUSED(text);

	UpdateCreatePackageInterfaceAvailability();
}

void MainWindow::SelectDeveloper(int row)
{
	UNUSED(row);

	UpdateCreatePackageInterfaceAvailability();
}

void MainWindow::AddDeveloper(void)
{
	workingPackageData.GetFirmwareInfo().GetDevelopers().append(createDeveloperNameLineEdit->text());

	createDevelopersListWidget->addItem(createDeveloperNameLineEdit->text());
	createDeveloperNameLineEdit->clear();
	
	UpdateCreatePackageInterfaceAvailability();
}

void MainWindow::RemoveDeveloper(void)
{
	workingPackageData.GetFirmwareInfo().GetDevelopers().removeAt(createDevelopersListWidget->currentRow());

	QListWidgetItem *item = createDevelopersListWidget->currentItem();
	createDevelopersListWidget->setCurrentRow(-1);
	delete item;

	removeDeveloperButton->setEnabled(false);
	
	UpdateInterfaceAvailability();
}

void MainWindow::DeviceInfoChanged(const QString& text)
{
	UNUSED(text);

	if (deviceManufacturerLineEdit->text().isEmpty() || deviceNameLineEdit->text().isEmpty() || deviceProductCodeLineEdit->text().isEmpty())
		addDeviceButton->setEnabled(false);
	else
		addDeviceButton->setEnabled(true);
}

void MainWindow::SelectDevice(int row)
{
	if (row >= 0)
		removeDeviceButton->setEnabled(true);
	else
		removeDeviceButton->setEnabled(false);
}

void MainWindow::AddDevice(void)
{
	workingPackageData.GetFirmwareInfo().GetDeviceInfos().append(DeviceInfo(deviceManufacturerLineEdit->text(), deviceProductCodeLineEdit->text(),
		deviceNameLineEdit->text()));

	createDevicesListWidget->addItem(deviceManufacturerLineEdit->text() + " " + deviceNameLineEdit->text() + ": " + deviceProductCodeLineEdit->text());
	deviceManufacturerLineEdit->clear();
	deviceNameLineEdit->clear();
	deviceProductCodeLineEdit->clear();
	
	UpdateInterfaceAvailability();
}

void MainWindow::RemoveDevice(void)
{
	workingPackageData.GetFirmwareInfo().GetDeviceInfos().removeAt(createDevicesListWidget->currentRow());

	QListWidgetItem *item = createDevicesListWidget->currentItem();
	createDevicesListWidget->setCurrentRow(-1);
	delete item;

	removeDeviceButton->setEnabled(false);
	
	UpdateInterfaceAvailability();
}
			
void MainWindow::BuildPackage(void)
{
	QString packagePath = PromptFileCreation("Save Package", "*.tar.gz");

	if (!packagePath.isEmpty())
	{
		if (!packagePath.endsWith(".tar.gz", Qt::CaseInsensitive))
		{
			if (packagePath.endsWith(".tar", Qt::CaseInsensitive))
				packagePath.append(".gz");
			else if (packagePath.endsWith(".gz", Qt::CaseInsensitive))
				packagePath.replace(packagePath.length() - 3, ".tar.gz");
			else if (packagePath.endsWith(".tgz", Qt::CaseInsensitive))
				packagePath.replace(packagePath.length() - 4, ".tar.gz");
			else
				packagePath.append(".tar.gz");
		}

		Packaging::BuildPackage(packagePath, workingPackageData.GetFirmwareInfo());
	}
}

void MainWindow::DetectDevice(void)
{
	deviceDetectedRadioButton->setChecked(false);
	utilityOutputPlainTextEdit->clear();

	heimdallState = HeimdallState::DetectingDevice;
	heimdallFailed = false;
	
	QStringList arguments;
	arguments.append("detect");

	if (verboseOutput)
		arguments.append("--verbose");

	arguments.append("--stdout-errors");

	StartHeimdall(arguments);
}

void MainWindow::ClosePcScreen(void)
{
	utilityOutputPlainTextEdit->clear();

	heimdallState = HeimdallState::ClosingPcScreen;
	heimdallFailed = false;
	
	QStringList arguments;
	arguments.append("close-pc-screen");
	
	if (resume)
		arguments.append("--resume");

	if (verboseOutput)
		arguments.append("--verbose");

	arguments.append("--stdout-errors");

	StartHeimdall(arguments);
}

void MainWindow::SelectPitDestination(void)
{
	QString path = PromptFileCreation("Save PIT", "*.pit");

	if (path != "")
	{
		if (!path.endsWith(".pit"))
			path.append(".pit");

		pitDestinationLineEdit->setText(path);

		UpdateInterfaceAvailability();
	}
}

void MainWindow::DownloadPit(void)
{
	deviceDetectedRadioButton->setChecked(false);
	utilityOutputPlainTextEdit->clear();

	heimdallState = HeimdallState::DownloadingPit;
	heimdallFailed = false;
	
	QStringList arguments;
	arguments.append("download-pit");

	arguments.append("--output");
	arguments.append(pitDestinationLineEdit->text());

	arguments.append("--no-reboot");

	if (resume)
		arguments.append("--resume");

	if (verboseOutput)
		arguments.append("--verbose");

	arguments.append("--stdout-errors");

	StartHeimdall(arguments);
}

void MainWindow::DevicePrintPitToggled(bool checked)
{
	if (checked)
	{
		if (printPitLocalFileRadioBox->isChecked())
			printPitLocalFileRadioBox->setChecked(false);
	}

	UpdateUtilitiesInterfaceAvailability();
}

void MainWindow::LocalFilePrintPitToggled(bool checked)
{
	if (checked)
	{
		if (printPitDeviceRadioBox->isChecked())
			printPitDeviceRadioBox->setChecked(false);
	}

	UpdateUtilitiesInterfaceAvailability();
}

void MainWindow::SelectPrintPitFile(void)
{
	QString path = PromptFileSelection("Select PIT", "*.pit");

	if (path != "")
		printLocalPitLineEdit->setText(path);

	if (printLocalPitLineEdit->text() != "")
		printPitButton->setEnabled(true);
	else
		printPitButton->setEnabled(false);
}

void MainWindow::PrintPit(void)
{
	utilityOutputPlainTextEdit->clear();

	heimdallState = HeimdallState::PrintingPit;
	heimdallFailed = false;
	
	QStringList arguments;
	arguments.append("print-pit");

	if (printPitLocalFileRadioBox->isChecked())
	{
		arguments.append("--file");
		arguments.append(printLocalPitLineEdit->text());
	}

	arguments.append("--stdout-errors");
	arguments.append("--no-reboot");
	
	if (resume)
		arguments.append("--resume");

	if (verboseOutput)
		arguments.append("--verbose");

	StartHeimdall(arguments);
}

void MainWindow::HandleHeimdallStdout(void)
{
	QString output = heimdallProcess.readAll();

	// We often receive multiple lots of output from Heimdall at one time. So we use regular expressions
	// to ensure we don't miss out on any important information.
	QRegExp uploadingExp("Uploading [^\n]+\n");
	if (output.lastIndexOf(uploadingExp) > -1)
		flashLabel->setText(uploadingExp.cap().left(uploadingExp.cap().length() - 1));

	QRegExp percentExp("[\b\n][0-9]+%");
	if (output.lastIndexOf(percentExp) > -1)
	{
		QString percentString = percentExp.cap();
		flashProgressBar->setValue(percentString.mid(1, percentString.length() - 2).toInt());
	}

	output.remove(QChar('\b'));
	output.replace(QChar('%'), QString("%\n"));

	if (heimdallState == HeimdallState::Flashing)
	{
		outputPlainTextEdit->insertPlainText(output);
		outputPlainTextEdit->ensureCursorVisible();
	}
	else
	{
		utilityOutputPlainTextEdit->insertPlainText(output);
		utilityOutputPlainTextEdit->ensureCursorVisible();
	}
}

void MainWindow::HandleHeimdallReturned(int exitCode, QProcess::ExitStatus exitStatus)
{
	HandleHeimdallStdout();

	if (exitStatus == QProcess::NormalExit && exitCode == 0)
	{
		bool executedNoReboot = (heimdallState == HeimdallState::Flashing && loadedPackageData.GetFirmwareInfo().GetNoReboot())
			|| (heimdallState == HeimdallState::PrintingPit && printPitDeviceRadioBox->isChecked()) || heimdallState == HeimdallState::DownloadingPit;

		SetResume(executedNoReboot);

		if (heimdallState == HeimdallState::Flashing)
		{
			flashLabel->setText("Flash completed successfully!");
		}
		else if (heimdallState == HeimdallState::DetectingDevice)
		{
			deviceDetectedRadioButton->setChecked(true);
		}
	}
	else
	{
		if (heimdallState == HeimdallState::Flashing)
		{
			QString error = heimdallProcess.readAllStandardError();

			int lastNewLineChar = error.lastIndexOf('\n');

			if (lastNewLineChar == 0)
				error = error.mid(1).remove("ERROR: ");
			else
				error = error.left(lastNewLineChar).remove("ERROR: ");

			flashLabel->setText(error);
		}
		else if (heimdallState == HeimdallState::DetectingDevice)
		{
			deviceDetectedRadioButton->setChecked(false);
		}
	}

	heimdallState = HeimdallState::Stopped;
	flashProgressBar->setEnabled(false);
	UpdateInterfaceAvailability();
}

void MainWindow::HandleHeimdallError(QProcess::ProcessError error)
{	
	if (error == QProcess::FailedToStart || error == QProcess::Timedout)
	{
		if (heimdallState == HeimdallState::Flashing)
		{
			flashLabel->setText("Failed to start Heimdall!");
			flashProgressBar->setEnabled(false);
		}
		else
		{
			utilityOutputPlainTextEdit->setPlainText("\nFRONTEND ERROR: Failed to start Heimdall!");
		}

		heimdallFailed = true;
		heimdallState = HeimdallState::Stopped;
		UpdateInterfaceAvailability();
	}
	else if (error == QProcess::Crashed)
	{
		if (heimdallState == HeimdallState::Flashing)
		{
			flashLabel->setText("Heimdall crashed!");
			flashProgressBar->setEnabled(false);
		}
		else
		{
			utilityOutputPlainTextEdit->appendPlainText("\nFRONTEND ERROR: Heimdall crashed!");
		}
		
		heimdallState = HeimdallState::Stopped;
		UpdateInterfaceAvailability();
	}
	else
	{
		if (heimdallState == HeimdallState::Flashing)
		{
			flashLabel->setText("Heimdall reported an unknown error!");
			flashProgressBar->setEnabled(false);
		}
		else
		{
			utilityOutputPlainTextEdit->appendPlainText("\nFRONTEND ERROR: Heimdall reported an unknown error!");
		}
		
		heimdallState = HeimdallState::Stopped;
		UpdateInterfaceAvailability();
	}
}