From 9d73e385153656ef998cc8f6378bb01ff36b2090 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Wed, 2 Nov 2016 19:24:07 +0100 Subject: [server] Rewrite positioning logic of connection frames This fixes sevceral bugs: * Frames moved into virtually expanded area (for keeping aspect ratio) could be out of bounds after a window resize before * Finding a free slot to place a frame was slightly sped up * Finding a free slot is not used when loading a room layout, as it was (still is) pretty sluggish for the user * Snap to grid worked incorrectly, did not pick closest grid position --- src/server/connectionframe/connectionframe.cpp | 45 ++++--- src/server/connectionframe/connectionframe.h | 26 ++-- src/server/mainwindow/mainwindow.cpp | 162 ++++++++++++++----------- src/server/mainwindow/mainwindow.h | 8 +- 4 files changed, 137 insertions(+), 104 deletions(-) diff --git a/src/server/connectionframe/connectionframe.cpp b/src/server/connectionframe/connectionframe.cpp index 90d72da..205433b 100644 --- a/src/server/connectionframe/connectionframe.cpp +++ b/src/server/connectionframe/connectionframe.cpp @@ -15,6 +15,7 @@ #include "connectionframe.h" +#include "../mainwindow/mainwindow.h" #include "../net/client.h" #include #include @@ -59,14 +60,17 @@ static QString style_disconnected( static QIcon *term = NULL, *cam = NULL, *eye = NULL, *lock = NULL; +bool ConnectionFrame::paintDisabled = false; + /** * Initialize frame for connected client. * @param parent * @param width * @param height */ -ConnectionFrame::ConnectionFrame(QWidget *parent, int width, int height) : - QGroupBox(parent), _client(NULL), _timerId(0), _timerCounter(0), _isSelected(false), _isTutor(false) +ConnectionFrame::ConnectionFrame(MainWindow* main, QWidget *parent) : + QGroupBox(parent), _client(NULL), _timerId(0), _timerCounter(0), _isSelected(false), _isTutor(false), + _mainWindow(main) { //defines the ui-stuff @@ -116,7 +120,6 @@ ConnectionFrame::ConnectionFrame(QWidget *parent, int width, int height) : _mainLayout->addWidget(_lblUserName); _mainLayout->addWidget(_lblHostName); this->setLayout(_mainLayout); - this->setSize(width, height); this->updateAppearance(); this->updateLabels(); } @@ -129,6 +132,25 @@ ConnectionFrame::~ConnectionFrame() _iconLayout->deleteLater(); } +void ConnectionFrame::setGridPosition(const QPoint& pos) +{ + _gridPosition = pos; + updateGeometry(); +} + +void ConnectionFrame::setGridPosition(int x, int y) +{ + setGridPosition(QPoint(x, y)); +} + +void ConnectionFrame::updateGeometry() +{ + const QRect rect = _mainWindow->calcFrameGeometry(this); + setGeometry(rect); + if (this->_client == NULL) + showDefaultThumb(); +} + /** * Add icon to connection frame. * @param icon @@ -144,18 +166,6 @@ QLabel* ConnectionFrame::addIcon(const QIcon* icon) return label; } -/** - * Set size of connectionFrame. - * @param width - * @param height - */ -void ConnectionFrame::setSize(int width, int height) -{ - this->resize(width, height); - if (this->_client == NULL) - showDefaultThumb(); -} - /** * Assign client to connectionFrame. * Set hostName, userName and tutor status to display. @@ -216,7 +226,7 @@ void ConnectionFrame::mouseReleaseEvent(QMouseEvent* event) QApplication::setOverrideCursor(QCursor(Qt::OpenHandCursor)); // Only recognize a move if the distance is larger than _startDragDistance if ((this->pos() - _previousPosition).manhattanLength() > _startDragDistance ) { - emit frameMoved(true, this); + emit frameMoved(this); } else { qDebug("Clicked"); move(_previousPosition); @@ -292,6 +302,9 @@ void ConnectionFrame::mouseDoubleClickEvent(QMouseEvent* event) */ void ConnectionFrame::paintEvent(QPaintEvent *event) { + if (ConnectionFrame::paintDisabled) { + return; + } QGroupBox::paintEvent(event); if (_remoteScreen.isNull()) { return; diff --git a/src/server/connectionframe/connectionframe.h b/src/server/connectionframe/connectionframe.h index 9d86c70..2549ae8 100644 --- a/src/server/connectionframe/connectionframe.h +++ b/src/server/connectionframe/connectionframe.h @@ -3,6 +3,8 @@ #include #include "../net/client.h" +class MainWindow; + /** * Class for representing the clients of current session, with a specific frame * displaying username and hostname for each one. @@ -29,11 +31,8 @@ private: QPoint _clickPoint; QPoint _previousPosition; - QPoint _currentPosition; - QPoint _gridPosition; - Client *_client; int _timerId, _timerCounter; @@ -45,22 +44,25 @@ private: void showDefaultThumb(); QLabel* addIcon(const QIcon* icon); + MainWindow *_mainWindow; + public: - ConnectionFrame(QWidget* parent, int width, int height); + + static bool paintDisabled; + + ConnectionFrame(MainWindow* main, QWidget* parent); virtual ~ConnectionFrame(); + const inline QPoint getGridPosition() const { return _gridPosition; } + void setGridPosition(int x, int y); + void setGridPosition(const QPoint& pos); + void updateGeometry(); + const QPixmap& getFramePixmap() const { return _remoteScreen; } - void setSize(int width, int height); - const inline QPoint& getPreviousPosition() const { return _previousPosition; } void assignClient(Client *client); void setSelection(bool selected); const inline bool isSelected() const { return _isSelected; } - inline void setGridPosition(QPoint pos) { _gridPosition = pos; } - inline QPoint getGridPosition() const { return _gridPosition; }; - const inline void setCurrentPosition(QPoint position) { _currentPosition = position; } - const inline QPoint& getCurrentPosition() const { return _currentPosition; } - const QString& computerId() const { return _computerId; } void setComputerId(QString computerId) { if (_client != NULL) return; _computerId = computerId; updateLabels(); } Client* client() const { return _client; } @@ -79,7 +81,7 @@ protected: void timerEvent(QTimerEvent* event); signals: - void frameMoved(bool activateTrash, ConnectionFrame* frame); + void frameMoved(ConnectionFrame* frame); void doubleClicked(ConnectionFrame* frame); void clicked(ConnectionFrame* frame); diff --git a/src/server/mainwindow/mainwindow.cpp b/src/server/mainwindow/mainwindow.cpp index cb2e4da..87ecca4 100644 --- a/src/server/mainwindow/mainwindow.cpp +++ b/src/server/mainwindow/mainwindow.cpp @@ -217,25 +217,23 @@ MainWindow::~MainWindow() delete ui; } -/** Euclidean distance (why is this not implemented in QPoint?) */ -float distance(QPoint a, QPoint b) +/** Squared euclidean distance (why is this not implemented in QPoint?) */ +int 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); + const int dx = a.x() - b.x(); + const int dy = a.y() - b.y(); + const int sum = dx * dx + dy * dy; + return sum; } -float distance(QPointF a, QPointF b) +int 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); + const int dx = a.x() - b.x(); + const int dy = a.y() - b.y(); + const int sum = dx * dx + dy * dy; + return sum; } - - /***************************************************************************//** * \brief: find the closest available frame. * @@ -251,6 +249,14 @@ float distance(QPointF a, QPointF b) */ QPoint MainWindow::closestFreeSlot(QPoint preferredPixels, ConnectionFrame* toIgnore) { + const bool pickFirstOne = ( preferredPixels == QPoint(-1, -1) ); + if (!pickFirstOne && toIgnore != NULL) { + // Check if we're already in the desired location and skip all the checks + QPoint desired = QPoint(preferredPixels.x() / getTileWidthPx(), preferredPixels.y() / getTileHeightPx()); + if (desired == toIgnore->getGridPosition()) { + return desired; + } + } const QSize& clientSize = serverApp->getCurrentRoom()->clientSize; bool grid[_tilesX][_tilesY]; memset(grid, 0, sizeof(bool) * _tilesX * _tilesY); /* set everything to false */ @@ -260,7 +266,7 @@ QPoint MainWindow::closestFreeSlot(QPoint preferredPixels, ConnectionFrame* toIg if (*it == toIgnore) { continue; } - const QPoint& p = (*it)->getGridPosition(); + 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++) { @@ -283,18 +289,23 @@ QPoint MainWindow::closestFreeSlot(QPoint preferredPixels, ConnectionFrame* toIg } } } -endLoop: - if (isFree) { freePositions << QPoint(x, y); } +endLoop: ; + if (isFree) { + freePositions << QPoint(x, y); + if (pickFirstOne) { + goto endSearch; + } + } } } +endSearch: ; /* among all the free positions, find the closest */ - float min_distance = 10000; - QPoint bestPosition = QPoint(-1, -1); + float min_distance = 1000000; + QPoint bestPosition = QPoint(0, 0); 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); + const QPoint freePosPx( freePos.x() * getTileWidthPx(), freePos.y() * getTileHeightPx() ); + const float dist = distance(freePosPx, preferredPixels); if (dist < min_distance) { min_distance = dist; bestPosition = freePos; @@ -307,10 +318,7 @@ endLoop: 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); } /***************************************************************************//** @@ -321,14 +329,12 @@ void MainWindow::placeFrameInFreeSlot(ConnectionFrame* frame, QPoint preferredPi */ ConnectionFrame* MainWindow::createFrame() { - // Allocate and resize - int width = getTileWidthPx() * serverApp->getCurrentRoom()->clientSize.width(); - int height = getTileHeightPx() * serverApp->getCurrentRoom()->clientSize.height(); - - ConnectionFrame *cf = new ConnectionFrame(ui->frmRoom, width, height); + ConnectionFrame *cf = new ConnectionFrame(this, ui->frmRoom); + // Move to any free tile + placeFrameInFreeSlot(cf); _clientFrames.append(cf); cf->show(); - connect(cf, SIGNAL(frameMoved(bool, ConnectionFrame *)), this, SLOT(onPlaceFrame(bool, ConnectionFrame *))); + connect(cf, SIGNAL(frameMoved(ConnectionFrame *)), this, SLOT(onPlaceFrame(ConnectionFrame *))); connect(cf, SIGNAL(clicked(ConnectionFrame *)), this, SLOT(onFrameClicked(ConnectionFrame *))); return cf; } @@ -339,20 +345,14 @@ ConnectionFrame* MainWindow::createFrame() * Also connect signals frameMoved() and clicked() with slots. * @return ConnectionFrame* */ -ConnectionFrame* MainWindow::createFrame(QString computerId, QPoint pxCoord, QPoint gridPosition) +ConnectionFrame* MainWindow::createFrame(QString computerId, QPoint gridPosition) { - // Allocate and resize - const Room* room = serverApp->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); + ConnectionFrame *cf = new ConnectionFrame(this, ui->frmRoom); 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(frameMoved(ConnectionFrame *)), this, SLOT(onPlaceFrame(ConnectionFrame *))); connect(cf, SIGNAL(clicked(ConnectionFrame *)), this, SLOT(onFrameClicked(ConnectionFrame *))); return cf; } @@ -480,41 +480,54 @@ AspectStatus checkAspectRatio(const QSize& frameSize, const QSize& gridSize) */ void MainWindow::resizeEvent(QResizeEvent* e) { - const Room* room = serverApp->getCurrentRoom(); - const QSize& clientSize = room->clientSize; - - if (ui->frmRoom->size().width() < 100 || ui->frmRoom->size().height() < 100 || _tilesX <= 0 || _tilesY <= 0) { return; } + if (ui->frmRoom->size().width() < 100 || ui->frmRoom->size().height() < 100) { + return; + } + const Room* room = serverApp->getCurrentRoom(); QSize newGridSize = room->gridSize; + const QSize& clientSize = room->clientSize; - /* 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); + // Check if any frames have been moved beyond the room dimensions + for (auto it = _clientFrames.begin(); it != _clientFrames.end(); ++it) { + QPoint bounds = (*it)->getGridPosition(); + while (bounds.x() + clientSize.width() > newGridSize.width()) { + newGridSize += QSize(1, 0); + } + while (bounds.y() + clientSize.height() > newGridSize.height()) { + newGridSize += QSize(0, 1); + } } + + /* do we have to add virtual columns or rows? */ + AspectStatus status; + do { + status = checkAspectRatio(ui->frmRoom->size(), newGridSize); + if (status == GRID_TOO_WIDE) { + /* add row */ + newGridSize += QSize(0, 1); + } + if (status == GRID_TOO_TALL) { + /* add column */ + newGridSize += QSize(1, 0); + } + } while (status != GRID_OK); this->_tilesX = newGridSize.width(); this->_tilesY = newGridSize.height(); + const int maxX = _tilesX - clientSize.width(); + const int maxY = _tilesY - clientSize.height(); /* Bring back frames which 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 ) { - placeFrameInFreeSlot(*it, (*it)->getCurrentPosition()); + if ( gp.x() > maxX || gp.y() > maxY ) { + placeFrameInFreeSlot(*it, (*it)->pos()); } } /* Resize all connection windows */ for (auto 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); + (*it)->updateGeometry(); } /* update background image label */ @@ -607,13 +620,13 @@ void MainWindow::reset() * 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) +void MainWindow::onPlaceFrame(ConnectionFrame* connectionFrame) { // if (_tilesX <= 0 || _tilesY <= 0) return; - const QPoint &preferredPixels = connectionFrame->frameGeometry().center(); + const QPoint preferredPixels = connectionFrame->pos(); placeFrameInFreeSlot(connectionFrame, preferredPixels); - resizeEvent(NULL); + //resizeEvent(NULL); } /***************************************************************************//** @@ -737,11 +750,9 @@ void MainWindow::reloadCurrentRoom() 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); + ConnectionFrame *cf = createFrame(computerId, pos); + onPlaceFrame(cf); if (computerId == room->tutorIP) { qDebug() << "set computer with id " << computerId << " as tutor per configuration"; if (getTutorFrame() != NULL) { @@ -808,13 +819,24 @@ void MainWindow::onReloadRoomOk() int MainWindow::getTileWidthPx() const { + return ui->frmRoom->size().width() / _tilesX; } + int MainWindow::getTileHeightPx() const { return ui->frmRoom->size().height() / _tilesY; } +QRect MainWindow::calcFrameGeometry(ConnectionFrame* frame) const +{ + const QPoint pos = frame->getGridPosition(); + const QSize& clientSize = serverApp->getCurrentRoom()->clientSize; + const int w = getTileWidthPx(); + const int h = getTileHeightPx(); + return QRect(pos.x() * w, pos.y() * h, w * clientSize.width(), h * clientSize.height()); +} + /***************************************************************************//** * Display popup which explains possible actions about the buttons. */ @@ -1133,12 +1155,10 @@ void MainWindow::onClientAuthenticated(Client* client) // New one, create ConnectionFrame *cf = createFrame(); - // Move to any free tile - placeFrameInFreeSlot(cf); // Assign client instance cf->assignClient(client); - resizeEvent(NULL); // This is where all the positioning should be + //resizeEvent(NULL); // This is where all the positioning should be tellClientCurrentSituation(client); updateExamMode(); @@ -1276,10 +1296,6 @@ void MainWindow::onDeleteClient() lockContextButtons(); updateExamMode(); return; - } else { - frame->move(frame->getPreviousPosition()); - updateExamMode(); - return; } } diff --git a/src/server/mainwindow/mainwindow.h b/src/server/mainwindow/mainwindow.h index b0ad498..988ac50 100644 --- a/src/server/mainwindow/mainwindow.h +++ b/src/server/mainwindow/mainwindow.h @@ -30,6 +30,8 @@ public: MainWindow(QWidget *parent = 0); ~MainWindow(); + QRect calcFrameGeometry(ConnectionFrame* frame) const; + private: // Ui stuff @@ -75,9 +77,9 @@ private: DiscoveryListener *_discoveryListener; QPoint closestFreeSlot(QPoint preferredPixels, ConnectionFrame* toIgnore); - void placeFrameInFreeSlot(ConnectionFrame* frame, QPoint preferred = QPoint(0, 0)); + void placeFrameInFreeSlot(ConnectionFrame* frame, QPoint preferred = QPoint(-1, -1)); ConnectionFrame* createFrame(); - ConnectionFrame* createFrame(QString computerId, QPoint position, QPoint gridPosition); + ConnectionFrame* createFrame(QString computerId, QPoint gridPosition); void savePosition(ConnectionFrame *cf); void startVncServerIfNecessary(int from); void tellClientCurrentSituation(Client* client); @@ -122,7 +124,7 @@ protected slots: void DisableButtons(); void EnableButtons(); // connection frame - void onPlaceFrame(bool activateTrash, ConnectionFrame* frame); + void onPlaceFrame(ConnectionFrame* frame); void onFrameClicked(ConnectionFrame* frame); // Net void onClientConnected(Client* client); -- cgit v1.2.3-55-g7522