15.1.343. tablet_qt/menu/whiskertestmenu.cpp¶
/*
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 "whiskertestmenu.h"
#include "common/uiconst.h"
#include "dialogs/logbox.h"
#include "lib/datetime.h"
#include "lib/uifunc.h"
#include "menulib/menuitem.h"
#include "questionnairelib/quboolean.h"
#include "questionnairelib/questionnaire.h"
#include "questionnairelib/questionnairefunc.h"
#include "questionnairelib/quhorizontalline.h"
#include "questionnairelib/qulineedit.h"
#include "questionnairelib/qulineeditinteger.h"
#include "questionnairelib/qupage.h"
#include "whisker/whiskerconstants.h"
#include "whisker/whiskerinboundmessage.h"
#include "whisker/whiskermanager.h"
// ----------------------------------------------------------------------------
// Constants for Whisker test task
// ----------------------------------------------------------------------------
// Line aliases
const QString digital_input("digital_input");
const QString digital_output("digital_output");
// Display, document
const QString main_display("main");
const QString second_display("virtualdisplay");
const QString doc("doc");
// Some demo objects
const QString text_obj = "objtext";
const QString bmp_obj_1("objbitmap1");
const QString bmp_obj_2("objbitmap2");
const QString line_obj("objline");
const QString arc_obj("objarc");
const QString bezier_obj("objbez");
const QString chord_obj("objchord");
const QString ellipse_obj("names_are_unimportant");
const QString pie_obj("objpie");
const QString polygon_obj_1("objpoly1");
const QString polygon_obj_2("objpoly2");
const QString rectangle_obj("objrect");
const QString roundrect_obj("objrr");
const QString camcogquadpattern_obj("camcogquadpattern");
const QString video_obj_1("vid1");
const QString video_obj_2("vid2");
const QString video_obj_both("vidboth");
// Test events.
const QString background_event("background");
const QString event_bmp_1("Bitmap_1");
const QString event_bmp_2("Bitmap_2");
const QString event_ellipse("Ellipse");
const QString event_roundrect("RoundRect");
const QString event_rectangle("Rectangle");
// const QString event_text("Text");
const QString event_polygon_1("Polygon_1");
const QString event_polygon_2("Polygon_2");
const QString event_chord("Chord");
const QString event_pie("Pie");
const QString event_camcogquadpattern("camcogquadpattern");
const QString suffix_event_clicked(" clicked");
const QString suffix_event_unclicked(" mouseup");
const QString suffix_event_touched(" touched");
const QString suffix_event_released(" released");
// const QString suffix_event_double_clicked(" double-clicked");
const QString suffix_event_mouse_moved(" mouse-moved");
const QString suffix_event_touch_moved(" touch-moved");
const QString event_video_1_touched("vid1touched");
const QString event_video_2_touched("vid2touched");
const QString suffix_event_video_play("play");
const QString suffix_event_video_pause("pause");
const QString suffix_event_video_stop("stop");
const QString suffix_event_video_back("back");
const QString suffix_event_video_fwd("fwd");
// Timer/line events
const QString event_single_tick("0.5Hz_tick_single");
const QString event_infinite_tick("0.2Hz_tick_infinite");
const QString event_counted_tick("1Hz_tick_5count");
const QString event_input_on("digital_input_on");
const QString event_input_off("digital_input_off");
// Timings
const unsigned int n_counted_ticks = 5;
const unsigned int single_tick_period_ms = 500;
const unsigned int infinite_tick_period_ms = 5000;
const unsigned int counted_tick_period_ms = 1000;
const int FIVE_SEC = 5000;
const unsigned int n_flashes = 10;
const unsigned int flash_on_ms = 300;
const unsigned int flash_off_ms = 700;
// Other
const QString DEFAULT_MEDIA_DIR(R"(C:\Program Files (x86)\WhiskerControl\Server Test Media)");
WhiskerTestMenu::WhiskerTestMenu(CamcopsApp& app) :
MenuWindow(app, uifunc::iconFilename(uiconst::ICON_WHISKER)),
m_whisker(nullptr),
m_logbox(nullptr),
m_host("localhost"),
m_main_port(whiskerconstants::WHISKER_DEFAULT_PORT),
m_display_num(0),
m_use_video(true),
m_use_two_videos(true),
m_media_directory(DEFAULT_MEDIA_DIR),
m_bmp_filename_1("Coffee.bmp"),
m_bmp_filename_2("santa_fe.bmp"),
m_video_filename_1("mediaexample.wmv"),
m_video_filename_2("mediaexample.wmv"),
m_input_line_num(0),
m_output_line_num(8)
{
}
QString WhiskerTestMenu::title() const
{
return tr("Test interface to Whisker");
}
void WhiskerTestMenu::makeItems()
{
m_items = {
MenuItem(
tr("Configure demo Whisker task"),
MenuItem::OpenableWidgetMaker(
std::bind(&WhiskerTestMenu::configureWhisker, this,
std::placeholders::_1)
)
),
MenuItem(tr("Connect to Whisker server"),
std::bind(&WhiskerTestMenu::connectWhisker, this)),
MenuItem(tr("Disconnect from Whisker server"),
std::bind(&WhiskerTestMenu::disconnectWhisker, this)),
MenuItem(tr("Test network latency to Whisker server"),
std::bind(&WhiskerTestMenu::testWhiskerNetworkLatency, this)),
MenuItem(
tr("Run demo Whisker task"),
std::bind(&WhiskerTestMenu::runDemoWhiskerTask, this)
),
};
}
void WhiskerTestMenu::ensureWhiskerManager()
{
if (!m_whisker) {
m_whisker = new WhiskerManager(this);
}
}
void WhiskerTestMenu::connectWhisker()
{
ensureWhiskerManager();
m_whisker->connectToServer(m_host.toString(),
static_cast<quint16>(m_main_port.toUInt()));
}
void WhiskerTestMenu::disconnectWhisker()
{
ensureWhiskerManager();
m_whisker->disconnectFromServer();
}
void WhiskerTestMenu::ensureWhiskerConnected()
{
ensureWhiskerManager();
if (!m_whisker->isConnected()) {
connectWhisker();
}
}
void WhiskerTestMenu::testWhiskerNetworkLatency()
{
ensureWhiskerManager();
if (!m_whisker->isConnected()) {
m_whisker->alertNotConnected();
return;
}
int latency_ms = m_whisker->getNetworkLatencyMs();
uifunc::alert(
tr("Network latency: %1 ms").arg(latency_ms),
whiskerconstants::WHISKER_ALERT_TITLE);
}
OpenableWidget* WhiskerTestMenu::configureWhisker(CamcopsApp& app)
{
auto makeTitle = [](const QString& part1, const QString& part2) -> QString {
return QString("<b>%1</b> (%2):").arg(part1, part2);
};
auto makeHint = [](const QString& part1, const QString& part2) -> QString {
return QString("%1 (%2)").arg(part1, part2);
};
app.clearCachedVars(); // ... in case any are left over
FieldRef::GetterFunction get_host = std::bind(
&WhiskerTestMenu::getValue, this, &m_host);
FieldRef::SetterFunction set_host = std::bind(
&WhiskerTestMenu::setValue, this, &m_host, std::placeholders::_1);
FieldRefPtr host_fr(new FieldRef(get_host, set_host, true));
const QString host_t(tr("Whisker host"));
const QString host_h(tr("host name or IP address; default: localhost"));
FieldRef::GetterFunction get_port = std::bind(
&WhiskerTestMenu::getValue, this, &m_main_port);
FieldRef::SetterFunction set_port = std::bind(
&WhiskerTestMenu::setValue, this, &m_main_port, std::placeholders::_1);
FieldRefPtr port_fr(new FieldRef(get_port, set_port, true));
const QString port_t(tr("Whisker main port"));
const QString port_h(tr("default 3233"));
FieldRef::GetterFunction get_display_num = std::bind(
&WhiskerTestMenu::getValue, this, &m_display_num);
FieldRef::SetterFunction set_display_num = std::bind(
&WhiskerTestMenu::setValue, this, &m_display_num, std::placeholders::_1);
FieldRefPtr display_num_fr(new FieldRef(get_display_num, set_display_num, true));
const QString display_num_t(tr("Whisker display number"));
const QString display_num_h(tr("e.g. 0"));
FieldRef::GetterFunction get_use_video = std::bind(
&WhiskerTestMenu::getValue, this, &m_use_video);
FieldRef::SetterFunction set_use_video = std::bind(
&WhiskerTestMenu::setValue, this, &m_use_video, std::placeholders::_1);
FieldRefPtr use_video_fr(new FieldRef(get_use_video, set_use_video, true));
const QString use_video_t(tr("Use video"));
FieldRef::GetterFunction get_use_two_videos = std::bind(
&WhiskerTestMenu::getValue, this, &m_use_two_videos);
FieldRef::SetterFunction set_use_two_videos = std::bind(
&WhiskerTestMenu::setValue, this, &m_use_two_videos, std::placeholders::_1);
FieldRefPtr use_two_videos_fr(new FieldRef(get_use_two_videos, set_use_two_videos, true));
const QString use_two_videos_t(tr("Use two videos"));
FieldRef::GetterFunction get_media_directory = std::bind(
&WhiskerTestMenu::getValue, this, &m_media_directory);
FieldRef::SetterFunction set_media_directory = std::bind(
&WhiskerTestMenu::setValue, this, &m_media_directory, std::placeholders::_1);
FieldRefPtr media_directory_fr(new FieldRef(get_media_directory, set_media_directory, true));
const QString media_directory_t(tr("Media directory"));
const QString media_directory_h(tr("(e.g. ") + DEFAULT_MEDIA_DIR);
FieldRef::GetterFunction get_bmp_filename_1 = std::bind(
&WhiskerTestMenu::getValue, this, &m_bmp_filename_1);
FieldRef::SetterFunction set_bmp_filename_1 = std::bind(
&WhiskerTestMenu::setValue, this, &m_bmp_filename_1, std::placeholders::_1);
FieldRefPtr bmp_filename_1_fr(new FieldRef(get_bmp_filename_1, set_bmp_filename_1, true));
const QString bmp_filename_1_t(tr("Bitmap (.BMP) filename 1"));
FieldRef::GetterFunction get_bmp_filename_2 = std::bind(
&WhiskerTestMenu::getValue, this, &m_bmp_filename_2);
FieldRef::SetterFunction set_bmp_filename_2 = std::bind(
&WhiskerTestMenu::setValue, this, &m_bmp_filename_2, std::placeholders::_1);
FieldRefPtr bmp_filename_2_fr(new FieldRef(get_bmp_filename_2, set_bmp_filename_2, true));
const QString bmp_filename_2_t(tr("Bitmap (.BMP) filename 2"));
FieldRef::GetterFunction get_video_filename_1 = std::bind(
&WhiskerTestMenu::getValue, this, &m_video_filename_1);
FieldRef::SetterFunction set_video_filename_1 = std::bind(
&WhiskerTestMenu::setValue, this, &m_video_filename_1, std::placeholders::_1);
FieldRefPtr video_filename_1_fr(new FieldRef(get_video_filename_1, set_video_filename_1, true));
const QString video_filename_1_t(tr("Video (.WMV) filename 1"));
FieldRef::GetterFunction get_video_filename_2 = std::bind(
&WhiskerTestMenu::getValue, this, &m_video_filename_2);
FieldRef::SetterFunction set_video_filename_2 = std::bind(
&WhiskerTestMenu::setValue, this, &m_video_filename_2, std::placeholders::_1);
FieldRefPtr video_filename_2_fr(new FieldRef(get_video_filename_2, set_video_filename_2, true));
const QString video_filename_2_t(tr("Video (.WMV) filename 2"));
FieldRef::GetterFunction get_input_line_num = std::bind(
&WhiskerTestMenu::getValue, this, &m_input_line_num);
FieldRef::SetterFunction set_input_line_num = std::bind(
&WhiskerTestMenu::setValue, this, &m_input_line_num, std::placeholders::_1);
FieldRefPtr input_line_num_fr(new FieldRef(get_input_line_num, set_input_line_num, true));
const QString input_line_num_t(tr("Digital input line number"));
const QString input_line_num_h(tr("e.g. 0"));
FieldRef::GetterFunction get_output_line_num = std::bind(
&WhiskerTestMenu::getValue, this, &m_output_line_num);
FieldRef::SetterFunction set_output_line_num = std::bind(
&WhiskerTestMenu::setValue, this, &m_output_line_num, std::placeholders::_1);
FieldRefPtr output_line_num_fr(new FieldRef(get_output_line_num, set_output_line_num, true));
const QString output_line_num_t(tr("Digital output line number"));
const QString output_line_num_h(tr("e.g. 8"));
const int max_display_num = 1000; // silly
const int max_line_num = 1000; // probably silly
QuPagePtr page(new QuPage{
questionnairefunc::defaultGridRawPointer({
{
makeTitle(host_t, host_h),
(new QuLineEdit(host_fr))->setHint(makeHint(host_t, host_h))
},
{
makeTitle(port_t, port_h),
new QuLineEditInteger(port_fr,
uiconst::IP_PORT_MIN, uiconst::IP_PORT_MAX)
},
}, 1, 1),
new QuHorizontalLine(),
questionnairefunc::defaultGridRawPointer({
{
makeTitle(display_num_t, display_num_h),
new QuLineEditInteger(display_num_fr, 0, max_display_num)
},
{
"",
(new QuBoolean(use_video_t, use_video_fr))->setAsTextButton(),
},
{
"",
(new QuBoolean(use_two_videos_t, use_two_videos_fr))->setAsTextButton(),
},
{
media_directory_t,
(new QuLineEdit(media_directory_fr))->setHint(media_directory_h)
},
{
bmp_filename_1_t,
new QuLineEdit(bmp_filename_1_fr)
},
{
bmp_filename_2_t,
new QuLineEdit(bmp_filename_2_fr)
},
{
video_filename_1_t,
new QuLineEdit(video_filename_1_fr)
},
{
video_filename_2_t,
new QuLineEdit(video_filename_2_fr)
},
{
makeTitle(input_line_num_t, input_line_num_h),
new QuLineEditInteger(input_line_num_fr, 0, max_line_num)
},
{
makeTitle(output_line_num_t, output_line_num_h),
new QuLineEditInteger(output_line_num_fr, 0, max_line_num)
},
}, 1, 1),
});
page->setTitle(tr("Configure Whisker demo task"));
page->setType(QuPage::PageType::Config);
auto questionnaire = new Questionnaire(m_app, {page});
connect(questionnaire, &Questionnaire::completed,
&app, &CamcopsApp::saveCachedVars);
connect(questionnaire, &Questionnaire::cancelled,
&app, &CamcopsApp::clearCachedVars);
return questionnaire;
}
void WhiskerTestMenu::runDemoWhiskerTask()
{
status(tr("Starting demo Whisker task")); // ensures modal logbox
ensureWhiskerManager();
if (m_whisker->isConnected()) {
status(tr("Whisker server already connected."));
demoWhiskerTaskMain();
} else {
status(tr("Connecting to Whisker server..."));
connect(m_whisker.data(), &WhiskerManager::onFullyConnected,
this, &WhiskerTestMenu::demoWhiskerTaskMain,
Qt::UniqueConnection);
connectWhisker();
}
}
void WhiskerTestMenu::demoWhiskerTaskMain()
{
using namespace whiskerapi;
using namespace whiskerconstants;
status(tr("Setting up Whisker manager"));
WhiskerManager* w = m_whisker.data(); // for briefer notation
connect(w, &WhiskerManager::eventReceived,
this, &WhiskerTestMenu::eventReceived,
Qt::UniqueConnection);
connect(w, &WhiskerManager::keyEventReceived,
this, &WhiskerTestMenu::keyEventReceived,
Qt::UniqueConnection);
connect(w, &WhiskerManager::clientMessageReceived,
this, &WhiskerTestMenu::clientMessageReceived,
Qt::UniqueConnection);
connect(w, &WhiskerManager::warningReceived,
this, &WhiskerTestMenu::otherMessageReceived,
Qt::UniqueConnection);
connect(w, &WhiskerManager::syntaxErrorReceived,
this, &WhiskerTestMenu::otherMessageReceived,
Qt::UniqueConnection);
connect(w, &WhiskerManager::errorReceived,
this, &WhiskerTestMenu::otherMessageReceived,
Qt::UniqueConnection);
// ... all will autodisconnect when "this" is deleted, as the menu closes
// We follow DemoConsoleClientTask.cpp from the Whisker SDK:
// ------------------------------------------------------------------------
// Additional constants
// ------------------------------------------------------------------------
const bool ignore_reply = true;
const QColor black(0, 0, 0);
const QColor red(255, 0, 0);
const QColor green(0, 255, 0);
const QColor blue(0, 0, 255);
const QColor yellow(255, 255, 0);
const QColor palergreen(0, 200, 0);
const QColor darkred(100, 0, 0);
const QColor darkcyan(0, 100, 100);
const QColor darkyellow(100, 100, 0);
const QColor vdarkgrey(50, 50, 50);
// ------------------------------------------------------------------------
// Variables
// ------------------------------------------------------------------------
const unsigned int display_num = m_display_num.toUInt();
const bool use_video = m_use_video.toBool();
const bool use_two_videos = m_use_two_videos.toBool();
const QString media_directory = m_media_directory.toString();
const QString bmp_filename_1 = m_bmp_filename_1.toString();
const QString bmp_filename_2 = m_bmp_filename_2.toString();
const QString video_filename_1 = m_video_filename_1.toString();
const QString video_filename_2 = m_video_filename_2.toString();
const unsigned int input_line_num = m_input_line_num.toUInt();
const unsigned int output_line_num = m_output_line_num.toUInt();
// ------------------------------------------------------------------------
// Setup
// ------------------------------------------------------------------------
status(tr("Claiming devices and setting up display documents"));
w->lineClaim(input_line_num, false, digital_input, ResetState::Leave);
w->lineClaim(output_line_num, true, digital_output, ResetState::Leave);
w->displayClaim(display_num, main_display);
w->displayScaleDocuments(main_display, true, ignore_reply);
if (!use_video) {
DisplayCreationOptions dco;
w->displayCreateDevice(second_display, dco);
w->displayScaleDocuments(second_display, true, ignore_reply);
}
w->displayCreateDocument(doc, ignore_reply);
w->displaySetDocumentSize(doc, QSize(1600, 1200), ignore_reply);
w->displaySetBackgroundColour(doc, darkred, ignore_reply);
// ------------------------------------------------------------------------
// Simple objects
// ------------------------------------------------------------------------
status(tr("Creating simple display objects"));
Pen pen(1, yellow, PenStyle::Solid);
Brush brush(blue, darkcyan, true,
BrushStyle::Solid, BrushHatchStyle::BDiagonal);
w->displayAddObject(
doc, line_obj,
Line(QPoint(50, 50), QPoint(700, 700), pen),
ignore_reply);
w->displayAddObject(
doc, arc_obj,
Arc(QRect(100, 100, 300, 300),
QPoint(150, 100), QPoint(350, 100), pen),
ignore_reply);
w->displayAddObject(
doc, bezier_obj,
Bezier(QPoint(100, 100), QPoint(100, 400),
QPoint(400, 100), QPoint(400, 400), pen),
ignore_reply);
pen.width = 2;
w->displayAddObject(
doc, chord_obj,
Chord(QRect(300, 300, 200, 200),
QPoint(300, 350), QPoint(500, 350), pen, brush),
ignore_reply);
brush.colour = palergreen;
w->displayAddObject(
doc, ellipse_obj,
Ellipse(QRect(650, 100, 100, 300), pen, brush),
ignore_reply);
w->displayAddObject(
doc, pie_obj,
Pie(QRect(600, 300, 200, 200),
QPoint(800, 300), QPoint(800, 500), pen, brush),
ignore_reply);
brush.style = BrushStyle::Hatched;
brush.opaque = false;
w->displayAddObject(
doc, rectangle_obj,
Rectangle(QRect(150, 450, 100, 100), pen, brush),
ignore_reply);
w->displayAddObject(
doc, roundrect_obj,
RoundRect(QRect(900, 450, 300, 100),
QSize(150, 150), pen, brush),
ignore_reply);
brush.hatch_style = BrushHatchStyle::FDiagonal;
brush.bg_colour = darkyellow;
w->displayAddObject(
doc, polygon_obj_1,
Polygon(
// triangle
QVector<QPoint>{{400, 500}, {600, 450}, {600, 550}},
pen, brush, false),
ignore_reply);
w->displayAddObject(
doc, polygon_obj_2,
Polygon(
// pentagram
QVector<QPoint>{{150, 425},
{75, 650},
{250, 500},
{50, 500},
{225, 650}},
pen, brush, false),
ignore_reply);
Text text(QPoint(50, 50), tr("CamCOPS Whisker demo"), 0, "Times New Roman");
text.italic = true;
w->displayAddObject(doc, text_obj, text, ignore_reply);
w->setMediaDirectory(media_directory, ignore_reply);
w->displayAddObject(
doc, bmp_obj_1,
Bitmap(QPoint(100, 100), bmp_filename_1),
ignore_reply);
w->displayAddObject(
doc, bmp_obj_2,
Bitmap(QPoint(200, 200), bmp_filename_2),
ignore_reply);
w->displayAddObject(
doc, camcogquadpattern_obj,
CamcogQuadPattern(
QPoint(350, 100),
QSize(10, 8),
QVector<uint8_t>{1, 2, 3, 4, 5, 6, 7, 8},
QVector<uint8_t>{9, 10, 11, 12, 13, 14, 15, 16},
QVector<uint8_t>{255, 254, 253, 252, 251, 250, 249, 248},
QVector<uint8_t>{247, 246, 245, 244, 243, 242, 241, 240},
red,
green,
blue,
yellow,
vdarkgrey
),
ignore_reply);
auto setDemoEvents = [&](const QString& obj,
const QString& eventstem) -> void {
w->displaySetEvent(doc, obj, DocEventType::MouseDown,
eventstem + suffix_event_clicked, ignore_reply);
w->displaySetEvent(doc, obj, DocEventType::MouseUp,
eventstem + suffix_event_unclicked, ignore_reply);
w->displaySetEvent(doc, obj, DocEventType::MouseMove,
eventstem + suffix_event_mouse_moved, ignore_reply);
w->displaySetEvent(doc, obj, DocEventType::TouchDown,
eventstem + suffix_event_touched, ignore_reply);
w->displaySetEvent(doc, obj, DocEventType::TouchUp,
eventstem + suffix_event_released, ignore_reply);
w->displaySetEvent(doc, obj, DocEventType::TouchMove,
eventstem + suffix_event_touch_moved, ignore_reply);
};
setDemoEvents(bmp_obj_1, event_bmp_1);
setDemoEvents(bmp_obj_2, event_bmp_2);
setDemoEvents(ellipse_obj, event_ellipse);
setDemoEvents(roundrect_obj, event_roundrect);
setDemoEvents(rectangle_obj, event_rectangle);
// setDemoEvents(strTextObj, event_text);
setDemoEvents(polygon_obj_1, event_polygon_1);
setDemoEvents(polygon_obj_2, event_polygon_2);
setDemoEvents(chord_obj, event_chord);
setDemoEvents(pie_obj, event_pie);
setDemoEvents(camcogquadpattern_obj, event_camcogquadpattern);
w->displaySetBackgroundEvent(doc, DocEventType::MouseDown,
background_event + suffix_event_clicked,
ignore_reply);
w->displaySetBackgroundEvent(doc, DocEventType::TouchDown,
background_event + suffix_event_touched,
ignore_reply);
// ------------------------------------------------------------------------
// Video
// ------------------------------------------------------------------------
status(tr("Creating video objects"));
auto setVideoDemoEvents = [&](const QString& obj,
const QString& touchevent) -> void {
w->displaySetEvent(doc, obj, DocEventType::MouseDown,
touchevent, ignore_reply);
w->displaySetEvent(doc, obj, DocEventType::TouchDown,
touchevent, ignore_reply);
};
auto createVideoCluster = [&](const QString& prefix, int top) -> void {
const QString strPlayObj(prefix + "play");
const QString strPauseObj(prefix + "pause");
const QString strStopObj(prefix + "stop");
const QString strBackObj(prefix + "back");
const QString strFwdObj(prefix + "fwd");
brush.style = BrushStyle::Solid;
brush.colour = blue;
w->displayAddObject(
doc, strPlayObj,
Polygon(QVector<QPoint>{QPoint(800, top),
QPoint(850, top + 25),
QPoint(800, top + 50)},
pen, brush),
ignore_reply);
brush.colour = black;
w->displayAddObject(
doc, strPauseObj,
Rectangle(QRect(900, top, 50, 50), pen, brush),
ignore_reply);
brush.colour = green;
w->displayAddObject(
doc, strPauseObj,
Rectangle(QRect(900, top, 15, 50), pen, brush),
ignore_reply);
w->displayAddObject(
doc, strPauseObj,
Rectangle(QRect(935, top, 15, 50), pen, brush),
ignore_reply);
brush.colour = red;
w->displayAddObject(
doc, strStopObj,
Rectangle(QRect(1000, top, 50, 50), pen, brush),
ignore_reply);
brush.colour = black;
w->displayAddObject(
doc, strBackObj,
Rectangle(QRect(1100, top, 50, 50), pen, brush),
ignore_reply);
brush.colour = black;
w->displayAddObject(
doc, strBackObj,
Polygon(QVector<QPoint>{QPoint(1125, top),
QPoint(1100, top + 25),
QPoint(1125, top + 50)},
pen, brush),
ignore_reply);
w->displayAddObject(
doc, strBackObj,
Polygon(QVector<QPoint>{QPoint(1150, top),
QPoint(1125, top + 25),
QPoint(1150, top + 50)},
pen, brush),
ignore_reply);
brush.colour = black;
w->displayAddObject(
doc, strFwdObj,
Rectangle(QRect(1200, top, 50, 50), pen, brush),
ignore_reply);
brush.colour = yellow;
w->displayAddObject(
doc, strFwdObj,
Polygon(QVector<QPoint>{QPoint(1200, top),
QPoint(1225, top + 25),
QPoint(1200, top + 50)},
pen, brush),
ignore_reply);
w->displayAddObject(
doc, strFwdObj,
Polygon(QVector<QPoint>{QPoint(1225, top),
QPoint(1250, top + 25),
QPoint(1225, top + 50)},
pen, brush),
ignore_reply);
setVideoDemoEvents(strPlayObj, prefix + suffix_event_video_play);
setVideoDemoEvents(strPauseObj, prefix + suffix_event_video_pause);
setVideoDemoEvents(strStopObj, prefix + suffix_event_video_stop);
setVideoDemoEvents(strFwdObj, prefix + suffix_event_video_fwd);
setVideoDemoEvents(strBackObj, prefix + suffix_event_video_back);
};
if (use_video) {
const QString strAudioDevice("audiodevice");
w->audioClaim(1, strAudioDevice);
// w->displaySetAudioDevice(0, 1);
w->displaySetAudioDevice(main_display, strAudioDevice, ignore_reply);
const int videotop1 = 50, videotop2 = 600;
bool bLoop = true;
w->displayAddObject(
doc, video_obj_1,
Video(QPoint(50, videotop1), video_filename_1, bLoop),
ignore_reply);
if (use_two_videos) {
w->displayAddObject(
doc, video_obj_2,
Video(QPoint(50, videotop2), video_filename_2, bLoop),
ignore_reply);
}
qDebug() << "~~~ Starting video 2 at 10s";
w->videoSeekAbsolute(doc, video_obj_2, 10000, ignore_reply);
w->videoTimestamps(true, ignore_reply);
createVideoCluster(video_obj_1, 50);
setVideoDemoEvents(video_obj_1, event_video_1_touched);
if (use_two_videos) {
createVideoCluster(video_obj_both, 300);
createVideoCluster(video_obj_2, 600);
setVideoDemoEvents(video_obj_2, event_video_2_touched);
}
}
// ------------------------------------------------------------------------
// OK; go.
// ------------------------------------------------------------------------
w->displayShowDocument(main_display, doc, ignore_reply);
if (!use_video) {
w->displayShowDocument(second_display, doc, ignore_reply);
}
w->displayKeyboardEvents(doc, KeyEventType::Both, false); // don't ignore this reply
w->lineSetEvent(digital_input, event_input_on, LineEventType::On, ignore_reply);
w->lineSetEvent(digital_input, event_input_off, LineEventType::Off, ignore_reply);
w->flashLinePulses(digital_output, n_flashes, flash_on_ms, flash_off_ms);
w->timerSetEvent(event_single_tick, single_tick_period_ms,
0, ignore_reply);
w->timerSetEvent(event_infinite_tick, infinite_tick_period_ms,
VAL_TIMER_INFINITE_RELOADS, ignore_reply);
const int counted_tick_reloads = n_counted_ticks - 1;
w->timerSetEvent(event_counted_tick, counted_tick_period_ms,
counted_tick_reloads, ignore_reply);
status(tr(
"All objects created. Try responding to display objects, providing "
"keyboard input, toggling digital input lines via Whisker."));
}
void WhiskerTestMenu::eventReceived(const WhiskerInboundMessage& msg)
{
ensureWhiskerManager();
WhiskerManager* w = m_whisker.data();
const QString event = msg.event();
stream() << "Received event: " << event;
// Video control:
auto reportVideoTimings = [&]() -> void {
unsigned int time = w->videoGetTimeMs(doc, video_obj_1);
unsigned int duration = w->videoGetDurationMs(doc, video_obj_1);
stream()
<< "video1 time: " << time
<< "; video1 duration: " << duration;
time = w->videoGetTimeMs(doc, video_obj_2);
duration = w->videoGetDurationMs(doc, video_obj_2);
stream()
<< "video2 time: " << time
<< "; video2 duration: " << duration;
};
if (event == event_video_1_touched) {
stream() << "~~~ Seeking video 1 forward 5s, playing video 1, pausing video 2";
w->videoSeekRelative(doc, video_obj_1, FIVE_SEC);
w->videoPlay(doc, video_obj_1);
w->videoPause(doc, video_obj_2);
reportVideoTimings();
} else if (event == event_video_2_touched) {
stream() << "~~~ Pausing video 1, seeking video 1 back 5s, playing video 2";
w->videoSeekRelative(doc, video_obj_1, -FIVE_SEC);
w->videoPlay(doc, video_obj_2);
w->videoPause(doc, video_obj_1);
reportVideoTimings();
} else if (event == video_obj_1 + suffix_event_video_play) {
stream() << "~~~ Playing video 1";
w->videoPlay(doc, video_obj_1);
} else if (event == video_obj_2 + suffix_event_video_play) {
stream() << "~~~ Playing video 2";
w->videoPlay(doc, video_obj_2);
} else if (event == video_obj_both + suffix_event_video_play) {
stream() << "~~~ Playing video 1+2";
w->videoPlay(doc, video_obj_1);
w->videoPlay(doc, video_obj_2);
} else if (event == video_obj_1 + suffix_event_video_pause) {
stream() << "~~~ Pausing video 1";
w->videoPause(doc, video_obj_1);
} else if (event == video_obj_2 + suffix_event_video_pause) {
stream() << "~~~ Pausing video 2";
w->videoPause(doc, video_obj_2);
} else if (event == video_obj_both + suffix_event_video_pause) {
stream() << "~~~ Pausing video 1+2";
w->videoPause(doc, video_obj_1);
w->videoPause(doc, video_obj_2);
} else if (event == video_obj_1 + suffix_event_video_stop) {
stream() << "~~~ Stopping video 1";
w->videoStop(doc, video_obj_1);
} else if (event == video_obj_2 + suffix_event_video_stop) {
stream() << "~~~ Stopping video 2";
w->videoStop(doc, video_obj_2);
} else if (event == video_obj_both + suffix_event_video_stop) {
stream() << "~~~ Stopping video 1+2";
w->videoStop(doc, video_obj_1);
w->videoStop(doc, video_obj_2);
} else if (event == video_obj_1 + suffix_event_video_fwd) {
stream() << "~~~ Fwding video 1";
w->videoSeekRelative(doc, video_obj_1, FIVE_SEC);
} else if (event == video_obj_2 + suffix_event_video_fwd) {
stream() << "~~~ Fwding video 2";
w->videoSeekRelative(doc, video_obj_2, FIVE_SEC);
} else if (event == video_obj_both + suffix_event_video_fwd) {
stream() << "~~~ Fwding video 1+2";
w->videoSeekRelative(doc, video_obj_1, FIVE_SEC);
w->videoSeekRelative(doc, video_obj_2, FIVE_SEC);
} else if (event == video_obj_1 + suffix_event_video_back) {
stream() << "~~~ Backing video 1";
w->videoSeekRelative(doc, video_obj_1, -FIVE_SEC);
} else if (event == video_obj_2 + suffix_event_video_back) {
stream() << "~~~ Backing video 2";
w->videoSeekRelative(doc, video_obj_2, -FIVE_SEC);
} else if (event == video_obj_both + suffix_event_video_back) {
stream() << "~~~ Backing video 1+2";
w->videoSeekRelative(doc, video_obj_1, -FIVE_SEC);
w->videoSeekRelative(doc, video_obj_2, -FIVE_SEC);
}
}
void WhiskerTestMenu::keyEventReceived(const WhiskerInboundMessage& msg)
{
const QString description = msg.keyEventDown()
? "down"
: (msg.keyEventUp() ? "up" : "?");
stream() << "Key event: keycode " << msg.keyEventCode()
<< ", " << description
<< " (from document " << msg.keyEventDoc() << ")";
}
void WhiskerTestMenu::clientMessageReceived(const WhiskerInboundMessage& msg)
{
stream() << "Client message from client "
<< msg.clientMessageSourceClientNum()
<< ": " << msg.clientMessage();
}
void WhiskerTestMenu::otherMessageReceived(const WhiskerInboundMessage& msg)
{
stream() << msg.message();
}
void WhiskerTestMenu::taskCancelled()
{
deleteLogBox();
if (m_whisker) {
m_whisker->disconnectServerAndSignals(this);
}
}
QVariant WhiskerTestMenu::getValue(const QVariant* member) const
{
return *member;
}
bool WhiskerTestMenu::setValue(QVariant* member, const QVariant& value)
{
const bool changed = value != *member;
if (changed) {
*member = value;
}
return changed;
}
void WhiskerTestMenu::ensureLogBox()
{
if (!m_logbox) {
m_logbox = new LogBox(this, tr("Whisker test task"), true);
m_logbox->setStyleSheet(
m_app.getSubstitutedCss(uiconst::CSS_CAMCOPS_MAIN));
m_logbox->useWaitCursor(false);
connect(m_logbox.data(), &LogBox::rejected,
this, &WhiskerTestMenu::taskCancelled,
Qt::UniqueConnection);
m_logbox->open();
}
}
void WhiskerTestMenu::deleteLogBox()
{
if (!m_logbox) {
return;
}
m_logbox->deleteLater();
m_logbox = nullptr;
}
void WhiskerTestMenu::status(const QString& msg)
{
ensureLogBox();
m_logbox->statusMessage(
QString("%1: %2").arg(datetime::nowTimestamp(), msg));
}
WhiskerTestMenu::StatusStream::StatusStream(WhiskerTestMenu& parent) :
QTextStream(&m_str),
m_parent(parent)
{
}
WhiskerTestMenu::StatusStream::~StatusStream()
{
// If inhering from std::stringstream:
// QString s = QString::fromStdString(str());
// m_parent.status(s);
//
// If inheriting from QTextStream:
flush();
m_parent.status(m_str);
}
WhiskerTestMenu::StatusStream WhiskerTestMenu::stream()
{
// Tricky, as std::stringstream has a deleted copy constructor.
// (So does QTextStream.)
// You can't do this:
// return StatusStream(*this);
// ... or you get:
// error: use of deleted function ‘WhiskerTestMenu::StatusStream::StatusStream(const WhiskerTestMenu::StatusStream&)’
//
// https://stackoverflow.com/questions/3442520/how-copy-from-one-stringstream-object-to-another-in-c
// https://stackoverflow.com/questions/7935639/can-we-return-objects-having-a-deleted-private-copy-move-constructor-by-value-fr
return {*this};
}