summaryrefslogblamecommitdiffstats
path: root/src/server/mainwindow/mainwindow.cpp
blob: cf2fe85752fce7da05d2170af6684c4443f2288a (plain) (tree)




























                                                                                
                                  






                                  
                                                            






                                                                                                   
                                 















                                                                        
                                     




                                                                                            
 

                                                                                  



                                                                                                      
                                                                                                      
                                                                                              




























































































































                                                                                                                                          
                                                                        






























































































                                                                                                                                    






                                                                                  


































































                                                                                                              










                                                                                                          



                                                                             

                                                             
                                      
         
                                            



                              
                                                   





                                                                                                           
                                                                             







                                                                                                                          
                                                                           


                                                                   
                                                                 

                                                                          
                                                                





                                                                           
                                       





                                                                






















                                                                                                                  




                                                               

                            


                                                                                                          

                                 
                                      


                                                      
         












                                                               
                       












                                                                                                          
         












                                                                    
                                                                  













                                                                                                          
         






                                                      
         

















                                                                    


                                                                                                          
                              
                                 



                                          
         
                       



                                                            
                              






                                                                    
                                                                  


                                               

 






                                                               







                                                                                                          










                                                                                                          





















                                                                                                          















                                                                                                          




                                                                                                                                  









































                                                                                                                                     
                                                                                                                                  
                                                                                                                                            
                                    




                                                                                                          

                                     
                                                    


                                                      














                                                                 

                             
                                            































                                                                   

                              

                                 





























                                                                                                          

                                                                                       







                                                                                                                         
                                                                        



                                                                                                                  




                                                                                                          









                                                                                                                    

                                                                                                          










                                                                                                                  
                                                                                            








                                                                                                                                    
                                                                                 
 

                                                                                       

                                                                                             










                                                                                                                            
                              
                                 
                                                    
                                   
                                                                                        



                                     




                                        
/*
 # Copyright (c) 2009 - OpenSLX Project, Computer Center University of Freiburg
 #
 # This program is free software distributed under the GPL version 2.
 # See http://openslx.org/COPYING
 #
 # If you have any feedback please consult http://openslx.org/feedback and
 # send your suggestions, praise, or complaints to feedback@openslx.org
 #
 # General information about OpenSLX can be found at http://openslx.org/
 # -----------------------------------------------------------------------------
 # mainWindow.cpp
 This is the Main class for the pvsManager. The GUI is constructed here.
 # -----------------------------------------------------------------------------
 */
// Self
#include "mainwindow.h"
// QT stuff
#include <QtGui>
#include <QFileDialog>
// Other custom UI elements
#include "../clicklabel/clicklabel.h"
#include "../sessionnamewindow/sessionnamewindow.h"
#include "../connectionframe/connectionframe.h"
// Network
#include "../net/listenserver.h"
#include "../net/client.h"
#include "../net/discoverylistener.h"
#include "../../shared/networkmessage.h"
#include "../net/filedownloader.h"
// Others
#include "../../shared/settings.h"
#include "../util/util.h"
#include "../util/global.h"
// Auto-generated ui class
#include "ui_mainwindow.h"

MainWindow::MainWindow(QString ipListUrl, QWidget* parent) :
	QMainWindow(parent), ui(new Ui::MainWindow), _tbIconSize(24), _tbArea(Qt::LeftToolBarArea),
	_buttonBlockTime(0)

