/* # 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 #include #include // Other custom UI elements #include "../clicklabel/clicklabel.h" #include "../sessionnamewindow/sessionnamewindow.h" #include "../connectionframe/connectionframe.h" #include "../helpwindow/helpwindow.h" #include "../reloadroomwindow/reloadroomwindow.h" #include "ui_reloadroom.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" const QString MainWindow::sStrTutorNdef = tr("No tutor defined."); const QString MainWindow::sStrTutorOffline = tr("Tutor is offline."); const QString MainWindow::sStrSourceNdef = tr("Please select a projection source."); const QString MainWindow::sStrSourceOffline = tr("The projection source is offline."); const QString MainWindow::sStrDestNdef = tr("Please select a projection destination."); const QString MainWindow::sStrDestOffline = tr("The projection destination is offline."); const QString MainWindow::sStrSourceDestSame = tr("Selected projection target is tutor."); const QString MainWindow::sStrClientOnline = tr("Selected client is currently online."); const QString MainWindow::sStrNoDestAv = 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; _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; _tileWidth = ui->frmRoom->size().width() / _tilesX; _tileHeight = ui->frmRoom->size().height() / _tilesY; 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::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; } } /***************************************************************************//** * 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, _tileWidth, _tileHeight); _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 position) { // Allocate and resize ConnectionFrame *cf = new ConnectionFrame(ui->frmRoom, _tileWidth, _tileHeight); cf->setComputerId(computerId); cf->setCurrentPosition(position); _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() / _tileWidth, cf->pos().y() / _tileHeight); 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)); } } } /***************************************************************************//** * @brief MainWindow::tryToUseRoomTemplate */ void MainWindow::tryToUseRoomTemplate() { QMap > roomsList; SYSTEM_SETTINGS(conf); if (!conf.contains("rooms")) { qDebug() << "Invalid config file!"; return; } QStringList rooms = conf.value("rooms").toStringList(); qDebug() << rooms; for (auto i : rooms) { qDebug() << "i: " << i; conf.beginGroup(i); if (!conf.contains("mgrIP")) { qDebug() << "Invalid config file!"; return; } // 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()); } conf.endArray(); // Find the managerIP of current room. QString mgrIP = conf.value("mgrIP").toString(); foreach (const QHostAddress &address, QNetworkInterface::allAddresses()) { if (address != QHostAddress(QHostAddress::LocalHost) && mgrIP == address.toString()) { qDebug("Found this ip in config."); Global::setCurrentRoom(i); 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 coord = conf.value("pos").toPoint(); qDebug() << computerId; qDebug() << coord; qDebug() << _tileWidth ; qDebug() << _tileHeight ; coord.setX(coord.x() * _tileWidth); coord.setY(coord.y() * _tileHeight); qDebug() << coord ; ConnectionFrame *cf = createFrame(computerId, coord); cf->move(cf->getCurrentPosition()); onPlaceFrame(false, cf); } conf.endArray(); } } conf.endGroup(); } 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::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::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::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, "Test", "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) { // 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::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); } // 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(); _tileWidth = nw; _tileHeight = nh; } /***************************************************************************//** * 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::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() / _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; // 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", "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::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::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 keyList = Global::getRooms().keys(); for (QList::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::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(); QMap room = Global::getRooms()[roomToReload]; // qDebug() << " Room: " << room; // qDebug() << "_rooms: " << _rooms; for (QMap::iterator it = room.begin(); it != room.end(); ++it) { QString computerId = it.key(); QPoint pos = it.value(); qDebug() << "ComputerID: " << computerId; qDebug() << "Pos: " << pos; // Transform to pixels for createFrame(computerId, position). pos.setX(pos.x() * _tileWidth); pos.setY(pos.y() * _tileHeight); ConnectionFrame *cf = createFrame(computerId, pos); cf->move(cf->getCurrentPosition()); onPlaceFrame(false, cf); } } } /***************************************************************************//** * 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::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::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::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::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::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::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::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; } if (_clientFrames.size() > 50) { client->deleteLater(); 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 * _tileWidth, y * _tileHeight); 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::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::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::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); }