/*
Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
Created by Rudolf Cardinal (rnc1001@cam.ac.uk).
This file is part of CamCOPS.
CamCOPS 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.
CamCOPS 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 CamCOPS. If not, see <https://www.gnu.org/licenses/>.
*/
#include "whiskerapi.h"
#include <QDebug>
#include "whisker/whiskerconstants.h"
using namespace whiskerconstants;
namespace whiskerapi {
// ============================================================================
// Helper functions
// ============================================================================
QString onVal(bool on)
{
return on ? VAL_ON : VAL_OFF;
}
// ============================================================================
// Helper structs
// ============================================================================
// ----------------------------------------------------------------------------
// Pen
// ----------------------------------------------------------------------------
Pen::Pen(int width, const QColor& colour, PenStyle style) :
width(width),
colour(colour),
style(style)
{
}
QString Pen::whiskerOptionString() const
{
const QStringList args{
FLAG_PEN_COLOUR,
rgbFromColour(colour),
FLAG_PEN_WIDTH,
QString::number(width),
FLAG_PEN_STYLE,
PEN_STYLE_FLAGS[style],
};
return msgFromArgs(args);
}
// ----------------------------------------------------------------------------
// Brush
// ----------------------------------------------------------------------------
Brush::Brush(
const QColor& colour,
const QColor& bg_colour,
bool opaque,
BrushStyle style,
BrushHatchStyle hatch_style
) :
colour(colour),
bg_colour(bg_colour),
opaque(opaque),
style(style),
hatch_style(hatch_style)
{
}
QString Brush::whiskerOptionString() const
{
QStringList args{BRUSH_STYLE_FLAGS[style]};
if (style == BrushStyle::Solid) {
args.append(rgbFromColour(colour));
} else if (style == BrushStyle::Hatched) {
args.append(BRUSH_HATCH_VALUES[hatch_style]);
args.append(rgbFromColour(colour));
if (opaque) {
args.append(FLAG_BRUSH_OPAQUE);
args.append(FLAG_BRUSH_BACKGROUND);
args.append(rgbFromColour(bg_colour));
} else {
args.append(FLAG_BRUSH_TRANSPARENT);
}
}
return msgFromArgs(args);
}
// ============================================================================
// Display object definition classes
// ============================================================================
QString DisplayObject::optionString() const
{
return msgFromArgs(options());
}
Arc::Arc(
const QRect& rect, const QPoint& start, const QPoint& end, const Pen& pen
) :
rect(rect),
start(start),
end(end),
pen(pen)
{
}
QStringList Arc::options() const
{
return QStringList{
VAL_OBJTYPE_ARC,
rectCoordinates(rect),
pointCoordinates(start),
pointCoordinates(end),
pen.whiskerOptionString(),
};
}
Bezier::Bezier(
const QPoint& start,
const QPoint& control1,
const QPoint& control2,
const QPoint& end,
const Pen& pen
) :
start(start),
control1(control1),
control2(control2),
end(end),
pen(pen)
{
}
QStringList Bezier::options() const
{
return QStringList{
VAL_OBJTYPE_BEZIER,
pointCoordinates(start),
pointCoordinates(control1),
pointCoordinates(control2),
pointCoordinates(end),
pen.whiskerOptionString(),
};
}
Bitmap::Bitmap(
const QPoint& pos,
const QString& filename,
bool stretch,
int height,
int width,
VerticalAlign valign,
HorizontalAlign halign
) :
pos(pos),
filename(filename),
stretch(stretch),
height(height),
width(width),
valign(valign),
halign(halign)
{
}
QStringList Bitmap::options() const
{
return QStringList{
VAL_OBJTYPE_BITMAP,
pointCoordinates(pos),
quote(filename),
stretch ? FLAG_BITMAP_STRETCH : FLAG_BITMAP_CLIP,
FLAG_HEIGHT,
QString::number(height),
FLAG_WIDTH,
QString::number(width),
HALIGN_FLAGS[halign],
VALIGN_FLAGS[valign],
};
}
CamcogQuadPattern::CamcogQuadPattern(
const QPoint& pos,
const QSize& pixel_size,
const QVector<uint8_t>& top_left_patterns,
const QVector<uint8_t>& top_right_patterns,
const QVector<uint8_t>& bottom_left_patterns,
const QVector<uint8_t>& bottom_right_patterns,
const QColor& top_left_colour,
const QColor& top_right_colour,
const QColor& bottom_left_colour,
const QColor& bottom_right_colour,
const QColor& bg_colour
) :
pos(pos),
pixel_size(pixel_size),
top_left_patterns(top_left_patterns),
top_right_patterns(top_right_patterns),
bottom_left_patterns(bottom_left_patterns),
bottom_right_patterns(bottom_right_patterns),
top_left_colour(top_left_colour),
top_right_colour(top_right_colour),
bottom_left_colour(bottom_left_colour),
bottom_right_colour(bottom_right_colour),
bg_colour(bg_colour)
{
}
QStringList CamcogQuadPattern::options() const
{
const int required_size = 8;
if (top_left_patterns.size() != required_size
|| top_right_patterns.size() != required_size
|| bottom_left_patterns.size() != required_size
|| bottom_right_patterns.size() != required_size) {
qWarning() << "Whisker CamcogQuadPattern used with wrong vector size; "
"will fail";
return QStringList();
}
auto vectorPattern = [](const QVector<uint8_t>& v) -> QString {
QStringList numbers;
for (const uint8_t n : v) {
numbers.append(QString::number(n));
}
return numbers.join(SPACE);
};
return QStringList{
VAL_OBJTYPE_CAMCOGQUADPATTERN,
pointCoordinates(pos),
sizeCoordinates(pixel_size),
vectorPattern(top_left_patterns),
vectorPattern(top_right_patterns),
vectorPattern(bottom_left_patterns),
vectorPattern(bottom_right_patterns),
rgbFromColour(top_left_colour),
rgbFromColour(top_right_colour),
rgbFromColour(bottom_left_colour),
rgbFromColour(bottom_right_colour),
rgbFromColour(bg_colour),
};
}
Chord::Chord(
const QRect& rect,
const QPoint& line_start,
const QPoint& line_end,
const Pen& pen,
const Brush& brush
) :
rect(rect),
line_start(line_start),
line_end(line_end),
pen(pen),
brush(brush)
{
}
QStringList Chord::options() const
{
return QStringList{
VAL_OBJTYPE_CHORD,
rectCoordinates(rect),
pointCoordinates(line_start),
pointCoordinates(line_end),
pen.whiskerOptionString(),
brush.whiskerOptionString(),
};
}
Ellipse::Ellipse(const QRect& rect, const Pen& pen, const Brush& brush) :
rect(rect),
pen(pen),
brush(brush)
{
}
QStringList Ellipse::options() const
{
return QStringList{
VAL_OBJTYPE_ELLIPSE,
rectCoordinates(rect),
pen.whiskerOptionString(),
brush.whiskerOptionString(),
};
}
Line::Line(const QPoint& start, const QPoint& end, const Pen& pen) :
start(start),
end(end),
pen(pen)
{
}
QStringList Line::options() const
{
return QStringList{
VAL_OBJTYPE_LINE,
pointCoordinates(start),
pointCoordinates(end),
pen.whiskerOptionString(),
};
}
Pie::Pie(
const QRect& rect,
const QPoint& arc_start,
const QPoint& arc_end,
const Pen& pen,
const Brush& brush
) :
rect(rect),
arc_start(arc_start),
arc_end(arc_end),
pen(pen),
brush(brush)
{
}
QStringList Pie::options() const
{
return QStringList{
VAL_OBJTYPE_PIE,
rectCoordinates(rect),
pointCoordinates(arc_start),
pointCoordinates(arc_end),
pen.whiskerOptionString(),
brush.whiskerOptionString(),
};
}
Polygon::Polygon(
const QVector<QPoint>& points,
const Pen& pen,
const Brush& brush,
bool alternate
) :
points(points),
pen(pen),
brush(brush),
alternate(alternate)
{
}
QStringList Polygon::options() const
{
if (points.size() < 3) {
qWarning(
) << "Whisker polygon used with fewer than 3 points; will fail";
return QStringList();
}
QStringList args{
VAL_OBJTYPE_POLYGON,
QString::number(points.size()),
};
for (const QPoint& point : points) {
args.append(pointCoordinates(point));
}
args.append({
alternate ? FLAG_POLYGON_ALTERNATE : FLAG_POLYGON_WINDING,
pen.whiskerOptionString(),
brush.whiskerOptionString(),
});
return args;
}
Rectangle::Rectangle(const QRect& rect, const Pen& pen, const Brush& brush) :
rect(rect),
pen(pen),
brush(brush)
{
}
QStringList Rectangle::options() const
{
return QStringList{
VAL_OBJTYPE_RECTANGLE,
rectCoordinates(rect),
pen.whiskerOptionString(),
brush.whiskerOptionString(),
};
}
RoundRect::RoundRect(
const QRect& rect,
const QSize& ellipse_size,
const Pen& pen,
const Brush& brush
) :
rect(rect),
ellipse_size(ellipse_size),
pen(pen),
brush(brush)
{
}
QStringList RoundRect::options() const
{
return QStringList{
VAL_OBJTYPE_ROUNDRECT,
rectCoordinates(rect),
sizeCoordinates(ellipse_size),
pen.whiskerOptionString(),
brush.whiskerOptionString(),
};
}
Text::Text(
const QPoint& pos, const QString& text, int height, const QString& font
) :
pos(pos),
text(text),
height(height),
font(font)
{
}
QStringList Text::options() const
{
QStringList args{
VAL_OBJTYPE_TEXT,
pointCoordinates(pos),
quote(text),
FLAG_HEIGHT,
QString::number(height),
FLAG_TEXT_WEIGHT,
QString::number(weight),
italic ? FLAG_TEXT_ITALIC : "",
underline ? FLAG_TEXT_UNDERLINE : "",
opaque ? FLAG_TEXT_OPAQUE : "",
FLAG_TEXT_COLOUR,
rgbFromColour(colour),
FLAG_BACKCOLOUR,
rgbFromColour(bg_colour),
TEXT_HALIGN_FLAGS[halign],
TEXT_VALIGN_FLAGS[valign],
};
if (!font.isEmpty()) {
args.append({FLAG_FONT, quote(font)});
}
return args;
}
Video::Video(
const QPoint& pos,
const QString& filename,
bool loop,
VideoPlayMode playmode,
int width,
int height
) :
pos(pos),
filename(filename),
loop(loop),
playmode(playmode),
width(width),
height(height)
{
}
QStringList Video::options() const
{
return QStringList{
VAL_OBJTYPE_VIDEO,
pointCoordinates(pos),
quote(filename),
loop ? FLAG_LOOP : FLAG_VIDEO_NOLOOP,
VIDEO_PLAYMODE_FLAGS[playmode],
FLAG_WIDTH,
QString::number(width),
FLAG_HEIGHT,
QString::number(height),
play_audio ? FLAG_VIDEO_AUDIO : FLAG_VIDEO_NOAUDIO,
HALIGN_FLAGS[halign],
VALIGN_FLAGS[valign],
FLAG_BACKCOLOUR,
rgbFromColour(bg_colour),
};
}
// ============================================================================
// Helper functions
// ============================================================================
bool onOffToBoolean(const QString& msg)
{
return msg == VAL_ON;
}
QString quote(const QString& s)
{
return QUOTE + s + QUOTE; // suboptimal! Doesn't escape quotes.
// Mind you, don't think Whisker deals with that anyway.
}
QString msgFromArgs(const QStringList& args)
{
QStringList nonempty_args;
for (const QString& arg : args) {
if (!arg.isEmpty()) {
nonempty_args.append(arg);
}
}
return nonempty_args.join(SPACE);
}
QString rgbFromColour(const QColor& colour)
{
return QString("%1 %2 %3")
.arg(
QString::number(colour.red()),
QString::number(colour.green()),
QString::number(colour.blue())
);
}
QString pointCoordinates(const QPoint& point)
{
return QString("%1 %2").arg(
QString::number(point.x()), QString::number(point.y())
);
}
QString rectCoordinates(const QRect& rect)
{
return QString("%1 %2 %3 %4")
.arg(
QString::number(rect.left()),
QString::number(rect.top()),
QString::number(rect.right()),
QString::number(rect.bottom())
);
}
QString sizeCoordinates(const QSize& size)
{
return QString("%1 %2").arg(
QString::number(size.width()), QString::number(size.height())
);
}
} // namespace whiskerapi