{
	_sessionNameWindow = new SessionNameWindow(this);
	ui->setupUi(this);

	Global::setSessionName();

	//conWin = new ConnectionWindow(ui->widget);
	//ui->VconWinLayout->addWidget(conWin);
	//conList = new ConnectionList(ui->ClWidget);
	//ui->ClientGLayout->addWidget(conList);

	// Show only session name label, not textbox
	_sessionNameLabel = new ClickLabel(this);
	ui->mainLayout->addWidget(_sessionNameLabel);

	ui->frmRoom->setStyleSheet("background-color:#444");

	// toolbar and actions in pvsmgr
	ui->action_Exit->setStatusTip(tr("Exit"));
	ui->action_Lock->setStatusTip(tr("Lock or Unlock all Clients"));

	// Initialize FileDownloader.
	if (!ipListUrl.isEmpty())
	{
		_fileDownloader.connectSlot(this, SLOT(onTutorListDownloaded(QByteArray&)));
		_fileDownloader.downloadFile(QUrl(ipListUrl));
	}

	// Close button in tool bar
	connect(ui->action_Exit, SIGNAL(triggered()), this, SLOT(onButtonExit()));
	connect(ui->action_BroadcastScreen, SIGNAL(triggered()), this, SLOT(onButtonStudentToAll()));
	connect(ui->action_TutorToAll, SIGNAL(triggered()), this, SLOT(onButtonTutorToAll()));
	connect(ui->action_StudentToTutor, SIGNAL(triggered()), this, SLOT(onButtonStudentToTutor()));
	connect(ui->action_TutorToStudent, SIGNAL(triggered()), this, SLOT(onButtonTutorToStudent()));
	connect(ui->action_StopProjection, SIGNAL(triggered()), this, SLOT(onButtonStopProjection()));
	connect(ui->action_SetAsTutor, SIGNAL(triggered()), this, SLOT(onButtonSetAsTutor()));
	connect(ui->action_Lock, SIGNAL(toggled(bool)), this, SLOT(onButtonLock(bool)));
	// Clicking the session name label shows the edit field for it
	connect(_sessionNameLabel, SIGNAL(clicked()), this, SLOT(onSessionNameClick()));
	// Listen to updates to the session name through the session name window
	connect(_sessionNameWindow, SIGNAL(updateSessionName()), this,
	   SLOT(onSessionNameUpdate()));

	setAttribute(Qt::WA_QuitOnClose);
	setUnifiedTitleAndToolBarOnMac(true);
	this->showMaximized(); // show the Mainwindow maximized

	_tilesX = 9;
	_tilesY = 7;
	_tileWidth = 10;
	_tileHeight = 10;

	_timerId = 0;
	_timerTimeout = 0;

	_listenServer = new ListenServer(CLIENT_PORT);
	connect(_listenServer, SIGNAL(newClient(Client*)), this, SLOT(onClientConnected(Client*)));
	_discoveryListener = new DiscoveryListener();

	// Finally
	this->onSessionNameUpdate();
	_timerId = startTimer(10);
	_timerTimeout = 8;
}

MainWindow::~MainWindow()
{
	_sessionNameLabel->deleteLater();
	delete ui;
}

void MainWindow::placeFrameInFreeSlot(ConnectionFrame* frame)
{
	// Get occupied cell of each frame and store status in an array
	const int elems = _tilesX * _tilesY;
	int currentIndex = 0;
	bool grid[elems];
	memset(grid, 0, sizeof(bool) * elems); // set everything to false
	for (QList<ConnectionFrame*>::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it)
	{
		const QPoint &oldpos = (*it)->frameGeometry().topLeft();
		const int tx = oldpos.x() / _tileWidth;
		const int ty = oldpos.y() / _tileHeight;
		const int index = tx + ty * _tilesX;
		if (frame == *it)
		{
			// Current frame is the frame to place, remember its old index
			currentIndex = index;
			continue;
		}
		if (index < 0 || index >= elems)
		{
			// Current frame is out of bounds, place in top left corner
			qDebug("Move A");
			(*it)->move(0, 0);
			grid[0] = true;
			continue;
		}
		// Just mark cell as occupied in array
		grid[index] = true;
	}
	// Safety
	if (currentIndex < 0 || currentIndex >= elems)
		currentIndex = 0;
	// Now place in next free cell, start looking at current position of frame
	for (int i = 0; i < elems; ++i)
	{
		const int index = (i + currentIndex) % elems;
		if (grid[index])
			continue;
		const int x = (index % _tilesX) * _tileWidth;
		const int y = (index / _tilesX) * _tileHeight;
		//qDebug() << "Index=" << index << " i=" << i << " cI=" << currentIndex << " elems=" << elems << " x=" << x << " y=" << y;
		qDebug("Move B");
		frame->move(x, y);
		break;
	}
}

