/*
# 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>
#include <QSvgRenderer>
#include <QPainter>
#include <QImage>
// 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"
#include <iostream>
#include <vector>
#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.")
using std::vector;
using std::cout;
using std::endl;
/***************************************************************************//**
* Initialize MainWindow and ListenServer.
* @param ipListUrl
* @param parent
*/
MainWindow::MainWindow(QWidget* parent) :
QMainWindow(parent), ui(new Ui::MainWindow), _tbIconSize(24), _tbArea(Qt::LeftToolBarArea)
{
qDebug() << "MainWindow(parent)";
_mode = Mode::Multicast;
_streamingSource = 0;
/* default value, these will be updated a room is loaded */
_tilesX = 10;
_tilesY = 10;
_virtCols = 0;
_virtRows = 0;
_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"));
// 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()));
connect(ui->action_DeleteClient, SIGNAL(triggered()), this, SLOT(onDeleteClient()));
/* In exam-mode: disable most features */
SYSTEM_SETTINGS(conf);
if (conf.contains("examMode")) {
Global::setExam(conf.value("examMode").toBool());
}
if (Global::isExam()) {
qDebug() << "Exam-Mode!";
ui->action_TutorToAll->setVisible(false);
ui->action_StudentToTutor->setVisible(false);
ui->action_StudentToTutorExclusive->setVisible(false);
ui->action_TutorToStudent->setVisible(false);
ui->action_StopProjection->setVisible(false);
QLabel* examModeLabel = new QLabel("Klausur-\nModus");
examModeLabel->setObjectName("examModeLabel");
examModeLabel->setAlignment(Qt::AlignCenter);
examModeLabel->setFixedHeight(400);
ui->toolBar->insertWidget(ui->action_TutorToStudent, examModeLabel);
}
/* disable context-sensitive buttons by default */
_contextButtons
<< ui->action_DeleteClient
<< ui->action_StudentToTutor
<< ui->action_StudentToTutorExclusive
<< ui->action_SetAsTutor
<< ui->action_TutorToStudent;
lockContextButtons();
/* 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;
}
/** Euclidean distance (why is this not implemented in QPoint?) */
float distance(QPoint a, QPoint b) {
int dx = a.x() - b.x();
int dy = a.y() - b.y();
int sum = dx*dx + dy*dy;
return sqrt((float) sum);
}
float distance(QPointF a, QPointF b) {
int dx = a.x() - b.x();
int dy = a.y() - b.y();
int sum = dx*dx + dy*dy;
return sqrt(sum);
}
/***************************************************************************//**
* \brief: find the closest available frame.
*
* Closest: smallest Euclidean distance from center to center
*
* 'Algorithm': ideally, go in circles around the preferred point until you find
* a free spot. Lazy way: store all free positions in an array and find later
* there the closest (<- that's what I do)
*
* @param preferred in Pixels!
* @param toIgnore, ignore this connectionframe when considering free slots
* @return the free slot
*/
QPoint MainWindow::closestFreeSlot(QPoint preferredPixels, ConnectionFrame* toIgnore) {
const QSize& clientSize = Global::getCurrentRoom()->clientSize;
bool grid[_tilesX][_tilesY];
memset(grid, 0, sizeof(bool) * _tilesX * _tilesY); /* set everything to false */
/* fill grid */
for (auto it = _clientFrames.begin(); it != _clientFrames.end(); ++it) {
if (*it == toIgnore) { continue; }
const QPoint& p = (*it)->getGridPosition();
for (int x = p.x(); x < p.x() + clientSize.width(); x++) {
for (int y = p.y(); y < p.y() + clientSize.height(); y++) {
grid[x][y] = true;
}
}
}
QList<QPoint> freePositions;
/* check all positions to see if they are available */
for (int x = 0; x <= _tilesX - clientSize.width(); x++) {
for ( int y = 0; y <= _tilesY - clientSize.height(); y++) {
/* check if (x,y) is free */
bool isFree = true;
for (int dx = 0; dx < clientSize.width(); dx++) {
for (int dy = 0; dy < clientSize.height(); dy++) {
if (grid[x + dx][y + dy]) {
isFree = false;
goto endLoop; // double-break
}
}
}
endLoop:
if (isFree) { freePositions << QPoint(x,y); }
}
}
/* among all the free positions, find the closest */
float min_distance = 10000;
QPoint bestPosition = QPoint(-1, -1);
for (QPoint freePos : freePositions) {
QPoint freePosCenter( freePos.x() * getTileWidthPx() + getTileWidthPx() * clientSize.width() / 2,
freePos.y() * getTileHeightPx() + getTileHeightPx() * clientSize.height() / 2);
float dist = distance(freePosCenter, preferredPixels);
if (dist < min_distance) {
min_distance = dist;
bestPosition = freePos;
}
}
return bestPosition;
}
/* place frame in the cloest available spot */
void MainWindow::placeFrameInFreeSlot(ConnectionFrame* frame, QPoint preferredPixels) {
QPoint bestPosition = closestFreeSlot(preferredPixels, frame);
frame->setGridPosition(bestPosition);
QPoint freePosPx(bestPosition.x() * getTileWidthPx(), bestPosition.y() * getTileHeightPx());
frame->setCurrentPosition(freePosPx);
}
/***************************************************************************//**
* 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
int width = getTileWidthPx() * Global::getCurrentRoom()->clientSize.width();
int height= getTileHeightPx() * Global::getCurrentRoom()->clientSize.height();
ConnectionFrame *cf = new ConnectionFrame(ui->frmRoom, width, height);
_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
const Room* room = Global::getCurrentRoom();
int width = getTileWidthPx() * (room == NULL ? 1 : room->clientSize.width());
int height = getTileHeightPx() * (room == NULL ? 1 : 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()
{
qDebug() << "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;
QString myRoom = "";
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();
/* read backgroundImage */
QString image = conf.contains("backgroundImage") ? conf.value("backgroundImage").toString() : "";
QString mgrIP = conf.value("mgrIP").toString();
QString tutorIP = conf.value("tutorIP").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.");
myRoom = roomName;
}
}
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, image, tutorIP);
qDebug() << "read new room: " << roomName << ": " << gridSize << ", " << clientSize;
roomsList.insert(roomName, r);
}
Global::setRooms(roomsList);
if (myRoom == "") {
/* so apparently this is not a manager of a room */
if (Global::manager_only) {
cout << "exiting because of the argument --manager-only was set and this computer is not a manager" << endl;
exit(0);
}
} else {
switchRoomTo(myRoom);
}
}
/***************************************************************************//**
* 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);
}
enum AspectStatus { GRID_OK, GRID_TOO_WIDE, GRID_TOO_TALL };
/**
* check the difference in the aspect ratio between the frame size and the grid
* size. The parameters in here are hand-adjusted. Feel free to make it more or
* less sensitive.
* */
AspectStatus checkAspectRatio(const QSize& frameSize, const QSize& gridSize) {
float aspectRoom = ((float) gridSize.height()) / ((float) gridSize.width());
float aspectFrame = ((float) frameSize.height()) / ((float) frameSize.width());
if (aspectRoom / aspectFrame < 0.8) {
return GRID_TOO_WIDE;
}
if ( aspectFrame / aspectRoom < 0.8) {
return GRID_TOO_TALL;
}
return GRID_OK;
}
/***************************************************************************//**
* Resize event.
* @param e
*/
void MainWindow::resizeEvent(QResizeEvent* e)
{
const Room* room = Global::getCurrentRoom();
const QSize& clientSize = room->clientSize;
if (ui->frmRoom->size().width() < 100 || ui->frmRoom->size().height() < 100 || _tilesX <= 0 || _tilesY <= 0) { return; }
QSize newGridSize = room->gridSize;
/* do we have to add virtual columns? */
while (checkAspectRatio(ui->frmRoom->size(), newGridSize) == GRID_TOO_WIDE) {
/* add row */
newGridSize.setHeight(newGridSize.height() + 1);
}
while (checkAspectRatio(ui->frmRoom->size(), newGridSize) == GRID_TOO_TALL) {
/* add column */
newGridSize.setWidth(newGridSize.width() + 1);
}
this->_tilesX = newGridSize.width();
this->_tilesY = newGridSize.height();
/* Bring back frame that are now out of the screen */
for (auto it = _clientFrames.begin(); it != _clientFrames.end(); ++it) {
const QPoint gp = (*it)->getGridPosition();
if ( gp.x() >= _tilesX || gp.y() >= _tilesY ) {
qDebug() << "bring frame back";
placeFrameInFreeSlot(*it, (*it)->getCurrentPosition());
}
}
/* Resize all connection windows */
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);
}
/* update background image label */
if (_backgroundImage != NULL) {
int w = ui->frmRoom->width() - 5; /* to make sure that we don't enlarge the window */
int h = ui->frmRoom->height() - 5;
ui->imageLabel->hide();
ui->imageLabel->setScaledContents(true);
ui->imageLabel->setPixmap(QPixmap::fromImage(*_backgroundImage).scaled(w,h, Qt::IgnoreAspectRatio));
ui->imageLabel->show();
} else {
ui->imageLabel->clear();
}
}
void MainWindow::lockContextButtons() {
for (auto it = _contextButtons.begin(); it != _contextButtons.end(); ++it) {
(*it)->setEnabled(false);
}
}
void MainWindow::unlockContextButtons() {
for (auto it = _contextButtons.begin(); it != _contextButtons.end(); ++it) {
(*it)->setEnabled(true);
}
/* and disable some again based on special rules */
if (getSelectedFrame()->client() != NULL) {
ui->action_DeleteClient->setEnabled(false);
}
if (getSelectedFrame()->client() == NULL) {
ui->action_SetAsTutor->setEnabled(false);
ui->action_StudentToTutorExclusive->setEnabled(false);
ui->action_StudentToTutor->setEnabled(false);
ui->action_TutorToStudent->setEnabled(false);
}
}
/***************************************************************************//**
* 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())
{
lockContextButtons();
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
*/
/***************************************************************************//**
* Place frame to from user specified position. Should be called when a
* connectionFrame is dropped during the "drag-n-drop".
*/
void MainWindow::onPlaceFrame(bool activateTrash, ConnectionFrame* connectionFrame)
{
// if (_tilesX <= 0 || _tilesY <= 0) return;
const QPoint &preferredPixels = connectionFrame->frameGeometry().center();
placeFrameInFreeSlot(connectionFrame, preferredPixels);
/* save in local config to remember position */
savePosition(connectionFrame);
resizeEvent(NULL);
}
/***************************************************************************//**
* 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();
qDebug() << "position of selectedFrame: " << getSelectedFrame()->getGridPosition();
unlockContextButtons();
}
/***************************************************************************//**
* 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::switchRoomTo(QString roomToReload) {
// qDebug() << roomToReload;
Global::setCurrentRoom(roomToReload);
Room *room = Global::getRooms()[roomToReload];
/* set tiles */
_tilesX = room->gridSize.width();
_tilesY = room->gridSize.height();
/* place connection frames */
for (auto it = room->clientPositions.begin(); it != room->clientPositions.end(); ++it) {
QString computerId = it.key();
QPoint pos = it.value();
QPoint pxPos(pos.x() * getTileWidthPx(), pos.y() * getTileHeightPx());
ConnectionFrame *cf = createFrame(computerId, pxPos, pos);
cf->move(cf->getCurrentPosition());
onPlaceFrame(false, cf);
if (computerId == room->tutorIP) {
qDebug() << "set computer with id " << computerId << " as tutor per configuration";
if (getTutorFrame() != NULL) {
getTutorFrame()->setTutor(false);
}
cf->setTutor(true);
} else {
//qDebug() << "not tutor because id is " << computerId << " and tutorIP is " << room->tutorIP;
}
}
/* load background image */
QString imgPath = Global::getCurrentRoom()->imagePath;
qDebug() << "imgPath is " << imgPath;
/* delete old image */
if (_backgroundImage != NULL) {delete _backgroundImage; }
_backgroundImage = NULL;
if (imgPath != "") {
qDebug() << "set background image path: " << imgPath;
if (imgPath.endsWith("svg")) {
/* render once with maximal screen size */
const QSize &s = QApplication::desktop()->screenGeometry().size(); // ui->frmRoom->geometry().size();
QSvgRenderer renderer(imgPath);
_backgroundImage = new QImage(s, QImage::Format_ARGB32);
_backgroundImage->fill(Qt::lightGray); /* background color */
QPainter painter(_backgroundImage);
renderer.render(&painter);
} else {
_backgroundImage = new QImage();
_backgroundImage->load(imgPath);
}
}
/* and force a resize event (this scales the image) */
resizeEvent(NULL);
}
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();
switchRoomTo(roomToReload);
_reloadWindow->ui->roomList->clear();
_reloadWindow->hide();
}
}
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);
resizeEvent(NULL); // This is where all the positioning should be
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()
{
qDebug() << "DisableButtons()";
_buttonLockTimer->start();
foreach (QAction* a, _lockingButtons)
a->setDisabled(true);
}
/***************************************************************************//**
* EnableButtons.
*/
void MainWindow::EnableButtons()
{
qDebug() << "EnableButtons()";
_buttonLockTimer->stop();
foreach (QAction* a, _lockingButtons)
a->setEnabled(true);
}
void MainWindow::onDeleteClient() {
// If no frame is selected, warning.
ConnectionFrame* frame = getSelectedFrame();
if (frame == NULL) {
QMessageBox::critical(this, tr("Selection"), tr("No client is selected."));
return;
}
if (frame->client() != NULL) {
QMessageBox::critical(this, tr("Selection"), tr("This client is still connected."));
return;
} else {
qDebug() << "Now delete the client";
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);
lockContextButtons();
return;
} else {
frame->move(frame->getPreviousPosition());
return;
}
}
}