Pool Video Switch v2
Software video switch for distributed remote display in a lecture environment
mainwindow.cpp
Go to the documentation of this file.
1 /*
2  # Copyright (c) 2009 - OpenSLX Project, Computer Center University of Freiburg
3  #
4  # This program is free software distributed under the GPL version 2.
5  # See http://openslx.org/COPYING
6  #
7  # If you have any feedback please consult http://openslx.org/feedback and
8  # send your suggestions, praise, or complaints to feedback@openslx.org
9  #
10  # General information about OpenSLX can be found at http://openslx.org/
11  # -----------------------------------------------------------------------------
12  # mainWindow.cpp
13  This is the Main class for the pvsManager. The GUI is constructed here.
14  # -----------------------------------------------------------------------------
15  */
16 // Self
17 #include "mainwindow.h"
18 // QT stuff
19 #include <QSvgRenderer>
20 #include <QPainter>
21 #include <QImage>
22 #include <QMessageBox>
23 #include <QCloseEvent>
24 #include <QDialogButtonBox>
25 #include <QScreen>
26 // Other custom UI elements
27 #include "../serverapp/serverapp.h"
28 #include "../clicklabel/clicklabel.h"
29 #include "../sessionnamewindow/sessionnamewindow.h"
30 #include "../connectionframe/connectionframe.h"
31 #include "../helpwindow/helpwindow.h"
32 #include "../reloadroomwindow/reloadroomwindow.h"
33 // Network
34 #include "../net/listenserver.h"
35 #include "../net/client.h"
36 #include "../net/discoverylistener.h"
37 // Others
38 #include "../../shared/settings.h"
39 #include "../util/platform/screensaver.h"
40 // Auto-generated ui class
41 #include "ui_mainwindow.h"
42 
43 #define sStrTutorNdef MainWindow::tr("No tutor defined.")
44 #define sStrTutorOffline MainWindow::tr("Tutor is offline.")
45 #define sStrSourceNdef MainWindow::tr("Please select a projection source.")
46 #define sStrSourceOffline MainWindow::tr("The projection source is offline.")
47 #define sStrDestNdef MainWindow::tr("Please select a projection destination.")
48 #define sStrDestOffline MainWindow::tr("The projection destination is offline.")
49 #define sStrSourceDestSame MainWindow::tr("Selected projection target is tutor.")
50 #define sStrNoDestAv MainWindow::tr("No projection destination available.")
51 
58  : QMainWindow(parent)
59  , ui(new Ui::MainWindow)
60  , _mode(Mode::Multicast)
61 {
62  qDebug() << "MainWindow(parent)";
63 
64  /* default value, these will be updated a room is loaded */
65  _tilesX = 10;
66  _tilesY = 10;
67 
69  _reloadWindow = new ReloadRoomWindow(this);
70  _reloadWindow->setWindowTitle(tr("Reload Room"));
71  ui->setupUi(this);
72  setWindowFlags(Qt::FramelessWindowHint);
73 
74  serverApp->setSessionName();
75 
76  //conWin = new ConnectionWindow(ui->widget);
77  //ui->VconWinLayout->addWidget(conWin);
78  //conList = new ConnectionList(ui->ClWidget);
79  //ui->ClientGLayout->addWidget(conList);
80 
81  // Show only session name label, not textbox
82  _sessionNameLabel = new ClickLabel(this);
83  ui->mainLayout->addWidget(_sessionNameLabel);
84 
85  ui->frmRoom->setStyleSheet("background-color:#444");
86 
87  // toolbar and actions in pvsmgr
88  ui->action_Exit->setStatusTip(tr("Exit"));
89  ui->action_Lock->setStatusTip(tr("Lock or Unlock all Clients"));
90 
91  /* the label */
92  _examModeLabel = new QLabel("Klausur-\nModus");
93  _examModeLabel->setObjectName("examModeLabel");
94  _examModeLabel->setAlignment(Qt::AlignCenter);
95  _examModeLabel->setFixedHeight(400);
96  ui->toolBar->insertWidget(ui->action_TutorToStudent, _examModeLabel);
97  _dropMarker = new QLabel(ui->frmRoom);
98  _dropMarker->setStyleSheet("background-color: #448; border-radius: 2px;");
99  _dropMarker->hide();
100  _helpWindow = new HelpWindow(ui->toolBar->actions(), this);
101 
102  serverApp->setExam(false);
103 
104  // Close button in tool bar
105  connect(ui->action_Exit, &QAction::triggered, this, &MainWindow::onButtonExit);
106  connect(ui->action_TutorToAll, &QAction::triggered, this, &MainWindow::onButtonTutorToAll);
107  connect(ui->action_StudentToTutor, &QAction::triggered, this, &MainWindow::onButtonStudentToTutor);
108  connect(ui->action_StudentToTutorExclusive, &QAction::triggered, this, &MainWindow::onButtonStudentToTutorExclusive);
109  connect(ui->action_TutorToStudent, &QAction::triggered, this, &MainWindow::onButtonTutorToStudent);
110  connect(ui->action_StopProjection, &QAction::triggered, this, &MainWindow::onButtonStopProjection);
111  connect(ui->action_SetAsTutor, &QAction::triggered, this, &MainWindow::onButtonSetAsTutor);
112  connect(ui->action_SetAsTutor, &QAction::triggered, this, &MainWindow::onButtonStopProjection);
113  connect(ui->action_Lock, &QAction::toggled, this, &MainWindow::onButtonLock);
114  connect(ui->action_LockSingle, &QAction::triggered, this, &MainWindow::onButtonLockSingle);
115  connect(ui->action_Help, &QAction::triggered, this, &MainWindow::onButtonHelp);
116  connect(ui->actionReload_Room_Configuration, &QAction::triggered, this, &MainWindow::onButtonReloadRoomConfig);
117  connect(ui->action_DeleteClient, &QAction::triggered, this, &MainWindow::onDeleteClient);
118 
119  /* Stuff for the button lock */
120  //Setup a timeout
121  _buttonLockTimer = new QTimer(this);
122  _buttonLockTimer->setSingleShot(true);
123  _buttonLockTimer->setInterval(BUTTON_BLOCK_TIME);
124  connect(_buttonLockTimer, &QTimer::timeout, this, &MainWindow::enableButtons);
125  // Define the locking buttons
127  << ui->action_DeleteClient
128  << ui->action_StudentToTutor
129  << ui->action_StudentToTutorExclusive
130  << ui->action_SetAsTutor
131  << ui->action_TutorToStudent
132  << ui->action_LockSingle
133  << ui->action_Lock
134  << ui->action_TutorToAll
135  << ui->action_StopProjection;
136 
137  // Clicking the session name label shows the edit field for it
139  // Listen to updates to the session name through the session name window
142 
143  setAttribute(Qt::WA_QuitOnClose);
144  setUnifiedTitleAndToolBarOnMac(true);
145  this->showMaximized(); // show the Mainwindow maximized
146 
147  auto *ls = new ListenServer(CLIENT_PORT, this);
149  new DiscoveryListener(this);
150 
151  // Finally
152  this->onSessionNameUpdate(); // Just make lable visible.
153 
155 }
156 
161 {
162  int examClientCount = 0;
163  int clientCount = 0;
164 
165 
166  for (auto * frame : _clientFrames) {
167  Client* c = frame->client();
168  if (c != nullptr) {
169  bool b = c->isExamMode();
170  examClientCount += b ? 1 : 0;
171  clientCount++;
172  }
173  }
174 
175  serverApp->setExam(examClientCount * 2 >= clientCount && clientCount > 0);
176  bool e = serverApp->isExam();
177  qDebug() << "isExam is " << e;
178  ui->action_TutorToAll->setVisible(!e);
179  ui->action_StudentToTutor->setVisible(!e);
180  ui->action_StudentToTutorExclusive->setVisible(!e);
181  ui->action_TutorToStudent->setVisible(!e);
182  ui->action_StopProjection->setVisible(!e);
183  _examModeLabel->setVisible(e);
184  _examModeLabel->setFixedHeight(e ? 400 : 0);
185 
186  if (_lastClientCount != clientCount) {
187  // Client count actually changed
188  if (clientCount == 0) {
189  // Last client must have disconnected, enable screen saver again
191  } else if (_lastClientCount == 0) {
192  // We didn't have a client before, but now we do, disable screen saver
195  }
196  // Remember client count
197  _lastClientCount = clientCount;
198  }
199  // Also we might have to enable/disable buttons in the toolbar if the
200  // client frame is currently selected
202 }
203 
205 {
206  _sessionNameLabel->deleteLater();
207  delete ui;
208 }
209 
211 static int distance(QPoint a, QPoint b)
212 {
213  const int dx = a.x() - b.x();
214  const int dy = a.y() - b.y();
215  const int sum = dx * dx + dy * dy;
216  return sum;
217 }
218 
232 QPoint MainWindow::closestFreeSlot(const QPoint &preferredPixels, const ConnectionFrame* toIgnore)
233 {
234  const bool pickFirstOne = ( preferredPixels == QPoint(-1, -1) );
235  const QSize& clientSize = serverApp->getCurrentRoom()->clientSize;
236 #define GRID(X,Y) (grid[((X) * _tilesY) + (Y)])
237  bool *grid = new bool[_tilesX * _tilesY];
238  memset(grid, 0, sizeof(bool) * size_t(_tilesX * _tilesY)); /* set everything to false */
239 
240  /* fill grid */
241  for (auto * frame : _clientFrames) {
242 
243  if (frame == toIgnore) { continue; }
244 
245  const QPoint p = frame->getGridPosition();
246 
247  for (int x = p.x(); x < p.x() + clientSize.width(); x++) {
248  for (int y = p.y(); y < p.y() + clientSize.height(); y++) {
249  GRID(x, y) = true;
250  }
251  }
252  }
253 
254  QList<QPoint> freePositions;
255  /* check all positions to see if they are available */
256  for (int x = 0; x <= _tilesX - clientSize.width(); x++) {
257  for ( int y = 0; y <= _tilesY - clientSize.height(); y++) {
258  /* check if (x,y) is free */
259  bool isFree = true;
260  int dx, dy = 0;
261  for (dx = 0; dx < clientSize.width(); dx++) {
262  for (dy = 0; dy < clientSize.height(); dy++) {
263  if (GRID(x + dx, y + dy)) {
264  isFree = false;
265  goto endLoop; // double-break
266  }
267  }
268  }
269 endLoop: ;
270  if (isFree) {
271  freePositions << QPoint(x, y);
272  if (pickFirstOne) {
273  goto endSearch;
274  }
275  }
276  }
277  }
278 endSearch: ;
279 #undef GRID
280  delete[] grid;
281  if (freePositions.isEmpty() && toIgnore != nullptr) {
282  return toIgnore->getGridPosition();
283  }
284  /* among all the free positions, find the closest */
285  int min_distance = 10000000;
286  QPoint bestPosition = QPoint(0, 0);
287 
288  for (QPoint freePos : freePositions) {
289  const QPoint freePosPx( freePos.x() * getTileWidthPx(), freePos.y() * getTileHeightPx() );
290  const int dist = distance(freePosPx, preferredPixels);
291  if (dist < min_distance) {
292  min_distance = dist;
293  bestPosition = freePos;
294  }
295  }
296  return bestPosition;
297 }
298 
299 /* place frame in the closest available spot */
300 void MainWindow::placeFrameInFreeSlot(ConnectionFrame* frame, QPoint preferredPixels)
301 {
302  QPoint bestPosition = closestFreeSlot(preferredPixels, frame);
303  frame->setGridPosition(bestPosition);
304 }
305 
312 ConnectionFrame* MainWindow::createFrame(const QString &computerId, const QPoint* gridPosition, bool fromRoomplan)
313 {
314  auto *cf = new ConnectionFrame(this, ui->frmRoom, fromRoomplan);
315  if (gridPosition == nullptr) {
317  } else {
318  cf->setGridPosition(*gridPosition);
319  }
320  cf->setComputerId(computerId);
321  _clientFrames.append(cf);
322  cf->show();
326  return cf;
327 }
328 
335 {
336  // If clients are currently locked, tell this new client
337  if (ui->action_Lock->isChecked() || _mode == Mode::LockedUnicast)
338  client->lockScreen(true);
339 
340  if (_mode == Mode::Broadcast) {
342  if (_streamingSource != 0) {
344  }
345  }
346 }
347 
355 {
356  for (auto * frame : _clientFrames) {
357  if (frame->client() != nullptr) {
358  if (frame->client()->id() == id)
359  return frame->client();
360  }
361  }
362  return nullptr;
363 }
364 
372 {
373  for (auto * frame : _clientFrames) {
374  if ((frame != nullptr) && (frame->isTutor()))
375  return frame;
376  }
377  return nullptr;
378 }
379 
387 {
388  for (auto * frame : _clientFrames) {
389  if ((frame != nullptr) && (frame->isSelected()))
390  return frame;
391  }
392  return nullptr;
393 }
394 
395 /*
396  * Overridden methods
397  */
398 
404 void MainWindow::closeEvent(QCloseEvent* e)
405 {
406  QMessageBox::StandardButton ret = QMessageBox::question(this, tr("Question"), tr("Are you sure you want to exit?"),
407  QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
408  if (ret == QMessageBox::Yes) {
409  QApplication::exit(0);
410  } else {
411  e->ignore();
412  }
413 }
414 
419 void MainWindow::changeEvent(QEvent* e)
420 {
421  QMainWindow::changeEvent(e);
422 }
423 
425 
431 AspectStatus checkAspectRatio(const QSize& frameSize, const QSize& gridSize)
432 {
433  float aspectRoom = float(gridSize.height()) / float(gridSize.width());
434  float aspectFrame = float(frameSize.height()) / float(frameSize.width());
435 
436  if (aspectRoom / aspectFrame < 0.8f) {
437  return GRID_TOO_WIDE;
438  }
439  if ( aspectFrame / aspectRoom < 0.8f) {
440  return GRID_TOO_TALL;
441  }
442  return GRID_OK;
443 }
444 
445 
450 void MainWindow::resizeEvent(QResizeEvent* /* e */ )
451 {
452  if (ui->frmRoom->size().width() < 100 || ui->frmRoom->size().height() < 100) {
453  return;
454  }
455 
456  const Room* room = serverApp->getCurrentRoom();
457  QSize newGridSize = room->gridSize;
458  const QSize& clientSize = room->clientSize;
459 
460  // Check if any frames have been moved beyond the room dimensions
461  for (auto * frame : _clientFrames) {
462  QPoint bounds = frame->getGridPosition();
463  while (bounds.x() + clientSize.width() > newGridSize.width()) {
464  newGridSize += QSize(1, 0);
465  }
466  while (bounds.y() + clientSize.height() > newGridSize.height()) {
467  newGridSize += QSize(0, 1);
468  }
469  }
470 
471  /* do we have to add virtual columns or rows? */
472  AspectStatus status;
473  do {
474  status = checkAspectRatio(ui->frmRoom->size(), newGridSize);
475  if (status == GRID_TOO_WIDE) {
476  /* add row */
477  newGridSize += QSize(0, 1);
478  }
479  if (status == GRID_TOO_TALL) {
480  /* add column */
481  newGridSize += QSize(1, 0);
482  }
483  } while (status != GRID_OK);
484  this->_tilesX = newGridSize.width();
485  this->_tilesY = newGridSize.height();
486  const int maxX = _tilesX - clientSize.width();
487  const int maxY = _tilesY - clientSize.height();
488 
489  /* Bring back frames which are now out of the screen */
490  for (auto * frame : _clientFrames) {
491  const QPoint gp = frame->getGridPosition();
492  if ( gp.x() > maxX || gp.y() > maxY ) {
493  placeFrameInFreeSlot(frame, frame->pos());
494  }
495  }
496 
497  /* Resize all connection windows */
498  for (auto * frame : _clientFrames) {
499  frame->updateGeometry();
500  }
501 
502  /* update background image label */
503  if (_backgroundImage != nullptr) {
504  int w = ui->frmRoom->width() - 5; /* to make sure that we don't enlarge the window */
505  int h = ui->frmRoom->height() - 5;
506  ui->imageLabel->hide();
507  ui->imageLabel->setScaledContents(true);
508  ui->imageLabel->setPixmap(QPixmap::fromImage(*_backgroundImage).scaled(w, h, Qt::IgnoreAspectRatio));
509  ui->imageLabel->show();
510  } else {
511  ui->imageLabel->clear();
512  }
513 }
514 
516 {
517  if (_buttonLockTimer->isActive()) // If buttons are disabled for cooldown, don't do anything
518  return;
519  const ConnectionFrame *selected = getSelectedFrame();
520  const ConnectionFrame *tutor = getTutorFrame();
521  const bool somethingSelected = (selected != nullptr);
522  const bool selectedOnline = (selected != nullptr && selected->client() != nullptr);
523  const bool tutorOnline = (tutor != nullptr && tutor->client() != nullptr);
524  // Deletion only for offline clients, tutor if anything is selected, locking only for online clients
525  ui->action_DeleteClient->setEnabled(somethingSelected && !selectedOnline);
526  ui->action_SetAsTutor->setEnabled(somethingSelected);
527  ui->action_LockSingle->setEnabled(selectedOnline && selected != tutor);
528  // Don't allow projection to self
529  ui->action_StudentToTutorExclusive->setEnabled(selectedOnline && tutorOnline && selected != tutor);
530  ui->action_StudentToTutor->setEnabled(selectedOnline && tutorOnline && selected != tutor);
531  ui->action_TutorToStudent->setEnabled(selectedOnline && tutorOnline && selected != tutor);
532  // Only allow tutor broadcast if they're online
533  ui->action_TutorToAll->setEnabled(tutorOnline);
534 }
535 
542 void MainWindow::mouseReleaseEvent(QMouseEvent* e)
543 {
544  // Try to figure out if the user clicked inside the "room" frame
545  // No idea if there is a more elegant way to do this without
546  // sub-classing that frame and overriding its mouseReleaseEvent
547  const QPoint pos(ui->frmRoom->mapFrom(this, e->pos()));
548  if (pos.x() < 0 || pos.y() < 0)
549  return;
550  const QSize frame(ui->frmRoom->size());
551  if (frame.width() > pos.x() && frame.height() > pos.y()) {
552  if (getSelectedFrame() != nullptr) {
553  getSelectedFrame()->setSelection(false);
555  }
556  }
557 }
558 
563 {
564  _mode = Mode::None;
565 
566  // Stop server (Clients get stopped on ACK)
567  if (getClientFromId(_streamingSource) != nullptr) {
569  }
570 
571  // Unlock all clients
572  for (auto * frame : _clientFrames) {
573  if (frame->client() != nullptr) {
574  frame->client()->lockScreen(lock);
575  frame->client()->removeAttention();
576  }
577  }
578 
579 }
580 
581 /*
582  * Slots
583  */
584 
586 {
587  // Get where the frame would be placed, and show a blue shadow in that spot
588  QPoint slot = closestFreeSlot(connectionFrame->pos(), connectionFrame);
589  _dropMarker->setGeometry(slot.x() * getTileWidthPx(), slot.y() * getTileHeightPx(), connectionFrame->width(), connectionFrame->height());
590  if (!_dropMarker->isVisible()) {
591  _dropMarker->lower();
592  ui->imageLabel->lower();
593  _dropMarker->show();
594  }
595 }
596 
602 {
603  _dropMarker->hide();
604  const QPoint preferredPixels = connectionFrame->pos();
605  placeFrameInFreeSlot(connectionFrame, preferredPixels);
606 }
607 
613 {
614  _dropMarker->hide();
615  ConnectionFrame *current = getSelectedFrame();
616  // If same frame is clicked again, do nothing
617  if (current == frame)
618  return;
619 
620  // If another frame has been selected, unselect it
621  // Set the ui selected and set a new reference
622  if (current != nullptr) {
623  current->setSelection(false);
624  }
625  frame->setSelection(true);
627 }
628 
633 {
634  _sessionNameWindow->show((serverApp->sessionName()));
635 }
636 
641 {
642  _sessionNameLabel->setText(tr("Session Name: %1 [click to edit]").arg(serverApp->sessionName()));
643  bool haveAdditionalClient = false;
644  for (QMutableListIterator<ConnectionFrame*> it(_clientFrames); it.hasNext(); ) {
645  if (!it.next()->isFromRoomplan()) {
646  haveAdditionalClient = true;
647  break;
648  }
649  }
650  if (!haveAdditionalClient)
651  return; // No additional client exists, don't ask about reset
652  // Current layout contains additional clients (voa session name), ask to delete
653  QMessageBox::StandardButton ret = QMessageBox::question(this,
654  tr("Question"), tr("Do you want to delete and disconnect any clients\n"
655  "not belonging to the current room layout?"),
656  QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
657  if (ret != QMessageBox::Yes)
658  return;
659  // Stop all projections and clear all clients, which were connected to old sessionName.
660  reset();
661  for (QMutableListIterator<ConnectionFrame*> it(_clientFrames); it.hasNext(); ) {
662  ConnectionFrame *cf = it.next();
663  if (!cf->isFromRoomplan()) {
664  cf->hide();
665  cf->deleteLater();
666  it.remove();
667  }
668  }
669 }
670 
676 {
678  Client* ns = getClientFromId(from);
679 
680  // if there is a server running which is not "from" stop it.
681  if (os != nullptr && _streamingSource != from)
682  os->stopVncServer();
683 
684  // Set new streaming source
685  _streamingSource = from;
686 
687  // If streaming source is already active avoid a restart
688  if (ns != nullptr) {
689  if (ns->isActiveVncServer()) {
690  this->onVncServerStateChange(ns);
691  } else { // Could not take shortcut, (re)start VNC server on source
692  ns->startVncServer();
693  }
694  ns->removeAttention();
695  }
696 }
697 
699 {
700  connect(_reloadWindow->buttonBox(), &QDialogButtonBox::accepted, this, &MainWindow::onReloadRoomOk);
701  connect(_reloadWindow->buttonBox(), &QDialogButtonBox::rejected, this, &MainWindow::onReloadRoomCancel);
702  QList<QString> keyList = serverApp->getRooms().keys();
703  for (const auto & it : keyList) {
704  _reloadWindow->addRoom(it);
705  }
706  _reloadWindow->show();
707 }
708 
710 {
711  disconnect(_reloadWindow->buttonBox(), &QDialogButtonBox::accepted, this, &MainWindow::onReloadRoomOk);
712  disconnect(_reloadWindow->buttonBox(), &QDialogButtonBox::rejected, this, &MainWindow::onReloadRoomCancel);
714  _reloadWindow->hide();
715 }
716 
718 {
719  /* delete old image */
720  delete _backgroundImage;
721  _backgroundImage = nullptr;
722 
723  const Room *room = serverApp->getCurrentRoom();
724  if (room != nullptr) {
725  /* set tiles */
726  _tilesX = room->gridSize.width();
727  _tilesY = room->gridSize.height();
728 
729 
730  /* place connection frames */
731  for (auto it = room->clientPositions.begin(); it != room->clientPositions.end(); ++it) {
732  const QString& computerId = it.key();
733  const QPoint& pos = it.value();
734 
735  ConnectionFrame *cf = createFrame(computerId, &pos, true);
736  onFrameDropped(cf);
737  if (computerId == room->tutorIP) {
738  qDebug() << "set computer with id " << computerId << " as tutor per configuration";
739  if (getTutorFrame() != nullptr) {
740  getTutorFrame()->setTutor(false);
741  }
742  cf->setTutor(true);
743  }
744  }
745 
746  /* load background image */
747  QString imgPath = room->imagePath;
748  qDebug() << "imgPath is " << imgPath;
749 
750  if (imgPath != "") {
751  qDebug() << "set background image path: " << imgPath;
752  if (imgPath.endsWith("svg")) {
753  /* render once with maximal screen size */
754  QSize s = QGuiApplication::screenAt(geometry().center())->size();
755  QSvgRenderer renderer(imgPath);
756  _backgroundImage = new QImage(s, QImage::Format_ARGB32);
757  _backgroundImage->fill(Qt::lightGray); /* background color */
758  QPainter painter(_backgroundImage);
759  renderer.render(&painter);
760  } else {
761  _backgroundImage = new QImage();
762  _backgroundImage->load(imgPath);
763  }
764  }
765  }
766 
767  /* and force a resize event (this scales the image) */
768  resizeEvent(nullptr);
770 }
771 
773 {
774  QString roomToReload = _reloadWindow->currentRoom();
775  if (roomToReload.isEmpty()) {
776  QMessageBox::critical(this, "Warning", tr("No item selected, please select room!"), 0, 1);
777  return;
778  }
779  QMessageBox::StandardButton ret = QMessageBox::question(this, tr("Warning"),
780  tr("Are you sure you want to reload the room?\n"
781  "Note that all clients will be deleted."),
782  QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
783  if (ret == QMessageBox::Yes) {
784  disconnect(_reloadWindow->buttonBox(), &QDialogButtonBox::accepted, this, &MainWindow::onReloadRoomOk);
785  disconnect(_reloadWindow->buttonBox(), &QDialogButtonBox::rejected, this, &MainWindow::onReloadRoomCancel);
786  // Delete all clients.
787  for (auto * frame : _clientFrames) {
788  frame->hide();
789  frame->deleteLater();
790  }
791  _clientFrames.clear();
792 
793  // Load new room configuration.
794  serverApp->setCurrentRoom(roomToReload);
796 
798  _reloadWindow->hide();
799 
800  }
801 }
802 
804 {
805 
806  return ui->frmRoom->size().width() / _tilesX;
807 }
808 
810 {
811  return ui->frmRoom->size().height() / _tilesY;
812 }
813 
815 {
816  const QPoint pos = frame->getGridPosition();
817  const QSize& clientSize = serverApp->getCurrentRoom()->clientSize;
818  const int w = getTileWidthPx();
819  const int h = getTileHeightPx();
820  return QRect(pos.x() * w, pos.y() * h, w * clientSize.width(), h * clientSize.height());
821 }
822 
827 {
828  _helpWindow->show();
829 }
830 
837 {
838  ui->action_Lock->setChecked(false);
839 
840  if (getTutorFrame() == nullptr)
841  QMessageBox::critical(this, tr("Projection"), sStrTutorNdef);
842  else if (getTutorFrame()->client() == nullptr)
843  QMessageBox::critical(this, tr("Projection"), sStrTutorOffline);
844  else if (_clientFrames.size() == 1)
845  QMessageBox::critical(this, tr("Projection"), sStrNoDestAv);
846  else {
847  // If this mode is already active, reset everything
848  if (_mode == Mode::Broadcast) {
849  reset();
850  return;
851  }
852 
853  // Set all clients as watchers of tutor. Except for the tutors desired source, which hase to be none
854  for (auto * frame : _clientFrames)
855  if (frame->client() != nullptr)
856  frame->client()->setDesiredProjectionSource(frame->client() == getTutorFrame()->client() ? NO_SOURCE : getTutorFrame()->client()->id());
857 
858  disableButtons();
860  startVncServerIfNecessary(getTutorFrame()->client()->id());
861  }
862 }
863 
869 {
870  ui->action_Lock->setChecked(false);
871 
872  if (getSelectedFrame() == nullptr)
873  QMessageBox::critical(this, tr("Projection"), sStrDestNdef);
874  else if (getTutorFrame() == nullptr)
875  QMessageBox::critical(this, tr("Projection"), sStrTutorNdef);
876  else if (getSelectedFrame() == getTutorFrame())
877  QMessageBox::critical(this, tr("Projection"), sStrSourceDestSame);
878  else if (getSelectedFrame()->client() == nullptr)
879  QMessageBox::critical(this, tr("Projection"), sStrDestOffline);
880  else if (getTutorFrame()->client() == nullptr)
881  QMessageBox::critical(this, tr("Projection"), sStrTutorOffline);
882  else {
883  auto sourceClient = getTutorFrame()->client();
884  auto destinationClient = getSelectedFrame()->client();
885  // If this is the first call in this mode clear the watchers
886  if (_mode != Mode::Multicast) {
887  for (auto c : _clientFrames) {
888  if (c->client() != nullptr) {
889  c->client()->stopVncClient();
890  }
891  }
892  }
893 
894  // If "to" already watches "from" stop it
895  if (destinationClient->desiredProjectionSource() == sourceClient->id() ) {
896  destinationClient->stopVncClient();
897  } else {
898  destinationClient->setDesiredProjectionSource(sourceClient->id());
899  }
900 
901  disableButtons();
902  int numClients = 0;
903  for (auto * c : _clientFrames) {
904  if (c->client() != nullptr && c->client()->desiredProjectionSource() == sourceClient->id()) {
905  numClients++;
906  }
907  }
908  if (numClients == 0) {
909  _mode = Mode::None;
910  sourceClient->stopVncServer();
911  } else {
913  startVncServerIfNecessary(sourceClient->id());
914  }
915  }
916 }
917 
922 {
923  this->vncOneOnOne(false);
924 }
925 
926 
931 {
932  this->vncOneOnOne(true);
933 }
934 
935 void MainWindow::vncOneOnOne(bool exclusive)
936 {
937  if (getSelectedFrame() == nullptr) {
938  QMessageBox::critical(this, tr("Projection"), sStrSourceNdef);
939  } else if (getTutorFrame() == nullptr) {
940  QMessageBox::critical(this, tr("Projection"), sStrTutorNdef);
941  } else if (getTutorFrame() == getSelectedFrame()) {
942  QMessageBox::critical(this, tr("Projection"), sStrSourceDestSame);
943  } else if (getSelectedFrame()->client() == nullptr) {
944  QMessageBox::critical(this, tr("Projection"), sStrSourceOffline);
945  } else if (getTutorFrame()->client() == nullptr) {
946  QMessageBox::critical(this, tr("Projection"), sStrTutorOffline);
947  } else {
948  const bool wasLocked = ui->action_Lock->isChecked();
949  ui->action_Lock->setChecked(false);
950  Client *source = getSelectedFrame()->client();
951  Client *dest = getTutorFrame()->client();
952  const Mode mode = exclusive ? Mode::LockedUnicast : Mode::Unicast;
953 
954  if (_mode == mode && source->id() == dest->desiredProjectionSource()) {
955  // Button clicked again with same selection, treat this as reset
956  reset();
957  return;
958  }
959 
960  for (ConnectionFrame* f : _clientFrames) {
961  if (f->client() == nullptr) // Disconnected frame
962  continue;
963  if (source != f->client() && dest != f->client()) { //Not source or destination
964  f->client()->setDesiredProjectionSource(NO_SOURCE);
965  if (_mode == Mode::Unicast) { // Already in unlocked unicast mode
966  if (exclusive) { // Only change lock state (to locked) if switching to locked unicast
967  f->client()->lockScreen(true); // (don't change otherwise, would reset individually locked clients)
968  }
969  } else if (wasLocked) { // Was in "lock all" mode, update lock mode for everyone
970  f->client()->lockScreen(exclusive);
971  }
972  }
973  }
974  dest->lockScreen(false);
975  source->lockScreen(false);
976  dest->setDesiredProjectionSource(source->id());
978  disableButtons();
979  _mode = mode;
980  startVncServerIfNecessary(source->id());
981  }
982 }
983 
990 {
991  ui->action_Lock->setChecked(false);
992  reset();
993 }
994 
1001 void MainWindow::onButtonLock(bool checked)
1002 {
1003  // Stop all projections and lock if requested
1004  reset(checked);
1005 }
1006 
1008 {
1009  // If no frame is selected, warning.
1010  if (getSelectedFrame() == nullptr) {
1011  QMessageBox::critical(this, tr("Selection"), tr("No client is selected."));
1012  return;
1013  }
1014  Client *client = getSelectedFrame()->client();
1015 
1016  // If frame of inactive client has been selected unselect it
1017  if (client == nullptr) {
1018  QMessageBox::critical(this, tr("Selection"), tr("The selected client is not connected."));
1019  return;
1020  } else { // If selected client is locked, first unlock
1021  bool newState = !client->isLocked();
1022  client->lockScreen(newState);
1023  if (!newState) {
1024  // If no more clients are locked, reset button
1025  for (auto * frame : _clientFrames) {
1026  if (frame->client() == nullptr)
1027  continue;
1028  if (frame->client()->isLocked())
1029  return;
1030  }
1031  ui->action_Lock->setChecked(false);
1032  }
1033  }
1034 }
1035 
1040 {
1041  this->close();
1042 }
1043 
1049 {
1050  ConnectionFrame *selected = getSelectedFrame();
1051  ConnectionFrame *tutor = getTutorFrame();
1052 
1053  if (selected == nullptr) {
1054  QMessageBox::critical(this, tr("Selection"), tr("No client is selected."));
1055  return;
1056  }
1057 
1058  // Unlock client about to become a tutor, just in case
1059  if (selected->client() != nullptr) {
1060  selected->client()->lockScreen(false);
1061  }
1062  // If same frame is already tutor, do nothing
1063  if (selected == tutor)
1064  return;
1065  // Else unset the old and set the new tutor
1066  if (tutor != nullptr) {
1067  tutor->setTutor(false);
1068  }
1069  selected->setTutor(true);
1071 }
1072 
1078 {
1080  connect(client, &Client::authenticated, this, &MainWindow::onClientAuthenticated);
1081 }
1082 
1094 {
1095  disconnect(client, &Client::authenticating, this, &MainWindow::onClientAuthenticating);
1096  /* copy examMode */
1097  client->setExamMode(request->examMode);
1098 
1099  if (!request->accept) // Another receiver of that signal already rejected the client, so nothing to do
1100  return;
1101  bool inuse;
1102  QString check = request->name;
1103  int addnum = 1;
1104  do {
1105  inuse = false;
1106  for (auto * frame : _clientFrames) {
1107  Client *c = frame->client();
1108  if (c == nullptr)
1109  continue;
1110  if (!c->isAuthed())
1111  continue;
1112  if (c->ip() == request->ip) {
1113  request->accept = false;
1114  return;
1115  }
1116  if (c->name() == check) {
1117  inuse = true;
1118  check = request->name + " (" + QString::number(++addnum) + ")";
1119  break;
1120  }
1121  }
1122  } while (inuse && addnum < 100);
1123  if (inuse) {
1124  request->accept = false;
1125  } else {
1126  request->name = check;
1127  }
1128 }
1129 
1141 {
1142  disconnect(client, &Client::authenticated, this, &MainWindow::onClientAuthenticated);
1145  ConnectionFrame *existing = nullptr;
1146  ConnectionFrame *cf = nullptr;
1147  for (auto * frame : _clientFrames) {
1148  if (frame->computerId() == client->ip()) {
1149  existing = frame;
1150  }
1151  }
1152 
1153  // Clients ip already exists, but was not active.
1154  if (existing != nullptr) {
1155  cf = existing;
1156  } else {
1157  cf = createFrame();
1158  }
1159 
1160  cf->assignClient(client);
1161  connect(client, &Client::disconnected, this, &MainWindow::clientCountChanged);
1164 }
1165 
1173 {
1174  if (client == getClientFromId(_streamingSource)) {
1175  enableButtons();
1176  }
1177 
1178  if (client->isActiveVncServer()) {
1179  // apply the desired projection sources
1180  for (ConnectionFrame *frame : _clientFrames) {
1181  if (frame->client() == nullptr) // Ignore offline clients
1182  continue;
1183  if (frame->client()->desiredProjectionSource() == client->id()) {
1184  frame->client()->startVncClient(client);
1185  client->lockScreen(false);
1186  } else {
1187  frame->client()->lockScreen(frame->client()->desiredProjectionSource() == NO_SOURCE && _mode == Mode::LockedUnicast);
1188  }
1189  }
1190  // Dont forget to unlock the vnc server
1191  client->lockScreen(false);
1192  } else {
1193  // VNC server stopped on some client or failed to start - reset local pending projection information
1194  for (auto * frame : _clientFrames) {
1195  if (frame->client() != nullptr) {
1196  if (frame->client()->desiredProjectionSource() == client->id()) {
1197  frame->client()->setDesiredProjectionSource(NO_SOURCE);
1198  frame->client()->stopVncClient();
1199  if (_mode == Mode::LockedUnicast)
1200  frame->client()->lockScreen(true);
1201  }
1202  }
1203  }
1204  // Dont forget to unlock the vnc server (if necesarry)
1206  || ui->action_Lock->isChecked());
1207 
1208  // If this was the current source remember that there is no source anymore and reset mode
1209  if (client == getClientFromId(_streamingSource)) {
1211  _mode = Mode::None;
1212  }
1213  }
1214 }
1215 
1223 {
1224  if (client != nullptr) {
1225  // VNC Client stopped -> remove from watchers
1226  if (!client->isActiveVncClient()) {
1227  // Only unset a desired Projection source if it has not changed meanwhile
1228  if (client->projectionSource() == client->desiredProjectionSource())
1230 
1231  /*
1232  * If nobody is watching the VNC server of the pvsclient that
1233  * stopped its vncclient stop it. (The _LAST_ vncserver, there
1234  * may be a new one)
1235  * This is necessary for the race condition when a server
1236  * is stopped and another started at the same time, since the new
1237  * server would be killed if all client disconnect before any of
1238  * the new connect.
1239  */
1240  bool serverHasWatchers = false;
1241  for (auto * frame : _clientFrames)
1242  if (frame->client() != nullptr)
1243  if (frame->client()->desiredProjectionSource() == client->projectionSource()) {
1244  serverHasWatchers = true;
1245  break;
1246  }
1247 
1248  if (!serverHasWatchers) {
1249  Client* c = getClientFromId(client->projectionSource());
1250  if (c != nullptr)
1251  c->stopVncServer();
1252  }
1253  }
1254  }
1255 }
1256 
1261 {
1262  _buttonLockTimer->start();
1263  for (QAction *a : _lockingButtons) {
1264  a->setDisabled(true);
1265  }
1266 }
1267 
1272 {
1273  _buttonLockTimer->stop();
1274  for (QAction *a : _lockingButtons) {
1275  a->setEnabled(true);
1276  }
1277  updateContextButtonStates(); // In case user changed selection while buttons were disabled
1278 }
1279 
1280 
1282 {
1283  // If no frame is selected, warning.
1284  ConnectionFrame* frame = getSelectedFrame();
1285  if (frame == nullptr) {
1286  QMessageBox::critical(this, tr("Selection"), tr("No client is selected."));
1287  return;
1288  }
1289  if (frame->client() != nullptr) {
1290  QMessageBox::critical(this, tr("Selection"), tr("This client is still connected."));
1291  return;
1292  }
1293  QMessageBox::StandardButton ret = QMessageBox::question(this, tr("Warning"), tr("Are you sure you want to delete the selected client?"),
1294  QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
1295  if (ret == QMessageBox::Yes) {
1296  frame->hide();
1297  frame->deleteLater();
1298  _clientFrames.removeAll(frame);
1300  return;
1301  }
1302 }
void clicked()
void setExamMode(bool mode)
Definition: client.h:49
QRect calcFrameGeometry(ConnectionFrame *frame) const
Definition: mainwindow.cpp:814
int id() const
Definition: client.h:36
MainWindow(QWidget *parent=nullptr)
Initialize MainWindow and ListenServer.
Definition: mainwindow.cpp:57
QSize clientSize
Definition: serverapp.h:19
#define CLIENT_PORT
Definition: settings.h:6
void enableButtons()
EnableButtons.
bool isFromRoomplan() const
bool isLocked() const
Definition: client.h:39
HelpWindow * _helpWindow
Definition: mainwindow.h:43
void onReloadRoomCancel()
Definition: mainwindow.cpp:709
void tellClientCurrentSituation(Client *client)
Tells the new client the current situation in class room.
Definition: mainwindow.cpp:334
void updateSessionName()
void reset(bool lock=false)
reset
Definition: mainwindow.cpp:562
QTimer * _buttonLockTimer
Definition: mainwindow.h:52
void authenticating(Client *client, ClientLogin *request)
Ui::MainWindow * ui
Definition: mainwindow.h:41
void authenticated(Client *client)
static const qint64 BUTTON_BLOCK_TIME
Definition: mainwindow.h:54
QString ip() const
Definition: client.cpp:349
void mouseReleaseEvent(QMouseEvent *e) override
Handle Mouse Release Event.
Definition: mainwindow.cpp:542
void onFrameDropped(ConnectionFrame *frame)
Place frame to from user specified position.
Definition: mainwindow.cpp:601
QPoint closestFreeSlot(const QPoint &preferredPixels, const ConnectionFrame *toIgnore)
: find the closest available frame.
Definition: mainwindow.cpp:232
void setTutor(bool b)
Set tutor status of frame.
void onFrameMoving(ConnectionFrame *frame)
Definition: mainwindow.cpp:585
Client * client() const
void allowSaverAndStandby(bool allow)
Whether we want to allow the screen saver to activate or the screen to enter standby.
Definition: screensaver.cpp:46
QDialogButtonBox * buttonBox() const
void clientCountChanged()
this function determines if the number of clients in exam mode comprise more than 50%...
Definition: mainwindow.cpp:160
QString imagePath
Definition: serverapp.h:20
ClickLabel * _sessionNameLabel
Definition: mainwindow.h:45
ConnectionFrame * createFrame(const QString &computerId=QString(), const QPoint *gridPosition=nullptr, bool fromRoomplan=false)
Create new Frame.
Definition: mainwindow.cpp:312
#define serverApp
Definition: serverapp.h:31
QString ip
Definition: client.h:18
int getTileHeightPx() const
Definition: mainwindow.cpp:809
void setGridPosition(int x, int y)
SessionNameWindow * _sessionNameWindow
Definition: mainwindow.h:42
void onReloadRoomOk()
Definition: mainwindow.cpp:772
int _lastClientCount
Definition: mainwindow.h:70
#define sStrSourceNdef
Definition: mainwindow.cpp:45
QImage * _backgroundImage
Definition: mainwindow.h:48
void changeEvent(QEvent *e) override
Change Event.
Definition: mainwindow.cpp:419
void disconnected()
int _tilesX
Definition: mainwindow.h:46
QSize gridSize
Definition: serverapp.h:18
void disableButtons()
DisableButtons.
void startVncServer()
Definition: client.cpp:264
QString tutorIP
Definition: serverapp.h:21
QString currentRoom() const
void placeFrameInFreeSlot(ConnectionFrame *frame, QPoint preferred=QPoint(-1,-1))
Definition: mainwindow.cpp:300
void onVncClientStateChange(Client *client)
Handle VNC client state change.
void onClientAuthenticated(Client *client)
New client was authenticated, make several checks.
void onButtonExit()
On button exit, close application.
int getTileWidthPx() const
Definition: mainwindow.cpp:803
QMap< QString, QPoint > clientPositions
Definition: serverapp.h:16
QWidget * _dropMarker
Definition: mainwindow.h:68
ReloadRoomWindow * _reloadWindow
Definition: mainwindow.h:44
void closeEvent(QCloseEvent *e) override
Handle closeEvent.
Definition: mainwindow.cpp:404
#define NO_SOURCE
Definition: client.h:12
int _streamingSource
Definition: mainwindow.h:65
void onButtonTutorToAll()
Handle projection from tutor to all.
Definition: mainwindow.cpp:836
QLabel * _examModeLabel
Definition: mainwindow.h:49
bool examMode
Definition: client.h:19
#define sStrNoDestAv
Definition: mainwindow.cpp:50
void startVncClient(const Client *to)
Definition: client.cpp:280
QList< QAction * > _lockingButtons
Definition: mainwindow.h:53
void onButtonStudentToTutor()
Handle projection from one student to tutor.
Definition: mainwindow.cpp:921
void stopVncServer()
Definition: client.cpp:272
ConnectionFrame * getSelectedFrame()
Return the Frame, which is currently selected by user.
Definition: mainwindow.cpp:386
void show(const QString &name)
const QPoint & getGridPosition() const
void onVncServerStateChange(Client *client)
Handle if VNC Server State has changed.
void setDesiredProjectionSource(int id)
Definition: client.h:48
#define sStrSourceOffline
Definition: mainwindow.cpp:46
void onButtonStudentToTutorExclusive()
Handle projection from one student to tutor, lock everyone else.
Definition: mainwindow.cpp:930
bool isActiveVncClient() const
Definition: client.h:37
Definition: client.h:23
#define sStrDestOffline
Definition: mainwindow.cpp:48
~MainWindow() override
Definition: mainwindow.cpp:204
void removeAttention()
Definition: client.h:44
void onSessionNameUpdate()
Update session name.
Definition: mainwindow.cpp:640
void addRoom(const QString &roomName)
const QString & name() const
Definition: client.h:33
int projectionSource() const
Definition: client.h:41
Class for clickable part on sessionName.
Definition: clicklabel.h:9
void onButtonLockSingle()
#define sStrDestNdef
Definition: mainwindow.cpp:47
void setSelection(bool selected)
Set frame as selected or not.
#define sStrTutorOffline
Definition: mainwindow.cpp:44
static int distance(QPoint a, QPoint b)
Squared euclidean distance (why is this not implemented in QPoint?)
Definition: mainwindow.cpp:211
bool isActiveVncServer() const
Definition: client.h:38
void vncOneOnOne(bool exclusive)
Definition: mainwindow.cpp:935
void onDeleteClient()
ConnectionFrame * getTutorFrame()
Return the Frame, which is currently beeing Tutor.
Definition: mainwindow.cpp:371
void resizeEvent(QResizeEvent *e) override
Resize event.
Definition: mainwindow.cpp:450
bool accept
Definition: client.h:15
void reloadCurrentRoom()
Definition: mainwindow.cpp:717
void frameMoved(ConnectionFrame *frame)
void onSessionNameClick()
Show session name, after it was clicked on.
Definition: mainwindow.cpp:632
void startVncServerIfNecessary(int from)
MainWindow::startVncServerIfNecessary.
Definition: mainwindow.cpp:675
void vncClientStateChange(Client *client)
void onButtonSetAsTutor()
Handle button to set specific client as tutor.
#define sStrTutorNdef
Definition: mainwindow.cpp:43
int _tilesY
Definition: mainwindow.h:47
void updateContextButtonStates()
Definition: mainwindow.cpp:515
AspectStatus checkAspectRatio(const QSize &frameSize, const QSize &gridSize)
check the difference in the aspect ratio between the frame size and the grid size.
Definition: mainwindow.cpp:431
Class for representing the clients of current session, with a specific frame displaying username and ...
void clicked(ConnectionFrame *frame)
bool isAuthed() const
Definition: client.h:32
void vncServerStateChange(Client *client)
void onClientConnected(Client *client)
Handle from ListenServer signaled new client connection.
Class for listing on new client connection.
Definition: listenserver.h:13
QString name
Definition: client.h:16
AspectStatus
Definition: mainwindow.cpp:424
void lockScreen(bool)
Definition: client.cpp:320
void onFrameClicked(ConnectionFrame *frame)
Mark given frame after it was clicked on.
Definition: mainwindow.cpp:612
Client * getClientFromId(int id)
Returns connected client which belongs to given id.
Definition: mainwindow.cpp:354
#define sStrSourceDestSame
Definition: mainwindow.cpp:49
void assignClient(Client *client)
Assign client to connectionFrame.
void onClientAuthenticating(Client *client, ClientLogin *request)
Authenticate new Client client.
void newClient(Client *client)
void stopVncClient()
Definition: client.cpp:297
void forceUnlockAndScreenOn()
Disable the screen saver (only if password locking is disabled!), power screen on if it's in standby...
Definition: screensaver.cpp:70
static QIcon * lock
int desiredProjectionSource() const
Definition: client.h:40
enum MainWindow::Mode _mode
int isExamMode() const
Definition: client.h:42
Definition: room.h:6
void onButtonStopProjection()
Handle Button StopProjection.
Definition: mainwindow.cpp:989
void onButtonLock(bool checked)
Handle button to lock or unlock screens of client(s).
Initializing MainWindow.
Definition: mainwindow.h:27
QList< ConnectionFrame * > _clientFrames
Definition: mainwindow.h:67
void frameMoving(ConnectionFrame *frame)
void onButtonHelp()
Display popup which explains possible actions about the buttons.
Definition: mainwindow.cpp:826
void onButtonTutorToStudent()
Handle the projection from Tutor to specific student.
Definition: mainwindow.cpp:868
void onButtonReloadRoomConfig()
Definition: mainwindow.cpp:698
#define GRID(X, Y)