ConnectionFrame* MainWindow::createFrame()
{
	// Allocate and resize
	ConnectionFrame *cf = new ConnectionFrame(ui->frmRoom, _tileWidth, _tileHeight);
	_clientFrames.append(cf);
	cf->show();
	connect(cf, SIGNAL(frameMoved(ConnectionFrame *)), this, SLOT(onPlaceFrame(ConnectionFrame *)));
	connect(cf, SIGNAL(clicked(ConnectionFrame *)), this, SLOT(onFrameClicked(ConnectionFrame *)));
	return cf;
}

bool MainWindow::loadPosition(QSettings& settings, const QString& id, int& x, int& y)
{
	settings.beginGroup("client_position");
	const QVariant retval(settings.value(id));
	settings.endGroup();
	if (retval.type() != QVariant::Point)
		return false;
	const QPoint point(retval.toPoint());
	x = point.x();
	y = point.y();
	return true;
}

void MainWindow::savePosition(ConnectionFrame *cf)
{
	Client *client = cf->client();
	if (client == NULL)
		return;
	QPoint pos(cf->pos().x() / _tileWidth, cf->pos().y() / _tileHeight);
	USER_SETTINGS(settings);
	settings.beginGroup("client_position");
	settings.setValue(client->computerId(), pos);
	settings.endGroup();
}

/*
 * Overridden methods
 */

void MainWindow::closeEvent(QCloseEvent* e)
{
	int ret = QMessageBox::question(this, "Test", "Exit?", 0, 1, 2);
	if (ret == 1)
		QApplication::exit(0);
	else
		e->ignore();
}

void MainWindow::changeEvent(QEvent* e)
{
	QMainWindow::changeEvent(e);
}

void MainWindow::resizeEvent(QResizeEvent* e)
{
	QMainWindow::resizeEvent(e);
	if (_timerId == 0)
	{
		_timerId = startTimer(5);
	}
	_timerTimeout = 8;
}

void MainWindow::mouseReleaseEvent(QMouseEvent* e)
{
	// Try to figure out if the user clicked inside the "room" frame
	// No idea if there is a more elegant way to do this without
	// sub-classing that frame and overriding its mouseReleaseEvent
	const QPoint pos(ui->frmRoom->mapFrom(this, e->pos()));
	if (pos.x() < 0 || pos.y() < 0)
		return;
	const QSize frame(ui->frmRoom->size());
	if (frame.width() > pos.x() && frame.height() > pos.y())
	{
		for (QList<ConnectionFrame*>::iterator it = _clientFrames.begin(); it != _clientFrames.end(); ++it)
		{
			(**it).setSelection(false);
		}
	}
}

void MainWindow::timerEvent(QTimerEvent* event)
{
	if (event->timerId() == _timerId)
	{
		_timerTimeout -= 1;
		if (_timerTimeout == 1)
		{
			// Move toolbar if necessary
			const int iconSize = this->size().width() / 30 + 14;
			const int barSpace = ui->toolBar->actions().size() * (iconSize + 8);
			const Qt::ToolBarArea area =
				(barSpace < this->size().height() ? Qt::LeftToolBarArea : Qt::TopToolBarArea);
			if (_tbIconSize != iconSize)
			{
				_tbIconSize = iconSize;
				ui->toolBar->setIconSize(QSize(iconSize, iconSize));
			}
			if (_tbArea != area)
			{
				_tbArea = area;
				this->addToolBar(area, ui->toolBar);
			}
			return;
		}
		// Kill timer when done
		if (_timerTimeout <= 0)
		{
			killTimer(_timerId);
			_timerId = 0;
			// Resize all connection windows
			if (ui->frmRoom->size().width() < 100 || ui->frmRoom->size().height() < 100 || _tilesX <= 0 || _tilesY <= 0)
				return;
			const int nw = ui->frmRoom->size().width() / _tilesX;
			const int nh = ui->frmRoom->size().height() / _tilesY;
			for (QList<ConnectionFrame*>::iterator it = _clientFrames.begin(); it != _clientFrames.end(); ++it)
			{
				const QPoint &oldpos = (*it)->frameGeometry().topLeft();
				qDebug("Move C");
				(*it)->move((oldpos.x() / _tileWidth) * nw, (oldpos.y() / _tileHeight) * nh);
				(*it)->setSize(nw, nh);
			}
			_tileWidth = nw;
			_tileHeight = nh;
		}
	}
	else
	{
		// Unknown timer ID, kill
		killTimer(event->timerId());
	}
}

