/*
# 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 <src/input/inputEvent.h>
#include "frame.h"
#include <src/gui/mainWindow.h>
#include <iostream>
#include <QPixmap>
#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_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()));
}
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; i<MainWindow::getConnectionList()->model->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<ConnectionFrame*> 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<QKeyEvent*>(event));
break;
case QEvent::KeyRelease:
recognized = true;
keyReleaseEvent(static_cast<QKeyEvent*>(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<SpecialInputEventDescription> specialEvents = SpecialInputEventDescription::describeSpecialEvents();
QListIterator<SpecialInputEventDescription> 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;
}