/*******************************************************************************
* FaceTrackNoIR This program is a private project of the some enthusiastic
* gamers from Holland, who don't like to pay much for
* head-tracking.
*
* Copyright (C) 2011 Wim Vriend (Developing)
* Ron Hendriks (Researching and Testing)
*
* Homepage
*
* 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 .
*********************************************************************************/
/* Copyright (c) 2013-2015, 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 "ui.h"
#include "opentrack/tracker.h"
#include "opentrack/options.hpp"
#include "facetracknoir/new_file_dialog.h"
#include
#include
#include
#ifndef _WIN32
# include
#else
# include
#endif
MainWindow::MainWindow() :
pose_update_timer(this),
kbd_quit(QKeySequence("Ctrl+Q"), this),
no_feed_pixmap(":/images/no-feed.png")
{
ui.setupUi(this);
setFixedSize(size());
updateButtonState(false, false);
ui.video_frame_label->setPixmap(no_feed_pixmap);
connect(ui.btnEditCurves, SIGNAL(clicked()), this, SLOT(showCurveConfiguration()));
connect(ui.btnShortcuts, SIGNAL(clicked()), this, SLOT(showKeyboardShortcuts()));
connect(ui.btnShowEngineControls, SIGNAL(clicked()), this, SLOT(showTrackerSettings()));
connect(ui.btnShowServerControls, SIGNAL(clicked()), this, SLOT(showProtocolSettings()));
connect(ui.btnShowFilterControls, SIGNAL(clicked()), this, SLOT(showFilterSettings()));
modules.filters().push_front(std::make_shared("", dylib::Filter));
for (auto x : modules.trackers())
ui.iconcomboTrackerSource->addItem(x->icon, x->name);
for (auto x : modules.protocols())
ui.iconcomboProtocol->addItem(x->icon, x->name);
for (auto x : modules.filters())
ui.iconcomboFilter->addItem(x->icon, x->name);
refresh_config_list();
tie_setting(s.tracker_dll, ui.iconcomboTrackerSource);
tie_setting(s.protocol_dll, ui.iconcomboProtocol);
tie_setting(s.filter_dll, ui.iconcomboFilter);
connect(ui.iconcomboTrackerSource,
static_cast(&QComboBox::currentIndexChanged),
[&](int) -> void { if (pTrackerDialog) pTrackerDialog = nullptr; save(); });
connect(ui.iconcomboProtocol,
static_cast(&QComboBox::currentIndexChanged),
[&](int) -> void { if (pProtocolDialog) pProtocolDialog = nullptr; save(); });
connect(ui.iconcomboFilter,
static_cast(&QComboBox::currentIndexChanged),
[&](int) -> void { if (pFilterDialog) pFilterDialog = nullptr; save(); });
connect(ui.btnStartTracker, SIGNAL(clicked()), this, SLOT(startTracker()));
connect(ui.btnStopTracker, SIGNAL(clicked()), this, SLOT(stopTracker()));
connect(ui.iconcomboProfile, SIGNAL(currentIndexChanged(int)), this, SLOT(profileSelected(int)));
connect(&pose_update_timer, SIGNAL(timeout()), this, SLOT(showHeadPose()));
connect(&kbd_quit, SIGNAL(activated()), this, SLOT(exit()));
auto menu = new QMenu;
menu->addAction("Create new empty config", this, SLOT(make_empty_config()));
menu->addAction("Create new copied config", this, SLOT(make_copied_config()));
menu->addAction("Open configuration directory", this, SLOT(open_config_directory()));
menu->addAction("Refresh configuration list", this, SLOT(refresh_config_list()));
ui.profile_button->setMenu(menu);
kbd_quit.setEnabled(true);
connect(&det_timer, SIGNAL(timeout()), this, SLOT(maybe_start_profile_from_executable()));
det_timer.start(1000);
ensure_tray();
set_working_directory();
if (!QFile(group::ini_pathname()).exists())
{
set_profile(OPENTRACK_DEFAULT_CONFIG);
const auto pathname = group::ini_pathname();
if (!QFile(pathname).exists())
{
QFile file(pathname);
if (file.open(QFile::ReadWrite))
{
QTextStream stream(&file);
stream << "\n";
}
}
}
}
bool MainWindow::get_new_config_name_from_dialog(QString& ret)
{
new_file_dialog dlg;
dlg.exec();
bool b = dlg.is_ok(ret);
if (b && !ret.endsWith(".ini"))
ret += ".ini";
return b;
}
MainWindow::~MainWindow()
{
if (tray)
tray->hide();
stopTracker();
save();
}
void MainWindow::set_working_directory()
{
QDir::setCurrent(QCoreApplication::applicationDirPath());
}
void MainWindow::save_mappings() {
pose.save_mappings();
}
void MainWindow::save() {
s.b->save();
save_mappings();
mem settings = group::ini_file();
settings->sync();
#if defined(__unix) || defined(__linux)
QString currentFile = group::ini_pathname();
QByteArray bytes = QFile::encodeName(currentFile);
const char* filename_as_asciiz = bytes.constData();
if (access(filename_as_asciiz, R_OK | W_OK))
{
QMessageBox::warning(this, "Something went wrong", "Check permissions and ownership for your .ini file!", QMessageBox::Ok, QMessageBox::NoButton);
}
#endif
}
void MainWindow::load_mappings() {
pose.load_mappings();
}
void MainWindow::load_settings() {
s.b->reload();
load_mappings();
}
void MainWindow::make_empty_config()
{
QString name;
const QString dir = group::ini_directory();
if (dir != "" && get_new_config_name_from_dialog(name))
{
QFile filename(dir + "/" + name);
if (filename.open(QFile::ReadWrite))
{
QTextStream stream(&filename);
stream << "\n";
refresh_config_list();
}
}
}
void MainWindow::make_copied_config()
{
const QString dir = group::ini_directory();
const QString cur = group::ini_pathname();
QString name;
if (cur != "" && dir != "" && get_new_config_name_from_dialog(name))
{
const QString new_name = dir + "/" + name;
(void) QFile::remove(new_name);
(void) QFile::copy(cur, new_name);
refresh_config_list();
}
}
void MainWindow::open_config_directory()
{
const QString path = group::ini_directory();
if (path != "")
{
QDesktopServices::openUrl("file:///" + QDir::toNativeSeparators(path));
}
}
extern "C" volatile const char* opentrack_version;
void MainWindow::refresh_config_list()
{
if (group::ini_list().size() == 0)
{
QFile filename(group::ini_directory() + "/" OPENTRACK_DEFAULT_CONFIG);
if (filename.open(QFile::ReadWrite))
{
QTextStream stream(&filename);
stream << "\n";
}
}
QStringList ini_list = group::ini_list();
set_title();
QString current = QFileInfo(group::ini_pathname()).fileName();
ui.iconcomboProfile->clear();
for (auto x : ini_list)
ui.iconcomboProfile->addItem(QIcon(":/images/settings16.png"), x);
ui.iconcomboProfile->setCurrentText(current);
}
void MainWindow::updateButtonState(bool running, bool inertialp)
{
bool not_running = !running;
ui.iconcomboProfile->setEnabled ( not_running );
ui.btnStartTracker->setEnabled ( not_running );
ui.btnStopTracker->setEnabled ( running );
ui.iconcomboProtocol->setEnabled ( not_running );
ui.iconcomboFilter->setEnabled ( not_running );
ui.iconcomboTrackerSource->setEnabled(not_running);
ui.video_frame_label->setVisible(not_running || inertialp);
ui.profile_button->setEnabled(not_running);
}
void MainWindow::bindKeyboardShortcuts()
{
if (work)
work->reload_shortcuts();
ensure_tray();
}
void MainWindow::startTracker() {
s.b->save();
load_settings();
// tracker dtor needs run first
work = nullptr;
libs = SelectedLibraries(ui.video_frame, current_tracker(), current_protocol(), current_filter());
{
double p[6] = {0,0,0, 0,0,0};
display_pose(p, p);
}
if (!libs.correct)
{
QMessageBox::warning(this, "Library load error",
"One of libraries failed to load. Check installation.",
QMessageBox::Ok,
QMessageBox::NoButton);
libs = SelectedLibraries();
return;
}
work = std::make_shared(s, pose, libs, this, winId());
bindKeyboardShortcuts();
if (pTrackerDialog)
pTrackerDialog->register_tracker(libs.pTracker.get());
if (pFilterDialog)
pFilterDialog->register_filter(libs.pFilter.get());
if (pProtocolDialog)
pProtocolDialog->register_protocol(libs.pProtocol.get());
pose_update_timer.start(50);
// NB check valid since SelectedLibraries ctor called
// trackers take care of layout state updates
const bool is_inertial = ui.video_frame->layout() == nullptr;
updateButtonState(true, is_inertial);
}
void MainWindow::stopTracker( ) {
//ui.game_name->setText("Not connected");
pose_update_timer.stop();
ui.pose_display->rotateBy(0, 0, 0, 0, 0, 0);
if (pTrackerDialog)
pTrackerDialog->unregister_tracker();
if (pProtocolDialog)
pProtocolDialog->unregister_protocol();
if (pFilterDialog)
pFilterDialog->unregister_filter();
work = nullptr;
libs = SelectedLibraries();
{
double p[6] = {0,0,0, 0,0,0};
display_pose(p, p);
}
updateButtonState(false, false);
set_title();
}
void MainWindow::display_pose(const double *mapped, const double *raw)
{
ui.pose_display->rotateBy(mapped[Yaw], mapped[Pitch], mapped[Roll],
mapped[TX], mapped[TY], mapped[TZ]);
if (mapping_widget)
mapping_widget->update();
double mapped_[6], raw_[6];
for (int i = 0; i < 6; i++)
{
mapped_[i] = (int) mapped[i];
raw_[i] = (int) raw[i];
}
ui.raw_x->display(raw_[TX]);
ui.raw_y->display(raw_[TY]);
ui.raw_z->display(raw_[TZ]);
ui.raw_yaw->display(raw_[Yaw]);
ui.raw_pitch->display(raw_[Pitch]);
ui.raw_roll->display(raw_[Roll]);
ui.pose_x->display(mapped_[TX]);
ui.pose_y->display(mapped_[TY]);
ui.pose_z->display(mapped_[TZ]);
ui.pose_yaw->display(mapped_[Yaw]);
ui.pose_pitch->display(mapped_[Pitch]);
ui.pose_roll->display(mapped_[Roll]);
QString game_title;
if (libs.pProtocol)
game_title = libs.pProtocol->game_name();
set_title(game_title);
}
void MainWindow::set_title(const QString& game_title_)
{
QString game_title;
if (game_title_ != "")
game_title = " :: " + game_title_;
QString current = QFileInfo(group::ini_pathname()).fileName();
setWindowTitle(const_cast(opentrack_version) + QStringLiteral(" :: ") + current + game_title);
}
void MainWindow::showHeadPose()
{
double mapped[6], raw[6];
work->tracker->get_raw_and_mapped_poses(mapped, raw);
display_pose(mapped, raw);
#if 0
if (libs.pProtocol)
{
const QString name = libs.pProtocol->game_name();
ui.game_name->setText(name);
}
#endif
}
template
bool mk_dialog(mem lib, mem& orig)
{
if (orig && orig->isVisible())
{
orig->show();
orig->raise();
return false;
}
if (lib && lib->Dialog)
{
auto dialog = mem(reinterpret_cast(lib->Dialog()));
dialog->setWindowFlags(Qt::Dialog);
dialog->setFixedSize(dialog->size());
orig = dialog;
dialog->show();
dialog->raise();
QObject::connect(dialog.get(), &BaseDialog::closing, [&]() -> void { orig = nullptr; });
return true;
}
return false;
}
void MainWindow::showTrackerSettings()
{
if (mk_dialog(current_tracker(), pTrackerDialog) && libs.pTracker)
pTrackerDialog->register_tracker(libs.pTracker.get());
}
void MainWindow::showProtocolSettings() {
if (mk_dialog(current_protocol(), pProtocolDialog) && libs.pProtocol)
pProtocolDialog->register_protocol(libs.pProtocol.get());
}
void MainWindow::showFilterSettings() {
if (mk_dialog(current_filter(), pFilterDialog) && libs.pFilter)
pFilterDialog->register_filter(libs.pFilter.get());
}
template
bool mk_window(mem* place, Args... params)
{
if (*place && (*place)->isVisible())
{
(*place)->show();
(*place)->raise();
return false;
}
else
{
*place = std::make_shared(params...);
(*place)->setWindowFlags(Qt::Dialog);
(*place)->show();
(*place)->raise();
return true;
}
}
void MainWindow::showKeyboardShortcuts() {
if (mk_window(&shortcuts_widget))
connect(shortcuts_widget.get(), SIGNAL(reload()), this, SLOT(bindKeyboardShortcuts()));
}
void MainWindow::showCurveConfiguration() {
mk_window(&mapping_widget, pose, s);
}
void MainWindow::exit() {
QCoreApplication::exit(0);
}
void MainWindow::profileSelected(int index)
{
if (index == -1)
return;
{
QSettings settings(OPENTRACK_ORG);
settings.setValue (OPENTRACK_CONFIG_FILENAME_KEY, ui.iconcomboProfile->itemText(index));
}
set_title();
load_settings();
}
void MainWindow::shortcutRecentered()
{
qDebug() << "Center";
if (work)
work->tracker->center();
}
void MainWindow::shortcutToggled()
{
qDebug() << "Toggle";
if (work)
work->tracker->toggle_enabled();
}
void MainWindow::shortcutZeroed()
{
qDebug() << "Zero";
if (work)
work->tracker->zero();
}
void MainWindow::ensure_tray()
{
if (tray)
tray->hide();
tray = nullptr;
if (s.tray_enabled)
{
tray = std::make_shared(this);
tray->setIcon(QIcon(":/images/facetracknoir.png"));
tray->show();
connect(tray.get(), SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
this, SLOT(restore_from_tray(QSystemTrayIcon::ActivationReason)));
}
}
void MainWindow::restore_from_tray(QSystemTrayIcon::ActivationReason)
{
show();
setWindowState(Qt::WindowNoState);
}
void MainWindow::changeEvent(QEvent* e)
{
if (s.tray_enabled && e->type() == QEvent::WindowStateChange && (windowState() & Qt::WindowMinimized))
{
if (!tray)
ensure_tray();
hide();
}
QMainWindow::changeEvent(e);
}
void MainWindow::maybe_start_profile_from_executable()
{
if (!work)
{
QString prof;
if (det.config_to_start(prof))
{
ui.iconcomboProfile->setCurrentText(prof);
startTracker();
}
}
else
{
if (det.should_stop())
stopTracker();
}
}
void MainWindow::set_profile(const QString &profile)
{
QSettings settings(OPENTRACK_ORG);
settings.setValue(OPENTRACK_CONFIG_FILENAME_KEY, profile);
}