/* # 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/ # ----------------------------------------------------------------------------- # src/gui/frame.cpp # This is the drawingarea, it will be set to the suitable ConnectionFrame. # Each instance of this class has is own VNCThread, this way is more simple # to show, update and scale the image from the suitable client. Only the point # on the image that need to be drawed or scaled will be processed. Thus the CPU # performance will not be affected. The pvsmgr can manage in this way a lot of clients. # ----------------------------------------------------------------------------- */ #include #include "frame.h" #include #include #include #define MOUSE_MOTION_SEND_INTERVAL 100 /* msecs */ #define SPECIAL_EVENT_WAIT_TIME 5000 /* msecs */ Frame::Frame(const QString & text, QWidget * parent) : QLabel(parent), _clientVNCThread(0) { _clientVNCThread = NULL; X = 0; Y = 0; setBackgroundRole(QPalette::Base); setStyleSheet(QString::fromUtf8("QLabel{border-radius:10px;\n" "background-color: rgb(150,150,150);}")); setAlignment(Qt::AlignCenter); setAutoFillBackground(true); setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); _isLocked = false; _dozent = false; _uy = _ux = 0; _isCloseUp = false; //QIcon icon; //icon.addFile(QString::fromUtf8(), QSize(), QIcon::Normal, QIcon::Off); // button_closeUp = createToolButton(tr("View"), QIcon(":/restore"),SLOT(closeUp())); // button_closeUp->setCheckable(true); // button_foto = createToolButton(tr("Foto"), QIcon(":/photos"),SLOT(foto())); // button_lock = createToolButton(tr("Lock this client"), QIcon(":/lock"),SLOT(setLock())); //button_unlock = createToolButton(tr("Unlock this client"), QIcon(":/lock"),SLOT(setLock())); // button_dozent = createToolButton(tr("Set as Superclient"), QIcon(":/dozent2"),SLOT(setDozent())); // button_dozent->setCheckable(true); button_control = createToolButton(tr("Enable Remote Control"), QIcon(":/remotecontrol"), SLOT(remoteControlClicked())); button_control->setCheckable(true); button_control_all = createToolButton(tr("Remote Control All Clients"), QIcon(":/remotecontrolall"), SLOT(remoteControlAllClicked())); button_control_all->setCheckable(true); connect(this, SIGNAL(clicked()), this, SLOT(slotClicked())); ip = ""; setToolButtonListVisible(false); _remoteControlEnabled = false; _remoteControlToAll = false; _mouseMotionEventTimer = new QTimer(this); _mouseMotionEventTimer->setInterval(MOUSE_MOTION_SEND_INTERVAL); _mouseMotionEventTimer->setSingleShot(false); connect(_mouseMotionEventTimer, SIGNAL(timeout()), this, SLOT(sendMouseMotionEvent())); _mousePositionChanged = true; _specialEventTimer = new QTimer(this); _specialEventTimer->setInterval(SPECIAL_EVENT_WAIT_TIME); _specialEventTimer->setSingleShot(true); connect(_specialEventTimer, SIGNAL(timeout()), this, SLOT(showSpecialEventMenu())); setStyleSheet("background-color: #DCDCDC;border: 0px solid #DCDCDC;"); } Frame::~Frame() { if (_clientVNCThread) { disconnect(_clientVNCThread, SIGNAL(imageUpdated(int,int,int,int)), this, SLOT(updateImage(int,int,int,int))); disconnect(_clientVNCThread, SIGNAL(finished()), this, SLOT(iamDown())); if (_clientVNCThread->isRunning()) { _clientVNCThread->terminate = true; _clientVNCThread->wait(_clientVNCThread->getUpdatefreq()+2000); } } } void Frame::setVNCThreadConnection(VNCClientThread * vncClientThread) { // initialize the vncthread for this connection //printf("Starting VNC thread for %s\n", ip.toUtf8().data()); _clientVNCThread = vncClientThread; connect(_clientVNCThread, SIGNAL(imageUpdated(int,int,int,int)), this, SLOT(updateImage(int,int,int,int)), Qt::BlockingQueuedConnection); connect(_clientVNCThread, SIGNAL(finished()), this, SLOT(iamDown())); // start the thread if(!_clientVNCThread->terminate) _clientVNCThread->start(); } /* * To stop the vncThreadConnection, we only disconnect all connected signals on _clientVNCThread * We don't need here to terminate the thread, this will appear in vncconnection, because * we have set this thread there (in vncconnection). */ void Frame::stopVNCThreadConnection() { disconnect(_clientVNCThread, SIGNAL(imageUpdated(int,int,int,int)), this, SLOT(updateImage(int,int,int,int))); disconnect(_clientVNCThread, SIGNAL(finished()), this, SLOT(iamDown())); } void Frame::updateImage(int x, int y, int w, int h) { if (_clientVNCThread == NULL) return; if (_clientVNCThread->getSize() != size()) { // grow the update rectangle to avoid artifacts x -= 3; y -= 3; w += 6; h += 6; _clientImg = _clientVNCThread->getImage().copy(x, y, w, h); qreal sx = qreal(width()) / qreal(_clientVNCThread->getSize().width()); qreal sy = qreal(height()) / qreal(_clientVNCThread->getSize().height()); x = qRound(qreal(x) * sx); y = qRound(qreal(y) * sy); w = qRound(qreal(w) * sx); h = qRound(qreal(h) * sy); } else { _clientImg = _clientVNCThread->getImage().copy(x, y, w, h); } _ux = w; _uy = h; repaint(x, y, w, h); // this will trigger the PaintEvent } void Frame::iamDown() { if (_clientVNCThread == NULL) return; _clientVNCThread = NULL; update(); } void Frame::paintEvent(QPaintEvent *event) { QPainter painter(this); QRect r = event->rect(); event->accept(); if (_clientImg.isNull()) { _clientImg = QImage(":/terminal"); painter.drawImage(28,0, _clientImg.scaled(145,123, Qt::KeepAspectRatio, Qt::SmoothTransformation)); setAlignment(Qt::AlignCenter); return; } if(_isLocked) { _clientImg = QImage(":/lock"); painter.drawImage(28,0, _clientImg.scaled(145,123, Qt::KeepAspectRatio, Qt::SmoothTransformation)); setAlignment(Qt::AlignCenter); return; } if (_clientVNCThread != NULL) { if (r.width() != _ux || r.height() != _uy) { _clientImg = _clientVNCThread->getImage(); // redraw complete image (e.g. on resize) r = rect(); } else { _ux = -1; } if (_clientVNCThread->getSize() == size()) { painter.drawImage(r.topLeft(), _clientImg); // don't scale } else { QImage i = _clientImg.scaled(r.size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); painter.drawImage(r.topLeft(), i); } } else { _clientImg = QImage(":/terminal"); painter.drawImage(28,0, _clientImg.scaled(145,123, Qt::KeepAspectRatio, Qt::SmoothTransformation)); setAlignment(Qt::AlignCenter); } } void Frame::slotClicked() { if (ip != "") { for (int i=0; imodel->rowCount(QModelIndex()); i++) { if (MainWindow::getConnectionList()->model->index(i, 1, QModelIndex()).data(Qt::DisplayRole).toString() .compare(ip) == 0) { MainWindow::getConnectionList()->selectRow(i); MainWindow::getConnectionList()->setCurrentIndex(MainWindow::getConnectionList()->currentIndex()); // red border for selected client QList clients = MainWindow::getConnectionWindow()->getAllFrameOnWindow(); foreach (ConnectionFrame *client, clients) client->setStyleSheet(STYLE_DEFAULT); _cFrame->setStyleSheet(STYLE_SELECTED); break; } } } } void Frame::mousePressEvent(QMouseEvent* event) { if(!_remoteControlEnabled) { emit clicked(); if (event->button() == Qt::RightButton) { /*if (!_dummy) DelDummy->setDisabled(true); menu->exec(QCursor::pos());*/ } else { } QLabel::mousePressEvent(event); } else { event->accept(); ConsoleLog writeLine("Captured remote control mousePressEvent"); updateMousePosition(event); sendInputEvent(InputEvent::mousePressRelease(event->button(), event->buttons())); } } void Frame::mouseReleaseEvent ( QMouseEvent * event ) { if(!_remoteControlEnabled) { QLabel::mouseReleaseEvent(event); } else { event->accept(); ConsoleLog writeLine("Captured remote control mouseReleaseEvent"); updateMousePosition(event); sendInputEvent(InputEvent::mousePressRelease(event->button(), event->buttons())); } } QToolButton* Frame::createToolButton(const QString &toolTip, const QIcon &icon, const char *member) { QToolButton *button = new QToolButton(this); button->setToolTip(toolTip); button->setIcon(icon); button->setIconSize(QSize(15, 15)); button->setStyleSheet(QString::fromUtf8("background-color: rgb(230, 230, 230);")); addButton(button); toolButtonList.append(button); connect(button, SIGNAL(clicked()), this, member); return button; } void Frame::addButton(QToolButton *button) { button->move(X,Y); Y += button->size().height()-5; } void Frame::setToolButtonListVisible(bool visible) { foreach (QToolButton* tb, toolButtonList) tb->setVisible(visible); // if (visible && MainWindow::getConnectionWindow()->hasDozent && !_dozent) // button_dozent->setVisible(false);//At this time this button should only be visible on the dozent machine (the superclient). } void Frame::setLockStatus(bool lock) { if (lock) { //button_lock->setToolTip(tr("Unlock this client")); //button_lock->setIcon() TODO } else { //button_lock->setToolTip(tr("Lock this client")); //button_lock->setIcon() TODO } _isLocked = lock; } QImage Frame::getImageForFoto() { return _clientVNCThread->getImage(); } void Frame::closeUp() { emit clicked(); if(_isCloseUp) MainWindow::getWindow()->unCloseUp(getConFrame()); else MainWindow::getWindow()->closeUp(getConFrame()); } void Frame::foto() { emit clicked(); MainWindow::getWindow()->foto(); } void Frame::setLock() { if (!_dozent) { emit clicked(); if (_isLocked) MainWindow::getConnectionWindow()->unlockStations(); else MainWindow::getConnectionWindow()->lockStations(); } else { QString message = QString(tr("You can't lock a Superclient-machine.")); QMessageBox::information(this, "PVS", message); } } void Frame::setDozent() { if (_dozent) { //button_dozent->setToolTip(tr("Set client as Superclient")); _dozent = false; MainWindow::getConnectionWindow()->hasDozent = false; getConFrame()->setDozent(false); } else { //button_dozent->setToolTip(tr("Unset client as Superclient")); _dozent = true; MainWindow::getConnectionWindow()->hasDozent = true; getConFrame()->setDozent(true); } } void Frame::setCloseUp(bool value) { _isCloseUp = value; //button_closeUp->setChecked(value); } void Frame::remoteControlClicked() { if(_remoteControlEnabled) { setMouseTracking(false); _mouseMotionEventTimer->stop(); button_control->setToolTip(tr("Enable Remote Control")); _remoteControlEnabled = false; button_control->setChecked(false); releaseKeyboard(); } else { button_control->setToolTip(tr("Disable Remote Control")); _remoteControlEnabled = true; button_control->setChecked(true); _mouseMotionEventTimer->start(); setMouseTracking(true); if(_mouseOver) grabKeyboard(); } } void Frame::remoteControlAllClicked() { if(_remoteControlToAll) { button_control_all->setToolTip(tr("Remote Control only this Client")); button_control_all->setChecked(false); _remoteControlToAll = false; } else { button_control_all->setToolTip(tr("Remote Control All Clients")); button_control_all->setChecked(true); _remoteControlToAll = true; } } void Frame::sendMouseMotionEvent() { InputEvent evt = InputEvent::mouseMotion(_lastRecordedMousePosition.x(), _lastRecordedMousePosition.y()); if(!_mousePositionChanged) return; _mousePositionChanged = false; sendInputEvent(evt); } void Frame::sendInputEvent(InputEvent const& evt) { QString str; eventToString(evt, str); PVSMsg msg(PVSCOMMAND, "INPUTEVENT", str); if(_remoteControlEnabled) { if(_remoteControlToAll) { ConsoleLog writeLine(QString("sendInputEvent(%1) to one").arg(evt.toString())); PVSConnectionManager::getManager()->getServer()->sendToAll(msg); } else { ConsoleLog writeLine(QString("sendInputEvent(%1) to all").arg(evt.toString())); _cFrame->getConnection()->sendMessage(msg); } } else { ConsoleLog writeLine("sendMouseMotionEvent() disabled"); } } void Frame::mouseMoveEvent(QMouseEvent* event) { QPoint newPosition = rescalePosition(event->posF()); if(newPosition != _lastRecordedMousePosition) { _lastRecordedMousePosition = newPosition; _mousePositionChanged = true; ConsoleLog writeLine(QString("Mouse moved to (%1,%2)").arg(_lastRecordedMousePosition.x()).arg(_lastRecordedMousePosition.y())); } } QPoint Frame::rescalePosition(QPointF guipos) { if(!_clientVNCThread) return QPoint(); QSize s = size(); QSize t = _clientVNCThread->getSize(); qreal px, py; px = guipos.x() * t.width() / (qreal)s.width(); py = guipos.y() * t.height() / (qreal)s.height(); return QPoint((int)px, (int)py); } void Frame::updateMousePosition(QMouseEvent* event) { QPoint oldPosition = _lastRecordedMousePosition; _lastRecordedMousePosition = rescalePosition(event->posF()); _mousePositionChanged = oldPosition != _lastRecordedMousePosition; sendMouseMotionEvent(); } void Frame::enterEvent(QEvent* event) { _mouseOver = true; if(_remoteControlEnabled) { grabKeyboard(); } } void Frame::leaveEvent(QEvent* event) { _mouseOver = false; if(_remoteControlEnabled) { releaseKeyboard(); } } void Frame::keyPressEvent(QKeyEvent* event) { if(_remoteControlEnabled) { if(event->key() == Qt::Key_Menu) { qDebug("Menu has been pressed"); if(!event->isAutoRepeat()) _specialEventTimer->start(); } else { // The action of the keyboard may depend on the position of the pointer sendMouseMotionEvent(); int key = event->key(); if(key >= ' ' && key < 0x100) { // the key is a Latin1 key. We need to find out the correct code to send. QString text = event->text(); if(text.length() == 1 && text.at(0).row() == 0) { QChar c = text.at(0); // The next problem is that it may be a control character. // This happens when Ctrl is pressed, so we only // modify keys if they are lowercase or uppercase // versions of the keycode. if(c.toLower().toLatin1() == (char)key || c.toUpper().toLatin1() == (char)key) { // We found a Latin1 char and pray it is the correct case. key = c.cell(); } } } sendInputEvent(InputEvent::keyboardPress(key, event->modifiers())); } } else { QLabel::keyPressEvent(event); } } void Frame::keyReleaseEvent(QKeyEvent* event) { if(_remoteControlEnabled) { sendMouseMotionEvent(); if(event->key() == Qt::Key_Menu) { if(!event->isAutoRepeat()) { qDebug("Menu has been released"); if(_specialEventTimer->isActive()) { qDebug("Pressing key on client"); // Pressing the key has been deferred, so do it now: sendInputEvent(InputEvent::keyboardPress(event->key(), event->modifiers())); } sendInputEvent(InputEvent::keyboardRelease(event->key(), event->modifiers())); _specialEventTimer->stop(); } } else { // The action of the keyboard may depend on the position of the pointer sendInputEvent(InputEvent::keyboardRelease(event->key(), event->modifiers())); } } else { QLabel::keyReleaseEvent(event); } } bool Frame::event(QEvent* event) { if(_remoteControlEnabled) { bool recognized; switch(event->type()) { case QEvent::ShortcutOverride: recognized = true; event->accept(); break; case QEvent::KeyPress: recognized = true; keyPressEvent(static_cast(event)); break; case QEvent::KeyRelease: recognized = true; keyReleaseEvent(static_cast(event)); break; default: recognized = false; } if(recognized && event->isAccepted()) return true; } return QLabel::event(event); } void Frame::showSpecialEventMenu() { qDebug("Trying to show menu..."); QMenu* menu = new QMenu(this); QList specialEvents = SpecialInputEventDescription::describeSpecialEvents(); QListIterator iter(specialEvents); int i; for(i = 0; iter.hasNext(); i++) { QAction* act = menu->addAction(iter.next().description); act->setData(i); } QAction* selected = menu->exec(QCursor::pos()); if(selected) { int index = selected->data().toInt(); sendInputEvent(specialEvents.at(index).toEvent()); } delete menu; }