/*
 * Slots
 */

void MainWindow::onTutorListDownloaded(QByteArray& tutorList)
{
	// qDebug() << tutorList;
	QString data = QString::fromUtf8(tutorList.constData(), tutorList.size());
	_tutorList = data.split(QRegExp("[\r\n]"),QString::SkipEmptyParts);
	qDebug() << _tutorList;
}
void MainWindow::onPlaceFrame(ConnectionFrame* frame)
{
	if (_tilesX <= 0 || _tilesY <= 0)
		return;
	const QPoint &p = frame->frameGeometry().center();
	const QSize &s = ui->frmRoom->geometry().size();
	int x = (p.x() / _tileWidth) * _tileWidth;
	int y = (p.y() / _tileHeight) * _tileHeight;
	if (x < 0)
		x = 0;
	else if (x > s.width() - _tileWidth)
		x = (_tilesX - 1) * _tileWidth;
	if (y < 0)
		y = 0;
	else if (y > s.height() - _tileHeight)
		y = (_tilesY - 1) * _tileHeight;
	qDebug("Move D");
	frame->move(x, y);
	savePosition(frame);
	const QPoint &newpos = frame->frameGeometry().topLeft();
	for (QList<ConnectionFrame*>::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it)
	{
		if (*it == frame)
			continue;
		if ((*it)->frameGeometry().topLeft() == newpos)
		{
			placeFrameInFreeSlot(*it);
		}
	}
}

void MainWindow::onFrameClicked(ConnectionFrame* frame)
{
	for (QList<ConnectionFrame*>::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it)
	{
		if (*it == frame)
			frame->setSelection(true);
		else
			(**it).setSelection(false);
	}
}

void MainWindow::onSessionNameClick()
{
	_sessionNameWindow->show(Global::sessionName());
}

void MainWindow::onSessionNameUpdate()
{
	_sessionNameLabel->setText(tr("Session Name: %1  [click to edit]").arg(Global::sessionName()));
}

void MainWindow::onButtonSettings()
{
	// Move to any free tile
	placeFrameInFreeSlot(createFrame());
}

void MainWindow::onButtonChat()
{
	/*
	 for (QList<ConnectionFrame*>::iterator it = _clientFrames.begin(); it != _clientFrames.end(); ++it) {
	 (*it)->setSize(100 + qrand() % 200);
	 }
	 */
}

bool MainWindow::isValidClient(Client* client)
{
	for (QList<ConnectionFrame*>::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it)
	{
		Client *c = (**it).client();
		if (c == client)
			return true;
	}
	return false;
}

void MainWindow::prepareForProjection(Client * const from, Client * const to)
{
	// Projection source is never allowed to be an active VNC viewer
	if (from->isActiveVncClient())
	{
		qDebug("From is active client, stopping...");
		from->stopVncClient();
	}
	from->setDesiredProjectionSource(0);

	if (to == NULL)
	{
		// One to many
		qDebug("One to many requested...");
		from->setProjectionSource(true);

		if (from->isActiveVncServer())
		{
			// From is already active, if there is at least one active client, assume it is not
			// shutting down, so we can directly tell the new client to connect to it
			qDebug("Source is already running a VNC server....");
			for (QList<ConnectionFrame*>::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it)
			{
				Client *c = (**it).client();
				if (c == NULL || c->id() == from->id())
					continue;
				if (c->currentProjectionSource() != from->id())
					continue;
				// Yep :-)
				qDebug("Reusing because of active client");
				this->onVncServerStateChange(from);
				return;
			}
			qDebug("But no active client found....");
		}
		// Could not take shortcut, (re)start VNC server on source
		qDebug("Starting VNC server on source machine");
		from->startVncServer();
		return;
	}

	// One to one is desired, figure out what to do with current client

	to->setProjectionSource(false);
	if (to->isActiveVncServer())
	{
		if (to->currentProjectionSource() == from->id())
			return; // Nothing to do
		to->stopVncServer();
	}
	to->setDesiredProjectionSource(from->id());

	if (from->isActiveVncServer())
	{
		// From is already active, if there is at least one active client, assume it is not
		// shutting down, so we can directly tell the new client to connect to it
		for (QList<ConnectionFrame*>::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it)
		{
			Client *c = (**it).client();
			if (c == NULL || c->id() == from->id())
				continue;
			if (c->currentProjectionSource() != from->id())
				continue;
			// Yep :-)
			this->onVncServerStateChange(from);
			return;
		}
	}
	// Could not take shortcut, (re)start VNC server on source
	from->startVncServer();
}

