/********************************************************************************
* FaceTrackNoIR This program is a private project of some enthusiastic *
* gamers from Holland, who don't like to pay much for *
* head-tracking. *
* *
* Copyright (C) 2012 Wim Vriend (Developing) *
* Ron Hendriks (Researching and Testing) *
* *
* Homepage http://facetracknoir.sourceforge.net/home/default.htm *
* *
* This program is free software; you can redistribute it and/or modify it *
* under the terms of the GNU General Public License as published by the *
* Free Software Foundation; either version 3 of the License, or (at your *
* option) any later version. *
* *
* This program is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY *
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for *
* more details. *
* *
* You should have received a copy of the GNU General Public License along *
* with this program; if not, see . *
* *
* The FunctionConfigurator was made by Stanislaw Halik, and adapted to *
* FaceTrackNoIR. *
* *
* All credits for this nice piece of code should go to Stanislaw. *
* *
* Copyright (c) 2011-2012, Stanislaw Halik *
* Permission to use, copy, modify, and/or distribute this *
* software for any purpose with or without fee is hereby granted, *
* provided that the above copyright notice and this permission *
* notice appear in all copies. *
********************************************************************************/
#include "qfunctionconfigurator/qfunctionconfigurator.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static const int pointSize = 5;
QFunctionConfigurator::QFunctionConfigurator(QWidget *parent)
: QWidget(parent)
{
//
// Defaults, for when the widget has no values different from the domXML()
//
MaxInput = 50; // Maximum input limit
MaxOutput = 180; // Maximum output limit
pPerEGU_Output = 1; // Number of pixels, per EGU
pPerEGU_Input = 4; // Number of pixels, per EGU
gDistEGU_Input = 5; // Distance of gridlines
gDistEGU_Output = 10; // Distance of gridlines
// Change compared to BezierConfigurator: X = horizontal (input), Y = vertical (output)
// This will require the Curve-Dialog to be higher (which was the reason it was reversed in the first place..)
range = QRectF(40, 20, MaxInput * pPerEGU_Input, MaxOutput * pPerEGU_Output);
setMouseTracking(true);
movingPoint = -1; // Index of that same point
//
// Variables for FunctionConfig
//
_config = 0;
_draw_background = true;
_draw_function = true;
// qDebug() << "QFunctionConfigurator::QFunctionConfigurator object created.";
}
//
// Attach an existing FunctionConfig to the Widget.
//
void QFunctionConfigurator::setConfig(FunctionConfig* config, QString settingsFile) {
QSettings settings("opentrack"); // Registry settings (in HK_USER)
QString currentFile = settings.value ( "SettingsFile", QCoreApplication::applicationDirPath() + "/settings/default.ini" ).toString();
QSettings iniFile( currentFile, QSettings::IniFormat ); // Application settings (in INI-file)
config->loadSettings(iniFile);
_config = config;
strSettingsFile = settingsFile; // Remember for Reset()
qDebug() << "QFunctionConfigurator::setConfig" << config->getTitle();
setCaption(config->getTitle());
_draw_function = _draw_background = true;
this->update();
}
//
// Load the FunctionConfig (points) from the INI-file.
//
void QFunctionConfigurator::loadSettings(QString settingsFile) {
QSettings iniFile( settingsFile, QSettings::IniFormat ); // Application settings (in INI-file)
strSettingsFile = settingsFile; // Remember for Reset()
qDebug() << "QFunctionConfigurator::loadSettings = " << settingsFile;
if (_config) {
_config->loadSettings(iniFile);
}
}
//
// Save the FunctionConfig (points) to the INI-file.
//
void QFunctionConfigurator::saveSettings(QString settingsFile) {
QSettings iniFile( settingsFile, QSettings::IniFormat ); // Application settings (in INI-file)
strSettingsFile = settingsFile; // Remember for Reset()
qDebug() << "QFunctionConfigurator::saveSettings = " << settingsFile;
if (_config) {
_config->saveSettings(iniFile);
}
}
//
// Draw the Background for the graph, the gridlines and the gridpoints.
// The static objects are drawn on a Pixmap, so it does not have to be repeated every paintEvent. Hope this speeds things up...
//
void QFunctionConfigurator::drawBackground(const QRectF &fullRect)
{
int i;
QRect scale;
_background = QPixmap(fullRect.width(), fullRect.height());
QPainter painter(&_background);
painter.save();
painter.setRenderHint(QPainter::Antialiasing);
painter.fillRect(fullRect, colBackground);
QColor bg_color(112, 154, 209);
painter.fillRect(range, bg_color);
QFont font("ComicSans", 4);
font.setPointSize(8);
painter.setFont(font);
QPen pen(QColor(55, 104, 170, 127), 1, Qt::SolidLine);
//
// Draw the Caption
//
if (_config) {
strCaption = _config->getTitle();
}
scale.setCoords(range.left(), 0, range.right(), 20);
painter.drawText(scale, Qt::AlignCenter, strCaption);
//
// Draw the horizontal grid
//
for (i = range.bottom() - gDistEGU_Output * pPerEGU_Output; i >= range.top(); i -= gDistEGU_Output * pPerEGU_Output) {
drawLine(&painter, QPointF(40, i), QPointF(range.right(), i), pen);
scale.setCoords(0, i - 5, range.left() - 5, i + 5);
painter.drawText(scale, Qt::AlignRight, tr("%1").arg(((range.bottom() - i))/pPerEGU_Output));
}
//
// Draw the vertical guidelines
//
for (i = range.left(); i <= range.right(); i += gDistEGU_Input * pPerEGU_Input) {
drawLine(&painter, QPointF(i, range.top()), QPointF(i, range.bottom()), pen);
scale.setCoords(i - 10, range.bottom() + 2, i + 10, range.bottom() + 15);
painter.drawText(scale, Qt::AlignCenter, tr("%1").arg(abs(((range.left() - i))/pPerEGU_Input)));
}
scale.setCoords(range.left(), range.bottom() + 20, range.right(), range.bottom() + 35);
painter.drawText(scale, Qt::AlignRight, strInputEGU);
//
// Draw the EGU of the vertical axis (vertically!)
//
font.setPointSize(10);
painter.translate(range.topLeft().x() - 35, range.topLeft().y());
painter.rotate(90);
painter.drawText(0,0,strOutputEGU );
//
// Draw the two axis
//
pen.setWidth(2);
pen.setColor( Qt::black );
drawLine(&painter, range.topLeft() - QPointF(2,0), range.bottomLeft() - QPointF(2,0), pen);
drawLine(&painter, range.bottomLeft(), range.bottomRight(), pen);
painter.restore();
}
//
// Draw the Function for the graph, on a Pixmap.
//
void QFunctionConfigurator::drawFunction(const QRectF &fullRect)
{
if (!_config)
return;
int i;
QPointF prevPoint;
QPointF currentPoint;
//
// Use the background picture to draw on.
// ToDo: find out how to add Pixmaps, without getting it all green...
//
_function = QPixmap(_background);
QPainter painter(&_function);
painter.save();
painter.setRenderHint(QPainter::Antialiasing, true);
//
// Draw the handles for the Points
//
QList points = _config->getPoints();
for (i = 0; i < points.size(); i++) {
currentPoint = graphicalizePoint( points[i] ); // Get the next point and convert it to Widget measures
drawPoint(&painter, currentPoint, QColor(200, 200, 210, 120));
lastPoint = currentPoint; // Remember which point is the rightmost in the graph
}
QPen pen(colBezier, 1.2, Qt::SolidLine);
prevPoint = graphicalizePoint( QPointF(0,0) ); // Start at the Axis
double max = maxInputEGU();
QPointF prev = graphicalizePoint(QPointF(0, 0));
const double step = 1.01;
for (double i = 0; i < max; i += step) {
double val = _config->getValue(i);
QPointF cur = graphicalizePoint(QPointF(i, val));
drawLine(&painter, prev, cur, pen);
prev = cur;
}
painter.restore();
}
//
// The Widget paints the surface every x msecs.
//
void QFunctionConfigurator::paintEvent(QPaintEvent *e)
{
QPointF prevPoint;
QPointF currentPoint;
QPointF actualPos;
int i;
// qDebug() << "QFunctionConfigurator::paintEvent.";
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing);
p.setClipRect(e->rect());
if (_draw_background) {
drawBackground(e->rect()); // Draw the static parts on a Pixmap
p.drawPixmap(0, 0, _background); // Paint the background
_draw_background = false;
}
if (_draw_function) {
drawFunction(e->rect()); // Draw the Function on a Pixmap
_draw_function = false;
}
p.drawPixmap(0, 0, _function); // Always draw the background and the function
QPen pen(Qt::white, 1, Qt::SolidLine);
//
// Draw the Points, that make up the Curve
//
if (_config) {
QList points = _config->getPoints();
//
// When moving, also draw a sketched version of the Function.
//
if (movingPoint >= 0 && movingPoint < points.size()) {
prevPoint = graphicalizePoint( QPointF(0,0) ); // Start at the Axis
for (i = 0; i < points.size(); i++) {
currentPoint = graphicalizePoint( points[i] ); // Get the next point and convert it to Widget measures
drawLine(&p, prevPoint, currentPoint, pen);
prevPoint = currentPoint;
// qDebug() << "QFunctionConfigurator::paintEvent, drawing while moving " << currentPoint;
}
//
// When moving, also draw a few help-lines, so positioning the point gets easier.
//
pen.setWidth(1);
pen.setColor( Qt::white );
pen.setStyle( Qt::DashLine );
actualPos = graphicalizePoint(points[movingPoint]);
drawLine(&p, QPoint(range.left(), actualPos.y()), QPoint(actualPos.x(), actualPos.y()), pen);
drawLine(&p, QPoint(actualPos.x(), actualPos.y()), QPoint(actualPos.x(), range.bottom()), pen);
}
//
// If the Tracker is active, the 'Last Point' it requested is recorded.
// Show that point on the graph, with some lines to assist.
// This new feature is very handy for tweaking the curves!
//
if (_config->getLastPoint( currentPoint )) {
// qDebug() << "QFunctionConfigurator::paintEvent, drawing tracked Point " << currentPoint;
actualPos = graphicalizePoint( currentPoint );
drawPoint(&p, actualPos, QColor(255, 0, 0, 120));
pen.setWidth(1);
pen.setColor( Qt::black );
pen.setStyle( Qt::SolidLine );
drawLine(&p, QPoint(range.left(), actualPos.y()), QPoint(actualPos.x(), actualPos.y()), pen);
drawLine(&p, QPoint(actualPos.x(), actualPos.y()), QPoint(actualPos.x(), range.bottom()), pen);
}
}
//
// Draw the delimiters
//
pen.setWidth(1);
pen.setColor( Qt::white );
pen.setStyle( Qt::SolidLine );
drawLine(&p, QPoint(lastPoint.x(), range.top()), QPoint(lastPoint.x(), range.bottom()), pen);
drawLine(&p, QPoint(range.left(), lastPoint.y()), QPoint(range.right(), lastPoint.y()), pen);
//QTimer::singleShot(50, this, SLOT(update()));
}
//
// Draw the handle, to move the Bezier-curve.
//
void QFunctionConfigurator::drawPoint(QPainter *painter, const QPointF &pos, QColor colBG )
{
painter->save();
painter->setPen(QColor(50, 100, 120, 200));
painter->setBrush( colBG );
painter->drawEllipse(QRectF(pos.x() - pointSize,
pos.y() - pointSize,
pointSize*2, pointSize*2));
painter->restore();
}
void QFunctionConfigurator::drawLine(QPainter *painter, const QPointF &start, const QPointF &end, QPen pen)
{
painter->save();
painter->setPen(pen);
painter->setBrush(Qt::NoBrush);
painter->drawLine(start, end);
painter->restore();
}
//
// If the mousebutton is pressed, check if it is inside one of the Points.
// If so: start moving that Point, until mouse release.
//
void QFunctionConfigurator::mousePressEvent(QMouseEvent *e)
{
QList points = _config->getPoints();
//
// First: check the left mouse-button
//
if (e->button() == Qt::LeftButton) {
//
// Check to see if the cursor is touching one of the points.
//
bool bTouchingPoint = false;
movingPoint = -1;
if (_config) {
for (int i = 0; i < points.size(); i++) {
if ( markContains( graphicalizePoint( points[i] ), e->pos() ) ) {
bTouchingPoint = true;
movingPoint = i;
timer.restart();
break;
}
}
//
// If the Left Mouse-button was clicked without touching a Point, add a new Point
//
if (!bTouchingPoint) {
if (withinRect(e->pos(), range)) {
_config->addPoint(normalizePoint(e->pos()));
emit CurveChanged( true );
}
}
}
}
// Then: check the right mouse-button
//
if (e->button() == Qt::RightButton) {
//
// Check to see if the cursor is touching one of the points.
//
if (_config) {
int found_pt = -1;
for (int i = 0; i < points.size(); i++) {
if ( markContains( graphicalizePoint( points[i] ), e->pos() ) ) {
found_pt = i;
break;
}
}
//
// If the Right Mouse-button was clicked while touching a Point, remove the Point
//
if (found_pt != -1) {
_config->removePoint(found_pt);
emit CurveChanged( true );
}
movingPoint = -1;
}
}
_draw_function = _draw_background = true;
update();
}
//
// If the mouse if moving, make sure the Bezier moves along.
// Of course, only when a Point is selected...
//
void QFunctionConfigurator::mouseMoveEvent(QMouseEvent *e)
{
QList points = _config->getPoints();
const int refresh_delay = 50;
if (movingPoint >= 0 && movingPoint < points.size()) {
setCursor(Qt::ClosedHandCursor);
if (timer.isValid() && timer.elapsed() > refresh_delay)
{
timer.restart();
QPointF new_pt = normalizePoint(e->pos());
points[movingPoint] = new_pt;
_config->movePoint(movingPoint, new_pt);
_draw_function = _draw_background = true;
update();
}
}
else {
if (withinRect(e->pos(), rect()))
{
//
// Check to see if the cursor is touching one of the points.
//
bool bTouchingPoint = false;
if (_config) {
for (int i = 0; i < points.size(); i++) {
if ( markContains( graphicalizePoint( points[i] ), e->pos() ) ) {
bTouchingPoint = true;
}
}
}
if ( bTouchingPoint ) {
setCursor(Qt::OpenHandCursor);
}
else {
setCursor(Qt::ArrowCursor);
}
}
}
}
void QFunctionConfigurator::mouseReleaseEvent(QMouseEvent *e)
{
QList points = _config->getPoints();
if (e->button() == Qt::LeftButton) {
timer.invalidate();
//qDebug()<<"releasing";
if (movingPoint >= 0 && movingPoint < points.size()) {
emit CurveChanged( true );
//
// Update the Point in the _config
//
if (_config) {
_config->movePoint(movingPoint, normalizePoint(e->pos()));
}
}
setCursor(Qt::ArrowCursor);
movingPoint = -1;
}
_draw_function = _draw_background = true;
update();
}
//
// Determine if the mousebutton was pressed within the range of the Point.
//
bool QFunctionConfigurator::markContains(const QPointF &pos, const QPointF &coord) const
{
QRectF rect(pos.x() - pointSize,
pos.y() - pointSize,
pointSize*2, pointSize*2);
QPainterPath path;
path.addEllipse(rect);
return path.contains(coord);
}
bool QFunctionConfigurator::withinRect( const QPointF &coord, const QRectF &rect ) const
{
QPainterPath path;
path.addRect(rect);
return path.contains(coord);
}
//
// Convert the Point in the graph, to the real-life Point.
//
QPointF QFunctionConfigurator::normalizePoint(QPointF point) const
{
QPointF norm;
norm.setX( (point.x() - range.left()) / pPerEGU_Input );
norm.setY( (range.bottom() - point.y()) / pPerEGU_Output );
if (norm.x() > maxInputEGU())
norm.setX(maxInputEGU());
else if (norm.x() < 0)
norm.setX(0);
if (norm.y() > maxOutputEGU())
norm.setY(maxOutputEGU());
else if (norm.y() < 0)
norm.setY(0);
return norm;
}
//
// Convert the real-life Point into the graphical Point.
//
QPointF QFunctionConfigurator::graphicalizePoint(QPointF point) const
{
QPointF graph;
graph.setX( range.left() + (fabs(point.x()) * pPerEGU_Input) );
graph.setY( range.bottom() - (fabs(point.y()) * pPerEGU_Output) );
return graph;
}
void QFunctionConfigurator::setmaxInputEGU(int value)
{
MaxInput = value;
setMinimumWidth(MaxInput * pPerEGU_Input + 55);
// resetCurve();
resize( MaxInput * pPerEGU_Input + 55, MaxOutput * pPerEGU_Output + 60 );
}
void QFunctionConfigurator::setmaxOutputEGU(int value)
{
MaxOutput = value;
setMinimumHeight(MaxOutput * pPerEGU_Output + 60);
// resetCurve();
resize( MaxInput * pPerEGU_Input + 55, MaxOutput * pPerEGU_Output + 60 );
}
//
// To make configuration more visibly attractive, the number of pixels 'per EGU' can be defined.
//
void QFunctionConfigurator::setpixPerEGU_Input(int value)
{
pPerEGU_Input = value;
setMinimumWidth(MaxInput * pPerEGU_Input + 55);
resize( MaxInput * pPerEGU_Input + 55, MaxOutput * pPerEGU_Output + 60 );
}
//
// To make configuration more visibly attractive, the number of pixels 'per EGU' can be defined.
//
void QFunctionConfigurator::setpixPerEGU_Output(int value)
{
pPerEGU_Output = value;
setMinimumHeight(MaxOutput * pPerEGU_Output + 60);
resize( MaxInput * pPerEGU_Input + 55, MaxOutput * pPerEGU_Output + 60 );
}
//
// Define the distance of the grid 'in EGU' points.
//
void QFunctionConfigurator::setgridDistEGU_Input(int value)
{
gDistEGU_Input = value;
_draw_background = true;
_draw_function = true;
repaint();
}
//
// Define the distance of the grid 'in EGU' points.
//
void QFunctionConfigurator::setgridDistEGU_Output(int value)
{
gDistEGU_Output = value;
_draw_background = true;
_draw_function = true;
repaint();
}
void QFunctionConfigurator::setColorBezier(QColor color)
{
colBezier = color;
update();
}
void QFunctionConfigurator::setColorBackground(QColor color)
{
colBackground = color;
update();
}
void QFunctionConfigurator::setInputEGU(QString egu)
{
strInputEGU = egu;
update();
}
void QFunctionConfigurator::setOutputEGU(QString egu)
{
strOutputEGU = egu;
update();
}
void QFunctionConfigurator::setCaption(QString cap)
{
strCaption = cap;
update();
}
void QFunctionConfigurator::resizeEvent(QResizeEvent *e)
{
range = QRectF(40, 20, MaxInput * pPerEGU_Input, MaxOutput * pPerEGU_Output);
qDebug() << "QFunctionConfigurator::resizeEvent, name = " << strCaption << ",range = " << range;
_draw_background = true;
_draw_function = true;
repaint();
}