/*
# 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>
#include <QNetworkInterface>
// Other custom UI elements
#include "../clicklabel/clicklabel.h"
#include "../sessionnamewindow/sessionnamewindow.h"
#include "../connectionframe/connectionframe.h"
#include "../helpwindow/helpwindow.h"
#include "../reloadroomwindow/reloadroomwindow.h"
// Network
#include "../net/listenserver.h"
#include "../net/client.h"
#include "../net/discoverylistener.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"
#include "ui_reloadroom.h"
#define sStrTutorNdef MainWindow::tr("No tutor defined.")
#define sStrTutorOffline MainWindow::tr("Tutor is offline.")
#define sStrSourceNdef MainWindow::tr("Please select a projection source.")
#define sStrSourceOffline MainWindow::tr("The projection source is offline.")
#define sStrDestNdef MainWindow::tr("Please select a projection destination.")
#define sStrDestOffline MainWindow::tr("The projection destination is offline.")
#define sStrSourceDestSame MainWindow::tr("Selected projection target is tutor.")
#define sStrClientOnline MainWindow::tr("Selected client is currently online.")
#define sStrNoDestAv MainWindow::tr("No projection destination available.")
/***************************************************************************//**
* Initialize MainWindow and ListenServer.
* @param ipListUrl
* @param parent
*/
MainWindow::MainWindow(QString ipListUrl, QWidget* parent) :
QMainWindow(parent), ui(new Ui::MainWindow), _tbIconSize(24), _tbArea(Qt::LeftToolBarArea)
{
_mode = Mode::Multicast;
_streamingSource = 0;
/* default value, these should be updated on loading a room */
_tilesX = 20;
_tilesY = 20;
_sessionNameWindow = new SessionNameWindow(this);
_helpWindow = new HelpWindow(this);
_helpWindow->setWindowTitle("Help");
_reloadWindow = new ReloadRoomWindow(this);
_reloadWindow->setWindowTitle("Reload Room");
ui->setupUi(this);
setWindowFlags(Qt::FramelessWindowHint);
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_TutorToAll, SIGNAL(triggered()), this, SLOT(onButtonTutorToAll()));
connect(ui->action_StudentToTutor, SIGNAL(triggered()), this, SLOT(onButtonStudentToTutor()));
connect(ui->action_StudentToTutorExclusive, SIGNAL(triggered()), this, SLOT(onButtonStudentToTutorExclusive()));
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_SetAsTutor, SIGNAL(triggered()), this, SLOT(onButtonStopProjection()));
connect(ui->action_Lock, SIGNAL(toggled(bool)), this, SLOT(onButtonLock(bool)));
connect(ui->action_Help, SIGNAL(triggered()), this, SLOT(onButtonHelp()));
connect(ui->actionReload_Room_Configuration, SIGNAL(triggered()), this, SLOT(onButtonReloadRoomConfig()));
/* Stuff for the button lock */
//Setup a timeout
_buttonLockTimer = new QTimer(this);
_buttonLockTimer->setSingleShot(true);
_buttonLockTimer->setInterval(_buttonBlockTime);
connect(_buttonLockTimer, SIGNAL(timeout()), this, SLOT(EnableButtons()));
// Define the locking buttons
_lockingButtons
<< ui->action_Lock
<< ui->action_TutorToAll
<< ui->action_StudentToTutor
<< ui->action_TutorToStudent
<< ui->action_StopProjection
<< ui->action_SetAsTutor;
// 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
_listenServer = new ListenServer(CLIENT_PORT);
connect(_listenServer, SIGNAL(newClient(Client*)), this, SLOT(onClientConnected(Client*)));
_discoveryListener = new DiscoveryListener();
// Finally
_countSessionNameUpdate = 0;
this->onSessionNameUpdate(); // Just make lable visible.
_countSessionNameUpdate = 0;
tryToUseRoomTemplate();
}
MainWindow::~MainWindow()
{
_sessionNameLabel->deleteLater();
delete ui;
}
/***************************************************************************//**
* Place the frames at possible Position.
* If current position out of range, place frame into next free cell.
* @param frame
*/
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() / getTileWidthPx();
const int ty = oldpos.y() / getTileHeightPx();
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) * getTileWidthPx();
const int y = (index / _tilesX) * getTileHeightPx();
//qDebug() << "Index=" << index << " i=" << i << " cI=" << currentIndex << " elems=" << elems << " x=" << x << " y=" << y;
qDebug("Move B");
frame->move(x, y);
break;
}
}
/***************************************************************************//**
* Create new Frame.
* Create new frame and add to current available frame list.
* Also connect signals frameMoved() and clicked() with slots.
* @return ConnectionFrame*
*/
ConnectionFrame* MainWindow::createFrame()
{
// Allocate and resize
ConnectionFrame *cf = new ConnectionFrame(ui->frmRoom, getTileWidthPx(), getTileHeightPx());
_clientFrames.append(cf);
cf->show();
connect(cf, SIGNAL(frameMoved(bool, ConnectionFrame *)), this, SLOT(onPlaceFrame(bool, ConnectionFrame *)));
connect(cf, SIGNAL(clicked(ConnectionFrame *)), this, SLOT(onFrameClicked(ConnectionFrame *)));
return cf;
}
/***************************************************************************//**
* Create new Frame.
* Create new frame and add to current available frame list.
* Also connect signals frameMoved() and clicked() with slots.
* @return ConnectionFrame*
*/
ConnectionFrame* MainWindow::createFrame(QString computerId, QPoint pxCoord, QPoint gridPosition)
{
// Allocate and resize
Room* room = Global::getRooms()[Global::getCurrentRoom()];
int width = getTileWidthPx() * room->clientSize.width();
int height = getTileHeightPx() * room->clientSize.height();
ConnectionFrame *cf = new ConnectionFrame(ui->frmRoom, width, height);
cf->setComputerId(computerId);
cf->setCurrentPosition(pxCoord);
cf->setGridPosition(gridPosition);
_clientFrames.append(cf);
cf->show();
connect(cf, SIGNAL(frameMoved(bool, ConnectionFrame *)), this, SLOT(onPlaceFrame(bool, ConnectionFrame *)));
connect(cf, SIGNAL(clicked(ConnectionFrame *)), this, SLOT(onFrameClicked(ConnectionFrame *)));
return cf;
}
/***************************************************************************//**
* Load position.
* @param settings
* @param id
* @param x
* @param y
* @return If loadPosition was successfull.
*/
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;
}
/***************************************************************************//**
* Save position of given connectionFrame.
* @param cf
*/
void MainWindow::savePosition(ConnectionFrame *cf)
{
Client *client = cf->client();
if (client == NULL)
return;
QPoint pos(cf->pos().x() / getTileWidthPx(), cf->pos().y() / getTileHeightPx());
USER_SETTINGS(settings);
settings.beginGroup("client_position");
settings.setValue(client->ip(), pos);
settings.endGroup();
}
/***************************************************************************//**
* Tells the new client the current situation in class room.
* Set the specific parameters like projection source or lock screen.
* @param client
*/
void MainWindow::tellClientCurrentSituation(Client* client)
{
// If clients are currently locked, tell this new client
if (ui->action_Lock->isChecked() || _mode == Mode::LockedUnicast)
client->lockScreen(true);
if (_mode == Mode::Broadcast){
client->setDesiredProjectionSource(_streamingSource);
if (_streamingSource != 0) {
client->startVncClient(getClientFromId(_streamingSource));
}
}
}
/**
* returns the minimal grid size such that all clients fit on the grid
**/
QSize minimalGridSize(const QMap<QString, QPoint>& clientPositions, QSize& clientSize) {
/* collect the maximum coordinates */
int x = 0;
int y= 0;
for (auto it = clientPositions.begin(); it != clientPositions.end(); ++it) {
QPoint pos = it.value();
if (pos.x() > x) { x = pos.x(); }
if (pos.y() > y) { y = pos.y(); }
}
/* need a little extra space */
QSize size(x + clientSize.width(), y + clientSize.height());
return size;
}
/***************************************************************************//**
* @brief MainWindow::tryToUseRoomTemplate
*/
void MainWindow::tryToUseRoomTemplate()
{
QMap<QString, Room* > roomsList;
SYSTEM_SETTINGS(conf);
if (!conf.contains("rooms")) { qDebug() << "Invalid config file!"; return; }
QStringList rooms = conf.value("rooms").toStringList();
qDebug() << rooms;
for (QString roomName : rooms)
{
conf.beginGroup(roomName);
if (!conf.contains("mgrIP")) {
qDebug() << "Invalid config file!";
return;
}
QMap<QString, QPoint> clientPositions;
// First store all room configurations in _rooms.
int size = conf.beginReadArray("client");
for (int j = 0; j < size; j++) {
conf.setArrayIndex(j);
// qDebug() << "ip: " << conf.value("ip").toString() << " pos: " << conf.value("pos").toPoint();
// roomsList[i].insert(conf.value("ip").toString(), conf.value("pos").toPoint());
clientPositions.insert(conf.value("ip").toString(), conf.value("pos").toPoint());
}
conf.endArray();
// Find the managerIP of current room.
QString mgrIP = conf.value("mgrIP").toString();
QSize gridSize;
QSize clientSize(1,1);
/* read some other properties of the room */
if (conf.contains("gridSize")) {
gridSize = conf.value("gridSize").toSize();
}
if (conf.contains("clientSize")) {
clientSize = conf.value("clientSize").toSize();
}
foreach (const QHostAddress &address, QNetworkInterface::allAddresses())
{
if (address != QHostAddress(QHostAddress::LocalHost) && mgrIP == address.toString())
{
qDebug("Found this ip in config.");
Global::setCurrentRoom(roomName);
int size = conf.beginReadArray("client");
for (int j = 0; j < size; ++j) {
conf.setArrayIndex(j);
/*
*
* CFG to test this
* http://git.openslx.org/tm-scripts.git/plain/server/modules/pvs2-freiburg/opt/openslx/pvs2.ini
*
*/
// Add the frame
// Position is given in grid coordinates, createFrame take pixels
QString computerId = conf.value("ip").toString();
QPoint gridPosition = conf.value("pos").toPoint();
QPoint pxCoord(gridPosition.x() * getTileWidthPx(), gridPosition.y() * getTileHeightPx());
ConnectionFrame *cf = createFrame(computerId, pxCoord, gridPosition);
cf->move(cf->getCurrentPosition());
onPlaceFrame(false, cf);
}
conf.endArray();
}
}
conf.endGroup();
if (!gridSize.isValid()) {
/* ok, let's choose the minimum gridSize to fit all clients */
gridSize = minimalGridSize(clientPositions, clientSize);
qDebug() << "had to use minimalGridSize(): = " << gridSize;
}
Room* r = new Room(clientPositions, gridSize, clientSize);
qDebug() << "read new room: " << roomName << ": " << gridSize << ", " << clientSize;
roomsList.insert(roomName, r);
}
Global::setRooms(roomsList);
}
/***************************************************************************//**
* Returns connected client which belongs to given id.
* Iterating over ConnectionFrames and comparing id to given id.
* @param id
* @return Client with given id, if not NULL.
*/
Client* MainWindow::getClientFromId(int id)
{
for (QList<ConnectionFrame*>::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it)
{
if ((*it)->client() != NULL)
{
if ((*it)->client()->id() == id)
return (*it)->client();
}
}
return NULL;
}
/***************************************************************************//**
* Return the Frame, which is currently beeing Tutor.
* Iterating over all ConnectionFrames, and looking for flag _isTutor.
* @return Frame with flag _isTutor = true,
* else NULL if no Tutor is available.
*/
ConnectionFrame* MainWindow::getTutorFrame()
{
for (QList<ConnectionFrame*>::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it)
{
if (((*it) != NULL) && ((*it)->isTutor()))
return (*it);
}
return NULL;
}
/***************************************************************************//**
* Return the Frame, which is currently selected by user.
* Iterating over all ConnectionFrame and looking for flag _isSelected.
* @return Frame with flag _isSelected = true,
* else NULL if no frame is selected.
*/
ConnectionFrame* MainWindow::getSelectedFrame()
{
for (QList<ConnectionFrame*>::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it)
{
if (((*it) != NULL) && ((*it)->isSelected()))
return (*it);
}
return NULL;
}
/*
* Overridden methods
*/
/***************************************************************************//**
* Handle closeEvent.
* If user calls closing MainWindow, close, else ignore.
* @param e
*/
void MainWindow::closeEvent(QCloseEvent* e)
{
int ret = QMessageBox::question(this, "Question", "Are you sure you want to exit?", 0, 1, 2);
if (ret == 1)
QApplication::exit(0);
else
e->ignore();
}
/***************************************************************************//**
* Change Event.
* @param e
*/
void MainWindow::changeEvent(QEvent* e)
{
QMainWindow::changeEvent(e);
}
/***************************************************************************//**
* Resize event.
* @param e
*/
void MainWindow::resizeEvent(QResizeEvent* e)
{
Room* room = Global::getRooms()[Global::getCurrentRoom()];
QSize& clientSize = room->clientSize;
// 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)
{
int newPosX = (*it)->getGridPosition().x() * getTileWidthPx();
int newPosY = (*it)->getGridPosition().y() * getTileHeightPx();
QPoint newPos(newPosX, newPosY);
(*it)->setSize(getTileWidthPx() * clientSize.width(), getTileHeightPx() * clientSize.height());
(*it)->move(newPos);
}
// Resize trash and set position.
const int width = ui->frmRoom->geometry().width() - (nw + 1);
const int height = ui->frmRoom->geometry().height() - (nh + 1);
ui->trash->move(width, height);
ui->trash->resize(nw, nh);
// qDebug() << "Trash pos: " << ui->trash->pos();
}
/***************************************************************************//**
* Handle Mouse Release Event.
* Check if click was inside the frame and if that is the case, set the selection of each connected
* client to false.
* @param e
*/
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())
{
if (getSelectedFrame() != NULL)
{
getSelectedFrame()->setSelection(false);
}
}
}
/***************************************************************************//**
* @brief reset
*/
void MainWindow::reset()
{
_mode = Mode::None;
// Unlock all clients
for (QList<ConnectionFrame*>::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it)
if ((*it)->client() != NULL)
(*it)->client()->lockScreen(false);
// Stop server (Clients get stopped on ACK)
if (getClientFromId(_streamingSource) != NULL)
getClientFromId(_streamingSource)->stopVncServer();
}
/*
* Slots
*/
/***************************************************************************//**
* Extract information from downloaded tutorList.
* Split downloaded file by new line and store IPs in _tutorList.
* @param tutorList
*/
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;
}
/***************************************************************************//**
* Place Frame to from user specified position.
* @param frame
*/
void MainWindow::onPlaceFrame(bool activateTrash, 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() / getTileWidthPx()) * getTileWidthPx();
int y = (p.y() / getTileHeightPx()) * getTileHeightPx();
if (x < 0)
x = 0;
else if (x > s.width() - getTileWidthPx())
x = (_tilesX - 1) * getTileWidthPx();
if (y < 0)
y = 0;
else if (y > s.height() - getTileHeightPx())
y = (_tilesY - 1) * getTileHeightPx();
// Check if x coordinate is in trash position. Check only if activateTrash = true!
if (activateTrash)
{
const QRect &trashCenter = ui->trash->geometry();
if (trashCenter.contains(p))
{
// Do not offer deleting online client.
if (frame->client() != NULL)
{
QMessageBox::critical(this, tr("Selection"), sStrClientOnline);
frame->move(frame->getPreviousPosition());
return;
}
else
{
int ret = QMessageBox::question(this, "Warning", tr("Sure, You want to delete selected client?"), 0, 1, 2);
if (ret == 1)
{
frame->hide();
frame->deleteLater();
_clientFrames.removeOne(frame);
return;
}
else
{
frame->move(frame->getPreviousPosition());
return;
}
}
}
}
qDebug("Moved");
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);
}
}
}
/***************************************************************************//**
* Mark given frame after it was clicked on.
* @param frame
*/
void MainWindow::onFrameClicked(ConnectionFrame* frame)
{
// If same frame is clicked again,, do nothing
if (getSelectedFrame() == frame)
return;
// If another frame has been selected, unselect it
// Set the ui selected and set a new reference
if (getSelectedFrame() != NULL)
{
getSelectedFrame()->setSelection(false);
}
frame->setSelection(true);
qDebug() << "ID of frame: " << frame->computerId();
qDebug() << "ID of selectedFrame: " << getSelectedFrame()->computerId();
}
/***************************************************************************//**
* Show session name, after it was clicked on.
*/
void MainWindow::onSessionNameClick()
{
_countSessionNameUpdate++;
if (_countSessionNameUpdate > 1)
{
int ret = QMessageBox::question(this, "Warning", tr("Sure, You want to change SessionName again?\n"
"All Clients will be deleted afterwards."), 0, 1, 2);
if (ret == 1)
{
_sessionNameWindow->show(Global::sessionName());
}
}
else
_sessionNameWindow->show((Global::sessionName()));
}
/***************************************************************************//**
* Update session name.
*/
void MainWindow::onSessionNameUpdate()
{
// Stop all projections and clear all clients, which where connected to old sessionName.
reset();
if (_countSessionNameUpdate > 1)
{
{
for (QList<ConnectionFrame*>::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it)
{
(*it)->hide();
(*it)->deleteLater();
}
_clientFrames.clear();
}
}
_sessionNameLabel->setText(tr("Session Name: %1 [click to edit]").arg(Global::sessionName()));
}
/***************************************************************************//**
* @brief MainWindow::startVncServerIfNecessary
* @param from
*/
void MainWindow::startVncServerIfNecessary(int from)
{
Client* os = getClientFromId(_streamingSource);
Client* ns = getClientFromId(from);
// if there is a server running which is not "from" stop it.
if (os != NULL && _streamingSource != from)
os->stopVncServer();
// Set new streaming source
_streamingSource = from;
// If streaming source is already active avoid a restart
if (ns != NULL) {
if (ns->isActiveVncServer())
this->onVncServerStateChange(ns);
else // Could not take shortcut, (re)start VNC server on source
ns->startVncServer();
}
}
void MainWindow::onButtonReloadRoomConfig()
{
connect(_reloadWindow->ui->buttonBox, SIGNAL(accepted()), this, SLOT(onReloadRoomOk()));
connect(_reloadWindow->ui->buttonBox, SIGNAL(rejected()), this, SLOT(onReloadRoomCancel()));
qDebug() << "in onButtonReloadRoomConfig!" << "size of room: " << Global::getRooms().size();
QList<QString> keyList = Global::getRooms().keys();
for (QList<QString>::iterator it = keyList.begin(); it != keyList.end() ; it++) {
_reloadWindow->ui->roomList->addItem(*it);
}
_reloadWindow->show();
}
void MainWindow::onReloadRoomCancel()
{
disconnect(_reloadWindow->ui->buttonBox, SIGNAL(accepted()), this, SLOT(onReloadRoomOk()));
disconnect(_reloadWindow->ui->buttonBox, SIGNAL(rejected()), this, SLOT(onReloadRoomCancel()));
_reloadWindow->ui->roomList->clear();
_reloadWindow->hide();
}
void MainWindow::onReloadRoomOk()
{
if (_reloadWindow->ui->roomList->currentItem() == NULL)
{
QMessageBox::critical(this, "Warning", tr("No item selected, please select room!"), 0, 1);
return;
}
int ret = QMessageBox::QMessageBox::question(this, "Warning", tr("Are you sure you want to reload the room?\n"
"Note that all clients will be deleted."), 0, 1, 2);
if (ret == 1) {
disconnect(_reloadWindow->ui->buttonBox, SIGNAL(accepted()), this, SLOT(onReloadRoomOk()));
disconnect(_reloadWindow->ui->buttonBox, SIGNAL(rejected()), this, SLOT(onReloadRoomCancel()));
// Delete all clients.
for (QList<ConnectionFrame*>::iterator it = _clientFrames.begin(); it != _clientFrames.end(); it++)
{
(*it)->hide();
(*it)->deleteLater();
}
_clientFrames.clear();
// Load new room configuration.
QString roomToReload = _reloadWindow->ui->roomList->currentItem()->data(0).toString();
// qDebug() << roomToReload;
Global::setCurrentRoom(roomToReload);
_reloadWindow->ui->roomList->clear();
_reloadWindow->hide();
Room *room = Global::getRooms()[roomToReload];
_tilesX = room->gridSize.width();
_tilesY = room->gridSize.height();
qDebug() << "changed room, grid size is now: " << _tilesX << ", " << _tilesY;
//qDebug() << " Room: " << room;
//qDebug() << "_rooms: " << _rooms;
for (QMap<QString, QPoint>::iterator it = room->clientPositions.begin(); it != room->clientPositions.end(); ++it)
{
QString computerId = it.key();
QPoint pos = it.value();
qDebug() << "ComputerID: " << computerId;
qDebug() << "Pos: " << pos;
// Transform to pixels for createFrame(computerId, position).
QPoint pxPos(pos.x() * getTileWidthPx(), pos.y() * getTileHeightPx());
ConnectionFrame *cf = createFrame(computerId, pxPos, pos);
cf->move(cf->getCurrentPosition());
onPlaceFrame(false, cf);
}
}
}
int MainWindow::getTileWidthPx() const {
return ui->frmRoom->size().width() / _tilesX;
}
int MainWindow::getTileHeightPx() const {
return ui->frmRoom->size().height() / _tilesY;
}
/***************************************************************************//**
* Display popup which explains possible actions about the buttons.
*/
void MainWindow::onButtonHelp()
{
_helpWindow->show();
}
/***************************************************************************//**
* Handle projection from tutor to all.
* Get the client who is tutor and set the projectionSource of all
* clients, except the tutor ones, to false.
*/
void MainWindow::onButtonTutorToAll()
{
ui->action_Lock->setChecked(false);
if (getTutorFrame() == NULL)
QMessageBox::critical(this, tr("Projection"), sStrTutorNdef);
else if (getTutorFrame()->client() == NULL)
QMessageBox::critical(this, tr("Projection"), sStrTutorOffline);
else if (_clientFrames.size() == 1)
QMessageBox::critical(this, tr("Projection"), sStrNoDestAv);
else
{
// If this mode is already active, reset everything
if (_mode == Mode::Broadcast) {
reset();
return;
}
// Set all clients as watchers of tutor. Except for the tutors desired source, which hase to be none
for (QList<ConnectionFrame*>::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it)
if ((*it)->client() != NULL)
(*it)->client()->setDesiredProjectionSource((*it)->client() == getTutorFrame()->client() ? NO_SOURCE : getTutorFrame()->client()->id());
DisableButtons();
_mode = Mode::Broadcast;
startVncServerIfNecessary(getTutorFrame()->client()->id());
}
}
/***************************************************************************//**
* Handle the projection from Tutor to specific student.
* Set the client who is tutor as from and the selected client as to.
*/
void MainWindow::onButtonTutorToStudent()
{
ui->action_Lock->setChecked(false);
if (getSelectedFrame() == NULL)
QMessageBox::critical(this, tr("Projection"), sStrDestNdef);
else if (getTutorFrame() == NULL)
QMessageBox::critical(this, tr("Projection"), sStrTutorNdef);
else if (getSelectedFrame() == getTutorFrame())
QMessageBox::critical(this, tr("Projection"), sStrSourceDestSame);
else if (getSelectedFrame()->client() == NULL)
QMessageBox::critical(this, tr("Projection"), sStrDestOffline);
else if (getTutorFrame()->client() == NULL)
QMessageBox::critical(this, tr("Projection"), sStrTutorOffline);
else
{
// If this is the first call in this mode clear the watchers
if (_mode != Mode::Multicast) {
for (QList<ConnectionFrame*>::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it)
if ((*it)->client() != NULL)
(*it)->client()->setDesiredProjectionSource(NO_SOURCE);
}
// If "to" already watches "from" stop it
if (getSelectedFrame()->client()->desiredProjectionSource() == getTutorFrame()->client()->id() )
getSelectedFrame()->client()->setDesiredProjectionSource(NO_SOURCE);
else
getSelectedFrame()->client()->setDesiredProjectionSource(getTutorFrame()->client()->id());
DisableButtons();
_mode = Mode::Multicast;
startVncServerIfNecessary(getTutorFrame()->client()->id());
}
}
/***************************************************************************//**
* Handle projection from one student to tutor.
* Get the client to project from and get client, who is tutor, as to.
*/
void MainWindow::onButtonStudentToTutor()
{
ui->action_Lock->setChecked(false);
if (getSelectedFrame() == NULL)
QMessageBox::critical(this, tr("Projection"), sStrSourceNdef);
else if (getTutorFrame() == NULL)
QMessageBox::critical(this, tr("Projection"), sStrTutorNdef);
else if (getTutorFrame() == getSelectedFrame())
QMessageBox::critical(this, tr("Projection"), sStrSourceDestSame);
else if (getSelectedFrame()->client() == NULL)
QMessageBox::critical(this, tr("Projection"), sStrSourceOffline);
else if (getTutorFrame()->client() == NULL)
QMessageBox::critical(this, tr("Projection"), sStrTutorOffline);
else
{
// If this is not the first run in this mode and the current source is selected, stop the streaming.
if (_mode == Mode::Unicast && getSelectedFrame()->client()->id() == _streamingSource)
{
// Stop reset everything
_mode = Mode::None;
reset();
return;
}
// Unset all clients desired sources. Except the tutors desired source, this has to be the selecteds frame
for (QList<ConnectionFrame*>::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it)
if ((*it)->client() != NULL)
(*it)->client()->setDesiredProjectionSource(getTutorFrame()->client()->id() == (*it)->client()->id() ? getSelectedFrame()->client()->id():NO_SOURCE);
DisableButtons();
_mode = Mode::Unicast;
startVncServerIfNecessary(getSelectedFrame()->client()->id());
}
}
/***************************************************************************//**
* @brief MainWindow::onButtonStudentToTutorExclusive
*/
void MainWindow::onButtonStudentToTutorExclusive()
{
ui->action_Lock->setChecked(false);
if (getSelectedFrame() == NULL)
QMessageBox::critical(this, tr("Projection"), sStrSourceNdef);
else if (getTutorFrame() == NULL)
QMessageBox::critical(this, tr("Projection"), sStrTutorNdef);
else if (getTutorFrame() == getSelectedFrame())
QMessageBox::critical(this, tr("Projection"), sStrSourceDestSame);
else if (getSelectedFrame()->client() == NULL)
QMessageBox::critical(this, tr("Projection"), sStrSourceOffline);
else if (getTutorFrame()->client() == NULL)
QMessageBox::critical(this, tr("Projection"), sStrTutorOffline);
else
{
// If this is not the first run in this mode and the current source is selected, stop the streaming.
if (_mode == Mode::Unicast && getSelectedFrame()->client()->id() == _streamingSource)
{
// Stop reset everything
_mode = Mode::None;
reset();
return;
}
// Unset all clients desired sources. Except the tutors desired source, this has to be the selecteds frame
for (QList<ConnectionFrame*>::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it)
if ((*it)->client() != NULL)
(*it)->client()->setDesiredProjectionSource(getTutorFrame()->client()->id() == (*it)->client()->id() ? getSelectedFrame()->client()->id():NO_SOURCE);
DisableButtons();
_mode = Mode::LockedUnicast;
startVncServerIfNecessary(getSelectedFrame()->client()->id());
}
}
/***************************************************************************//**
* Handle Button StopProjection.
* Set ProjectionSource of each client to false, stop the active VNC Server
* and the active VNC Client(s) and unlock all screens.
*/
void MainWindow::onButtonStopProjection()
{
ui->action_Lock->setChecked(false);
reset();
}
/***************************************************************************//**
* Handle button to lock or unlock screens of client(s).
* If already locked, do nothing, else lock or unlock the clients, except the
* tutor and the manager running machine.
* @param checked
*/
void MainWindow::onButtonLock(bool checked)
{
// Stop all projections
reset();
for (QList<ConnectionFrame*>::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it)
{
if ((*it)->client() == NULL)
continue;
(*it)->client()->lockScreen(checked);
}
}
/***************************************************************************//**
* On button exit, close application.
*/
void MainWindow::onButtonExit()
{
this->close();
}
/***************************************************************************//**
* Handle button to set specific client as tutor.
* Unset active tutor and set selected client, if not inactive, as new tutor.
*/
void MainWindow::onButtonSetAsTutor()
{
ui->action_Lock->setChecked(false);
// If no frame is selected, warning.
if (getSelectedFrame() == NULL)
{
QMessageBox::critical(this, tr("Selection"), tr("No client is selected."));
return;
}
// If frame of inactive client has been selected unselect it
if (getSelectedFrame()->client() == NULL)
{
QMessageBox::critical(this, tr("Selection"), tr("The selected client is not connected."));
return;
}
else // If selected client is locked, first unlock
{
getSelectedFrame()->client()->lockScreen(false);
}
// If same frame is already tutor, do nothing
if (getSelectedFrame() == getTutorFrame())
return;
// Else unset the old and set the new tutor
if (getTutorFrame() != NULL)
{
getTutorFrame()->setTutor(false);
}
getSelectedFrame()->setTutor(true);
}
/***************************************************************************//**
* Handle from ListenServer signaled new client connection.
* @param client
*/
void MainWindow::onClientConnected(Client* client)
{
connect(client, SIGNAL(authenticating(Client*, ClientLogin*)), this, SLOT(onClientAuthenticating(Client*, ClientLogin*)));
connect(client, SIGNAL(authenticated(Client*)), this, SLOT(onClientAuthenticated(Client*)));
}
/***************************************************************************//**
* Authenticate new Client client.
* Check if incoming client ip already exists,
* if yes --> return.
* Check if same name of client already exist,
* if yes --> name new client as name(x).
* x = number of clients with the same name.
* @param client
* @param request
*/
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;
}
/***************************************************************************//**
* New client was authenticated, make several checks.
* Check if new clients ip already exists,
* if yes --> not necessary to create new clientFrame.
* if not --> create a new one and adapt this one to current
* situation in class room(projection / lock screen).
* Check if avctiv tutor is available,
* if not --> check if new client is possible tutor.
* @param client
*/
void MainWindow::onClientAuthenticated(Client* client)
{
disconnect(client, SIGNAL(authenticated(Client*)), this, SLOT(onClientAuthenticated(Client*)));
connect(client, SIGNAL(vncServerStateChange(Client*)), this, SLOT(onVncServerStateChange(Client*)));
connect(client, SIGNAL(vncClientStateChange(Client*)), this, SLOT(onVncClientStateChange(Client*)));
bool hasActiveTutor = false;
ConnectionFrame *existing = NULL;
for (QList<ConnectionFrame*>::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it)
{
// qDebug() << "Existing frame ip: " << (*it)->computerId();
// qDebug() << "New Clients ip: " << client->ip();
// qDebug() << ((*it)->computerId() == client->ip());
if ((*it)->computerId() == client->ip())
{
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->ip() == _tutorList[i])
{
isTutor = true;
break;
}
}
}
// Clients ip already exists, but was not active.
if (existing != NULL)
{
// qDebug() << "Should go into this if clause.";
existing->setTutor(isTutor);
existing->assignClient(client);
tellClientCurrentSituation(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->ip(), x, y);
if (!ok)
{
SYSTEM_SETTINGS(sys);
ok = loadPosition(sys, client->ip(), 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 * getTileWidthPx(), y * getTileHeightPx());
onPlaceFrame(true, cf);
}
else
{
// Move to any free tile
placeFrameInFreeSlot(cf);
}
// Set Tutor option
cf->setTutor(isTutor);
// Assign client instance
cf->assignClient(client);
tellClientCurrentSituation(client);
}
/***************************************************************************//**
* Handle if VNC Server State has changed.
* Check if VNC server has been started/stopped on some client and start VNC server/reset pending
* VNC projection information.
* @param client
*/
void MainWindow::onVncServerStateChange(Client* client)
{
if (client == getClientFromId(_streamingSource))
EnableButtons();
if (client->isActiveVncServer())
{
// apply the desired projection sources
for (QList<ConnectionFrame*>::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it)
if ( (*it)->client() != NULL) // Ignore offline clients
{
if ( (*it)->client()->desiredProjectionSource() == client->id() )
(*it)->client()->startVncClient(client);
else
(*it)->client()->stopVncClient();
(*it)->client()->lockScreen((*it)->client()->desiredProjectionSource() == NO_SOURCE && _mode == Mode::LockedUnicast);
}
// Dont forget to unlock the vnc server
client->lockScreen(false);
}
else
{
// VNC server stopped on some client or failed to start - reset local pending projection information
for (QList<ConnectionFrame*>::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it)
{
if ((*it)->client() != NULL)
{
if ((*it)->client()->desiredProjectionSource() == client->id()) {
(*it)->client()->setDesiredProjectionSource(NO_SOURCE);
(*it)->client()->stopVncClient();
if (_mode == Mode::LockedUnicast)
(*it)->client()->lockScreen(true);
}
}
}
// Dont forget to unlock the vnc server (if necesarry)
client->lockScreen(client->desiredProjectionSource() == NO_SOURCE && _mode == Mode::LockedUnicast);
// If this was the current source remember that there is no source anymore and reset mode
if (client == getClientFromId(_streamingSource)){
_streamingSource = NO_SOURCE;
_mode = Mode::None;
}
}
}
/***************************************************************************//**
* Handle VNC client state change.
* Check if any vnc client is in use, and if that is not the case, stop VNC Server.
* @param client
* @param lastProjectionSource
*/
void MainWindow::onVncClientStateChange(Client* client)
{
if (client != NULL)
{
// VNC Client stopped -> remove from watchers
if (!client->isActiveVncClient())
{
// Only unset a desired Projection source if it has not changed meanwhile
if (client->projectionSource() == client->desiredProjectionSource())
client->setDesiredProjectionSource(NO_SOURCE);
/*
* If nobody is watching the VNC server of the pvsclient that
* stopped its vncclient stop it. (The _LAST_ vncserver, there
* may be a new one)
* This is necessary for the race condition when a server
* is stopped and another started at the same time, since the new
* server would be killed if all client disconnect before any of
* the new connect.
*/
bool serverHasWatchers = false;
for (QList<ConnectionFrame*>::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it)
if ((*it)->client() != NULL)
if ((*it)->client()->desiredProjectionSource() == client->projectionSource()) {
serverHasWatchers = true;
break;
}
if ( !(serverHasWatchers || _mode == Mode::Broadcast) )
{
Client* c = getClientFromId(client->projectionSource());
if (c != NULL)
c->stopVncServer();
}
}
}
}
/***************************************************************************//**
* DisableButtons.
*/
void MainWindow::DisableButtons()
{
_buttonLockTimer->start();
foreach (QAction* a, _lockingButtons)
a->setDisabled(true);
}
/***************************************************************************//**
* EnableButtons.
*/
void MainWindow::EnableButtons()
{
_buttonLockTimer->stop();
foreach (QAction* a, _lockingButtons)
a->setEnabled(true);
}