void MainWindow::onButtonStudentToAll()
{
	const qint64 now = QDateTime::currentMSecsSinceEpoch();
	if (now < _buttonBlockTime)
		return;
	_buttonBlockTime = now + 3000;
	//
	Client *from = NULL;
	for (QList<ConnectionFrame*>::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it)
	{
		Client *c = (**it).client();
		if (c == NULL)
			continue;
		if ((**it).selected())
			from = c;
		else
			c->setProjectionSource(false);
	}
	if (from == NULL)
		QMessageBox::critical(this,
			tr("Projection"),
			tr("No projection source selected.")
		);
	else
		prepareForProjection(from, NULL);
}

void MainWindow::onButtonStudentToTutor()
{
	const qint64 now = QDateTime::currentMSecsSinceEpoch();
	if (now < _buttonBlockTime)
		return;
	_buttonBlockTime = now + 3000;
	//
	Client *from = NULL;
	Client *to = NULL;
	for (QList<ConnectionFrame*>::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it)
	{
		Client *c = (**it).client();
		if (c == NULL)
			continue;
		if ((**it).selected())
			from = c;
		else if ((**it).isTutor())
			to = c;
	}
	if (from == NULL)
		QMessageBox::critical(this,
			tr("Projection"),
			tr("No projection source selected.")
		);
	else if (to == NULL)
		QMessageBox::critical(this,
			tr("Projection"),
			tr("No tutor defined, or tutor is offline.")
		);
	else if (to == from)
		QMessageBox::critical(this,
			tr("Projection"),
			tr("Selected projection source is tutor.")
		);
	else
		prepareForProjection(from, to);
}

void MainWindow::onButtonTutorToAll()
{
	const qint64 now = QDateTime::currentMSecsSinceEpoch();
	if (now < _buttonBlockTime)
		return;
	_buttonBlockTime = now + 3000;
	//
	Client *from = NULL;
	for (QList<ConnectionFrame*>::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it)
	{
		Client *c = (**it).client();
		if (c == NULL)
			continue;
		if ((**it).isTutor())
			from = c;
		else
			c->setProjectionSource(false);
	}
	if (from == NULL)
		QMessageBox::critical(this,
			tr("Projection"),
			tr("No tutor defined, or tutor is offline.")
		);
	else
		prepareForProjection(from, NULL);
}

void MainWindow::onButtonTutorToStudent()
{
	const qint64 now = QDateTime::currentMSecsSinceEpoch();
	if (now < _buttonBlockTime)
		return;
	_buttonBlockTime = now + 3000;
	//
	Client *from = NULL;
	Client *to = NULL;
	for (QList<ConnectionFrame*>::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it)
	{
		Client *c = (**it).client();
		if (c == NULL)
			continue;
		if ((**it).selected())
			to = c;
		else if ((**it).isTutor())
			from = c;
	}
	if (to == NULL)
		QMessageBox::critical(this,
			tr("Projection"),
			tr("No projection source selected.")
		);
	else if (from == NULL)
		QMessageBox::critical(this,
			tr("Projection"),
			tr("No tutor defined, or tutor is offline.")
		);
	else if (to == from)
		QMessageBox::critical(this,
			tr("Projection"),
			tr("Selected projection target is tutor.")
		);
	else
		prepareForProjection(from, to);
}

void MainWindow::onButtonStopProjection()
{
	const qint64 now = QDateTime::currentMSecsSinceEpoch();
	if (now < _buttonBlockTime)
		return;
	_buttonBlockTime = now + 3000;
	//
	for (QList<ConnectionFrame*>::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it)
	{
		Client *c = (**it).client();
		if (c == NULL)
			continue;
		c->setDesiredProjectionSource(0);
		c->setProjectionSource(false);
	}
	NetworkMessage msg;
	msg.setField(_ID, _VNCCLIENT);
	for (QList<ConnectionFrame*>::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it)
	{
		Client *c = (**it).client();
		if (c == NULL)
			continue;
		c->sendMessage(msg);
	}
}

