/********************************************************************************
* 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. *
********************************************************************************/
/*
Modifications (last one on top):
20120830 - WVR: Changed functionality a bit. Now only draw the handles, when the function is drawn.
Only check mouseMoves, when they occur 'in range'. Redraw the curve, when a resize occurs.
Somehow, the curve was not drawn correctly, when this was not done (all points were too high).
After a 'Reset' this would disappear...
20120828 - WVR: Removed bSkipText, which was used to not show a number below each vertical gridline.
*/
#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;
_points = QList();
_draw_points = QList();
_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) {
QPointF currentPoint;
QPointF drawPoint;
qreal x;
_config = config;
_points = config->getPoints();
strSettingsFile = settingsFile; // Remember for Reset()
qDebug() << "QFunctionConfigurator::setConfig" << config->getTitle();
setCaption(config->getTitle());
//
// Get the Function Points, one for each pixel in the horizontal range.
// If the curve does not change, there is no need to run this code every time (it slows down drawing).
//
_draw_points.clear();
for (int j = 0; j < MaxInput * pPerEGU_Input; j++) {
//
// Weird: not casting to float causes C++ to round the number...
//
x = (float) j / (float) pPerEGU_Input;
currentPoint.setX ( x );
currentPoint.setY (_config->getValue( x ));
drawPoint = graphicalizePoint(currentPoint, "setConfig");
if (withinRect(drawPoint, range)) {
_draw_points.append(drawPoint);
// qDebug() << "QFunctionConfigurator::setConfig _draw_Point to add = " << drawPoint;
}
}
_draw_function = 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);
setConfig(_config, settingsFile);
}
}
//
// 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;
qDebug() << "QFunctionConfigurator::drawBackground.";
_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
//
for (i = 0; i < _points.size(); i++) {
currentPoint = graphicalizePoint( _points[i], "drawFunction handles" ); // 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
//qDebug() << "QFunctionConfigurator::paintEvent, drawing handle for " << currentPoint;
}
QPen pen(colBezier, 1.2, Qt::SolidLine);
prevPoint = graphicalizePoint( QPointF(0,0), "drawFunction lines" ); // Start at the Axis
double max = maxInputEGU();
QPointF prev = graphicalizePoint(QPointF(0, 0));
double step = 1e-1 / (double) pixPerEGU_Input();
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) {
//
// When moving, also draw a sketched version of the Function.
//
if (movingPoint >= 0 && movingPoint < _points.size()) {
prevPoint = graphicalizePoint( QPointF(0,0), "paintEvent moving" ); // Start at the Axis
for (i = 0; i < _points.size(); i++) {
currentPoint = graphicalizePoint( _points[i], "paintEvent moving" ); // 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], "paintEvent moving help line(s)");
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, "paintEvent tracking" );
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)
{
//
// 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], "mousePressEvent markContains" ), e->pos() ) ) {
bTouchingPoint = true;
movingPoint = i;
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()));
setConfig(_config, strSettingsFile);
movingPoint = -1;
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) {
for (int i = 0; i < _points.size(); i++) {
if ( markContains( graphicalizePoint( _points[i], "mousePressEvent RightButton" ), e->pos() ) ) {
movingPoint = i;
break;
}
}
//
// If the Right Mouse-button was clicked while touching a Point, remove the Point
//
if (movingPoint >= 0 && movingPoint < _points.size()) {
_config->removePoint(movingPoint);
movingPoint = -1;
setConfig(_config, strSettingsFile);
emit CurveChanged( true );
}
else
movingPoint = -1;
}
}
}
//
// If the mouse if moving, make sure the Bezier moves along.
// Of course, only when a Point is selected...
//
void QFunctionConfigurator::mouseMoveEvent(QMouseEvent *e)
{
if (movingPoint >= 0 && movingPoint < _points.size()) {
setCursor(Qt::ClosedHandCursor);
//
// Change the currently moving Point.
//
_points[movingPoint] = normalizePoint(e->pos());
update();
}
else {
//
// 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], "mouseMoveEvent" ), e->pos() ) ) {
bTouchingPoint = true;
}
}
}
if ( bTouchingPoint ) {
setCursor(Qt::OpenHandCursor);
}
else {
setCursor(Qt::ArrowCursor);
}
}
}
void QFunctionConfigurator::mouseReleaseEvent(QMouseEvent *e)
{
if (e->button() == Qt::LeftButton) {
//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()));
setConfig(_config, strSettingsFile);
}
}
setCursor(Qt::ArrowCursor);
movingPoint = -1;
}
}
//
// 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, QString source) const
{
QPointF graph;
graph.setX( range.left() + (fabs(point.x()) * pPerEGU_Input) );
graph.setY( range.bottom() - (fabs(point.y()) * pPerEGU_Output) );
// qDebug() << "QFunctionConfigurator::graphicalizePoint source = " << source << ", point = " << point << ", graph = " << graph;
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;
if (_config) {
setConfig(_config, strSettingsFile);
}
_draw_background = true;
_draw_function = true;
repaint();
}