You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2367 lines
78 KiB

//------------------------------------------------------------------------------
//
// Project: Anonymizer
//
// Brno University of Technology
// Faculty of Information Technology
//
//------------------------------------------------------------------------------
//
// This project was financially supported by project VG20102015006 funds
// provided by Ministry of the Interior of the Czech republic.
//
//------------------------------------------------------------------------------
/*!
@file mainwindow.cpp
@brief Implementation of methods ...
@details Details
@authors Martin Borek (mborekcz@gmail.com)
@authors Filip Orsag (orsag@fit.vutbr.cz)
@date 2014-2015
@note This project was supported by MV CR project VG20102015006.
@copyright BUT OPEN SOURCE LICENCE (see License.txt)
*/
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "selection.h"
#include "colors.h"
#include "objectshape.h"
#include <cereal/archives/json.hpp>
#include <cereal/archives/xml.hpp>
#include <utility> // for std::pair
#include <fstream> // for cereal output
#include <QtGui>
#include <QDebug>
#include <QFileDialog>
#include <QPainter>
#include <QProgressDialog>
#include <QInputDialog>
#include <QColorDialog>
#include <QMessageBox>
#include <QMenu>
#include <QTranslator>
#include <QSplitter>
#include <QDockWidget>
#include <helpbrowser.h>
#define VIDEOTRACKING_10MS2S 100 // 1 second is 100*10 milliseconds
#define VIDEOTRACKING_TIMER_CONSTANT 1.5
#define VIDEOTRACKING_TIMER_CONSTANT_FAST 0.5
#define VIDEOTRACKING_TIMER_CONSTANT_SLOW 0.2
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
applicationName("Video Anonymizer"),
ui(new Ui::MainWindow)
{
settings = new QSettings("FIT", applicationName, this); // application and company name
qApp->setApplicationName(applicationName);
this->setWindowTitle(applicationName);
// TRANSLATOR - BEGINNING
QTranslator * translator = new QTranslator(this);
bool checkEnglish = false; // To find out what language should be checked in application menu
if (settings->contains("language"))
{ // load language
qDebug() << "Language loaded";
QString languageFile = settings->value("language").toString();
translator->load(languageFile);
if (languageFile.isEmpty()) // Empty string means default language (English) is set
checkEnglish = true;
}
else
{// set language according locale
qDebug() << "Language set according locale";
QString locale = QLocale::system().name();
if (!translator->load(QString("video_anonymizer_" + locale)))
checkEnglish = true; // Loading of language was not succesful, check English (default) option
else // English is default
checkEnglish = false;
}
// apply requested language
// must be done before setting ui (ui->setupUi) and setting any text in widgets
qApp->installTranslator(translator);
// TRANSLATOR - END
ui->setupUi(this);
// Set language menu
if (checkEnglish)
ui->actionEnglish->setChecked(true);
else
ui->actionCzech->setChecked(true);
tracker = nullptr; // Will be initialized when new video is loaded
timer = new QTimer(this); //Is set when play button clicked
// Should original video be showed?
if (settings->contains("showOriginalVideo"))
{ // Is showOriginalVideo variable set in settings?
showOriginalVideo = settings->value("showOriginalVideo").toBool();
}
else
{ // Not set => set default value
showOriginalVideo = true;
settings->setValue("showOriginalVideo", showOriginalVideo);
}
ui->originalVideoFrame->setVisible(showOriginalVideo);
// Display time / frame numbers
if (settings->contains("showTime"))
{ // Is displayTime variable set in settings?
qDebug() << "Time/Frame num loaded";
displayTime = settings->value("showTime").toBool();
}
else
{
displayTime = true;
settings->setValue("showTime", displayTime); // Not set => set default
}
initial_application_settings();
computingInfoText = tr("Computing positions of tracked object");
computingInfoTitle = tr("Computing");
//progressDialog = new QProgressDialog(tr("Tracking"), tr("Cancel"), 0, 0, this);
//progressDialog->setModal(true);
// Setup help data:
//helpEngine = new QHelpEngine("help/help_cs.qhc", this);
QString helpDir(QCoreApplication::applicationDirPath() + "/" + tr("help_en.qhc"));
qDebug() << "help:" << helpDir;
helpEngine = new QHelpEngine(helpDir, this);
if (!helpEngine->setupData())
{
qDebug() << "Help not loaded successfully";
qDebug() << helpEngine->error();
}
else
qDebug() << "Help loaded";
connect_signals(); // Connects GUI elements with slots
}
MainWindow::~MainWindow()
{
timer->stop(); // "timer" is deleted automatically; stopping is enough
//delete tracker; // is smart pointer => will be deleted automatically
delete ui;
qDebug() << "Destruct: MainWindow destroyed";
}
void MainWindow::closeEvent(QCloseEvent *event)
{
if (!ask_save_project())
event->ignore();
else
event->accept();
}
/** Using reload_video_label() instead
void MainWindow::resizeEvent(QResizeEvent *)
{
playerWidget->setGeometry(0, 0, ui->videoWidget->width(), ui->videoWidget->height());
ui->videoLabel->setGeometry(0, 0, ui->videoFrame->width(), ui->videoFrame->height());
timeLabel->setGeometry(0, 0, ui->timeWidget->width(), ui->timeWidget->height());
}
*/
void MainWindow::reload_video_label()
{
// make size of originalVideoFrame fixed and videoFrame will expand
ui->originalVideoFrame->setFixedWidth(ui->videosLayout->geometry().width() / 2.5);
if (tracker)
{
ui->videoLabel->set_image(frame, ui->videoFrame->width(), ui->videoFrame->height());
if (showOriginalVideo)
ui->originalVideoLabel->set_image(originalFrame, ui->originalVideoFrame->width(), ui->originalVideoFrame->height());
}
}
void MainWindow::pause()
{
isPlaying = false;
ui->playButton->setText(tr("Play"));
timer->stop();
}
/* Play / Pause */
void MainWindow::play()
{
if (isPlaying)
pause();
else
{
if (endOfVideo) // End of video => Start from beginning
show_first_frame();
isPlaying = true;
ui->playButton->setText(tr("Pause"));
if (!tracker)
{
qDebug() << "WARNING: play() called with no video loaded";
return;
}
timer->start(); // Displays new frames in given interval.
}
}
/* Stop playing, return to first frame and display it */
void MainWindow::stop()
{
isPlaying = false;
timer->stop();
ui->playButton->setText(tr("Play"));
ui->stepBackButton->setEnabled(true);
ui->positionSlider->setEnabled(true);
set_application_menu();
show_first_frame();
}
/* Select a file and open it */
void MainWindow::open_video()
{
if (tracker)
{ // Project exists, ask to save/discard
if (!ask_save_project())
return; // User canceled the dialog, do not open a new video
initial_application_settings(); // Restores default application settings
}
if (!open_video_dialog())
return; // Cancel button pressed
QProgressDialog progressDialog(tr("Opening video"), tr("Cancel"), 0, 0, this);
progressDialog.setWindowTitle(tr("Opening"));
progressDialog.setModal(true);
progressDialog.show();
try
{
tracker = std::unique_ptr<VideoTracker>(new VideoTracker(inputFileName, &progressDialog));
} catch (OpenException) {
QMessageBox *alert = new QMessageBox(this);
alert->setWindowTitle(tr("Opening video"));
alert->setText(tr("This video file cannot be opened."));
alert->exec();
return;
} catch (UserCanceledOpeningException) {
QMessageBox *alert = new QMessageBox(this);
alert->setWindowTitle(tr("Opening video"));
alert->setText(tr("Video opening was canceled."));
alert->exec();
return;
}
//progressDialog->reset();
open_video_successful();
}
bool MainWindow::open_video_dialog()
{
QFileDialog fileDialog(this);
fileDialog.setWindowTitle(tr("Open Video"));
if (settings->contains("lastPath"))
{ // begin with the directory where the last video was opened
QString lastPath = settings->value("lastPath").toString();
fileDialog.setDirectory(lastPath);
}
QStringList nameFilters;
// List of abbreviations considered to belong to video files
nameFilters << tr("Video files") + " (*.3g2 *.3gp *.asf *.avi *.flv *.m2v *.m4p *.m4v *.mkv *.mov *.mp2 *.mp4 "
"*.mpe *.mpeg *.mpg *.mpv *.ogg *.ogv *.qt *.rm *.rmvb *.vob *.webm *.wmv *.yuv)";
nameFilters << tr("All files") + " (*)";
fileDialog.setNameFilterDetailsVisible(false);
//fileDialog.setNameFilterDetailsVisible(true); //there is too many abbreviations to show
fileDialog.setNameFilters(nameFilters);
fileDialog.setFileMode(QFileDialog::ExistingFile);
fileDialog.setViewMode(QFileDialog::Detail);
if (fileDialog.exec() && !fileDialog.selectedFiles().isEmpty())
{
// save directory path for next opening:
inputFileName = fileDialog.selectedFiles().takeFirst().toStdString();
QString directoryPath = fileDialog.directory().absolutePath();
settings->setValue("lastPath", directoryPath);
return true;
}
else
return false; // Cancel button pressed
}
void MainWindow::open_video_successful()
{
qDebug() << "Video correctly loaded";
show_appearance_tab();
endOfVideo = false;
timerInterval = 1000 / (tracker->get_fps());
timer->setInterval(timerInterval);
ui->timeLabel->set_total_time(tracker->get_total_time());
ui->timeLabel->set_frame_count(tracker->get_frame_count());
if (displayTime)
ui->timeLabel->display_time(0); // First frame is at time 0
else
ui->timeLabel->display_frame_num(1); // First frame is at number 1
ui->positionSlider->setMinimum(1);
//ui->positionSlider->setMaximum(tracker->get_total_time()/VIDEOTRACKING_10MS2S); // value is in 0.1s
ui->positionSlider->setMaximum(tracker->get_frame_count()); // value is in 0.1s
// Disable slider steps. Jump straight to pressed position instead (signal pressed_position);
ui->positionSlider->setSingleStep(0);
ui->positionSlider->setPageStep(0);
ui->positionSlider->setEnabled(true);
ui->playerControlsWidget->setEnabled(true);
//ui->createOutputButton->setEnabled(true);
ui->actionCreateOutput->setEnabled(true);
ui->objectsControlWidget->setEnabled(true);
ui->originalVideoTextLabel->setVisible(true);
set_application_menu();
show_next_frame(); // Displays first frame
}
void MainWindow::end_of_video()
{
qDebug() << "End of video.";
//tracker->set_frame_position(0);
//endOfVideo = true;
isPlaying = false;
ui->playButton->setText(tr("Play"));
ui->stepBackButton->setEnabled(true);
ui->positionSlider->setEnabled(true);
timer->stop();
set_application_menu();
}
void MainWindow::step_back()
{
pause();
show_previous_frame();
}
void MainWindow::step_forward()
{
pause();
show_next_frame();
}
void MainWindow::show_frame()
{
if (tracker->get_frame_number() == tracker->get_frame_count())
endOfVideo = true;
else
endOfVideo = false;
//ui->positionSlider->setValue(tracker->get_time_position()/VIDEOTRACKING_10MS2S);
ui->positionSlider->setValue(tracker->get_frame_number());
if (displayTime)
ui->timeLabel->display_time(tracker->get_time_position());
else
ui->timeLabel->display_frame_num(tracker->get_frame_number());
//qDebug() << "frame number" << tracker->get_frame_number();
ui->videoLabel->set_image(frame, ui->videoFrame->width(), ui->videoFrame->height());
if (showOriginalVideo)
ui->originalVideoLabel->set_image(originalFrame, ui->originalVideoFrame->width(), ui->originalVideoFrame->height());
if (tracker->get_objects_count())
set_trajectory_tab(ui->objectsBox->currentData().toUInt(), false, false);
//qDebug()<< "slider position: " << ui->positionSlider->value();
}
void MainWindow::show_next_frame()
{
// Save timestamp to be restored if operation is aborted
int64_t currentTimestamp = tracker->get_frame_timestamp();
QProgressDialog progressDialog(computingInfoText, tr("Cancel"), 0, 0, this);
progressDialog.setWindowTitle(computingInfoTitle);
progressDialog.setModal(true);
try
{
if (!tracker->get_next_frame(frame, originalFrame, showOriginalVideo, &progressDialog))
{
qDebug() << "No next frame to show";
end_of_video(); // Frame was not read successfully -> end of video
}
else
show_frame();
} catch (UserCanceledException)
{
progressDialog.cancel();
show_frame_by_timestamp(currentTimestamp); // restore player position before the computing started
return;
}
}
void MainWindow::show_previous_frame()
{
// Save timestamp to be restored if operation is aborted
int64_t currentTimestamp = tracker->get_frame_timestamp();
QProgressDialog progressDialog(computingInfoText, tr("Cancel"), 0, 0, this);
progressDialog.setWindowTitle(computingInfoTitle);
progressDialog.setModal(true);
try
{
if (!tracker->get_previous_frame(frame, originalFrame, showOriginalVideo, &progressDialog))
qDebug() << "No previous frame to show";
else
show_frame();
} catch (UserCanceledException)
{
progressDialog.cancel();
show_frame_by_timestamp(currentTimestamp); // restore player position before the computing started
return;
}
}
void MainWindow::show_frame_by_time(int position)
{
// Save timestamp to be restored if operation is aborted
int64_t currentTimestamp = tracker->get_frame_timestamp();
QProgressDialog progressDialog(computingInfoText, tr("Cancel"), 0, 0, this);
progressDialog.setWindowTitle(computingInfoTitle);
progressDialog.setModal(true);
try
{
if (!tracker->get_frame_by_time(frame, originalFrame, showOriginalVideo,
position*VIDEOTRACKING_10MS2S, &progressDialog))
{
qDebug() << "WARNING: Cannot read frame at given time position";
end_of_video();
}
else
show_frame();
} catch (UserCanceledException)
{
progressDialog.cancel();
show_frame_by_timestamp(currentTimestamp); // restore player position before the computing started
//progressDialog->reset();
return;
}
}
void MainWindow::show_frame_by_timestamp(int64_t timestamp)
{
// Save timestamp to be restored if operation is aborted
int64_t currentTimestamp = tracker->get_frame_timestamp();
QProgressDialog progressDialog(computingInfoText, tr("Cancel"), 0, 0, this);
progressDialog.setWindowTitle(computingInfoTitle);
progressDialog.setModal(true);
try
{
if (!tracker->get_frame_by_timestamp(frame, originalFrame, showOriginalVideo, timestamp, &progressDialog))
{
qDebug() << "ERROR: show_frame_by_timestamp() - cannot read frame";
end_of_video();
}
else
show_frame();
} catch (UserCanceledException)
{
progressDialog.cancel();
show_frame_by_timestamp(currentTimestamp); // restore player position before the computing started
//progressDialog->reset();
return;
}
}
void MainWindow::show_frame_by_number(unsigned long position)
{
// Save timestamp to be restored if operation is aborted
int64_t currentTimestamp = tracker->get_frame_timestamp();
QProgressDialog progressDialog(computingInfoText, tr("Cancel"), 0, 0, this);
progressDialog.setWindowTitle(computingInfoTitle);
progressDialog.setModal(true);
try
{
if (!tracker->get_frame_by_number(frame, originalFrame, showOriginalVideo, position, &progressDialog))
{
qDebug() << "WARNING: Cannot read frame at given number position";
end_of_video();
}
else
show_frame();
} catch (UserCanceledException)
{
progressDialog.cancel();
show_frame_by_timestamp(currentTimestamp); // restore player position before the computing started
//progressDialog->reset();
return;
}
}
void MainWindow::show_first_frame()
{
// Save timestamp to be restored if operation is aborted
int64_t currentTimestamp = tracker->get_frame_timestamp();
QProgressDialog progressDialog(computingInfoText, tr("Cancel"), 0, 0, this);
progressDialog.setWindowTitle(computingInfoTitle);
progressDialog.setModal(true);
try
{
if (!tracker->get_first_frame(frame, originalFrame, showOriginalVideo, &progressDialog))
{
qDebug() << "WARNING: Cannot get first frame";
end_of_video();
}
else
show_frame();
} catch (UserCanceledException)
{
progressDialog.cancel();
show_frame_by_timestamp(currentTimestamp); // restore player position before the computing started
//progressDialog->reset();
return;
}
}
void MainWindow::faster()
{
timerSpeed++;
speed_general();
}
void MainWindow::slower()
{
if (timerSpeed <= -4)
return; // Cannot make it slower
timerSpeed--;
speed_general();
}
void MainWindow::original_speed()
{
timerSpeed = 0;
speed_general();
}
void MainWindow::speed_general()
{
double speed = 1;
if (timerSpeed > 0)
speed += timerSpeed*VIDEOTRACKING_TIMER_CONSTANT_FAST;
else if (timerSpeed < 0)
speed += timerSpeed*VIDEOTRACKING_TIMER_CONSTANT_SLOW;
timer->setInterval(timerInterval / speed);
ui->speedLabel->setText(tr("Speed:") + " " + QString::number(speed, 'f', 2) + "x");
}
void MainWindow::create_output()
{
pause();
// Save timestamp to restore it after the operation is complete
int64_t currentTimestamp = tracker->get_frame_timestamp();
if (!ask_save_appearance_changes())
return;
std::string input = inputFileName;
auto extensionPosition = input.find_last_of('.');
std::string defaultFileName;
if (extensionPosition != std::string::npos)
{ // extension is present in the filename
defaultFileName = input.substr(0, extensionPosition) + "_output";
defaultFileName += input.substr(extensionPosition);
}
else
defaultFileName = input + "_output";
QString filename = QFileDialog::getSaveFileName(this, tr("Select output file"),
QString::fromStdString(defaultFileName));
if (filename.isEmpty())
return;
// qApp->processEvents();
qDebug() << "Creating output started";
QProgressDialog fileProgressDialog(tr("Creating output file..."), tr("Cancel"), 0, tracker->get_total_time(), this);
fileProgressDialog.setWindowTitle(tr("Creating Output"));
fileProgressDialog.setWindowModality(Qt::WindowModal);
//fileProgressDialog.setWindowFlags(Qt::Window | Qt::);
fileProgressDialog.setAutoReset(false);
fileProgressDialog.setMinimumDuration(0); // Always show the dialog; Otherwise it would be shower only if process would take at least certain time
QProgressDialog trackingProgressDialog(tr("Computing trajectories of all tracked objects"), tr("Cancel"), 0, 0, this);
fileProgressDialog.setWindowTitle(computingInfoTitle);
trackingProgressDialog.setModal(true);
//trackingProgressDialog.show();
QMessageBox *alert = new QMessageBox(this);
alert->setWindowTitle(tr("Creating Output"));
std::string inFileExtension = "";
auto inExtensionPosition = inputFileName.find_last_of('.');
if (inExtensionPosition != std::string::npos)
inFileExtension = inputFileName.substr(inExtensionPosition);
try
{
tracker->create_output(filename.toStdString(), fileProgressDialog, &trackingProgressDialog, inFileExtension);
alert->setText(tr("Output successfully created"));
} catch (OutputException) {
qDebug() << "ERROR: Output creating not successful";
alert->setText(tr("Error occured when creating the output file. Output was not created."));
} catch (UserCanceledException) {
qDebug() << "User canceled output creation";
alert->setText(tr("Creating output file was canceled. Output was not created."));
}
fileProgressDialog.cancel();
alert->exec();
show_frame_by_timestamp(currentTimestamp); // restore player position before the computing started
}
void MainWindow::slider_pressed()
{
timer->stop();
}
void MainWindow::slider_released()
{
//show_frame_by_number(ui->positionSlider->value());
if (isPlaying)
{
timer->start();
}
}
void MainWindow::set_form_values()
{
colorsMap[Colors::BLACK] = std::make_pair(tr("Black").toStdString(), ObjectColor(0, 0, 0));
colorsMap[Colors::GRAY] = std::make_pair(tr("Gray").toStdString(), ObjectColor(128, 128, 128));
colorsMap[Colors::SILVER] = std::make_pair(tr("Silver").toStdString(), ObjectColor(192, 192, 192));
colorsMap[Colors::WHITE] = std::make_pair(tr("White").toStdString(), ObjectColor(255, 255, 255));
colorsMap[Colors::RED] = std::make_pair(tr("Red").toStdString(), ObjectColor(255, 0, 0));
colorsMap[Colors::GREEN] = std::make_pair(tr("Green").toStdString(), ObjectColor(0, 255, 0));
colorsMap[Colors::BLUE] = std::make_pair(tr("Blue").toStdString(), ObjectColor(0, 0, 255));
colorsMap[Colors::YELLOW] = std::make_pair(tr("Yellow").toStdString(), ObjectColor(255, 255, 0));
colorsMap[Colors::CYAN] = std::make_pair(tr("Cyan").toStdString(), ObjectColor(0, 255, 255));
colorsMap[Colors::MAGENTA] = std::make_pair(tr("Magenta").toStdString(), ObjectColor(255, 0, 255));
shapesMap.clear();
shapesMap[ObjectShape::RECTANGLE] = tr("Rectangle");
shapesMap[ObjectShape::ELLIPSE] = tr("Ellipse");
}
void MainWindow::show_anchors_menu(QListWidgetItem *item)
{
editingAnchorItem = static_cast<AnchorItem *>(ui->anchorsWidget->itemWidget(item));
ItemType type = editingAnchorItem->get_type();
QAction *showFrame = nullptr;
QAction *changePosition = nullptr;
QAction *setEndFrame = nullptr;
QAction *setVideoEnd = nullptr;
QAction *deleteItem = nullptr;
QMenu *menu = new QMenu(tr("Edit item"), this);
if (editingAnchorItem->is_set())
{
showFrame = menu->addAction(tr("Show frame"));
}
if (type == ItemType::CHANGE || type == ItemType::BEGINNING)
{
changePosition = menu->addAction(tr("Change position"));
changePosition->setToolTip(tr("Change the initial position of this object"));
if (type == ItemType::CHANGE)
{
deleteItem = menu->addAction(tr("Delete item"));
deleteItem->setToolTip(tr("Delete this item"));
}
}
else if (type == ItemType::END)
{
if (editingAnchorItem->is_set())
{
setEndFrame = menu->addAction(tr("Change last frame of tracking"));
setEndFrame->setToolTip(tr("Change the frame where tracking for this object shall stop"));
setVideoEnd = menu->addAction(tr("Set tracking till the end"));
setVideoEnd->setToolTip(tr("Set tracking till the end of the video for this object"));
}
else // Show different menu label for "setEndFrame", but action is the same
{
setEndFrame = menu->addAction(tr("Set last frame of tracking"));
setEndFrame->setToolTip(tr("Set a frame where tracking for this object shall stop"));
}
}
editingAnchorItem->set_highlight(true); // Keeps the item highlighted even with context menu over it
//menu->exec(editingAnchorItem->mapToGlobal(QPoint(0,0))); // Show at widget position
QAction *selectedOption = menu->exec(QCursor::pos()); // Show at cursor position
editingAnchorItem->set_highlight(false);
if (!selectedOption)
return; // No option was selected
pause(); // Pause video playing (in case it was playing)
if (selectedOption == showFrame)
{
qDebug() << "Show frame";
show_frame_by_timestamp(editingAnchorItem->get_timestamp());
}
else if (selectedOption == changePosition)
{
qDebug() << "Change position";
editingAnchorItem->set_highlight(true);
// Goes to the event frame so that user does not need to look for it
show_frame_by_timestamp(editingAnchorItem->get_timestamp());
set_selection(SelectionState::CHANGE_POSITION);
}
else if (selectedOption == setEndFrame)
{
set_end_frame();
}
else if (selectedOption == setVideoEnd)
{
set_video_end();
}
else if (selectedOption == deleteItem)
{
qDebug() << "Delete item";
// Ask if they really want to delete this item
QMessageBox *msgBox = new QMessageBox(this);
msgBox->setWindowTitle(tr("Delete trajectory item"));
msgBox->setText(tr("Do you really want to delete this item?"));
msgBox->setStandardButtons(QMessageBox::Yes | QMessageBox::No);
msgBox->setButtonText(QMessageBox::Yes, tr("Yes"));
msgBox->setButtonText(QMessageBox::No, tr("No"));
msgBox->setDefaultButton(QMessageBox::Yes);
if (msgBox->exec() == QMessageBox::Yes)
{ // Deletion confirmed by user
unsigned int objectID = ui->objectsBox->currentData().toUInt();
tracker->delete_object_trajectory_section(objectID, editingAnchorItem->get_timestamp());
set_anchors_tab(objectID);
}
projectChanged = true;
}
else
{
qDebug() << "ERROR: Action from trajectory menu was not recognized";
}
}
void MainWindow::set_change_beginning()
{
qDebug() << "Change Beginning position";
set_anchors_tab(ui->objectsBox->currentData().toUInt());
QListWidgetItem *beginItem = ui->anchorsWidget->item(0); // The Beginning item
editingAnchorItem = static_cast<AnchorItem *>(ui->anchorsWidget->itemWidget(beginItem));
editingAnchorItem->set_highlight(true);
// Goes to the event frame so that user does not need to look for it
show_frame_by_timestamp(editingAnchorItem->get_timestamp());
set_selection(SelectionState::CHANGE_POSITION);
}
void MainWindow::set_end_frame()
{
qDebug() << "Set end frame";
set_anchors_tab(ui->objectsBox->currentData().toUInt());
//int width = ui->anchorsWidget->size().width()-1;
//qDebug() << width;
QListWidgetItem *endItem = ui->anchorsWidget->item(ui->anchorsWidget->count()-1); // The End item
editingAnchorItem = static_cast<AnchorItem *>(ui->anchorsWidget->itemWidget(endItem));
editingAnchorItem->set_highlight(true);
if (editingAnchorItem->is_set())
show_frame_by_timestamp(editingAnchorItem->get_timestamp());
set_selection(SelectionState::SET_END_FRAME, false);
}
void MainWindow::set_video_end()
{
qDebug() << "Set video end";
set_anchors_tab(ui->objectsBox->currentData().toUInt());
unsigned int objectID = ui->objectsBox->currentData().toUInt();
tracker->change_object_end_frame(objectID, false);
projectChanged = true;
}
void MainWindow::set_selection(SelectionState state, bool enable_selecting)
{
QString text;
switch (state)
{
case SelectionState::NEW_OBJECT:
text = tr("Select an area for tracking");
break;
case SelectionState::CHANGE_POSITION:
text = tr("Select a new position");
break;
case SelectionState::SET_END_FRAME:
text = tr("Select a frame where you want the object to end");
break;
case SelectionState::NEW_SECTION:
text = tr("Select a new position for this object");
break;
default:
qDebug() << "ERROR: SelectionState not recognized";
return;
}
selectionState = state;
ui->selectionLabel->setText(text);
ui->objectsControlWidget->setEnabled(false);
ui->newObjectButton->setEnabled(false);
//ui->openVideoButton->setEnabled(false);
ui->actionOpenVideo->setEnabled(false);
ui->actionLoadProject->setEnabled(false);
ui->actionSaveProject->setEnabled(false);
//ui->createOutputButton->setEnabled(false);
ui->actionCreateOutput->setEnabled(false);
ui->selectionWidget->setVisible(true);
if (enable_selecting)
ui->videoLabel->set_selection_enabled(true); // Enables selection of area to be tracked
set_application_menu();
}
// When an object is selected, display its settings
void MainWindow::set_object_settings()
{
if (!tracker || tracker->get_objects_count() == 0) // No tracking object is added
return;
else if (!ask_save_appearance_changes())
return;
unsigned int id = ui->objectsBox->currentData().toUInt();
set_appearance_tab(id);
set_anchors_tab(id);
}
void MainWindow::set_appearance_tab(unsigned int id)
{
originalObjectAppearance = tracker->get_object_appearance(id);
alteredObjectAppearance = originalObjectAppearance;
settingObjectSettings = true; // Disables slots for appearance change (change_color(), ...)
ui->shapeBox->setCurrentIndex(ui->shapeBox->findData(originalObjectAppearance.shape));
//qDebug() << "defocus:" << originalObjectAppearance.defocus;
ui->defocusButton->setChecked(originalObjectAppearance.defocus);
ui->colorButton->setChecked(!originalObjectAppearance.defocus);
ui->defocusSizeBox->setValue(originalObjectAppearance.defocusSize);
ui->drawInsideBox->setChecked(originalObjectAppearance.drawInside);
ui->drawBorderBox->setChecked(originalObjectAppearance.drawBorder);
ui->colorBox->setCurrentIndex(ui->colorBox->findData(originalObjectAppearance.colorID));
ui->borderColorBox->setCurrentIndex(ui->borderColorBox->findData(originalObjectAppearance.borderColorID));
ui->borderThicknessBox->setValue(originalObjectAppearance.borderThickness);
ui->defocusWidget->setEnabled(originalObjectAppearance.defocus);
ui->colorWidget->setEnabled(!originalObjectAppearance.defocus);
settingObjectSettings = false;
}
/* if clear==true, trajectory is set from scratch */
void MainWindow::set_trajectory_tab(unsigned int id, bool clear, bool showProgressBar)
{
if (clear)
ui->trajectoryWidget->clear(); // Clears to add all items again to empty list
QListWidgetItem *listItem = nullptr;
TrajectoryItem *item = nullptr;
auto trajectory = tracker->get_object_trajectory(id);
auto iterator = trajectory.begin();
if (ui->trajectoryWidget->count() > 0)
{ // the list is not empty, find the last one and start adding from the following one
// Find last item and its timestamp:
QListWidgetItem *lastItem = ui->trajectoryWidget->item(ui->trajectoryWidget->count()-1); // The Last item
TrajectoryItem *lastTrajectoryItem = static_cast<TrajectoryItem *>(ui->trajectoryWidget->itemWidget(lastItem));
int64_t lastTimestamp = lastTrajectoryItem->get_timestamp();
iterator = trajectory.find(lastTimestamp);
if (iterator == trajectory.end())
{ // This should never occur; It is here just for a safety reason.
iterator = trajectory.begin();
ui->trajectoryWidget->clear();
}
else
{
iterator++; // Start adding from the next one
}
}
QProgressDialog progressDialog(tr("Setting Trajectory tab"), tr("Cancel"), 0, 0, this);
if (showProgressBar && iterator != trajectory.end())
{ // This operation might take a while -> show a progress dialog
progressDialog.setWindowTitle(tr("Trajectory tab"));
progressDialog.setModal(true);
progressDialog.show();
}
while (iterator != trajectory.end())
{
TrajectoryEntry entry = iterator->second;
listItem = new QListWidgetItem();
item = new TrajectoryItem(iterator->first, entry.timePosition, tracker->get_total_time(),
entry.frameNumber, tracker->get_frame_count(), entry.position,
this, displayTime);
ui->trajectoryWidget->addItem(listItem);
ui->trajectoryWidget->setItemWidget(listItem, item); // Takes ownership of listItem => frees automatically
listItem->setSizeHint(QSize(listItem->sizeHint().width(), 50));
if (progressDialog.wasCanceled()) // User cancelled the progress dialog
return;
qApp->processEvents(); // Keeps progress bar active
iterator++;
}
progressDialog.cancel();
}
void MainWindow::trajectory_show_frame(QListWidgetItem *item)
{
TrajectoryItem *selectedItem = static_cast<TrajectoryItem *>(ui->trajectoryWidget->itemWidget(item));
show_frame_by_timestamp(selectedItem->get_timestamp());
}
void MainWindow::set_anchors_tab(unsigned int id)
{
assert(tracker != nullptr);
ui->anchorsWidget->clear(); // Clears to add all items again to empty list
QListWidgetItem *listItem = nullptr;
AnchorItem *item = nullptr;
ItemType type = ItemType::BEGINNING;
auto trajectorySections = tracker->get_object_trajectory_sections(id);
for (auto &section: trajectorySections)
{
listItem = new QListWidgetItem();
item = new AnchorItem(type, section.first, section.second.initialTimePosition, tracker->get_total_time(),
section.second.initialFrameNumber, tracker->get_frame_count(), this, true, displayTime);
ui->anchorsWidget->addItem(listItem);
ui->anchorsWidget->setItemWidget(listItem, item); // Takes ownership of listItem => frees automatically
type = ItemType::CHANGE;
}
int64_t endTimestamp;
unsigned long endTimePosition;
unsigned long endFrameNumber;
isEndTimestampSet = tracker->get_object_end(id, endTimestamp, endTimePosition, endFrameNumber);
listItem = new QListWidgetItem();
item = new AnchorItem(ItemType::END, endTimestamp, endTimePosition, tracker->get_total_time(),
endFrameNumber, tracker->get_frame_count(), this, isEndTimestampSet, displayTime);
ui->anchorsWidget->addItem(listItem);
ui->anchorsWidget->setItemWidget(listItem, item);
set_trajectory_tab(id, true, true);
set_application_menu(); // Because of isEndTimestampSet that might be changed
}
void MainWindow::add_trajectory_change()
{
set_anchors_tab(ui->objectsBox->currentData().toUInt());
set_selection(SelectionState::NEW_SECTION);
}
void MainWindow::change_shape()
{
if (settingObjectSettings)
return;
//qDebug() << "Shape changed";
alteredObjectAppearance.shape = ui->shapeBox->currentData().toUInt();
change_appearance();
}
void MainWindow::change_defocus()
{
if (settingObjectSettings)
return;
alteredObjectAppearance.defocus = ui->defocusButton->isChecked();
ui->defocusWidget->setEnabled(alteredObjectAppearance.defocus);
ui->colorWidget->setEnabled(!alteredObjectAppearance.defocus);
change_appearance();
}
void MainWindow::change_defocus_size()
{
if (settingObjectSettings)
return;
alteredObjectAppearance.defocusSize = ui->defocusSizeBox->value();
change_appearance();
}
void MainWindow::change_color()
{
if (settingObjectSettings)
return;
//qDebug() << "Change color";
unsigned int newColorID = ui->colorBox->currentData().toUInt();
if (newColorID == 0)
custom_color();
else
{
alteredObjectAppearance.colorID = newColorID;
alteredObjectAppearance.color = colorsMap[newColorID].second;
change_appearance();
}
}
void MainWindow::change_border_color()
{
if (settingObjectSettings)
return;
//qDebug() << "Change border color";
unsigned int newColorID = ui->borderColorBox->currentData().toUInt();
if (newColorID == 0)
custom_border_color();
else
{
alteredObjectAppearance.borderColorID = newColorID;
alteredObjectAppearance.borderColor = colorsMap[newColorID].second;
change_appearance();
}
}
void MainWindow::change_border_thickness()
{
if (settingObjectSettings)
return;
//qDebug() << "Border thickness changed";
alteredObjectAppearance.borderThickness = ui->borderThicknessBox->value();
change_appearance();
}
void MainWindow::change_draw_inside()
{
if (settingObjectSettings)
return;
//qDebug() << "Draw inside changed";
alteredObjectAppearance.drawInside = ui->drawInsideBox->isChecked();
change_appearance();
}
void MainWindow::change_draw_border()
{
if (settingObjectSettings)
return;
//qDebug() << "Draw border changed";
alteredObjectAppearance.drawBorder = ui->drawBorderBox->isChecked();
change_appearance();
}
void MainWindow::change_appearance()
{
//qDebug() << "Change appearance";
if (!appearanceChanged)
{
ui->appearanceButtonsWidget->setVisible(true);
appearanceChanged = true;
}
tracker->change_object_appearance(ui->objectsBox->currentData().toUInt(), alteredObjectAppearance);
if (!tracker->get_current_frame(frame, originalFrame, showOriginalVideo))
qDebug() << "ERROR: change_appearance(): Cannot show current frame";
else
show_frame();
}
bool MainWindow::ask_save_appearance_changes()
{
if (!appearanceChanged) // No changes to appearance made, everything is all right
return true;
QMessageBox *msgBox = new QMessageBox(this);
msgBox->setWindowTitle(tr("Appearance changes"));
msgBox->setText(tr("Changes to appearance of object") + " \"" + ui->objectsBox->currentText() +
"\" "+ tr("were made. Do you wish to save your changes?"));
msgBox->setStandardButtons(QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
msgBox->setButtonText(QMessageBox::Yes, tr("Yes"));
msgBox->setButtonText(QMessageBox::No, tr("No"));
msgBox->setButtonText(QMessageBox::Cancel, tr("Cancel"));
msgBox->setDefaultButton(QMessageBox::Yes);
int state = msgBox->exec();
if (state == QMessageBox::Yes)
{
confirm_appearance_changes();
return true;
}
else if (state == QMessageBox::No)
{
discard_appearance_changes();
return true;
}
else // Cancel button pressed
return false;
}
void MainWindow::confirm_appearance_changes()
{
assert(appearanceChanged);
appearanceChanged = false;
projectChanged = true;
originalObjectAppearance = alteredObjectAppearance;
ui->appearanceButtonsWidget->setVisible(false);
}
void MainWindow::discard_appearance_changes()
{
assert(appearanceChanged);
alteredObjectAppearance = originalObjectAppearance;
change_appearance(); // Is needed to revert changes and show current image
appearanceChanged = false;
unsigned int id = ui->objectsBox->currentData().toUInt();
set_appearance_tab(id); // Sets Appearance tab to original values (state before changes were made)
ui->appearanceButtonsWidget->setVisible(false);
}
/* Sets items in Appearance tab to behave correctly */
void MainWindow::show_appearance_tab()
{
settingObjectSettings = true; // Disables signal handlings
ui->colorBox->clear();
ui->colorBox->insertItem(0, tr("+ Add new"), 0);
ui->colorBox->insertSeparator(1);
ui->borderColorBox->clear();
ui->borderColorBox->insertItem(0, tr("+ Add new"), 0);
ui->borderColorBox->insertSeparator(1);
QPixmap colorPixmap(ui->colorBox->iconSize());
for (auto const &color: colorsMap)
{
ObjectColor const &objColor = color.second.second;
colorPixmap.fill(QColor(objColor.r, objColor.g, objColor.b));
QIcon colorIcon(colorPixmap);
ui->colorBox->addItem(colorIcon, QString::fromStdString(color.second.first), color.first);
ui->borderColorBox->addItem(colorIcon, QString::fromStdString(color.second.first), color.first);
}
ui->shapeBox->clear();
for (auto const &shape: shapesMap)
{
ui->shapeBox->addItem(shape.second, shape.first);
}
ui->defocusSizeBox->setRange(2, 10000); // Value 1 would not have any effect; Maximum is much higher than could be needed.
ui->borderThicknessBox->setRange(0, 255); // OpenCV accepts border thickness in range 0-255
settingObjectSettings = false;
}
void MainWindow::custom_color()
{
unsigned int newColorID;
if (color_picker(newColorID, colorsMap[alteredObjectAppearance.colorID].second))
ui->colorBox->setCurrentIndex(ui->colorBox->findData(newColorID));
else // No color selected, set previous color
ui->colorBox->setCurrentIndex(ui->colorBox->findData(alteredObjectAppearance.colorID));
}
void MainWindow::custom_border_color()
{
unsigned int newColorID;
if (color_picker(newColorID, colorsMap[alteredObjectAppearance.borderColorID].second))
ui->borderColorBox->setCurrentIndex(ui->borderColorBox->findData(newColorID));
else // No color selected, set previous color
ui->borderColorBox->setCurrentIndex(ui->borderColorBox->findData(alteredObjectAppearance.borderColorID));
}
bool MainWindow::color_picker(unsigned int &newColorID, ObjectColor const &initialColor)
{
//static unsigned int num = 1;
QColorDialog dialog(this);
QColor selectedColor = dialog.getColor(QColor(initialColor.r, initialColor.g, initialColor.b));
if (!selectedColor.isValid()) // user hit "Cancel" button
return false;
QPixmap colorPixmap(ui->colorBox->iconSize());
colorPixmap.fill(selectedColor);
QIcon colorIcon(colorPixmap);
QString colorName = tr("Custom #") + QString::number(++customColorsCount);
newColorID = colorsMap.rbegin()->first + 1; // Gets the highest value and increases; careful if changing order e.g. alphabetically
colorsMap[newColorID] = std::make_pair(colorName.toStdString(), ObjectColor(selectedColor.red(), selectedColor.green(), selectedColor.blue()));
ui->colorBox->addItem(colorIcon, colorName, newColorID);
ui->borderColorBox->addItem(colorIcon, colorName, newColorID);
return true;
}
bool MainWindow::enter_object_name(bool change)
{
assert(tracker);
//static unsigned int objectNumber = 1; // Used for default (recommended) text
unsigned int objectNumber = tracker->get_objects_count() + 1; // Used for default (recommended) text
//bool ok; // or check "name" for null
int ok;
bool firstTime = true;
QString defaultText;
QString heading;
if (change)
{
heading = tr("Change object name");
defaultText = ui->objectsBox->currentText();
}
else
{
heading = tr("New object");
defaultText = tr("Object") + " " + QString::number(objectNumber);
while (ui->objectsBox->findText(defaultText) >= 0) // name already exists, increase default number
defaultText = tr("Object") + " " + QString::number(++objectNumber);
}
while (true)
{ // If user enters a name that already exists, allow him to enter another one
if (!firstTime)
defaultText = newObjectName;
else
firstTime = false;
// newObjectName = QInputDialog::getText(this, heading, tr("Object name:"), QLineEdit::Normal, defaultText, &ok);
QInputDialog dialog(this);
dialog.setInputMode(QInputDialog::TextInput);
dialog.setLabelText(tr("Object name:"));
dialog.setWindowTitle(heading);
dialog.setTextValue(defaultText);
dialog.setOkButtonText(tr("Ok"));
dialog.setCancelButtonText(tr("Cancel"));
ok = dialog.exec();
if (ok)
{ // OK button pressed
newObjectName = dialog.textValue();
if (newObjectName.isEmpty())
{ // Input string is empty
QMessageBox *alert = new QMessageBox(this);
alert->setWindowTitle(tr("Object name"));
alert->setText(tr("Object name cannot be empty"));
alert->exec();
continue;
}
else if (ui->objectsBox->findText(newObjectName) >= 0)
{ // Object with this name already exists, show warning and continue with adding
if (change && newObjectName == ui->objectsBox->currentText())
{ // When changing object name, user left the old name and clicked OK
return false;
}
else
{
QMessageBox *alert = new QMessageBox(this);
alert->setWindowTitle(tr("Object exists"));
alert->setText(tr("Object with this name already exists. Enter a new one."));
alert->exec();
continue;
}
}
return true;
}
else // Cancel button pressed
return false;
}
}
void MainWindow::add_new_object()
{
pause();
if (!ask_save_appearance_changes())
return;
if (enter_object_name(false))
{ // User selected name correctly
set_selection(SelectionState::NEW_OBJECT);
}
}
void MainWindow::compute_trajectory()
{
assert(tracker);
pause();
// Save timestamp to restore it after the operation is complete
int64_t currentTimestamp = tracker->get_frame_timestamp();
unsigned currentObject = ui->objectsBox->currentData().toUInt();
QProgressDialog progressDialog(computingInfoText, tr("Cancel"), 0, 0, this);
progressDialog.setWindowTitle(computingInfoTitle);
progressDialog.setModal(true);
progressDialog.show();
QMessageBox *alert = new QMessageBox(this);
alert->setWindowTitle(tr("Computing trajectory"));
try
{
if (!tracker->track_object(currentObject, &progressDialog))
{
qDebug() << "WARNING: Cannot compute trajectory";
alert->setText(tr("Trajectory was not computed"));
}
else
{ //success
alert->setText(tr("Trajectory was successfully computed"));
}
} catch (UserCanceledException)
{
if (displayTime)
{
TimeConversion::Time timePosition;
timePosition.raw = tracker->get_time_position();
TimeConversion::convert_time(timePosition);
bool showHours = timePosition.hr ? true : false;
QString timeString;
TimeConversion::Time2QString(timePosition, timeString, showHours);
alert->setText(tr("Trajectory computing was cancelled. Last computed frame time position is ")
+ timeString);
}
else
{
alert->setText(tr("Trajectory computing was cancelled. Last computed frame's number is ")
+ QString::number(tracker->get_frame_number()));
}
}
progressDialog.cancel();
alert->exec();
set_trajectory_tab(currentObject, false, true);
show_frame_by_timestamp(currentTimestamp); // restore player position before the computing started
}
void MainWindow::change_name()
{
if (enter_object_name(true))
{ // User selected name correctly
tracker->set_object_name(ui->objectsBox->currentData().toUInt(), newObjectName.toStdString());
show_objects_box(true);
ui->objectsBox->setCurrentText(newObjectName);
projectChanged = true;
}
}
void MainWindow::selection_confirmed()
{
if (selectionState == SelectionState::NEW_OBJECT)
{
Selection selectedPosition = ui->videoLabel->get_selection();
new_object_confirm(selectedPosition);
}
else if (selectionState == SelectionState::CHANGE_POSITION)
{
Selection selectedPosition = ui->videoLabel->get_selection();
if (!change_position_confirm(selectedPosition))
{ // Position cannot take place after the End frame
QMessageBox *alert = new QMessageBox(this);
alert->setWindowTitle(tr("Changing position"));
alert->setText(tr("Beginning frame cannot be after End frame"));
alert->exec();
return;
}
}
else if (selectionState == SelectionState::SET_END_FRAME)
{
if (!set_end_frame_confirm())
{ // End frame cannot be before Beginning frame
QMessageBox *alert = new QMessageBox(this);
alert->setWindowTitle(tr("Setting end frame"));
alert->setText(tr("End frame cannot be before Beginning frame"));
alert->exec();
return;
}
}
else if (selectionState == SelectionState::NEW_SECTION)
{
Selection selectedPosition = ui->videoLabel->get_selection();
if (!new_section_confirm(selectedPosition))
{ // Position cannot take place after the End frame
QMessageBox *alert = new QMessageBox(this);
alert->setWindowTitle(tr("New trajectory change"));
alert->setText(tr("Section cannot begin after End frame"));
alert->exec();
return;
}
}
if (!tracker->get_current_frame(frame, originalFrame, showOriginalVideo))
qDebug() << "ERROR-selection_confirmed(): Cannot show current frame";
else
show_frame();
projectChanged = true;
selection_end();
}
/* Confirm new trajectory change */
bool MainWindow::new_section_confirm(Selection const &selectedPosition)
{
if (!tracker->set_object_trajectory_section(ui->objectsBox->currentData().toUInt(), tracker->get_frame_timestamp(),
selectedPosition, tracker->get_time_position(), tracker->get_frame_number()))
return false;
set_anchors_tab(ui->objectsBox->currentData().toUInt());
return true;
}
bool MainWindow::change_position_confirm(Selection const &selectedPosition)
{
if (!tracker->change_object_trajectory_section(ui->objectsBox->currentData().toUInt(), editingAnchorItem->get_timestamp(),
tracker->get_frame_timestamp(), selectedPosition, tracker->get_time_position(),
tracker->get_frame_number()))
return false;
set_anchors_tab(ui->objectsBox->currentData().toUInt());
return true;
}
bool MainWindow::set_end_frame_confirm()
{
if (!tracker->change_object_end_frame(ui->objectsBox->currentData().toUInt(), true, tracker->get_frame_timestamp(),
tracker->get_time_position(), tracker->get_frame_number()))
return false;
set_anchors_tab(ui->objectsBox->currentData().toUInt());
return true;
}
void MainWindow::new_object_confirm(Selection const &selectedPosition)
{
assert(selectionState == SelectionState::NEW_OBJECT);
unsigned defaultColor = Colors::BLACK;
unsigned defaultBorderColor = Colors::RED;
ObjectColor const &color = colorsMap[defaultColor].second;
ObjectColor const &borderColor = colorsMap[defaultBorderColor].second;
Characteristics defaultAppearance(ObjectShape::RECTANGLE, true, 20, true, color,
Colors::BLACK, true, borderColor, Colors::RED, 3);
int id;
if ((id = tracker->add_object(defaultAppearance, newObjectName.toStdString(), selectedPosition, tracker->get_frame_timestamp(),
tracker->get_time_position(), tracker->get_frame_number(), false, 0, 0, 0)) < 0)
{
qDebug() << "ERROR-new_object_confirm(): Object wasn't added correctly";
return;
}
//ui->videoLabel->set_image(frame, ui->videoFrame->width(), ui->videoFrame->height());
show_objects_box(true);
if (ui->objectsBox->currentText() == newObjectName)
{ // the object is already active. thus, tabs need to be updated manually
set_object_settings();
}
else
{ // Changing current text updates all tabs
ui->objectsBox->setCurrentText(newObjectName); // this changes values in all 3 tabs //test
}
}
void MainWindow::show_objects_box(bool noTabsUpdate)
{
// First disconnect this signal so alterig ui->objectsBox does not fire it every time
// Connect it again at the end of this function
if (noTabsUpdate)
QObject::disconnect(ui->objectsBox, SIGNAL(currentIndexChanged(int)), this, SLOT(set_object_settings()));
ui->objectsBox->clear();
std::vector<std::string> objects;
if (!tracker || (objects=tracker->get_all_objects_names()).empty())
{ // No objects exist, disable Objects menu
ui->objectsBox->addItem(tr("No objects added"));
ui->objectsBox->setEnabled(false);
ui->tabWidget->setEnabled(false);
}
else
{
unsigned int i = 0;
for (std::string const &object: objects)
{
ui->objectsBox->setEnabled(true);
ui->tabWidget->setEnabled(true);
ui->objectsBox->addItem(QString::fromStdString(object), i);
i++;
}
}
set_application_menu();
// Connect the signal that was disconnected at the beginning of this function
if (noTabsUpdate)
QObject::connect(ui->objectsBox, SIGNAL(currentIndexChanged(int)), this, SLOT(set_object_settings()));
}
void MainWindow::selection_end()
{
assert(selectionState != SelectionState::NO_SELECTION);
if (selectionState == SelectionState::CHANGE_POSITION || selectionState == SelectionState::SET_END_FRAME)
{
editingAnchorItem->set_highlight(false);
editingAnchorItem = nullptr;
}
ui->objectsControlWidget->setEnabled(true);
ui->newObjectButton->setEnabled(true);
//ui->openVideoButton->setEnabled(true);
ui->actionOpenVideo->setEnabled(true);
ui->actionLoadProject->setEnabled(true);
ui->actionSaveProject->setEnabled(true);
//ui->createOutputButton->setEnabled(true);
ui->actionCreateOutput->setEnabled(true);
ui->selectionWidget->setVisible(false);
ui->videoLabel->set_selection_enabled(false); // Disables selection of area to be tracked
selectionState = SelectionState::NO_SELECTION;
set_application_menu();
}
void MainWindow::delete_object()
{
// Ask if they really want to delete this object
QMessageBox *msgBox = new QMessageBox(this);
msgBox->setWindowTitle(tr("Delete object"));
msgBox->setText(tr("Do you really want to delete object") + " \"" + ui->objectsBox->currentText() + "\"?");
msgBox->setStandardButtons(QMessageBox::Yes | QMessageBox::No);
msgBox->setButtonText(QMessageBox::Yes, tr("Yes"));
msgBox->setButtonText(QMessageBox::No, tr("No"));
msgBox->setDefaultButton(QMessageBox::Yes);
if (msgBox->exec() == QMessageBox::Yes)
{ // Deletion confirmed by user
tracker->delete_object(ui->objectsBox->currentData().toUInt());
appearanceChanged = false; // Object is deleted -> no object's appearance changes
show_objects_box(); // Changes were made, display current objectsBox
// Display current frame (without deleted object)
if (!tracker->get_current_frame(frame, originalFrame, showOriginalVideo))
qDebug() << "ERROR-delete_object: Cannot show current frame";
else
show_frame();
projectChanged = true;
}
}
void MainWindow::display_time()
{
displayTime = true;
settings->setValue("showTime", true);
ui->actionTime->setChecked(true);
ui->actionFrameNumbers->setChecked(false);
if (tracker)
{ // Video is loaded, display its time
for (int i = 0; i < ui->anchorsWidget->count(); i++)
{
QListWidgetItem* item = ui->anchorsWidget->item(i);
AnchorItem * anchorItem = static_cast<AnchorItem *>(ui->anchorsWidget->itemWidget(item));
anchorItem->display_time();
}
for (int i = 0; i < ui->trajectoryWidget->count(); i++)
{
QListWidgetItem* item = ui->trajectoryWidget->item(i);
TrajectoryItem* trajectoryItem = static_cast<TrajectoryItem *>(ui->trajectoryWidget->itemWidget(item));
trajectoryItem->display_time();
}
ui->timeLabel->display_time(tracker->get_time_position());
}
else // Shows default text when no video is loaded
ui->timeLabel->setText("--:--");
}
void MainWindow::display_frame_numbers()
{
displayTime = false;
settings->setValue("showTime", false);
ui->actionFrameNumbers->setChecked(true);
ui->actionTime->setChecked(false);
if (tracker)
{ // Video is loaded, display its frame numbers
for (int i = 0; i < ui->anchorsWidget->count(); i++)
{
QListWidgetItem* item = ui->anchorsWidget->item(i);
AnchorItem * anchorItem = static_cast<AnchorItem *>(ui->anchorsWidget->itemWidget(item));
anchorItem->display_frame_num();
}
for (int i = 0; i < ui->trajectoryWidget->count(); i++)
{
QListWidgetItem* item = ui->trajectoryWidget->item(i);
TrajectoryItem* trajectoryItem = static_cast<TrajectoryItem *>(ui->trajectoryWidget->itemWidget(item));
trajectoryItem->display_frame_num();
}
ui->timeLabel->display_frame_num(tracker->get_frame_number());
}
else // Shows default text when no video is loaded
ui->timeLabel->setText("--/--");
}
void MainWindow::show_original_video()
{
showOriginalVideo = !showOriginalVideo;
settings->setValue("showOriginalVideo", showOriginalVideo);
ui->actionShowOriginalVideo->setChecked(showOriginalVideo);
ui->originalVideoFrame->setVisible(showOriginalVideo);
if (showOriginalVideo && tracker)
{ // Video is loaded, current frame needs to be loaded since originalFrame does not exist yet
if (!tracker->get_current_frame(frame, originalFrame, showOriginalVideo))
qDebug() << "ERROR: show_original_video(): Cannot show current frame";
else
show_frame();
}
}
void MainWindow::set_czech_language()
{
ui->actionPlay->setEnabled(false);
qDebug() << "Czech language set";
// Only one language item must be checked
ui->actionCzech->setChecked(true);
ui->actionEnglish->setChecked(false);
settings->setValue("language", ":/language/video_anonymizer_cs");
QMessageBox *alert = new QMessageBox(this);
alert->setWindowTitle("Čeština"); // No need for translation (tr()) - display message in selected language
alert->setText("Změna se projeví až při novém spuštění aplikace");
alert->exec();
}
void MainWindow::set_english_language()
{
qDebug() << "English set";
// Only one language item must be checked
ui->actionEnglish->setChecked(true);
ui->actionCzech->setChecked(false);
settings->setValue("language", "");
QMessageBox *alert = new QMessageBox(this);
alert->setWindowTitle("English"); // No need for translation (tr()) - display message in selected language
alert->setText("The change will take effect after restarting the application");
alert->exec();
}
bool MainWindow::ask_save_project()
{
if (!ask_save_appearance_changes()) // Before saving the project, changed appearance of tracked objects must be saved or discarded
return false;
if (!tracker || !projectChanged) // Project is empty, cannot be saved
return true;
QMessageBox *msgBox = new QMessageBox(this);
msgBox->setWindowTitle(tr("Save project"));
msgBox->setText(tr("Do you wish to save the current project?"));
msgBox->setStandardButtons(QMessageBox::Yes | QMessageBox::No);
msgBox->setStandardButtons(msgBox->standardButtons() | QMessageBox::Cancel);
msgBox->setButtonText(QMessageBox::Cancel, tr("Cancel"));
msgBox->setButtonText(QMessageBox::Yes, tr("Yes"));
msgBox->setButtonText(QMessageBox::No, tr("No"));
msgBox->setDefaultButton(QMessageBox::Yes);
int state = msgBox->exec();
if (state == QMessageBox::Yes)
return save_project();
else if (state == QMessageBox::No)
return true;
else // Cancel button pressed
return false;
}
bool MainWindow::save_project()
{
assert(tracker);
if (!ask_save_appearance_changes())
return false;
QString jsonFilter = "JSON (*.json)";
QString xmlFilter = "XML (*.xml)";
QString filters = jsonFilter + ";;" + xmlFilter;
QString selectedFilter;
// projectFormat is saved in settings so the last choice is remembered even after restarting the application
if (settings->contains("projectFormat"))
{ // Is projectFormat variable set in settings?
selectedFilter = settings->value("projectFormat").toString();
}
else // default
selectedFilter = jsonFilter;
QString defaultPath = projectFileName; // If project was loaded, sets its destination, otherwise is empty
if (defaultPath.isEmpty() && settings->contains("lastPath"))
{ // begin with the directory where the last video was opened
defaultPath = settings->value("lastPath").toString();
qDebug() << "last path:" << defaultPath;
}
QString filename = QFileDialog::getSaveFileName(this, tr("Select project file name"),
defaultPath,
filters, &selectedFilter);
if (filename.isEmpty())
return false; // No filename selected
settings->setValue("projectFormat", selectedFilter);
{ // Serialize with CEREAL
if (selectedFilter == xmlFilter)
{ // XML
if (!filename.endsWith(".xml"))
filename += ".xml"; // If it doesn't have the right extension, append it
std::ofstream os(filename.toStdString());
cereal::XMLOutputArchive oarchive(os);
oarchive(CEREAL_NVP(inputFileName), CEREAL_NVP(tracker), CEREAL_NVP(customColorsCount), cereal::make_nvp("colors", colorsMap));
//oarchive(inputFileName, tracker, colorsMap);
}
else
{ // JSON
if (!filename.endsWith(".json"))
filename += ".json"; // If it doesn't have the right extension, append it
std::ofstream os(filename.toStdString());
cereal::JSONOutputArchive oarchive(os);
//oarchive(inputFileName, tracker, colorsMap);
oarchive(CEREAL_NVP(inputFileName), CEREAL_NVP(tracker), CEREAL_NVP(customColorsCount), cereal::make_nvp("colors", colorsMap));
}
}
projectFileName = filename;
int projectNameBeginning = projectFileName.lastIndexOf("/");
if (projectNameBeginning < 0)
projectNameBeginning = 0;
else
projectNameBeginning++;
projectName = projectFileName.mid(projectNameBeginning);
set_application_menu(); // It shows the window's title with the project's name
projectChanged = false;
return true;
}
void MainWindow::load_project()
{
if (tracker)
{ // Project exists, ask to save/discard
if (!ask_save_project())
return; // User canceled the dialog, do not open a new video
initial_application_settings(); // Restores default application settings
}
QString jsonFilter = "JSON (*json)";
QString xmlFilter = "XML (*.xml)";
QString filters = jsonFilter + ";;" + xmlFilter;
QString selectedFilter;
// projectFormat is saved in settings so the last choice is remembered even after restarting the application
if (settings->contains("projectFormat"))
{ // Is projectFormat variable set in settings?
selectedFilter = settings->value("projectFormat").toString();
}
else
selectedFilter = "JSON (*json)";
QString defaultDir = "";
if (settings->contains("lastPath"))
{ // begin with the directory where the last video was opened
defaultDir = settings->value("lastPath").toString();
qDebug() << "last path:" << defaultDir;
}
projectFileName = QFileDialog::getOpenFileName(this, tr("Load project"),
defaultDir,
filters, &selectedFilter);
if (projectFileName.isEmpty())
{
qDebug() << "projectFileName is empty";
return; // No filename selected
}
settings->setValue("projectFormat", selectedFilter); // Remember file format choice also after restarting the application
colorsMap.clear();
try
{
{ // Deserialize with CEREAL
std::ifstream os(projectFileName.toStdString());
if (selectedFilter == xmlFilter)
{ // XML
cereal::XMLInputArchive iarchive(os);
iarchive(CEREAL_NVP(inputFileName), CEREAL_NVP(tracker), CEREAL_NVP(customColorsCount), cereal::make_nvp("colors", colorsMap));
//iarchive(inputFileName, tracker, colorsMap);
}
else
{ // JSON
cereal::JSONInputArchive iarchive(os);
iarchive(CEREAL_NVP(inputFileName), CEREAL_NVP(tracker), CEREAL_NVP(customColorsCount), cereal::make_nvp("colors", colorsMap));
//iarchive(inputFileName, tracker, colorsMap);
}
}
} catch (...) {
QMessageBox *alert = new QMessageBox(this);
alert->setWindowTitle(tr("Project opening"));
alert->setText(tr("Project could not be loaded"));
alert->exec();
return;
}
while (true)
{ // loading video - until correct video is loaded or user pressed "Cancel"
QProgressDialog progressDialog(tr("Opening video"), tr("Cancel"), 0, 0, this);
progressDialog.setWindowTitle(tr("Opening"));
progressDialog.setModal(true);
progressDialog.show();
try
{
tracker->load_video(inputFileName, &progressDialog); // inputFileName was loaded with the loaded project
} catch (OpenException) {
qDebug() << "Video cannot be opened.";
QMessageBox *alert = new QMessageBox(this);
alert->setWindowTitle(tr("Opening video"));
alert->setText(tr("Video from the project was not found or cannot be opened. Please set its path."));
alert->exec();
if (!open_video_dialog()) // Shows dialog to select a file
{ // Cancel button pressed
tracker = nullptr;
return;
}
else // Video file selected, try opening again
continue;
} catch (UserCanceledOpeningException) {
QMessageBox *alert = new QMessageBox(this);
alert->setWindowTitle(tr("Opening video"));
alert->setText(tr("Video opening was canceled."));
alert->exec();
return;
}
break;
}
tracker->erase_object_trajectories_to_comply();
int projectNameBeginning = projectFileName.lastIndexOf("/");
if (projectNameBeginning < 0)
projectNameBeginning = 0;
else
projectNameBeginning++;
projectName = projectFileName.mid(projectNameBeginning);
// set_application_menu() is needed to show the window's title
// with the project's name. However, the open_video_successful()
// calls the function.
open_video_successful();
show_objects_box();
}
void MainWindow::show_help()
{
qDebug() << "showing help";
QTabWidget* tWidget = new QTabWidget;
tWidget->setMaximumWidth(200);
tWidget->addTab((QWidget*)helpEngine->contentWidget(), tr("Contents"));
tWidget->addTab((QWidget*)helpEngine->indexWidget(), tr("Index"));
HelpBrowser *textViewer = new HelpBrowser(helpEngine);
//textViewer->setSource( QUrl("qthelp://walletfox.qt.helpexample/doc/index.html"));
textViewer->setSource(QUrl("qthelp://fit.cz.videoanonymizer/doc/" + tr("en_doc.html")));
connect((QWidget*)helpEngine->contentWidget(), SIGNAL(linkActivated(QUrl)), textViewer, SLOT(setSource(QUrl)));
connect((QWidget*)helpEngine->indexWidget(), SIGNAL(linkActivated(QUrl, QString)), textViewer, SLOT(setSource(QUrl)));
QSplitter *horizSplitter = new QSplitter(Qt::Horizontal);
horizSplitter->insertWidget(0, tWidget);
horizSplitter->insertWidget(1, textViewer);
horizSplitter->hide();
QDockWidget *helpWindow = new QDockWidget(tr("Help"), this);
helpWindow->setWidget(horizSplitter);
// helpWindow->hide();
addDockWidget(Qt::BottomDockWidgetArea, helpWindow);
qDebug() << "help showed";
}
void MainWindow::show_about()
{
QMessageBox::about(this, tr("Anonymizer"),
tr("Application for anonymization of objects in a video.\n") +
tr("(C) 2015 FIT BUT.\n"));
}
void MainWindow::initial_application_settings()
{
if (tracker)
{
//delete tracker;
tracker = nullptr; //is deleted automatically as tracker is a smart pointer
}
if (timer)
timer->stop();
isPlaying = false;
selectionState = SelectionState::NO_SELECTION;
editingAnchorItem = nullptr;
appearanceChanged = false;
projectChanged = false;
settingObjectSettings = false; // Disables slots for appearance change (change_color(), ...) when setting new object
isEndTimestampSet = false;
projectFileName = "";
projectName = "";
timerInterval = 0; // Original interval based on fps
timerSpeed = 0; // power used with constant VIDEOTRACKING_TIMER_CONSTANT to play video faster/slower
customColorsCount = 0;
ui->selectionWidget->setVisible(false);
ui->appearanceButtonsWidget->setVisible(false);
ui->originalVideoTextLabel->setVisible(false);
ui->playerControlsWidget->setEnabled(false);
ui->positionSlider->setEnabled(false);
ui->objectsControlWidget->setEnabled(false);
//ui->createOutputButton->setEnabled(false);
ui->actionCreateOutput->setEnabled(false);
// Move position slider to the beginning
ui->positionSlider->setValue(ui->positionSlider->minimum());
set_application_menu();
set_form_values();
//show_appearance_tab(); // It is called from open_video_successful()
show_objects_box();
// Display default time/frame numbers label
if (displayTime)
display_time();
else
display_frame_numbers();
// Make the videoLabel empty
ui->videoLabel->set_selection_enabled(false);
ui->videoLabel->clear();
ui->originalVideoLabel->set_selection_enabled(false);
ui->originalVideoLabel->clear();
}
/**
void MainWindow::exit_application()
{
if (!ask_save_project()) // User canceled the dialog telling him to Save or Discard the current project
return;
}
*/
void MainWindow::set_application_menu()
{
// Set window title:
if (projectName.isEmpty())
{
if (tracker)
setWindowTitle(tr("Unsaved Project") + " | " + applicationName);
else
setWindowTitle(applicationName);
}
else
setWindowTitle(projectName + " | " + applicationName);
ui->actionPlay->setEnabled(ui->playButton->isEnabled());
ui->actionStop->setEnabled(ui->stopButton->isEnabled());
ui->actionStepBack->setEnabled(ui->stepBackButton->isEnabled());
ui->actionStepForward->setEnabled(ui->stepForwardButton->isEnabled());
ui->actionSlower->setEnabled(ui->slowerButton->isEnabled());
ui->actionFaster->setEnabled(ui->fasterButton->isEnabled());
if (tracker)
ui->actionSaveProject->setEnabled(true);
else
ui->actionSaveProject->setEnabled(false);
ui->actionNewObject->setEnabled(ui->newObjectButton->isEnabled());
if (ui->generalTab->isEnabled())
{
ui->actionChangeName->setEnabled(true);
ui->actionRemoveObject->setEnabled(true);
}
else
{
ui->actionChangeName->setEnabled(false);
ui->actionRemoveObject->setEnabled(false);
}
ui->menuTrajectory->setEnabled(ui->anchorsTab->isEnabled());
ui->actionComputeTrajectory->setEnabled(ui->computeTrajectoryButton->isEnabled());
ui->actionVideoEnd->setChecked(!isEndTimestampSet);
ui->actionShowOriginalVideo->setChecked(showOriginalVideo);
}
/**
* Connects signals (from GUI elements and others) with slots of this object.
* @brief MainWindow::connect_signals
*/
void MainWindow::connect_signals()
{
// Signals are automatically disconnected with QObject destructor
// PLAYER CONTROL SECTION
QObject::connect(ui->playButton, SIGNAL(clicked()), this, SLOT(play()));
QObject::connect(ui->actionPlay, SIGNAL(triggered()), this, SLOT(play()));
QObject::connect(ui->stopButton, SIGNAL(clicked()), this, SLOT(stop()));
QObject::connect(ui->actionStop, SIGNAL(triggered()), this, SLOT(stop()));
QObject::connect(ui->stepForwardButton, SIGNAL(clicked()), this, SLOT(step_forward()));
QObject::connect(ui->actionStepForward, SIGNAL(triggered()), this, SLOT(step_forward()));
QObject::connect(ui->stepBackButton, SIGNAL(clicked()), this, SLOT(step_back()));
QObject::connect(ui->actionStepBack, SIGNAL(triggered()), this, SLOT(step_back()));
//QObject::connect(ui->fasterButton, SIGNAL(clicked()), this, SLOT(faster()));
//QObject::connect(ui->slowerButton, SIGNAL(clicked()), this, SLOT(slower()));
QObject::connect(ui->fasterButton, SIGNAL(clicked()), this, SLOT(faster()));
QObject::connect(ui->actionFaster, SIGNAL(triggered()), this, SLOT(faster()));
QObject::connect(ui->slowerButton, SIGNAL(clicked()), this, SLOT(slower()));
QObject::connect(ui->actionSlower, SIGNAL(triggered()), this, SLOT(slower()));
QObject::connect(ui->actionOriginalSpeed, SIGNAL(triggered()), this, SLOT(original_speed()));
//QObject::connect(ui->positionSlider, SIGNAL(sliderMoved(int)), this, SLOT(show_frame_by_time(int)));
QObject::connect(ui->positionSlider, SIGNAL(clicked(ulong)), this, SLOT(show_frame_by_number(ulong)));
QObject::connect(ui->positionSlider, SIGNAL(sliderPressed()), this, SLOT(slider_pressed()));
QObject::connect(ui->positionSlider, SIGNAL(sliderReleased()), this, SLOT(slider_released()));
//QObject::connect(ui->positionSlider, SIGNAL(), this, SLOT(show_frame_by_number(int)));
// FILE
//QObject::connect(ui->openVideoButton, SIGNAL(clicked()), this, SLOT(open_video()));
QObject::connect(ui->actionOpenVideo, SIGNAL(triggered()), this, SLOT(open_video()));
//QObject::connect(ui->createOutputButton, SIGNAL(clicked()), this, SLOT(create_output()));
QObject::connect(ui->actionCreateOutput, SIGNAL(triggered()), this, SLOT(create_output()));
QObject::connect(ui->actionSaveProject, SIGNAL(triggered()), this, SLOT(save_project()));
QObject::connect(ui->actionLoadProject, SIGNAL(triggered()), this, SLOT(load_project()));
QObject::connect(ui->actionQuit, SIGNAL(triggered()), this, SLOT(close()));
// OBJECTS
QObject::connect(ui->objectsBox, SIGNAL(currentIndexChanged(int)), this, SLOT(set_object_settings()));
QObject::connect(ui->newObjectButton, SIGNAL(clicked()), this, SLOT(add_new_object()));
QObject::connect(ui->actionNewObject, SIGNAL(triggered()), this, SLOT(add_new_object()));
// OBJECT APPEARANCE TAB
QObject::connect(ui->shapeBox, SIGNAL(currentIndexChanged(int)), this, SLOT(change_shape()));
//handling defocusButton handles also colorButton since toggling colorButton switches off defocusButton
QObject::connect(ui->defocusButton, SIGNAL(toggled(bool)), this, SLOT(change_defocus()));
QObject::connect(ui->defocusSizeBox, SIGNAL(valueChanged(int)), this, SLOT(change_defocus_size()));
QObject::connect(ui->drawInsideBox, SIGNAL(stateChanged(int)), this, SLOT(change_draw_inside()));
QObject::connect(ui->drawBorderBox, SIGNAL(stateChanged(int)), this, SLOT(change_draw_border()));
QObject::connect(ui->colorBox, SIGNAL(currentIndexChanged(int)), this, SLOT(change_color()));
QObject::connect(ui->borderColorBox, SIGNAL(currentIndexChanged(int)), this, SLOT(change_border_color()));
QObject::connect(ui->borderThicknessBox, SIGNAL(valueChanged(int)), this, SLOT(change_border_thickness()));
QObject::connect(ui->saveAppearanceChangesButton, SIGNAL(clicked()), this, SLOT(confirm_appearance_changes()));
QObject::connect(ui->discardAppearanceChangesButton, SIGNAL(clicked()), this, SLOT(discard_appearance_changes()));
// OBJECT APPEARANCE - CUSTOM COLORS
//QObject::connect(ui->colorBox, SIGNAL(activated(int)), this, SLOT(custom_color(int)));
//QObject::connect(ui->borderColorBox, SIGNAL(activated(int)), this, SLOT(custom_border_color(int)));
// OBJECT ANCHORS TAB
QObject::connect(ui->anchorsWidget, SIGNAL(itemPressed(QListWidgetItem*)), this, SLOT(show_anchors_menu(QListWidgetItem*)));
QObject::connect(ui->addTrajectoryChangeButton, SIGNAL(clicked()), this, SLOT(add_trajectory_change()));
QObject::connect(ui->actionChangeTrajectory, SIGNAL(triggered()), this, SLOT(add_trajectory_change()));
QObject::connect(ui->actionVideoEnd, SIGNAL(triggered()), this, SLOT(set_video_end()));
QObject::connect(ui->actionSetEndFrame, SIGNAL(triggered()), this, SLOT(set_end_frame()));
QObject::connect(ui->actionChangeBeginning, SIGNAL(triggered()), this, SLOT(set_change_beginning()));
// OBJECT TRAJECTORY TAB
QObject::connect(ui->trajectoryWidget, SIGNAL(itemPressed(QListWidgetItem*)), this, SLOT(trajectory_show_frame(QListWidgetItem*)));
QObject::connect(ui->computeTrajectoryButton, SIGNAL(clicked()), this, SLOT(compute_trajectory()));
QObject::connect(ui->actionComputeTrajectory, SIGNAL(triggered()), this, SLOT(compute_trajectory()));
// OBJECT GENERAL TAB
QObject::connect(ui->changeNameButton, SIGNAL(clicked()), this, SLOT(change_name()));
QObject::connect(ui->actionChangeName, SIGNAL(triggered()), this, SLOT(change_name()));
QObject::connect(ui->removeObjectButton, SIGNAL(clicked()), this, SLOT(delete_object()));
QObject::connect(ui->actionRemoveObject, SIGNAL(triggered()), this, SLOT(delete_object()));
// SELECTION
QObject::connect(ui->confirmSelectionButton, SIGNAL(clicked()), this, SLOT(selection_confirmed()));
QObject::connect(ui->cancelSelectionButton, SIGNAL(clicked()), this, SLOT(selection_end()));
// LANGUAGE MENU
QObject::connect(ui->actionEnglish, SIGNAL(triggered()), this, SLOT(set_english_language()));
QObject::connect(ui->actionCzech, SIGNAL(triggered()), this, SLOT(set_czech_language()));
// SETTINGS MENU
QObject::connect(ui->actionTime, SIGNAL(triggered()), this, SLOT(display_time()));
QObject::connect(ui->actionFrameNumbers, SIGNAL(triggered()), this, SLOT(display_frame_numbers()));
QObject::connect(ui->actionShowOriginalVideo, SIGNAL(triggered()), this, SLOT(show_original_video()));
// OTHERS
QObject::connect(ui->videoFrame, SIGNAL(resized()), this, SLOT(reload_video_label()));
QObject::connect(timer, SIGNAL(timeout()), this, SLOT(show_next_frame()));
QObject::connect(ui->actionHelp, SIGNAL(triggered()), this, SLOT(show_help()));
QObject::connect(ui->actionAbout, SIGNAL(triggered()), this, SLOT(show_about()));
//QObject::connect(qApp, SIGNAL(aboutToQuit()), this, SLOT(exit_application()));
}