void MainWindow::onButtonLock(bool checked)
{
	NetworkMessage msg;
	msg.setField(_ID, _LOCK);
	if (checked)
		msg.setField("ENABLE", QByteArray("1"));
	else
		msg.setField("ENABLE", QByteArray("0"));
	for (QList<ConnectionFrame*>::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it)
	{
		Client *c = (**it).client();
		if (c == NULL || (**it).isTutor())
			continue; // Don't lock the tutor
		c->sendMessage(msg);
	}
}

void MainWindow::onButtonExit()
{
	this->close();
}

void MainWindow::onButtonSetAsTutor()
{
	bool selected = false;
	for (QList<ConnectionFrame*>::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it)
		{
		    selected = (*it)->selected();
		    if ((*it)->isTutor())
		    	(*it)->setTutor(false);
			if (selected)
			{
				// This frame is marked to be Tutor.
				(*it)->setTutor(true);
			}
		}
}

void MainWindow::onClientConnected(Client* client)
{
	qDebug("ListenServer told MainWindow about new connection");
	connect(client, SIGNAL(authenticating(Client*, ClientLogin*)), this, SLOT(onClientAuthenticating(Client*, ClientLogin*)));
	connect(client, SIGNAL(authenticated(Client*)), this, SLOT(onClientAuthenticated(Client*)));
}

void MainWindow::onClientAuthenticating(Client* client, ClientLogin* request)
{
	disconnect(client, SIGNAL(authenticating(Client*, ClientLogin*)), this, SLOT(onClientAuthenticating(Client*, ClientLogin*)));
	if (!request->accept) // Another receiver of that signal already rejected the client, so nothing to do
		return;
	bool inuse;
	QString check = request->name;
	int addnum = 1;
	do
	{
		inuse = false;
		for (QList<ConnectionFrame*>::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it)
		{
			Client *c = (**it).client();
			if (c == NULL)
				continue;
			if (!c->isAuthed())
				continue;
			if (c->ip() == request->ip)
			{
				request->accept = false;
				return;
			}
			if (c->name() == check)
			{
				inuse = true;
				check = request->name + " (" + QString::number(++addnum) + ")";
				break;
			}
		}
	} while (inuse && addnum < 100);
	if (inuse)
		request->accept = false;
	else
		request->name = check;
}

void MainWindow::onClientAuthenticated(Client* client)
{
	disconnect(client, SIGNAL(authenticated(Client*)), this, SLOT(onClientAuthenticated(Client*)));
	connect(client, SIGNAL(vncServerStateChange(Client*)), this, SLOT(onVncServerStateChange(Client*)), Qt::QueuedConnection);
	connect(client, SIGNAL(vncClientStateChange(Client*, int)), this, SLOT(onVncClientStateChange(Client*, int)), Qt::QueuedConnection);
	bool hasActiveTutor = false;
	ConnectionFrame *existing = NULL;
	for (QList<ConnectionFrame*>::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it)
	{
		if ((*it)->computerId() == client->computerId())
			existing = *it;
		if ((*it)->isTutor())
		{
			if ((*it)->client() != NULL)
				hasActiveTutor = true;
		}
	}

	bool isTutor = false;
	if (!hasActiveTutor)
	{
		for (int i = 0; i < _tutorList.size(); i++)
		{
			// Check if client is possible tutor
			if (client->computerId()== _tutorList[i])
			{
				isTutor = true;
				break;
			}
		}
	}

	if (existing != NULL)
	{
		existing->setTutor(isTutor);
		existing->assignClient(client);
		return;
	}
	// New one, create
	ConnectionFrame *cf = createFrame();
	// Try to load last known position
	int x, y;
	bool ok;
	USER_SETTINGS(usr);
	ok = loadPosition(usr, client->computerId(), x, y);
	if (!ok)
	{
		SYSTEM_SETTINGS(sys);
		ok = loadPosition(sys, client->computerId(), x, y);
	}
	if (x >= _tilesX || y >= _tilesY)
		ok = false;
	if (ok)
	{
		if (x < 0)
			x = 0;
		if (y < 0)
			y = 0;
		qDebug("Move E");
		cf->move(x * _tileWidth, y * _tileHeight);
		onPlaceFrame(cf);
	}
	else
	{
		// Move to any free tile
		placeFrameInFreeSlot(cf);
	}
	// Set Tutor option
	cf->setTutor(isTutor);
	// Assign client instance
	cf->assignClient(client);
	// ################
	NetworkMessage msg;
	// If clients are currently locked, tell this new client
	if (ui->action_Lock->isChecked())
	{
		msg.reset();
		msg.setField(_ID, _LOCK);
		msg.setField("ENABLE", QByteArray("1"));
		client->sendMessage(msg);
	}
	// Same for VNC projections
	for (QList<ConnectionFrame*>::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it)
	{
		Client *c = (**it).client();
		if (c != NULL && c != client && c->isActiveVncServer() && c->isProjectionSource())
		{
			msg.reset();
			msg.setField(_ID, _VNCCLIENT);
			msg.setField("HOST", c->ip());
			msg.setField("PORT", QString::number(c->vncPort()));
			msg.setField("ROPASS", c->vncRoPass());
			msg.setField("CAPTION", c->name() + " @ " + c->host());
			client->sendMessage(msg);
			break;
		}
	}
}

void MainWindow::onVncServerStateChange(Client* client)
{
	if (!isValidClient(client)) // Check here because this slot is connected queued
		return;
	if (client->vncPort() > 0)
	{
		// VNC Server started on some client - start projection on all clients interested in that client's screen
		NetworkMessage msg;
		msg.setField(_ID, _VNCCLIENT);
		msg.setField("HOST", client->ip());
		msg.setField("PORT", QString::number(client->vncPort()));
		msg.setField("ROPASS", client->vncRoPass());
		msg.setField("CLIENTID", QString::number(client->id()));
		msg.setField("CAPTION", client->name() + " @ " + client->host());
		for (QList<ConnectionFrame*>::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it)
		{
			Client *c = (**it).client();
			if (c == NULL || c->id() == client->id())
				continue; // Offline or self
			if (c->currentProjectionSource() == client->id())
				continue; // Already watching this client
			if (c->desiredProjectionSource() != client->id() && !client->isProjectionSource())
				continue; // Not interested
			c->sendMessage(msg);
		}
	}
	else
	{
		// VNC server stopped on some client or failed to start - reset local pending projection information
		// If at least one other client seemed to be waiting for this client's screen, pop up a message
		// on the console
		bool wasInterested = client->isProjectionSource();
		//qDebug() << "On VNC stop:" << client->ip() << "was projection source:" << wasInterested;
		client->setProjectionSource(false);
		for (QList<ConnectionFrame*>::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it)
		{
			Client *c = (**it).client();
			if (c == NULL)
				continue;
			if (c->desiredProjectionSource() == client->id())
			{
				wasInterested = true;
				c->setDesiredProjectionSource(0);
			}
		}
		if (wasInterested && QDateTime::currentMSecsSinceEpoch() < _buttonBlockTime)
		{
			QMessageBox::information(this,
				tr("Projection Error"),
				tr("Could not send screen contents of %1 to other clients: VNC Startup failed.").arg(client->name())
			);
		}
	}
}

void MainWindow::onVncClientStateChange(Client* client, int lastProjectionSource)
{
	if (!isValidClient(client)) // Check here because this slot is connected queued
		return;
	// If last source is not known or not existent, we cannot do anything meaningful here
	if (lastProjectionSource == 0)
		return;
	// Client is not a vnc client anymore. Check if the VNC server it was (supposedly) connected
	// to is still running, and kill it if there are no more clients connected to it.
	// (client->currentProjectionSource() will still contain the id of the server it was connected to during this event)
	// 1. check if vnc server is still running
	// 2. check if there are any other clients "using" that server
	bool inUse = false;
	Client *server = NULL;
	for (QList<ConnectionFrame*>::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it)
	{
		Client *c = (**it).client();
		if (c == NULL)
			continue;
		if (c->id() == lastProjectionSource)
			server = c;
		if (c != client && c->currentProjectionSource() == lastProjectionSource)
		{
			inUse = true;
			break;
		}
	}
	// 3. kill server if applicable
	if (!inUse && server != NULL)
		server->stopVncServer();
}