Initial commit

This commit is contained in:
torsten committed 2026-08-13 18:33:10 +02:00
commit b7d90b9afe
16245 files changed
+3396666

No files matched your search

@@ -0,0 +1,107 @@
#
# $Id$
# $HeadURL$
# $Author$
#
# Created by Mike Wille, SLSE, 2016-03-17
#
add_library(ExportBdvTopicSetting INTERFACE)
target_compile_definitions(ExportBdvTopicSetting INTERFACE EXPORT_BDV_TOPIC_AS_JSON)
SET(pre_script_gui_SRCS
pre_journal_dialog.h
pre_journal_dialog.cpp
pre_journal_types.h
pre_journal_types.cpp
pre_screen.h
pre_screen.cpp
pre_screen_recorder.h
pre_screen_recorder.cpp
pre_screen_shotter.h
pre_screen_shotter.cpp
pre_script_timeline_gui_plugin.cpp
pre_script_timeline_gui_plugin.h
pre_script_delegate.cpp
pre_script_delegate.h
pre_script_dialog.h
pre_script_dialog.cpp
pre_script_dialog_drag_handler.cpp
pre_script_dialog_drag_handler.h
pre_script_dialog_resize_handler.cpp
pre_script_dialog_resize_handler.h
pre_script_dialog_select_handler.cpp
pre_script_dialog_select_handler.h
pre_script_model.cpp
pre_script_model.h
pre_topic_recorder.cpp
pre_topic_recorder.h
pre_topic_shotter.cpp
pre_topic_shotter.h
pre_storyboard.cpp
pre_storyboard.h
pre_topic_item.cpp
pre_topic_item.h
pre_topic_track.cpp
pre_topic_track.h
pre_utils.cpp
pre_utils.h
)
QT5_WRAP_UI(pre_script_gui_SRCS
pre_storyboard.ui
pre_script_dialog.ui
pre_journal_dialog.ui
)
NGL_ADD_LIBRARY(WS PreScriptTimelineGuiPlugin MODULE ${pre_script_gui_SRCS})
NGL_TARGET_COMPILE_DEFINITIONS(PreScriptTimelineGuiPlugin PRIVATE L=QStringLiteral)
NGL_TARGET_LINK_LIBRARIES(PreScriptTimelineGuiPlugin
NGLLogger
LtsSession
PreDataPluginInterface
PreScriptTimelineGuiPluginInterface
PreGuiCommon
PreGuiScriptCommon
LtsTimeline
svcPreCommon
Qt5::Widgets
LtsWidgets
LtsDataView
LtsSessionData
ProjectConfig
LtsScriptTimeline
LtsUtilsCoreHeaderOnly
LtsMultiMonitor
Thp2Common
LtsAppManager # start replay app
LtsSessionData # set partition preset for replay app
TopicEditorInterface
TopicEditorBase
LtsPluginManager # manipulate apps
# for Topics
EvlGuiPluginInterface
SrmDataPluginInterface # start playback
LtsPrinting
SituationMapGuiPluginInterface
# Script Player Controls:
SrpGuiPluginInterface
# file topic
FlmExplorerGuiPluginInterface
# building topic
BdvViewerGuiPluginInterface
# report topic
RepGuiPluginInterface
LtsReportDisplay
MrkCommon # for ApplyFilter @ SitmapFilter
LtsUtilsSettings
ngltools # DTG
situationMap # set time string on map
ExportBdvTopicSetting
)
NGL_INSTALL_TARGET(PreScriptTimelineGuiPlugin)
@@ -0,0 +1,73 @@
#include "pre_journal_dialog.h"
#include "ui_pre_journal_dialog.h"
#include <LtsPrinting/printing.h>
#include <Services/PresentationManagement/logger.h>
namespace PresentationManagement
{
JournalDialog::JournalDialog(LtsSession::LtsSession *session, QWidget *parent)
: LtsWidgets::LtsDialog(parent)
, m_session(session)
, m_ui(new Ui::JournalDialog)
, m_logger(getLogger(QStringLiteral("JournalDialog")))
{
m_ui->setupUi(this);
m_buttonBox->connectButton(LtsWidgets::DialogButton::Print, this, &JournalDialog::onPrint);
setOnlineHelpFilename(QStringLiteral("Script_Editor-Journal"));
}
JournalDialog::~JournalDialog()
{
delete m_ui;
}
///////////////////////////////////////////////////////////////////////////////
// PUBLIC
/**
* @brief
*
* @param journal
* @return bool
*/
bool JournalDialog::init(MainJournal *journal)
{
m_journal = journal;
return true;
}
void JournalDialog::retranslate()
{
translate(m_ui, tr("Script Journal"));
fillJournal();
}
///////////////////////////////////////////////////////////////////////////////
// PRIVATE
void JournalDialog::fillJournal()
{
m_ui->listWidget->clear();
if (!m_journal)
return;
m_ui->listWidget->addItem(m_journal->getText());
for (const Journal *j : qAsConst(m_journal->journalList))
m_ui->listWidget->addItem(j->getText());
}
///////////////////////////////////////////////////////////////////////////////
// PRIVATE SLOTS
void JournalDialog::onPrint()
{
LtsPrinting::printView(m_session, this, m_ui->listWidget);
}
} // namespace PresentationManagement
@@ -0,0 +1,44 @@
#pragma once
#include "pre_journal_types.h"
#include <LtsWidgets/lts_dialog.h>
#include <LtsSession/lts_session.h>
namespace Ui
{
class JournalDialog;
}
namespace PresentationManagement
{
class JournalDialog : public LtsWidgets::LtsDialog
{
Q_OBJECT
public:
explicit JournalDialog(LtsSession::LtsSession *session, QWidget *parent);
~JournalDialog();
bool init(MainJournal *journal);
void retranslate() override final;
private:
void fillJournal();
private slots:
void onPrint();
private:
LtsSession::LtsSession *m_session { nullptr };
MainJournal *m_journal { nullptr };
Ui::JournalDialog *m_ui { nullptr };
NGL::INGLLogger &m_logger;
};
} // namespace PresentationManagement
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>JournalDialog</class>
<widget class="QWidget" name="JournalDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>707</width>
<height>464</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string notr="true">Script Editor</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>9</number>
</property>
<property name="leftMargin">
<number>9</number>
</property>
<property name="topMargin">
<number>9</number>
</property>
<property name="rightMargin">
<number>9</number>
</property>
<property name="bottomMargin">
<number>9</number>
</property>
<item>
<widget class="QListWidget" name="listWidget">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
@@ -0,0 +1,105 @@
#include "pre_journal_types.h"
#include <QDateTime>
#include <ngltools/ngltools_dtg.h>
#include <Common/LtsTimeline/timeline_utils.h>
namespace PresentationManagement
{
QString timeSinceEpochInDTG(qint64 msecsSinceEpoch)
{
return LtsTimeline::DateTime::toString(msecsSinceEpoch,LtsTimeline::DateTime::DTG);
}
//QString presentationTimeToTime(int presentationTime)
//{
// return QTime::fromMSecsSinceStartOfDay(presentationTime * 1000).toString();
//}
QString msecsToString(int presentationTime)
{
int milliseconds = presentationTime;
int seconds = milliseconds / 1000;
milliseconds = milliseconds % 1000;
int minutes = seconds / 60;
seconds = seconds % 60;
int hours = minutes / 60;
minutes = minutes % 60;
QTime time;
time.setHMS(hours, minutes, seconds, milliseconds);
return time.toString(QStringLiteral("hh:mm:ss"));
}
Journal::Journal()
: m_started(QDateTime::currentMSecsSinceEpoch())
{}
QString Journal::started() const
{
return timeSinceEpochInDTG(m_started);
}
MainJournal::~MainJournal()
{
qDeleteAll(journalList);
}
QString MainJournal::getText() const
{
return Journal::tr("Script Player started on %1").arg(timeSinceEpochInDTG(started));
}
NextTopicJournal::NextTopicJournal(int presentationTime, bool isFilter, const QString &topicTypeName, int length)
: Journal()
, m_isFilter(isFilter)
, m_topicTypeName(topicTypeName)
, m_length(length)
, m_presentationTime(presentationTime)
{}
QString NextTopicJournal::getText() const
{
return Journal::tr("%1 '%2' started at %3 for %4 seconds on %5").arg(m_isFilter ? tr("FilterTopic") : tr("Topic"),
m_topicTypeName, msecsToString(m_presentationTime)).arg(m_length).arg(started());
}
QString PlayJournal::getText() const
{
return Journal::tr("Started / resumed script at %1 on %2").arg(msecsToString(m_presentationTime), started());
}
QString PauseJournal::getText() const
{
return Journal::tr("Paused script at %1 on %2").arg(msecsToString(m_presentationTime), started());
}
QString EndedJournal::getText() const
{
return Journal::tr("Script ended at %1 on %2").arg(msecsToString(m_presentationTime), started());
}
QString StopJournal::getText() const
{
return Journal::tr("Script stopped at %1 on %2").arg(msecsToString(m_presentationTime), started());
}
QString SetTimeJournal::getText() const
{
return Journal::tr("Position time set to %1 on %2").arg(msecsToString(m_toPresentationTime), started());
}
} // namespace PresentationManagement
@@ -0,0 +1,117 @@
#pragma once
#include <QCoreApplication>
#include <QVector>
namespace PresentationManagement
{
class Journal
{
Q_DECLARE_TR_FUNCTIONS(Journal)
public:
Journal();
virtual ~Journal() = default;
QString started() const;
virtual QString getText() const = 0;
qint64 m_started;
};
class MainJournal
{
public:
~MainJournal();
QString getText() const;
qint64 started = 0;
QVector<Journal *> journalList;
};
class NextTopicJournal : public Journal
{
public:
NextTopicJournal(int presentationTime, bool isFilter, /*int topicId, */const QString &topicTypeName, int length);
QString getText() const override final;
bool m_isFilter;
//int m_topicId;
QString m_topicTypeName;
int m_length;
int m_presentationTime;
};
class PlayJournal : public Journal
{
public:
explicit PlayJournal(int presentationTime)
: Journal()
, m_presentationTime(presentationTime)
{}
QString getText() const override final;
int m_presentationTime;
};
class PauseJournal : public Journal
{
public:
explicit PauseJournal(int presentationTime)
: Journal()
, m_presentationTime(presentationTime)
{}
QString getText() const override final;
int m_presentationTime;
};
class EndedJournal : public Journal
{
public:
explicit EndedJournal(int presentationTime)
: Journal()
, m_presentationTime(presentationTime)
{}
QString getText() const override final;
int m_presentationTime;
};
class StopJournal : public Journal
{
public:
explicit StopJournal(int presentationTime)
: Journal()
, m_presentationTime(presentationTime)
{}
QString getText() const override final;
int m_presentationTime;
};
class SetTimeJournal : public Journal
{
public:
explicit SetTimeJournal(int toPresentationTime)
: Journal()
, m_toPresentationTime(toPresentationTime)
{}
QString getText() const override final;
int m_toPresentationTime;
};
} // namespace PresentationManagement
@@ -0,0 +1,39 @@
#include "pre_screen.h"
#include <LtsUtilsGui/LtsUtilsGuiDds/dds_vector_to_pixmap.h>
#include <QPainter>
#include <QStyleOption>
Screen::Screen()
{
setBgColor(QColor("black"));
setContentsMargins(0, 0, 0, 0);
setWindowIcon(LtsUtils::iconFromSvg(QStringLiteral(":/style/execute_presentation.svg")));
setWindowTitle(tr("Presentation Screen"));
}
Screen::~Screen()
{
const auto p1Widgets = findChildren<QWidget *>(QString(), Qt::FindDirectChildrenOnly);
for (QWidget *w : p1Widgets)
w->setParent(nullptr);
}
void Screen::setBgColor(const QColor &color)
{
setStyleSheet(QStringLiteral("Screen { background: %1; background-color: %1; }").arg(color.name()));
}
void Screen::paintEvent(QPaintEvent *) // needed to apply the style sheet...........
{
QStyleOption opt;
opt.init(this);
QPainter p(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
}
@@ -0,0 +1,18 @@
#pragma once
#include <LtsMultiMonitor/lts_dummy_widget.h>
class Screen : public LtsMultiMonitor::SecondaryWidget
{
Q_OBJECT
public:
Screen();
virtual ~Screen();
void setBgColor(const QColor &color);
private:
void paintEvent(QPaintEvent *) override;
};
@@ -0,0 +1,146 @@
/**
* @RDE_HEADER@
* class for recording topics on a screen
* $Id:
* $HeadURL:
* $Author:
* Created by Hannes Fritsche, SLTE, 2023-01-18
*/
#include "pre_screen_recorder.h"
#include "pre_screen.h"
#include "pre_topic_item.h"
#include <QProcess>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
namespace PresentationManagement
{
ScreenRecorder::ScreenRecorder(QObject *parent)
: QObject(parent)
, m_logger(getLogger(QStringLiteral("ScreenRecorder")))
{
connect(this, &ScreenRecorder::newTime, this, [this](qint64 _t1){ DEBUG_LOG(m_logger) << "newTime: " << _t1; });
}
void ScreenRecorder::cut()
{
for (TopicRecorder *tr : qAsConst(m_topicRecorder))
{
if (tr->isFailed()) // skip, plus already in m_failedExportTopicIds by exportFailed signal
continue;
try
{
tr->cut();
}
catch (const QString &msg)
{
ERROR_LOG(m_logger) << "Cutting recording of topic '" << tr->topicName() << "', id: " << tr->topicId() << " failed! " << msg;
m_failedExportTopicIds += tr->topicId();
}
}
}
void ScreenRecorder::save(const QString &tmpDirPath)
{
for (TopicRecorder *tr : qAsConst(m_topicRecorder))
{
if (tr->isFailed()) // skip, plus already in m_failedExportTopicIds by exportFailed signal
continue;
try
{
tr->save(tmpDirPath);
}
catch (const QString &msg)
{
ERROR_LOG(m_logger) << "Saving recording of topic '" << tr->topicName() << "', id: " << tr->topicId() << " failed! " << msg;
m_failedExportTopicIds += tr->topicId();
}
}
}
void ScreenRecorder::addTopicsForScreen(Screen *screen, const std::vector<TopicItem> &topicItems)
{
m_topicRecorder.reserve(m_topicRecorder.size() + topicItems.size());
const qint32 startTimeOffset = -10000;
for (const TopicItem &currentTopicItem : topicItems)
{
const auto id = currentTopicItem.topicId();
const qint64 startTime = currentTopicItem.startTime() + startTimeOffset;
const qint64 stopTime = currentTopicItem.startTime() + currentTopicItem.duration();
const QString name(currentTopicItem.channel().replace(QStringLiteral(" "), QStringLiteral("_")) + QString::number(currentTopicItem.id()));
TopicRecorder *topicRecorder = new TopicRecorder(id, name, screen, startTimeOffset, startTime, stopTime,
currentTopicItem.topicData().x, currentTopicItem.topicData().y,
currentTopicItem.topicData().w, currentTopicItem.topicData().h, this);
connect(this, &ScreenRecorder::newTime, topicRecorder, &TopicRecorder::onNewTime);
connect(this, &ScreenRecorder::cancel, topicRecorder, &TopicRecorder::cancel);
connect(topicRecorder, &TopicRecorder::exportFailed, this, [this, id]() { emit topicExportFailed(id); });
connect(topicRecorder, &TopicRecorder::exportFailed, this, [this, name, id]()
{
ERROR_LOG(m_logger) << "Export failed for topic '" << name << "', id: " << id;
m_failedExportTopicIds += id;
});
m_topicRecorder.append(topicRecorder);
}
DEBUG_LOG(m_logger) << "Added " << topicItems.size() << " TopicRecorder.";
}
//void ScreenRecorder::saveRecordings(const QString &tmpDirPath)
//{
// for (TopicRecorder *tr : qAsConst(m_topicRecorder))
// {
// if (tr->isFailed()) // skip, plus already in m_failedExportTopicIds by exportFailed signal
// continue;
// try
// {
// tr->saveRecording(tmpDirPath);
// }
// catch (const QString &msg)
// {
// ERROR_LOG(m_logger) << "Saving recording of topic '" << tr->topicName() << "', id: " << tr->topicId() << " failed! " << msg;
// m_failedExportTopicIds += tr->topicId();
// }
// }
//}
void ScreenRecorder::setCurrentTime(qint64 currentTime)
{
emit newTime(currentTime);
}
void ScreenRecorder::clear()
{
DEBUG_LOG(m_logger) << "Clearing TopicRecorder.";
for (TopicRecorder *currentTopicRecorder : qAsConst(m_topicRecorder))
{
currentTopicRecorder->killAndClear();
delete currentTopicRecorder;
}
m_topicRecorder.clear();
}
void ScreenRecorder::waitForFinishedRecording()
{
DEBUG_LOG(m_logger) << "Waiting for TopicRecorder to finish ...";
for (TopicRecorder *currentTopicRecorder : qAsConst(m_topicRecorder))
currentTopicRecorder->waitForFinishedRecording();
DEBUG_LOG(m_logger) << "... all TopicRecorder finished.";
}
} // namespace PresentationManagement
@@ -0,0 +1,63 @@
/**
* @RDE_HEADER@
* class for recording topics on a screen
* $Id:
* $HeadURL:
* $Author:
* Created by Hannes Fritsche, SLTE, 2023-01-18
*/
#pragma once
#include <QObject>
#include <QTemporaryDir>
#include <QSet>
#include <Services/PresentationManagement/logger.h>
#include "pre_topic_recorder.h"
class QProcess;
class Screen;
namespace PresentationManagement
{
class TopicItem;
class ScreenRecorder : public QObject
{
Q_OBJECT
public: // methods
ScreenRecorder(QObject *parent = nullptr);
~ScreenRecorder() { clear(); }
void cut();
void save(const QString &tmpDirPath);
void addTopicsForScreen(Screen *screen, const std::vector<TopicItem> &topicItems);
void setCurrentTime(qint64 currentTime);
void clear();
void waitForFinishedRecording();
void saveRecordings(const QString &tmpDirPath); // old
const QSet<int> &failedExportTopicIds() const { return m_failedExportTopicIds; }
signals:
void newTime(qint64 time);
void topicExportFailed(const int failedTopicId);
void cancel();
private: // attributes
QVector<TopicRecorder *> m_topicRecorder;
QSet<int> m_failedExportTopicIds;
LOGGER_T m_logger;
}; // class ScreenRecorder
} // namespace PresentationManagement
@@ -0,0 +1,84 @@
/**
* @RDE_HEADER@
* class for recording topics on a screen
* $Id:
* $HeadURL:
* $Author:
* Created by Hannes Fritsche, SLTE, 2023-01-18
*/
#include "pre_screen_shotter.h"
#include <QProcess>
#include "pre_screen.h"
#include <limits>
#include "pre_topic_item.h"
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
namespace PresentationManagement
{
ScreenShotter::ScreenShotter(QObject *parent)
: QObject(parent)
, m_logger(getLogger(QStringLiteral("ScreenShotter")))
{}
void ScreenShotter::addTopicsForScreen(Screen *screen, const std::vector<TopicItem> &topicItems)
{
m_topicShotter.reserve(m_topicShotter.size() + topicItems.size());
for (const TopicItem &currentTopicItem : topicItems)
{
const auto id = currentTopicItem.topicId();
const qint64 startTime = currentTopicItem.startTime();
const qint64 stopTime = currentTopicItem.startTime() + currentTopicItem.duration();
const QString name(currentTopicItem.channel().replace(QStringLiteral(" "), QStringLiteral("_")) + QString::number(currentTopicItem.id()));
const double topicX = currentTopicItem.topicData().x;
const double topicY = currentTopicItem.topicData().y;
const double topicW = currentTopicItem.topicData().w;
const double topicH = currentTopicItem.topicData().h;
ERROR_LOG(m_logger) << "ScreenShotter::addTopicsForScreen: x: " << topicX << ", y: " << topicY << ", w: " << topicW << ", h: " << topicH;
TopicShotter *topicShotter = new TopicShotter(id, name, screen, 0, startTime, stopTime, topicX, topicY,
topicW, topicH, (!currentTopicItem.parentChannel().isEmpty()),
currentTopicItem.description(), this);
connect(this, &ScreenShotter::newTime, topicShotter, &TopicShotter::onNewTime);
connect(topicShotter, &TopicShotter::exportFailed, this, [this, id]() { emit topicExportFailed(id); });
connect(topicShotter, &TopicShotter::exportFailed, this, [this, name, id]()
{
ERROR_LOG(m_logger) << "Export failed for topic '" << name << "', id: " << id;
m_failedExportTopicIds += id;
});
m_topicShotter.append(topicShotter);
}
DEBUG_LOG(m_logger) << "Added " << topicItems.size() << " TopicShotter.";
}
void ScreenShotter::saveRecordings(const QString &tmpDirPath)
{
for (TopicShotter *tr : qAsConst(m_topicShotter))
{
if (tr->isFailed())
continue;
try
{
tr->saveRecording(tmpDirPath);
}
catch (const QString &msg)
{
ERROR_LOG(m_logger) << "Saving screenshot of topic '" << tr->topicName() << "', id: " << tr->topicId() << " failed! " << msg;
m_failedExportTopicIds += tr->topicId();
}
}
}
void ScreenShotter::setCurrentTime(qint64 currentTime)
{
emit newTime(currentTime);
}
} // namespace PresentationManagement
@@ -0,0 +1,50 @@
/**
* @RDE_HEADER@
* class for recording topics on a screen
* $Id:
* $HeadURL:
* $Author:
* Created by Hannes Fritsche, SLTE, 2023-01-18
*/
#pragma once
#include <QObject>
#include <QTemporaryDir>
#include <QSet>
#include <Services/PresentationManagement/logger.h>
#include "pre_topic_shotter.h"
class QProcess;
class Screen;
namespace PresentationManagement
{
class TopicItem;
class ScreenShotter : public QObject
{
Q_OBJECT
public: // methods
ScreenShotter(QObject *parent = nullptr);
void addTopicsForScreen(Screen *screen, const std::vector<TopicItem> &topicItems);
void setCurrentTime(qint64 currentTime);
void saveRecordings(const QString &tmpDirPath);
const QSet<int> &failedExportTopicIds() const { return m_failedExportTopicIds; }
signals:
void newTime(qint64 time);
void topicExportFailed(const int failedTopicId);
private: // attributes
QVector<TopicShotter *> m_topicShotter;
QSet<int> m_failedExportTopicIds;
LOGGER_T m_logger;
}; // class ScreenShotter
} // namespace PresentationManagement
@@ -0,0 +1,210 @@
#include "pre_script_delegate.h"
#include "pre_script_dialog_drag_handler.h"
#include "pre_script_model.h"
#include <LtsTimeline/timeline_header_view.h>
#include <LtsTimeline/timeline_view.h>
#include <QApplication>
#include <QGuiApplication>
#include <QPainter>
#include <QDebug>
namespace PresentationManagement
{
QColor ScriptDelegate::s_blockedBackgroundColor = QGuiApplication::palette().color(QPalette::Disabled, QPalette::Base);
QColor ScriptDelegate::s_validDragColor = QRgb(0x00FF66);
QColor ScriptDelegate::s_invalidDragColor = QRgb(0xFF3333);
ScriptDelegate::ScriptDelegate(LtsTimeline::TimelineView *parent)
: LtsTimeline::ItemDelegate(parent)
{
m_model = static_cast<ScriptModel *>(m_view->timelineModel());
Q_ASSERT(m_model);
m_dragHandler = static_cast<ScriptDialogDragHandler *>(m_view->dragHandler());
Q_ASSERT(m_dragHandler);
}
///////////////////////////////////////////////////////////////////////////////
// PUBLIC
void ScriptDelegate::paintDragItems(QPainter *painter) const
{
if (!m_dragHandler->isActive() && !m_dragHandler->m_isDragEventFromTopicEditor)
return;
if (m_dragHandler->m_isDragEventFromTopicEditor)
{
paintDragItemFromEditor(painter);
return;
}
paintDragItemsFromView(painter);
}
///////////////////////////////////////////////////////////////////////////////
// PRIVATE
void ScriptDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
if (index.column() == LtsTimeline::Section::eActuator)
{
painter->save();
QStyledItemDelegate::paint(painter, option, index);
paintTrackButtons(painter, option, index);
painter->restore();
return;
}
auto drawItem = [this, &option, &painter, &index](const TopicItem &topicItem)
{
QStyleOptionViewItem opt = option;
opt.rect.setLeft(std::max(m_view->toPixel(topicItem.startTime()), option.rect.left()));
opt.rect.setRight(std::min(m_view->toPixel(topicItem.startTime() + topicItem.duration()), option.rect.right()) - 1);
opt.rect.setBottom(opt.rect.bottom() - 1);
topicItem.paint(painter, opt, index, m_view->selectedItemIds().contains(topicItem.id()));
};
try
{
const TopicTrack &track = static_cast<const TopicTrack &>(m_view->timelineModel()->track(index.internalId()));
painter->save();
painter->setPen(Qt::black);
painter->drawRect(option.rect);
painter->setClipping(true);
painter->setClipRect(option.rect);
if (m_model->isFilterTrack(track.id()))
{
try
{
const TopicTrack &parentTrack = static_cast<const TopicTrack &>(m_view->timelineModel()->track(track.parentId()));
const LtsTimeline::Items &parentItems = parentTrack.items();
Q_ASSERT(m_model->isMainTrack(parentTrack.id()));
QRect blockedBackgroundRect = option.rect;
blockedBackgroundRect.setLeft(blockedBackgroundRect.left() + 1);
blockedBackgroundRect.setTop(blockedBackgroundRect.top() + 1);
blockedBackgroundRect.setBottom(blockedBackgroundRect.bottom() - 1);
painter->setPen(s_blockedBackgroundColor);
painter->setBrush(QBrush(s_blockedBackgroundColor));
for (const LtsTimeline::ItemPtr &itemPtr : parentItems)
{
blockedBackgroundRect.setRight(m_view->toPixel(itemPtr->startTime()) - 1);
if (blockedBackgroundRect.width() > 0)
painter->drawRect(blockedBackgroundRect);
blockedBackgroundRect.setLeft(m_view->toPixel(itemPtr->startTime() + itemPtr->duration()) + 1);
}
blockedBackgroundRect.setRight(option.rect.right() - 1);
if (blockedBackgroundRect.width() > 0)
painter->drawRect(blockedBackgroundRect);
}
catch (...)
{
qCritical() << "ItemDelegate::paint -> painting background failed";
}
}
painter->setPen(Qt::black);
const LtsTimeline::Items &items = track.items();
for (const LtsTimeline::ItemPtr &item : items)
drawItem(static_cast<const TopicItem &>(*item.get()));
painter->restore();
}
catch (...)
{
qCritical() << "ItemDelegate::paint -> CATCH";
}
}
void ScriptDelegate::paintDragItemsFromView(QPainter *painter) const
{
if (m_dragHandler->m_dragItemIds.isEmpty() || !m_dragHandler->m_canDropTopicTypes)
return;
const QModelIndex index = m_view->indexAt(m_dragHandler->m_dragPos);
if (!index.isValid() || index.column() == LtsTimeline::Section::eActuator)
return;
painter->save();
const QRect rightSectionRect = QRect(
QPoint(m_view->header()->sectionPosition(LtsTimeline::Section::eItems), 0), m_view->geometry().bottomRight()
);
painter->setClipRect(rightSectionRect);
int dx = m_dragHandler->deltaX();
int dy = m_dragHandler->deltaY();
try
{
if (index == m_dragHandler->m_dragStartIndex)
{
if (m_dragHandler->canMoveItemsTo(m_dragHandler->m_dragPos))
painter->setBrush(QBrush(s_validDragColor));
else
painter->setBrush(QBrush(s_invalidDragColor));
}
else
{
if (m_dragHandler->canDragItemsTo(m_dragHandler->m_dragPos))
painter->setBrush(QBrush(s_validDragColor));
else
painter->setBrush(QBrush(s_invalidDragColor));
}
for (const int &itemId : m_dragHandler->m_dragItemIds)
{
LtsTimeline::Item &item = m_view->timelineModel()->item(itemId);
QModelIndex idx = m_view->timelineModel()->indexOfTrack(item.parentTrackId());
QRect visualRect = m_view->visualRect(idx);
int left = m_view->toPixel(item.startTime()) + dx;
int right = m_view->toPixel(item.startTime() + item.duration()) + dx;
int top = visualRect.top() + dy;
int bottom = visualRect.bottom() + dy;
if (m_view->selectedItemIds().contains(itemId))
painter->setPen(QPen(LtsTimeline::Item::s_highlightColor, 5));
painter->drawRect(QRect(left, top, right - left - 1, bottom - top));
}
} catch (...) {}
painter->restore();
}
void ScriptDelegate::paintDragItemFromEditor(QPainter *painter) const
{
try
{
const QModelIndex index = m_view->indexAt(m_dragHandler->m_dragPos);
if (!index.isValid())// || index.column() != LtsTimeline::Section::eItems)
return;
ScriptModel *model = static_cast<ScriptModel *>(m_view->timelineModel());
if (!model)
return;
if (!model->canDropTopicType(m_dragHandler->m_dragItem->topicType(), index.internalId()))
return;
painter->save();
QCursor *cursor = QApplication::overrideCursor();
if (cursor)
painter->setBrush(QBrush(cursor->shape() == Qt::ForbiddenCursor ? s_invalidDragColor : s_validDragColor));
else // extremely rare, but possible ...
painter->setBrush(QBrush(m_view->cursor().shape() == Qt::ForbiddenCursor ? s_invalidDragColor : s_validDragColor));
QRect rect = m_view->visualRect(index);
painter->setClipRect(rect);
rect.setLeft(m_dragHandler->m_dragPos.x() - m_dragHandler->m_dragItemOffset + m_dragHandler->deltaX());
rect.setRight(rect.left() + m_dragHandler->dragItemFromTopicEditorWidth());
rect.setBottom(rect.top() + m_dragHandler->deltaY() - 1);
painter->drawRect(rect);
painter->restore();
} catch (...) {}
}
} // namespace PresentationManagement
@@ -0,0 +1,43 @@
#pragma once
#include <LtsTimeline/item_delegate.h>
namespace PresentationManagement
{
class ScriptDialogDragHandler;
class ScriptModel;
class ScriptTimelineDialog;
class ScriptDelegate : public LtsTimeline::ItemDelegate
{
Q_OBJECT
public:
static QColor s_blockedBackgroundColor;
static QColor s_validDragColor;
static QColor s_invalidDragColor;
explicit ScriptDelegate(LtsTimeline::TimelineView *parent = nullptr);
/**
* @brief paintDragItems paints dragged items over the view
* @param painter painter to draw all dragged items on the view
*/
void paintDragItems(QPainter *painter) const override;
private:
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
void paintDragItemsFromView(QPainter *painter) const;
void paintDragItemFromEditor(QPainter *painter) const;
private:
ScriptModel *m_model { nullptr };
ScriptDialogDragHandler *m_dragHandler { nullptr };
};
} // namespace PresentationManagement
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,470 @@
/**
* @RDE_HEADER@
* PresentationManagement - Script Editor
* $Id$
* $HeadURL$
* $Author$
*
* Created by Mike Wille, SLSE, 2017-03-17
*/
#pragma once
#include "pre_screen.h"
#include "pre_journal_types.h"
#include "pre_utils.h"
#include <DDS/Core/core_dtc.h>
#include <LtsPluginManager/lts_plugin_manager.h>
#include <LtsSession/lts_session.h>
#include <LtsUtilsGuiDds/request_helper.h>
#include <LtsWidgets/lts_dialog.h>
#include <PluginInterfaces/topic_replay_interface.h>
#include <Plugins/PresentationManagement/Gui/Common/topic_assignment.h>
#include <Plugins/SituationMapPlugin/situation_map_gui_plugin_interface.h>
#include <Plugins/SituationReplay/srp_gui_plugin_interface.h>
#include <Services/OperatorManagement/opm_dtc.h>
#include <Services/ExerciseManagement/exm_dtc.h>
#include <Services/PresentationManagement/pre_dtc.h>
#include <Services/PresentationManagement/pre_ftc.h>
#include <Services/SituationReplayManagement/srm_ftc.h>
#include <TopicEditorBase/topic_editor_utils.h>
#include <LtsWidgets/lts_overlay_loading.h>
#include <functional>
#include <memory>
#include <QString>
#include <QStringList>
#include <QMap>
#include <QTemporaryDir>
namespace Ui
{
class ScriptDialog;
class ScriptPlayerDialog;
}
namespace LtsPluginManager { class LtsPluginManager; }
namespace LtsSession { class LtsSession; }
namespace LtsTimeline { class TimelineWidget; }
namespace LtsUtils { class Promise; }
namespace SituationReplayManagement { class ForeignDataInterface; }
namespace TakeHomePackageGenerator { class ExportProcess; }
namespace PresentationManagement
{
class ScreenShotter;
class ScreenRecorder;
class ScriptModel;
class StoryboardDialog;
class TopicDesigner;
class TopicItem;
class CoolExporter;
namespace Interface { class Ftc; }
//template<typename Type = void *>
class Scope
{
public:
explicit Scope(const std::function<void (void)> &ctor, const std::function<void (void)> &dtor)
//: m_fn(fn)
//: m_data(ctor())
: m_dtor(dtor)
{
ctor();
}
~Scope()
{
m_dtor();
}
// Scope(const std::function<Type (void)> &ctor, const std::function<void (Type)> &dtor)
// //: m_fn(fn)
// : m_data({})
// {
// }
private:
//Type m_data;
std::function<void (void)> m_dtor;
};
class ScriptTimelineDialog : public LtsWidgets::LtsDialog
{
public:
Q_OBJECT
friend class ScriptDelegate;
public:
friend class CoolExporter;
enum class ProjectionWidget { Unknown = 0, Screen1, Screen2, };
enum class SpecialTopicType { Normal = 0, Replay, Filter, };
static TopicEditorBase::TopicData topicDataFromContent(
const PresentationManagement::PresentationContent &content, int topicId, TopicEditorBase::TopicType topicType
);
static bool isFilter(TopicEditorBase::TopicType topicType);
static bool isUsingAvsWindow(TopicEditorBase::TopicType topicType);
static QString toReadableTime(qint64 msecs);
explicit ScriptTimelineDialog(const QString &menuGroup, QWidget *parent = nullptr);
~ScriptTimelineDialog();
bool init(
LtsSession::LtsSession *session, const int scriptId, const std::shared_ptr<Interface::Dtc> &dtc,
const std::shared_ptr<Interface::Ftc> &ftc,
const std::shared_ptr<SituationReplayManagement::ForeignDataInterface> &srmForeign,
const std::shared_ptr<Core::Interface::Dtc> &coreDtc, const std::shared_ptr<OperatorManagement::Interface::Dtc> &opmDtc,
const std::shared_ptr<ExerciseManagement::Interface::Dtc> &exmDtc
);
void initInternal();
LtsTimeline::TimelineWidget *timeline();
ScriptModel *model() { return m_model; }
int scriptId() const { return m_scriptId; }
bool isScriptRunning() const { return m_isScriptRunning; }
qint64 scriptEndTime() const;
LtsSession::LtsSession *essentialsViewSession() { return m_essentialsViewSession; }
bool isEssentialsViewOpen() const;
bool import(const QVector<PresentationContent> &contents);
void load();
TopicEditorBase::TopicEditor *getTopicEditor(const TopicAssignment &topicAssignment);
TopicEditorBase::TopicEditor *getTopicEditor(const TopicEditorBase::TopicData &data);
bool topicStringToType(const QString &typeName, TopicEditorBase::TopicType &topicType) const;
std::shared_ptr<SituationReplay::ForeignGuiInterface> getReplayGuiIfc() const
{
return m_essentialsViewsSrpIfc;
}
private:
// LtsDialog override
void retranslate() override;
bool apply() override;
void aboutToClose() override;
void getReplaySession() noexcept(false);
bool getTopics(QStringList &resultList);
bool getTopic(
TopicEditorBase::TopicType topicType, const std::shared_ptr<TopicEditorBase::Interface> &topicEditorIfc,
const std::shared_ptr<TopicReplay::Factory> &topicReplayFactory,
const std::shared_ptr<TopicReplay::Factory> &topicExportFactory, QStringList &resultList,
const SpecialTopicType &specialTopicType = SpecialTopicType::Normal
);
bool getTopicItems();
void getSitMapApp() noexcept(false);
void getPresentationContent(); // tryAddTopics has to be called first to populate m_topicAssignments
void getProjectionWidget(Screen **pointerToWidgetPointer, bool placeAutomatically = false);
// dds stuff
bool enableFireEventFlash(bool enable);
// io stuff
void exportPresentationContent(const Core::FunctionReply &baseReply);
// ui stuff
void disableSessionDialog(LtsSession::LtsSession *session, bool disable);
void startEssentialsViewStage2(const Core::ReplayPartition &replay, bool openTopicEditor, int topicType, int topicItemId);
static bool hasEssentialsViewTopicType(TopicEditorBase::TopicType topicType);
void previewSecond(qint64 time, int ignoreTopicForReplay = -1);
TopicEditorBase::TopicEditor *createTopicEditor(const TopicEditorBase::TopicData &data);
Screen *getProjectionWidget(const ProjectionWidget &projectionWidget);
ProjectionWidget identifyProjectionWidget(const TopicItem &topicItem) const;
static void deleteProjectionWidget(Screen **pointerToWidgetPointer);
void addReplayWidget(int topicItemId, QWidget *replayWidget);
void setReplayWidgetPosition(int topicItemId);
void setReplayWidgetFreestanding(int topicItemId, bool freestanding);
void displayPlaylist();
bool isScreenFreestanding(const ProjectionWidget &projectionWidget) const;
void setProjectionWidgetFreestanding(bool freestanding, const ProjectionWidget &projectionWidget);
void avsVisibilityHack(QWidget *parentForScreen, bool visible);
void avsFreestandingHack(bool freestanding, QWidget *projectionWidget, QWidget **parentForScreen);
void enaDisa();
void startEssentialsView(int topicItemId = -1, int topicType = -1, bool openTopicEditor = false);
// topic stuff
void openContextMenu(QPoint pos, int topicItemId);
void setTopicDescription(int topicItemId);
void enableTopicEditors(bool enable);
// replay stuff
qint64 positionTime() const;
void setPositionTime(qint64 time, bool setByUser = false);
void setPositionTimeActive(qint64 time, bool setByUser = false); // setPositionTime, but, if playing, also ticks and resets timeout
void playTopic(int topicItemId);
void pause();
void stop(bool ended = false, bool ignoreFireEventFlashing = false);
qint64 previousTopicTime() const;
qint64 nextTopicTime() const;
bool hasPreviousTopic() const { return previousTopicTime() > -1; }
bool hasNextTopic() const { return nextTopicTime() > -1; }
void removeTopicItemFromPlaylist(int topicItemId); // WARNING: use a mutable iterator while iterating over m_currentlyPlayingTopics and remove the entry before calling this method!
bool isTopicAlreadyOpened(const TopicEditorBase::TopicData &data);
template <typename TopicEditorInterface>
std::shared_ptr<TopicEditorBase::Interface> getTopicEditorIfc(TopicEditorBase::TopicType topicType)
{
std::shared_ptr<TopicEditorBase::Interface> topicEditorIfc;
if (!m_exerciseSession->interfaceFromGui<TopicEditorInterface>(topicEditorIfc))
{
ERROR_LOG(m_logger) << "getTopicEditorIfc failed: Cannot add topic type "
<< TopicEditorBase::topicTypeString(topicType)
<< " because topic editor ifc is missing.";
return nullptr;
}
return topicEditorIfc;
}
template <typename TopicReplayInterface>
std::shared_ptr<TopicReplay::Factory> getTopicReplayFactory(TopicEditorBase::TopicType topicType)
{
std::shared_ptr<TopicReplay::Factory> topicReplayFactory;
if (!m_replaySession->interfaceFromGui<TopicReplayInterface>(topicReplayFactory))
{
ERROR_LOG(m_logger) << "getTopicIfcFactory failed: Cannot add topic type "
<< TopicEditorBase::topicTypeString(topicType)
<< " because topic ifc factory is missing.";
return nullptr;
}
return topicReplayFactory;
}
bool isTopicDisabled(TopicEditorBase::TopicType topicType) const;
template <typename TopicEditorGuiInterface,
typename TopicReplayFactory = TopicReplay::Factory,
typename TopicExportFactory = TopicReplayFactory>
bool tryAddTopic(TopicEditorBase::TopicType topicType,
QStringList &resultList,
const SpecialTopicType &specialTopicType = SpecialTopicType::Normal)
{
// qWarning() << "mep" << QStringLiteral("%1:%2").arg(QString::fromUtf8(__FILE__).split(QStringLiteral("/")).last()).arg(__LINE__);
if (isTopicDisabled(topicType))
return true;
std::shared_ptr<TopicEditorBase::Interface> topicEditorIfc;
std::shared_ptr<TopicReplay::Factory> topicReplayFactory;
std::shared_ptr<TopicReplay::Factory> topicExportFactory;
if (!std::is_same<TopicEditorBase::Interface, TopicEditorGuiInterface>::value)
{
topicEditorIfc = getTopicEditorIfc<TopicEditorGuiInterface>(topicType);
if (topicEditorIfc == nullptr)
{
resultList.append(TopicEditorBase::topicTypeString(topicType));
return false;
}
}
if (!std::is_same<TopicReplay::Factory, TopicReplayFactory>::value)
{
topicReplayFactory = getTopicReplayFactory<TopicReplayFactory>(topicType);
if (topicReplayFactory == nullptr)
{
resultList.append(TopicEditorBase::topicTypeString(topicType));
return false;
}
}
if (!std::is_same<TopicReplay::Factory, TopicExportFactory>::value)
{
topicExportFactory = getTopicReplayFactory<TopicExportFactory>(topicType);
if (topicExportFactory == nullptr)
{
resultList.append(TopicEditorBase::topicTypeString(topicType));
return false;
}
}
else
topicExportFactory = topicReplayFactory;
return getTopic(topicType, topicEditorIfc, topicReplayFactory, topicExportFactory, resultList, specialTopicType);
}
bool hasPlacedBothProjectionWidgets() const;
private slots:
void onChangesMade();
void onChangesWereMade();
void onDiscard();
void onNull(QObject *obj);
// io stuff
void onLoad();
void onExport();
void onExport2();
void onExportSuccessful(const QString &path, const QString &failedTopics);
void onBurnDvD(const QString &strExportPath);
void onBurningFinished(bool success);
// ui stuff
void onPresentationContent(const GetPresentationContentReply &reply);
void onEssentialsView();
void onTopicItemPositionChanged(QWidget *previewWidget, int ignoreTopicForReplay);
void onCheckTopicItemDuration(int duration, LtsUtils::Promise *promise);
void onTopicItemModified();
void onTopicItemRemoved(int topicItemId);
void onTopicItemSelectionChanged(const QVector<int> &selectedItemIds);
void onUpdateTopicEditor(int topicItemId);
void onTopicDesignerChangesMade();
void onTopicEditorChangesSaved(const TopicEditorBase::TopicData &data);
// topic stuff
void onGetTopicEditor(const TopicEditorBase::TopicData &data);
void onEditSelectedTopicItem();
void onEditTopicItem(int itemId);
void onRemoveSelectedTopics();
void onContextMenu(QPoint pos);
// void onMergeTopics();
// void onSplitTopic();
// replay stuff
void onTimeout();
void onPlay();
void onPause();
void onStop();
void onNextTopic();
void onPreviousTopic();
void onPlayUntilNextTopic();
void onShowStoryboard();
void onShowJournal();
// other stuff
bool checkBuildingViewerAndEssentialsView();
signals:
void scriptChanged(int);
void isPlaying(bool);
void stopped(bool ended);
void scriptTick(qint64 positionTime);
private:
LtsSession::LtsSession *m_exerciseSession { nullptr }; // aka. script editor app
LtsSession::LtsSession *m_replaySession { nullptr };
LtsSession::LtsSession *m_sitMapSession { nullptr };
LtsSession::LtsSession *m_essentialsViewSession { nullptr };
LtsPluginManager::LtsPluginManager *m_essentialsViewApp { nullptr };
LtsPluginManager::LtsPluginManager *m_sitMapApp { nullptr };
LtsUtils::RequestHelper *m_requestHelper { nullptr };
TakeHomePackageGenerator::ExportProcess *m_exportProcess { nullptr }; // OLD exporter!
std::shared_ptr<Core::Interface::Dtc> m_coreDtc { nullptr };
std::shared_ptr<PresentationManagement::Interface::Dtc> m_dtc { nullptr };
std::shared_ptr<PresentationManagement::Interface::Ftc> m_ftc { nullptr };
std::shared_ptr<OperatorManagement::Interface::Dtc> m_opmDtc { nullptr };
std::shared_ptr<ExerciseManagement::Interface::Dtc> m_exmDtc { nullptr };
std::shared_ptr<SituationReplay::ForeignGuiInterface> m_essentialsViewsSrpIfc;
std::shared_ptr<SituationMap::ForeignGuiInterface> m_essentialsViewsSitMapIfc;
std::shared_ptr<SituationReplayManagement::ForeignDataInterface> m_srmForeignDataIfc { nullptr };
TopicEditorBase::TopicEditor *m_currentTopicEditor { nullptr };
QPointer<TopicDesigner> m_currentTopicDesigner;
QPointer<StoryboardDialog> m_storyboardDialog;
QWidget *m_previousParentForScreen1 { nullptr };
QWidget *m_previousParentForScreen2 { nullptr };
Screen *m_projectionWidget1 { nullptr };
Screen *m_projectionWidget2 { nullptr };
QString m_menuGroup;
QString m_exerciseName;
int m_scriptId { 0 };
int m_scriptsExerciseId { 0 };
QString m_scriptName;
QString m_scriptOwner;
qint64 m_lastModified { 0 };
bool m_ignoreChanges { false };
bool m_isApplyEnabled { true };
bool m_isContextMenuOpen { false };
int m_selectedTeId;
QMap<QString, TopicAssignment> m_topicAssignments;
QMap<TopicEditorBase::TopicType, TopicAssignment> m_trackEntryToTopicAssignment;
QMultiMap<int, LtsScriptTimeline::TrackEntry *> m_topicMap; // <seconds, trackEntry>
QTimer *m_presentationTimer { nullptr };
qint64 m_firstTopicTime { -1 };
qint64 m_lastTopicTime { -1 };
qint64 m_nextPauseTime { -1 };
int m_duration { 0 };
int m_ignoreTopicForReplay { -1 };
bool m_isFirstPlayAfterStop { true };
bool m_isScriptRunning { false };
bool m_doNotAllowPrevNext { false };
bool m_isPlayTopicMethodStillRunning { false };
bool m_stopAfterPlayingFilterTopic { false };
MainJournal m_journal;
bool m_isTopicsScreen1Freestanding { false };
bool m_isTopicsScreen2Freestanding { false };
bool m_isAvsVisibilityHackActive { false };
bool m_isPaused { false };
ScriptModel *m_model { nullptr };
QVector<int> m_playlistTopicItemIds;
QVector<int> m_mainTopicItemIds;
QVector<int> m_filterTopicItemIds;
Ui::ScriptDialog *m_ui { nullptr };
bool m_lastScriptHasEnded { false };
NGL::INGLLogger &m_logger;
QMap<TopicEditorBase::TopicType, int> m_avaibleTopics{}; // <data.topicType, data.id>
QVector<TopicEditorBase::TopicType> m_modifiedTopicTypesList;
LtsSession::LtsSession *m_session { nullptr };
};
class CoolExporter : public QObject
{
Q_OBJECT
public:
explicit CoolExporter(ScriptTimelineDialog &dialog);
QSet<int> go() noexcept(false);
signals:
void scriptTick(qint64 positionTime);
void cancel();
private:
void cancelationPoint() noexcept(false);
ScriptTimelineDialog &m_this;
std::unique_ptr<LtsWidgets::LtsOverlayLoading> m_loading;
bool m_stoppedPrematurely { false };
NGL::INGLLogger &m_logger;
};
} // namespace PresentationManagement
@@ -0,0 +1,332 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ScriptDialog</class>
<widget class="QWidget" name="ScriptDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1010</width>
<height>215</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Script Editor</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>9</number>
</property>
<property name="topMargin">
<number>9</number>
</property>
<property name="rightMargin">
<number>9</number>
</property>
<property name="bottomMargin">
<number>9</number>
</property>
<item>
<widget class="LtsTimeline::TimelineWidget" name="timeline" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="gbReplayControls">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Replay Controls</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QWidget" name="wReplayControls" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0,999">
<item>
<layout class="QHBoxLayout" name="hboxReplayControls">
<item>
<layout class="QVBoxLayout" name="vboxReplayControlsMain">
<item>
<layout class="QHBoxLayout" name="hboxReplayControlsScreens">
<item>
<widget class="QPushButton" name="pbInitScreen1">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Set Screen 1</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pbInitScreen2">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Set Screen 2</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="pbStop">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Stop</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pbPause">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Pause</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pbPlay">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Play</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="vboxReplayControlsJump">
<item>
<widget class="QPushButton" name="pbUntilNextTopic">
<property name="text">
<string>Play Until Next Topic</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pbPreviousTopic">
<property name="text">
<string>Previous Topic</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pbNextTopic">
<property name="text">
<string>Next Topic</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QFormLayout" name="formReplayInfo">
<property name="horizontalSpacing">
<number>9</number>
</property>
<property name="verticalSpacing">
<number>9</number>
</property>
<property name="leftMargin">
<number>9</number>
</property>
<property name="topMargin">
<number>9</number>
</property>
<property name="rightMargin">
<number>9</number>
</property>
<property name="bottomMargin">
<number>9</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="durationLabel">
<property name="text">
<string>Presentation duration:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="lblDuration">
<property name="text">
<string notr="true"> - </string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="lbxPos">
<property name="text">
<string>Pos:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSlider" name="sPos">
<property name="enabled">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="tickPosition">
<enum>QSlider::TicksBelow</enum>
</property>
<property name="const" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="lbxCurrentPosition">
<property name="text">
<string>Current position:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<layout class="QHBoxLayout" name="hboxCurrentPosition">
<item>
<widget class="QLabel" name="lblPos">
<property name="text">
<string notr="true">0</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="lbxSlash">
<property name="text">
<string>/</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="lblMax">
<property name="text">
<string notr="true">0</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="vboxStatus">
<item>
<widget class="QLabel" name="lbxStatus">
<property name="text">
<string>Status:</string>
</property>
</widget>
</item>
<item>
<widget class="QListWidget" name="lwStatus">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustToContentsOnFirstShow</enum>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="showDropIndicator" stdset="0">
<bool>false</bool>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::NoSelection</enum>
</property>
<property name="verticalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<property name="movement">
<enum>QListView::Free</enum>
</property>
<property name="isWrapping" stdset="0">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>LtsTimeline::TimelineWidget</class>
<extends>QWidget</extends>
<header>LtsTimeline/timeline_widget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
@@ -0,0 +1,495 @@
#include "pre_script_dialog_drag_handler.h"
#include "pre_script_model.h"
#include <LtsTimeline/item_delegate.h>
#include <LtsTimeline/timeline_widget.h>
#include <TopicEditorBase/topic_editor_utils.h>
#include <QApplication>
#include <QDebug>
#include <QDrag>
#include <QMimeData>
#include <QMouseEvent>
#include <QTimer>
namespace PresentationManagement
{
ScriptDialogDragHandler::ScriptDialogDragHandler(TimelineView *view)
: TimelineViewDragHandler(view)
, m_timer(new QTimer(this))
{
m_model = qobject_cast<ScriptModel *>(m_view->timelineModel());
Q_ASSERT(m_model);
m_timer->setSingleShot(true);
connect(m_timer, &QTimer::timeout, this, [this]()
{
m_mutex = false;
});
}
///////////////////////////////////////////////////////////////////////////////
// PUBLIC
void ScriptDialogDragHandler::dragEnterEvent(QDragEnterEvent *e)
{
if (!m_model->isEditable())
{
e->setAccepted(false);
return;
}
if (e->source() == m_view)
{
TimelineViewDragHandler::dragEnterEvent(e);
return;
}
if (!e->mimeData()->hasFormat(TopicEditorBase::TopicData::mime))
{
e->ignore();
return;
}
QDataStream dataStream(e->mimeData()->data(TopicEditorBase::TopicData::mime));
dataStream >> m_dragTopicData;
m_dragItem = std::make_unique<TopicItem>(TopicItem(m_dragTopicData));
m_isDragEventFromTopicEditor = true;
m_dragPos = e->pos();
if (m_dragItem->duration() > m_view->zoomFactor())
{
ZoomFactor zoom = Zoom::ceil(m_dragItem->duration());
if (m_dragItem->duration() > m_view->duration())
m_view->setDuration(zoom);
m_view->setZoomFactor(zoom);
}
e->acceptProposedAction();
}
void ScriptDialogDragHandler::dragMoveEvent(QDragMoveEvent *e)
{
if (!m_isDragEventFromTopicEditor)
{
TimelineViewDragHandler::dragMoveEvent(e);
setCanDropTopicTypes();
try
{
adjust();
}
catch (const QString &errMsg) { qCritical() << LOGFUNCTION <<"zoom and scroll failed:" << errMsg; }
catch (...) { qCritical() << LOGFUNCTION <<"zoom and scroll failed:"; }
return;
}
m_dragPos = e->pos();
setCanDropTopicTypes();
m_view->header()->setHoverPosition(m_dragPos.x());
e->setAccepted(canDropTopicItemAt(e->pos()));
try
{
adjust();
}
catch (const QString &errMsg) { qCritical() << LOGFUNCTION <<"zoom and scroll failed:" << errMsg; }
catch (...) { qCritical() << LOGFUNCTION <<"zoom and scroll failed:"; }
m_view->viewport()->update();
}
void ScriptDialogDragHandler::dragLeaveEvent(QDragLeaveEvent *e)
{
if (!m_isDragEventFromTopicEditor)
{
TimelineViewDragHandler::dragLeaveEvent(e);
m_view->setEndTime(std::max(ScriptModel::s_minScriptDuration, m_model->lastTopicEnd() + ScriptModel::s_scriptEndOffset));
return;
}
qint64 end = -1;
try
{
end = maxDragItemEnd();
}
catch (const QString &errMsg) { qCritical() << LOGFUNCTION << "obtaining end time failed:" << errMsg; }
catch (...) { qCritical() << LOGFUNCTION << "obtaining end time failed:"; }
m_view->setEndTime(std::max(ScriptModel::s_minScriptDuration, m_model->lastTopicEnd() + ScriptModel::s_scriptEndOffset));
if (end != -1 && m_view->currentTime() + m_view->zoomFactor() < end)
m_view->setCurrentTime(end + scrollOffset() - m_view->zoomFactor());
m_dragPos = {};
m_isDragEventFromTopicEditor = false;
m_dragTopicData = TopicEditorBase::TopicData();
m_dragItemOffset = 0;
}
void ScriptDialogDragHandler::dropEvent(QDropEvent *e)
{
if (!m_isDragEventFromTopicEditor)
{
const QVector<int> dragItemIdsCopy = m_dragItemIds;
TimelineViewDragHandler::dropEvent(e);
for (int dragItemId : dragItemIdsCopy)
{
try
{
TopicItem &topicItem = m_model->topicItem(dragItemId);
const TopicTrack &track = static_cast<const TopicTrack &>(m_model->track(topicItem.parentTrackId()));
topicItem.topicData().startTime = topicItem.startTime() / 1000LL;
topicItem.setChannel(track.internalName());
if (track.parentId() != m_model->rootTrack().id())
topicItem.setParentChannel(static_cast<const TopicTrack &>(m_model->track(track.parentId())).internalName());
}
catch (const QString &errMsg) { qCritical() << LOGFUNCTION << "failed for item with id =" << dragItemId << ": " << errMsg; }
catch (...) { qCritical() << LOGFUNCTION << "failed for item with id =" << dragItemId; }
}
m_view->setEndTime(std::max(ScriptModel::s_minScriptDuration, m_model->lastTopicEnd() + ScriptModel::s_scriptEndOffset));
return;
}
if (canDropTopicItemAt(e->pos()))
{
try
{
const QModelIndex index = m_view->indexAt(e->pos());
m_dragTopicData.startTime = m_view->toTime(m_dragPos.x() - m_dragItemOffset) / 1000;
m_model->insertTopicItem(index.internalId(), std::move(TopicItem(m_dragTopicData)));
emit m_view->timelineModel()->dataChanged(index, index);
e->accept();
}
catch (const QString &errMsg)
{
qCritical() << errMsg;
e->ignore();
}
catch (...)
{
qDebug() << "drop of item failed";
e->ignore();
}
}
else
e->ignore();
qint64 end = -1;
try
{
end = maxDragItemEnd();
}
catch (const QString &errMsg) { qCritical() << LOGFUNCTION << "obtaining end time failed:" << errMsg; }
catch (...) { qCritical() << LOGFUNCTION << "obtaining end time failed:"; }
m_view->setEndTime(std::max(ScriptModel::s_minScriptDuration, m_model->lastTopicEnd() + ScriptModel::s_scriptEndOffset));
if (end != -1 && m_view->currentTime() + m_view->zoomFactor() < end)
m_view->setCurrentTime(end + scrollOffset() - m_view->zoomFactor());
m_dragPos = {};
m_isDragEventFromTopicEditor = false;
m_dragTopicData = TopicEditorBase::TopicData();
m_dragItemOffset = 0;
}
void ScriptDialogDragHandler::mousePressEvent(QMouseEvent *e)
{
TimelineViewDragHandler::mousePressEvent(e);
addFilterItemsToDragItems();
}
void ScriptDialogDragHandler::mouseMoveEvent(QMouseEvent *e)
{
TimelineViewDragHandler::mouseMoveEvent(e);
try
{
if (e->buttons() == Qt::LeftButton)
adjust();
}
catch (const QString &errMsg) { qCritical() << LOGFUNCTION << "zoom and scroll failed:" << errMsg; }
catch (...) { qCritical() << LOGFUNCTION <<"zoom and scroll failed:"; }
try
{
QModelIndex index = m_view->indexAt(e->pos());
qint64 time = m_view->toTime(e->pos().x());
const Track &track = m_model->track(index.internalId());
TopicItem &topicItem = m_model->topicItem(track.itemAt(time).id());
qint64 replayTime = topicItem.replayTime(time);
if (replayTime != -1)
{
m_view->setCursorTextVisible(true);
m_view->setCursorText(DateTime::toString(replayTime, DateTimeFormat::DTG));
}
else
m_view->setCursorTextVisible(false);
}
catch (...)
{
m_view->setCursorTextVisible(false);
}
}
void ScriptDialogDragHandler::mouseReleaseEvent(QMouseEvent *e)
{
if (m_timer->isActive())
m_timer->stop();
m_mutex = false;
qint64 end = -1;
try
{
end = maxDragItemEnd();
}
catch (const QString &errMsg) { qCritical() << LOGFUNCTION << "obtaining end time failed:" << errMsg; }
catch (...) { qCritical() << LOGFUNCTION << "obtaining end time failed:"; }
const QVector<int> dragIds = m_dragItemIds;
TimelineViewDragHandler::mouseReleaseEvent(e);
for (int dragId : dragIds)
{
try
{
TopicItem &topicItem = m_model->topicItem(dragId);
topicItem.topicData().startTime = topicItem.startTime() / 1000LL;
}
catch (const QString &errMsg) { qCritical() << LOGFUNCTION << "adjusting topic data failed:" << errMsg; }
catch (...) { qCritical() << LOGFUNCTION << "adjusting topic data failed"; }
}
m_view->setEndTime(std::max(ScriptModel::s_minScriptDuration, m_model->lastTopicEnd() + ScriptModel::s_scriptEndOffset));
if (end != -1 && m_view->currentTime() + m_view->zoomFactor() < end)
m_view->setCurrentTime(end + scrollOffset() - m_view->zoomFactor());
}
int ScriptDialogDragHandler::deltaX() const
{
if (!m_isDragEventFromTopicEditor)
return TimelineViewDragHandler::deltaX();
int xCursor = m_dragPos.x();
qint64 t = m_view->toTime(xCursor);
t -= (t % m_view->timelineModel()->snapStep());
int x = m_view->toPixel(t);
return x - xCursor;
}
int ScriptDialogDragHandler::deltaY() const
{
Q_ASSERT(m_view->itemDelegate());
return m_isDragEventFromTopicEditor ? m_view->itemDelegate()->sizeHint().height() : TimelineViewDragHandler::deltaY();
}
int ScriptDialogDragHandler::dragItemFromTopicEditorWidth() const
{
int xStart = m_view->toPixel(static_cast<qint64>(m_dragTopicData.startTime) * 1000);
int xEnd = m_view->toPixel(static_cast<qint64>(m_dragTopicData.startTime + m_dragTopicData.duration) * 1000);
return xEnd - xStart;
}
///////////////////////////////////////////////////////////////////////////////
// PRIVATE
void ScriptDialogDragHandler::moveItemsLeft(const QVector<int> &, int dt)
{
m_dragItemIds = m_view->selectedItemIds();
addFilterItemsToDragItems();
TimelineViewDragHandler::moveItemsLeft(m_dragItemIds, dt);
try
{
auto iter = std::min_element(m_dragItemIds.begin(), m_dragItemIds.end(), [this](int leftItemId, int rightItemId)
{
const Item &leftItem = m_model->item(leftItemId);
const Item &rightItem = m_model->item(rightItemId);
return leftItem.startTime() + leftItem.duration() < rightItem.startTime() + rightItem.duration();
});
const Item &startItem = m_model->item(*iter);
adjustLeft(std::max(m_view->startTime(), startItem.startTime() - scrollOffset()));
}
catch (...) {}
m_dragItemIds.clear();
}
void ScriptDialogDragHandler::moveItemsRight(const QVector<int> &, int dt)
{
m_dragItemIds = m_view->selectedItemIds();
addFilterItemsToDragItems();
TimelineViewDragHandler::moveItemsRight(m_dragItemIds, dt);
try
{
auto iter = std::max_element(m_dragItemIds.begin(), m_dragItemIds.end(), [this](int leftItemId, int rightItemId)
{
const Item &leftItem = m_model->item(leftItemId);
const Item &rightItem = m_model->item(rightItemId);
return leftItem.startTime() + leftItem.duration() < rightItem.startTime() + rightItem.duration();
});
const Item &endItem = m_model->item(*iter);
adjustRight(endItem.startTime() + endItem.duration() + scrollOffset());
}
catch (...) {}
m_dragItemIds.clear();
}
int ScriptDialogDragHandler::scrollOffset() const
{
return static_cast<qint64>(m_view->zoomFactor()) * 5 / 100;
}
qint64 ScriptDialogDragHandler::minDragItemStart() const noexcept(false)
{
qint64 start = m_view->endTime();
if (!m_dragItemIds.isEmpty())
{
auto iter = std::min_element(m_dragItemIds.begin(), m_dragItemIds.end(), [this](int leftItemId, int rightItemId)
{
const Item &leftItem = m_model->item(leftItemId);
const Item &rightItem = m_model->item(rightItemId);
return leftItem.startTime() + leftItem.duration() < rightItem.startTime() + rightItem.duration();
});
const Item &startItem = m_model->item(*iter);
start = std::max(m_view->startTime(), startItem.startTime() + deltaTime());
}
else if (m_isDragEventFromTopicEditor)
start = m_view->toTime(m_dragPos.x());
return start;
}
qint64 ScriptDialogDragHandler::maxDragItemEnd() const noexcept(false)
{
qint64 end = m_view->startTime();
if (!m_dragItemIds.isEmpty())
{
auto iter = std::max_element(m_dragItemIds.begin(), m_dragItemIds.end(), [this](int leftItemId, int rightItemId)
{
const Item &leftItem = m_model->item(leftItemId);
const Item &rightItem = m_model->item(rightItemId);
return leftItem.startTime() + leftItem.duration() < rightItem.startTime() + rightItem.duration();
});
const Item &endItem = m_model->item(*iter);
end = endItem.startTime() + endItem.duration() + deltaTime();
}
else if (m_isDragEventFromTopicEditor)
end = m_view->toTime(m_dragPos.x()) + static_cast<qint64>(m_dragTopicData.duration) * 1000LL;
return end;
}
void ScriptDialogDragHandler::adjust() noexcept(false)
{
qint64 start = std::max(m_view->startTime(), minDragItemStart() - scrollOffset());
qint64 end = maxDragItemEnd() + scrollOffset();
if (end > m_view->currentTime() + m_view->zoomFactor())
{
qDebug() << "adjusting right";
adjustRight(end);
}
else if (start < m_view->currentTime())
{
qDebug() << "adjusting left";
adjustLeft(start);
}
}
void ScriptDialogDragHandler::adjustLeft(qint64 start) noexcept(false)
{
if (start >= m_view->currentTime())
return;
ZoomFactor newZoom = Zoom::increment(m_view->zoomFactor());
if (m_view->zoomFactor() < newZoom && newZoom <= m_view->upperZoomFactor())
{
m_view->setCurrentTime(m_view->currentTime() - (newZoom - m_view->zoomFactor()));
m_view->setZoomFactor(newZoom);
return;
}
m_view->setCurrentTime(m_view->currentTime() - scrollOffset());
}
void ScriptDialogDragHandler::adjustRight(qint64 end) noexcept(false)
{
if (end <= m_view->currentTime() + m_view->zoomFactor())
return;
ZoomFactor newZoom = Zoom::increment(m_view->zoomFactor());
if (m_view->zoomFactor() < newZoom && newZoom <= m_view->maximumZoomFactor())
{
if (m_view->currentTime() + newZoom > m_view->zoomFactor() && !m_mutex)
{
m_mutex = true;
m_timer->start(1000);
m_view->setEndTime(m_view->currentTime() + newZoom);
}
m_view->setZoomFactor(newZoom);
return;
}
m_view->setEndTime(std::max(m_view->endTime(), end));
m_view->setCurrentTime(m_view->endTime() - m_view->zoomFactor());
}
void ScriptDialogDragHandler::addFilterItemsToDragItems()
{
for (const int &itemId : qAsConst(m_dragItemIds))
{
try
{
const Item &item = m_model->item(itemId);
const Track &track = m_model->track(item.parentTrackId());
if (track.hasTracks())
{
for (const TrackPtr &trackPtr : track.tracks())
{
const QVector<int> itemIds = trackPtr->itemIdsBetween(item.startTime(), item.startTime() + item.duration(), false, true);
for (const int &id : qAsConst(itemIds))
if (!m_dragItemIds.contains(id))
m_dragItemIds.append(id);
}
}
}
catch (...) {}
}
}
void ScriptDialogDragHandler::setCanDropTopicTypes()
{
m_canDropTopicTypes = true;
for (const int &itemId : qAsConst(m_dragItemIds))
{
try
{
if (!m_model->canDropTopicType(m_model->topicItem(itemId).topicType(), dragItemTrackId(m_model->item(itemId))))
{
m_canDropTopicTypes = false;
break;
}
}
catch (...) {}
}
}
bool ScriptDialogDragHandler::canDropTopicItemAt(const QPoint &point) const
{
if (!m_isDragEventFromTopicEditor)
return TimelineViewDragHandler::canDragItemsTo(point);
const QModelIndex index = m_view->indexAt(point);
if (!index.isValid() || index.column() == Section::eActuator)
return false;
try
{
qint64 start = m_model->snapTime(m_view->toTime(point.x() - m_dragItemOffset));
if (m_model->track(index.internalId()).isItemsBetween(start, start + m_dragItem->duration(), QVector<int>(), true))
return false;
return m_model->canDropItem(*m_dragItem.get(), index.internalId(), start);
}
catch (...)
{
return true;
}
Q_UNREACHABLE();
}
} // namespace PresentationManagement
@@ -0,0 +1,71 @@
#pragma once
#include "pre_topic_item.h"
#include "pre_utils.h"
#include <LtsTimeline/timeline_view_drag_handler.h>
namespace PresentationManagement
{
class ScriptDelegate;
class ScriptModel;
class ScriptDialogDragHandler : public QObject, public LtsTimeline::TimelineViewDragHandler
{
Q_OBJECT
friend class ScriptDelegate;
public:
explicit ScriptDialogDragHandler(LtsTimeline::TimelineView *view);
void dragEnterEvent(QDragEnterEvent *e) override;
void dragMoveEvent(QDragMoveEvent *e) override;
void dragLeaveEvent(QDragLeaveEvent *e) override;
void dropEvent(QDropEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
int deltaX() const override;
int deltaY() const override;
int dragItemFromTopicEditorWidth() const;
const QVector<int> &dragItemIds() const { return m_dragItemIds; }
bool isDragEventFromTopicEditor() const { return m_isDragEventFromTopicEditor; }
private:
void moveItemsLeft(const QVector<int> &, int dt) override;
void moveItemsRight(const QVector<int> &, int dt) override;
int scrollOffset() const;
qint64 minDragItemStart() const noexcept(false);
qint64 maxDragItemEnd() const noexcept(false);
void adjust() noexcept(false);
void adjustLeft(qint64 start) noexcept(false);
void adjustRight(qint64 end) noexcept(false);
void addFilterItemsToDragItems();
void setCanDropTopicTypes();
bool canDropTopicItemAt(const QPoint &point) const;
private:
ScriptModel *m_model{nullptr};
TopicItemPtr m_dragItem;
TopicEditorBase::TopicData m_dragTopicData;
int m_dragItemOffset{0};
bool m_isDragEventFromTopicEditor{false};
bool m_canDropTopicTypes{true};
bool m_mutex{false};
QTimer *m_timer{nullptr};
};
} // namespace PresentationManagement
@@ -0,0 +1,136 @@
#include "pre_script_dialog_resize_handler.h"
#include "pre_script_dialog.h"
#include "pre_script_model.h"
#include "pre_topic_item.h"
#include <LtsTimeline/timeline_view.h>
#include <QDebug>
#include <QTimer>
namespace PresentationManagement
{
ScriptDialogResizeHandler::ScriptDialogResizeHandler(LtsTimeline::TimelineView *view)
: LtsTimeline::TimelineViewResizeHandler(view)
, m_timer(new QTimer(m_view))
{
m_model = qobject_cast<ScriptModel *>(m_view->model());
Q_ASSERT(m_model);
m_timer->setSingleShot(true);
connect(m_timer, &QTimer::timeout, this, [this]()
{
m_mutex = false;
});
}
///////////////////////////////////////////////////////////////////////////////
// PUBLIC
void ScriptDialogResizeHandler::mouseMoveEvent(QMouseEvent *e)
{
LtsTimeline::TimelineViewResizeHandler::mouseMoveEvent(e);
if (m_currentItemId == -1)
return;
try
{
TopicItem &topicItem = m_model->topicItem(m_currentItemId);
topicItem.topicData().duration = topicItem.duration() / 1000;
emit m_model->topicItemChanged(topicItem.id());
}
catch (const QString &errMsg) { qCritical() << errMsg; }
catch (...) { qCritical() << "could not update topic item with id =" << m_currentItemId; }
if (e->buttons() != Qt::LeftButton)
return;
try
{
const LtsTimeline::Item &item = m_model->item(m_currentItemId);
if (m_resizePos == ResizePosition::eStart)
{
if (item.startTime() < m_view->currentTime() + scrollOffset() && m_view->startTime() < m_view->currentTime())
adjustLeft();
}
else if (m_resizePos == ResizePosition::eEnd)
{
if (item.startTime() + item.duration() > m_view->currentTime() + m_view->zoomFactor() - scrollOffset())
adjustRight(item);
}
}
catch (const QString &errMsg) { qCritical() << LOGFUNCTION << "adjusting zoom failed:" << errMsg; }
catch (...) { qCritical() << LOGFUNCTION << "adjusting zoom failed"; }
}
void ScriptDialogResizeHandler::mouseReleaseEvent(QMouseEvent *e)
{
if (m_timer->isActive())
m_timer->stop();
m_mutex = false;
try
{
m_view->setEndTime(std::max(ScriptModel::s_minScriptDuration,
static_cast<ScriptModel *>(m_view->model())->lastTopicEnd() + ScriptModel::s_scriptEndOffset));
const LtsTimeline::Item &item = m_model->item(m_currentItemId);
qint64 end = item.startTime() + item.duration() + scrollOffset();
if (end > m_view->currentTime() + m_view->zoomFactor())
{
ZoomFactor newZoom = Zoom::increment(m_view->zoomFactor());
if (newZoom < m_view->maximumZoomFactor() && newZoom <= m_view->duration())
m_view->setZoomFactor(newZoom);
else
m_view->setCurrentTime(end - m_view->zoomFactor());
}
}
catch (const QString &errMsg) { qCritical() << LOGFUNCTION << "adjusting zoom failed:" << errMsg; }
catch (...) { qCritical() << LOGFUNCTION << "adjusting zoom failed";}
TimelineViewResizeHandler::mouseReleaseEvent(e);
}
///////////////////////////////////////////////////////////////////////////////
// PRIVATE
int ScriptDialogResizeHandler::scrollOffset() const
{
return m_view->zoomFactor() * 5 / 100;
}
void ScriptDialogResizeHandler::adjustLeft()
{
ZoomFactor newZoom = Zoom::increment(m_view->zoomFactor());
if (m_view->zoomFactor() < newZoom && newZoom <= m_view->maximumZoomFactor())
{
m_view->setCurrentTime(m_view->currentTime() - (newZoom - m_view->zoomFactor()));
m_view->setZoomFactor(newZoom);
return;
}
m_view->setCurrentTime(m_view->currentTime() - m_view->zoomFactor() * 2 / 100);
}
void ScriptDialogResizeHandler::adjustRight(const LtsTimeline::Item &item)
{
ZoomFactor newZoom = Zoom::increment(m_view->zoomFactor());
if (m_view->zoomFactor() < newZoom && newZoom <= m_view->maximumZoomFactor())
{
if (m_view->currentTime() + newZoom > m_view->endTime() && !m_mutex)
{
m_mutex = true;
m_timer->start(1000);
m_view->setEndTime(std::max(ScriptModel::s_minScriptDuration, m_view->currentTime() + newZoom));
}
m_view->setZoomFactor(newZoom);
return;
}
m_view->setEndTime(std::max(std::max(ScriptModel::s_minScriptDuration, m_view->endTime()),
item.startTime() + item.duration() + ScriptModel::s_scriptEndOffset));
m_view->setCurrentTime(m_view->endTime() - m_view->zoomFactor());
}
} // namespace PresentationManagement
@@ -0,0 +1,38 @@
#pragma once
#include <LtsTimeline/timeline_view_resize_handler.h>
namespace LtsTimeline { class Item; }
namespace PresentationManagement
{
class ScriptModel;
class ScriptDialogResizeHandler : public QObject, public LtsTimeline::TimelineViewResizeHandler
{
Q_OBJECT
public:
explicit ScriptDialogResizeHandler(LtsTimeline::TimelineView *view);
void mouseMoveEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
private:
int scrollOffset() const;
void adjustLeft();
void adjustRight(const LtsTimeline::Item &item);
private:
ScriptModel *m_model{nullptr};
QTimer *m_timer{nullptr};
bool m_mutex{false};
};
} // namespace PresentationManagement
@@ -0,0 +1,73 @@
#include "pre_script_dialog_select_handler.h"
#include "pre_script_model.h"
#include <QToolTip>
#include <QDebug>
namespace PresentationManagement
{
ScriptDialogSelectHandler::ScriptDialogSelectHandler(LtsTimeline::TimelineView *view)
: TimelineViewSelectHandler(view)
{}
///////////////////////////////////////////////////////////////////////////////
// PUBLC
void ScriptDialogSelectHandler::mouseMoveEvent(QMouseEvent *e)
{
LtsTimeline::TimelineViewSelectHandler::mouseMoveEvent(e);
// hover text
ScriptModel *model = static_cast<ScriptModel *>(m_view->timelineModel());
if (!model)
return;
QModelIndex index = m_view->indexAt(e->pos());
if (!index.isValid() || index.column() == LtsTimeline::Section::eActuator)
{
m_view->setCursorTextVisible(false);
return;
}
try
{
qint64 time = m_view->toTime(e->pos().x());
const LtsTimeline::Track &track = model->track(index.internalId());
TopicItem &topicItem = model->topicItem(track.itemAt(time).id());
qint64 replayTime = topicItem.replayTime(time);
if (replayTime != -1)
{
m_view->setCursorTextVisible(true);
m_view->setCursorText(LtsTimeline::DateTime::toString(replayTime, LtsTimeline::DateTimeFormat::DTG));
}
else
m_view->setCursorTextVisible(false);
}
catch (...)
{
m_view->setCursorTextVisible(false);
}
// tooltip
try
{
const LtsTimeline::Track &track = m_view->timelineModel()->track(index.internalId());
const LtsTimeline::Item &item = track.itemAt(m_view->toTime(e->pos().x()));
m_view->setToolTip(LtsTimeline::DateTime::toString(item.startTime(), LtsTimeline::DateTimeFormat::TimeOnly));
}
catch (...)
{
if (!m_view->toolTip().isEmpty())
{
QToolTip::hideText();
m_view->setToolTip({});
m_view->update();
}
}
}
} // namespace PresentationManagement
@@ -0,0 +1,19 @@
#pragma once
#include <LtsTimeline/timeline_view_selection_handler.h>
namespace PresentationManagement
{
class ScriptDialogSelectHandler : public LtsTimeline::TimelineViewSelectHandler
{
public:
explicit ScriptDialogSelectHandler(LtsTimeline::TimelineView *view);
void mouseMoveEvent(QMouseEvent *e) override;
};
} // namespace PresentationManagement
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,146 @@
#pragma once
#include "pre_topic_item.h"
#include "pre_topic_track.h"
#include <LtsTimeline/timeline_model.h>
namespace LtsSession { class LtsSession; }
using namespace LtsTimeline;
using namespace TopicEditorBase;
namespace PresentationManagement
{
class PresentationContent;
class ScriptTimelineDialog;
class ScriptModel : public LtsTimeline::TimelineModel
{
Q_OBJECT
public:
enum class PasteTestResult { No = 0, Yes, YesWithShift };
static bool isReplayTopic(TopicType topicType);
static bool isScreenTopic(TopicType topicType);
static bool isFilterTopic(TopicType topicType);
static bool hasReplayTime(TopicType topicType);
static const qint64 s_minScriptDuration;
static const int s_scriptEndOffset;
explicit ScriptModel(LtsSession::LtsSession *session, ScriptTimelineDialog *dialog, QObject *parent = nullptr);
~ScriptModel() = default;
void clear();
int addItem(int _trackId, TopicItem &&_topicItem) noexcept(false);
int addItem(const QString &trackName, TopicItem &&_topicItem, qint64 offset = 0) noexcept(false);
int nextUniqueId() { return m_currentItemId + 1; }
TopicItem &topicItem(int _topicItemId) noexcept(false);
const TopicItem &topicItem(int _topicItemId) const noexcept(false);
std::vector<TopicItem> topicItems() const;
std::vector<TopicItem> topicItemsByTrackId(int trackId) const;
QVector<int> topicItemIds() const { return mainTopicItemIds() + filterTopicItemIds(); }
QVector<int> mainTopicItemIds() const;
int mainTopicItemId(const Track &_track, qint64 start, qint64 end) const noexcept(false);
int mainTopicItemId(const Track &_track, const TopicItem &_topicItem) const noexcept(false)
{
return mainTopicItemId(_track, _topicItem.startTime(), _topicItem.startTime() + _topicItem.duration());
}
int mainTopicItemId(const TopicItem &_topicItem) const noexcept(false);
int mainTopicItemId(int _topicItemId) const noexcept(false)
{
return mainTopicItemId(topicItem(_topicItemId));
}
QVector<int> filterTopicItemIds() const;
QVector<int> filterTopicItemIds(int _topicItemId) const;
qint64 firstTopicStart() const;
qint64 lastTopicEnd() const;
TopicTrack &audioTrack() noexcept(false) { return static_cast<TopicTrack &>(track(m_audioTrackId)); }
const TopicTrack &audioTrack() const noexcept(false) { return static_cast<const TopicTrack &>(track(m_audioTrackId)); }
TopicTrack &replayTrack() noexcept(false) { return static_cast<TopicTrack &>(track(m_replayTrackId)); }
const TopicTrack &replayTrack() const noexcept(false) { return static_cast<const TopicTrack &>(track(m_replayTrackId)); }
TopicTrack &screen1Track() noexcept(false) { return static_cast<TopicTrack &>(track(m_screen1TrackId)); }
const TopicTrack &screen1Track() const noexcept(false) { return static_cast<const TopicTrack &>(track(m_screen1TrackId)); }
TopicTrack &screen2Track() noexcept(false) { return static_cast<TopicTrack &>(track(m_screen2TrackId)); }
const TopicTrack &screen2Track() const noexcept(false) { return static_cast<const TopicTrack &>(track(m_screen2TrackId)); }
int channelToId(const QString &channel) const;
int audioTrackId() const { return m_audioTrackId; }
int replayTrackId() const { return m_replayTrackId; }
int screen1TrackId() const { return m_screen1TrackId; }
int screen2TrackId() const { return m_screen2TrackId; }
bool isMainTrack(int _trackId) const;
bool isScreenTrack(int _trackId) const;
bool isFilterTrack(int _trackId) const;
bool isReplayTopic(const TopicItem &_topicItem) const { return isReplayTopic(_topicItem.topicType()); }
bool isScreenTopic(const TopicItem &_topicItem) const { return isScreenTopic(_topicItem.topicType()); }
bool isFilterTopic(const TopicItem &_topicItem) const { return isFilterTopic(_topicItem.topicType()); }
// bool canBeMerged(const TopicItem &_firstTopicItem, const TopicItem &_secondTopicItem, const TopicItem &_thirdTopicItem) const;
// bool canBeSplit(const TopicItem &_topicItem) const;
// void mergeTopicItems(int _firstTopicItemId, int _secondTopicItemId, int _thirdTopicItemId) noexcept(false);
// void splitTopicItem(int _topicItemId) noexcept(false);
bool import(const QVector<PresentationContent> &contents) noexcept(false);
PresentationContent topicItemPresentationContent(int topicItemId) noexcept(false);
void insertTopicItem(int _trackId, TopicItem &&__topicItem);
void moveItemToTrack(int _itemId, int _trackId, qint64 time) noexcept(false) override;
void removeItem(int _itemId) noexcept(false) override;
void setItemStartTime(Track &_track, Item &_item, qint64 newStart) override;
void setItemDuration(Track &_track, Item &_item, int duration) override;
bool canDropItem(const Item &_item, int _trackId, qint64 start) const override;
bool canMoveItem(const Item &_item, qint64 start) const override;
bool canResizeItem(const Item &_item, qint64 start, int duration) const override;
void copyToClipboard(const QVector<int> &_itemIds) noexcept(false) override;
void cutToClipboard(const QVector<int> &_itemIds) noexcept(false) override;
void pasteFromClipboard(qint64 time) noexcept(false) override;
PasteTestResult canPasteFromClipboard(qint64 time) const;
bool canDropTopicType(TopicType _topicType, int _trackId) const;
private:
bool isTopicItemBlocked(const TopicItem &_topicItem, const TopicTrack &_topicTrack, qint64 start, int duration) const;
void getActualFilterTopicRange(int parentTrackId, TopicItem &_topicItem) const noexcept(false);
public slots:
void onInsertTrack(const QModelIndex &idx) override;
void onRemoveTrack(const QModelIndex &idx) override;
signals:
void topicItemChanged(int);
void pasteFailed();
private:
LtsSession::LtsSession *m_session{nullptr};
ScriptTimelineDialog *m_dialog{nullptr};
int m_audioTrackId{-1};
int m_replayTrackId{-1};
int m_screen1TrackId{-1};
int m_screen2TrackId{-1};
NGL::INGLLogger &m_logger;
};
} // namespace PresentationManagement
@@ -0,0 +1,341 @@
/**
* @RDE_HEADER@
* GUI Plugin for Script Editor
* $Id$
* $HeadURL$
* $Author$
*
* Created by Mike Wille, SLSE, 2017-03-17
*/
#include "pre_script_timeline_gui_plugin.h"
#include "pre_script_dialog.h"
#include "pre_script_model.h"
#include <Common/TopicEditorBase/topic_editor_base.h>
#include <Plugins/OperatorManagement/opm_data_plugin_interface.h>
#include <Plugins/PresentationManagement/Gui/Script/Common/ScriptChooser/script_chooser.h>
#include <Plugins/PresentationManagement/Gui/Common/pre_edit_dialog.h>
#include <LtsWidgets/lts_base_layout_widget.h>
#include <LtsSession/lts_session.h>
#include <LtsSession/lts_session_manager.h>
#include <LtsSession/lts_session_data.h>
#include <LtsWidgets/lts_background_widget.h>
#include <LtsWidgets/lts_message_dialog.h>
#include <LtsScriptTimeline/track_entry.h>
#include <LtsScriptTimeline/temporary_pointer_vector.h>
#include <TopicEditorBase/topic_editor_utils.h>
// Control- & Widget-Ifcs:
#include <Plugins/SituationReplay/srp_gui_plugin_interface.h>
#include <Plugins/SituationMapPlugin/situation_map_gui_plugin_interface.h>
#include <Plugins/FileManagement/flm_explorer_gui_plugin_interface.h>
#include <Plugins/BuildingViewer/bdv_viewer_gui_plugin_interface.h>
#include <Plugins/AudioVideoManagement/avm_menu_edit_gui_plugin_interface.h>
#include <Plugins/SituationMapPlugin/situation_map_script_filter_gui_plugin_interface.h>
#include <Plugins/MarkerManagement/mrk_gui_plugin_interface.h>
#include <Plugins/Evaluations/evl_gui_plugin_interface.h>
#include <Plugins/ReportManagement/rep_gui_plugin_interface.h>
#include <Plugins/PlanOverlays/plan_overlays_plugin_interface.h>
#include <Services/PresentationManagement/pre_operator_rights.h>
#include <Services/PresentationManagement/pre_dtc.h>
#include <stdlib.h> // for abort
#include <QDir>
#include <QJsonParseError>
#include <QJsonDocument>
#include <QProgressDialog>
#include <QTimer>
#include <QFileDialog>
#include <QVBoxLayout>
#include <QDateTime>
#include <QDebug>
namespace PresentationManagement
{
std::shared_ptr<GuiPluginBase::Interface> GuiPlugin::createGuiInterface(LtsSession::LtsSession *session)
{
return std::make_shared<InterfaceImpl>(session);
}
InterfaceImpl::InterfaceImpl(LtsSession::LtsSession *session)
: GuiPluginBase::Interface(session, getLogger("Plugins.Gui.Pre.ScriptTimeline"))
, m_preDtc(nullptr)
, m_preFtc(nullptr)
, m_opmDtc(nullptr)
, m_srpIfc(nullptr)
, m_scriptId(0) // invalid
, m_scriptDialog(nullptr)
, m_requestHelper(new LtsUtils::RequestHelper(this))
{
connect(this, &InterfaceImpl::success, m_session, &LtsSession::LtsSession::slotExecuteNextPayload, Qt::QueuedConnection);
}
QVector<LtsWidgets::PluginData> InterfaceImpl::getActions()
{
QVector<LtsWidgets::PluginData> ret;
{
LtsWidgets::PluginData data;
data.menuGroup = menuGroup();
data.identifier = QStringLiteral("PresentationManagement.ScriptTimelineDialog");
data.menuText = tr("Show Script Timeline Editor");
if (getFlags().testFlag(LtsAppDescriptor::PluginInfo::Flag::MAIN))
{
data.layoutArea = LtsWidgets::LayoutArea::eMainTab;
data.createInstant = LtsWidgets::CreateInstantType::CreateInstantAndDiscardAction;
data.closable = false;
}
else // (as dialog) = rather unlikely..
{
data.layoutArea = LtsWidgets::LayoutArea::eDialogTab;
}
data.operatorRight = OperatorRights::ScriptEditorShow;
data.createWidgetFunc = [this](QWidget *parent)
{
return onShowScriptDialog(parent);
};
ret.append(data);
}
{
LtsWidgets::PluginData data;
data.menuGroup = menuGroup();
data.identifier = QStringLiteral("PresentationManagement.ScriptDialog.ImportScript");
data.menuIcon = QIcon(QStringLiteral("icon:import_documents.svg"));
data.menuText = tr("Insert Script");
data.operatorRight = OperatorRights::ScriptEditorEdit;
data.directFunc = [this](const LtsWidgets::PluginData &)
{
return onImportScript();
};
data.createWidgetFunc = nullptr;
ret.append(data);
}
return ret;
}
bool InterfaceImpl::fetchInterfaces()
{
if (!m_session->getDtc<PresentationManagement::DataInterface>(m_preDtc))
{
DEBUG_LOG(m_logger) << "getDtc for PresentationManagement::DataInterface failed.";
return false;
}
if (!m_session->getFtc<PresentationManagement::DataInterface>(m_preFtc))
{
DEBUG_LOG(m_logger) << "getFtc for PresentationManagement::DataInterface failed.";
return false;
}
if (!m_session->getDtc<SituationReplayManagement::DataInterface>(m_coreDtc))
{
DEBUG_LOG(m_logger) << "getDtc for SituationReplayManagement::DataInterface failed.";
return false;
}
if (!m_session->getDtc<OperatorManagement::DataInterface>(m_opmDtc))
{
DEBUG_LOG(m_logger) << "getDtc for OperatorManagement::DataInterface failed.";
return false;
}
if (!m_session->getDtc<ExerciseManagement::DataInterface>(m_exmDtc))
{
DEBUG_LOG(m_logger) << "getDtc for OperatorManagement::DataInterface failed.";
return false;
}
m_exmDtc->enableExercise();
if (!m_session->getForeignDataInterface<SituationReplayManagement::DataInterface>(m_srpIfc))
{
WARNING_LOG(m_logger) << "getForeignDataInterface for SituationReplayManagement::DataInterface failed.";
return false;
}
return true;
}
void InterfaceImpl::clearInterfaces()
{
m_preDtc = nullptr;
m_preFtc = nullptr;
m_coreDtc = nullptr;
m_opmDtc = nullptr;
m_exmDtc = nullptr;
m_srpIfc = nullptr;
}
QString InterfaceImpl::menuGroup() const
{
return tr("Presentation");
}
void InterfaceImpl::executePayload(LtsWidgets::LtsBackgroundWidget *mainBackground)
{
Q_UNUSED(mainBackground);
if (m_session->partitionType() != IDL::PartitionType::Exercise) // only on valid exercise
{
emit success();
return;
}
int scriptId = m_session->getSavegame()[QStringLiteral("ScriptId")].toInt();
if (scriptId > 0)
{
onSelectScript(scriptId);
return;
}
ScriptChooser *scriptChooser = new ScriptChooser(m_preDtc,m_preFtc, m_opmDtc, false, false, mainBackground);
scriptChooser->setAttribute(Qt::WA_DeleteOnClose);
connect(scriptChooser, &ScriptChooser::signalSelectScript,
this, &InterfaceImpl::onSelectScript, Qt::QueuedConnection);
connect(scriptChooser, &ScriptChooser::signalQuit,
m_session, &LtsSession::LtsSession::signalQuit);
connect(scriptChooser, &ScriptChooser::signalNewScript,
this, [this, scriptChooser]()
{
PresentationEditDialog *dialog = new PresentationEditDialog(nullptr);
if (!dialog->init(-1 /* new presentation */, m_preDtc, m_preFtc, m_opmDtc))
{
delete dialog;
return;
}
QObject::connect(dialog, &PresentationEditDialog::rejected, dialog, &PresentationEditDialog::deleteLater);
QObject::connect(dialog, &PresentationEditDialog::newScript, this, &InterfaceImpl::onSelectScript, Qt::QueuedConnection);
QObject::connect(dialog, &PresentationEditDialog::newScript, scriptChooser, &QObject::deleteLater);
dialog->show();
});
if (!LtsSessionData::LtsSessionData::getInstance()->isTablet())
scriptChooser->setModal(true);
scriptChooser->show();
}
bool InterfaceImpl::importScript(int iSource)
{
if (!m_scriptDialog)
{
ERROR_LOG(m_logger) << "no script dialog open for import script";
return false;
}
PresentationManagement::Presentation presentation = m_preDtc->presentation(iSource);
if (presentation.id() < 1)
return false;
// import topics
std::function<void(const Core::FunctionReply &baseReply)> importPresentationContent = [&](const Core::FunctionReply &baseReply)
{
const GetPresentationContentReply &reply = static_cast<const GetPresentationContentReply &>(baseReply);
const auto &contentList = reply.content();
if (contentList.empty())
{
ERROR_LOG(m_logger) << "No Presentation Content for Script Id " << reply.id();
return;
}
QVector<PresentationContent> contents;
contents.reserve(contentList.size());
for (const PresentationContent &content : contentList)
{
TopicEditorBase::TopicType topicType = TopicEditorBase::topicTypeFromString(QString::fromStdString(content.presentationTopicType()));
if (topicType == TopicEditorBase::TopicType::Invalid)
{
ERROR_LOG(m_logger) << "Topic ignored - invalid topic type for presentation's content id = " << content.id();
continue;
}
contents.append(content);
}
m_scriptDialog->import(contents);
};
GetPresentationContentRequest req(iSource);
req.presentationId(iSource);
if (!m_requestHelper->startSingleRequest([req, this](){ return m_preFtc->getPresentationContentRequest(req, this); }, importPresentationContent))
{
LtsWidgets::LtsMessageDialog::showMessage(m_session->baseLayout(), tr("There is no content to import for this script."));
return false;
}
return true;
}
LtsWidgets::LtsDialog *InterfaceImpl::onShowScriptDialog(QWidget *parent)
{
m_scriptDialog = new ScriptTimelineDialog(menuGroup(), parent);
// m_scriptDialog = new ScriptTimelineDialog(
// menuGroup(), m_session, m_scriptId, m_coreDtc, m_opmDtc, m_preDtc, m_preFtc, m_srpIfc, parent
// );
//const int exId = m_session->exerciseId();
//ExerciseManagement::Exercise exercise = m_exmDtc->exercise(exId);
//DEBUG_LOG(m_logger) << "### ###ExId: " << exId << " ExName: " << exercise.name();
m_scriptDialog->init(m_session, m_scriptId, m_preDtc, m_preFtc, m_srpIfc, m_coreDtc, m_opmDtc, m_exmDtc);
connect(m_scriptDialog, &ScriptTimelineDialog::scriptChanged, this, [this](int scriptId) { m_scriptId = scriptId; });
return m_scriptDialog.data();
}
void InterfaceImpl::onImportScript()
{
ScriptChooser *scriptChooser = new ScriptChooser(m_preDtc, m_preFtc, m_opmDtc, false, true, m_session->baseLayout());
scriptChooser->setAttribute(Qt::WA_DeleteOnClose);
connect(scriptChooser, &ScriptChooser::signalSelectScript, this, &InterfaceImpl::onImportSpecificScript, Qt::QueuedConnection);
connect(scriptChooser, &ScriptChooser::signalQuit, m_session, &LtsSession::LtsSession::signalQuit);
if (!LtsSessionData::LtsSessionData::getInstance()->isTablet())
scriptChooser->setModal(true);
scriptChooser->show();
}
void InterfaceImpl::onImportSpecificScript(int scriptId)
{
if (m_scriptId == scriptId)
{
ERROR_LOG(m_logger) << "Not possible to import same script!";
new LtsWidgets::LtsMessageDialogNew(m_session->baseLayout(), tr("Not possible to import same script!"));
return;
}
if (!importScript(scriptId))
{
ERROR_LOG(m_logger) << "Import of script with id = " << scriptId << " failed";
new LtsWidgets::LtsMessageDialogNew(m_session->baseLayout(), tr("Failed to import Script with Id: %1").arg(scriptId));
return;
}
DEBUG_LOG(m_logger) << "Succesfully imported script with id = " << scriptId;
//LtsWidgets::LtsMessageDialog::showMessage(nullptr, tr("Successfully imported Script Id %1").arg(scriptId));
}
void InterfaceImpl::onSelectScript(int scriptId)
{
m_scriptId = scriptId;
// std::shared_ptr<ForeignGuiInterface> preGuiIfc;
// if (!m_session->getForeignGuiInterface<ForeignGuiInterface>(preGuiIfc))
// {
// // TODO: MSG !!1
// return;
// }
// preGuiIfc->setScriptId(scriptId);
emit success();
}
} // namespace PresentationManagement
@@ -0,0 +1,105 @@
/**
* @RDE_HEADER@
* GUI Plugin for Script Editor
* $Id$
* $HeadURL$
* $Author$
*
* Created by Mike Wille, SLSE, 2017-03-17
*/
#pragma once
#include <PresentationManagement/logger.h>
#include <PluginInterfaces/gui_plugin_interface.h>
#include <Plugins/PresentationManagement/pre_data_plugin_interface.h>
#include <Plugins/PresentationManagement/pre_script_timeline_gui_plugin_interface.h>
#include <Services/PresentationManagement/pre_ftc.h> // reply handler
#include <Services/OperatorManagement/opm_dtc.h>
#include <ExerciseManagement/exm_data_plugin_interface.h>
#include <ExerciseManagement/exm_dtc.h>
#include <Plugins/SituationReplayManagement/srm_data_plugin_interface.h>
#include <Common/ProjectConfig/project_defines.h>
#include <LtsUtilsGuiDds/request_helper.h>
// test:
#include <LtsScriptTimeline/track_entry_painters.h>
#include "pre_script_dialog.h"
#include <LtsSession/lts_session.h>
#include <PluginInterfaces/gui_plugin_interface.h>
#include <PluginInterfaces/topic_replay_interface.h>
#include <Plugins/PresentationManagement/Gui/Common/topic_assignment.h>
namespace PresentationManagement
{
class GuiPlugin final : public QObject, public GuiPluginBase::Plugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "gui:3:pre.script_timeline_edit")
Q_INTERFACES(GuiPluginBase::Plugin)
public:
explicit GuiPlugin() = default;
~GuiPlugin() = default;
QString pluginName() override { return ForeignScriptTimelineDialogGuiInterface::pluginName(); }
std::shared_ptr<GuiPluginBase::Interface> createGuiInterface(LtsSession::LtsSession *session) override;
bool availableInPartition(IDL::PartitionType partition) const override { return (partition == IDL::PartitionType::Exercise); }
};
class InterfaceImpl final : public QObject, public GuiPluginBase::Interface
, protected PresentationManagement::Interface::Ftc::PresentationModifyReplyHandler
, protected PresentationManagement::Interface::Ftc::GetPresentationContentReplyHandler
{
Q_OBJECT
public:
explicit InterfaceImpl(LtsSession::LtsSession *session);
QVector<LtsWidgets::PluginData> getActions() override;
QString menuGroup() const override;
bool fetchInterfaces() override;
void clearInterfaces() override;
bool availableInPartition(IDL::PartitionType partition) const override { return partition == IDL::PartitionType::Exercise; }
// be able to show script chooser
int executionPriority() const override { return 9999; }
void executePayload(LtsWidgets::LtsBackgroundWidget *mainBackground) override;
private:
void onImportScript();
bool importScript(/*int iDestination, */int iSource);
protected slots:
void onPresentationModifyReply(const Core::FunctionReplyObject &replyObject) override final { m_requestHelper->requestReply(replyObject); }
void onGetPresentationContentReply(const GetPresentationContentReply &reply) override { m_requestHelper->requestReply(reply); }
private slots:
LtsWidgets::LtsDialog *onShowScriptDialog(QWidget *parent);
void onSelectScript(int scriptId);
void onImportSpecificScript(int scriptId);
signals:
void success();
private:
std::shared_ptr<PresentationManagement::Interface::Dtc> m_preDtc { nullptr };
std::shared_ptr<PresentationManagement::Interface::Ftc> m_preFtc { nullptr };
std::shared_ptr<Core::Interface::Dtc> m_coreDtc { nullptr };
std::shared_ptr<OperatorManagement::Interface::Dtc> m_opmDtc { nullptr };
std::shared_ptr<ExerciseManagement::Interface::Dtc> m_exmDtc { nullptr };
std::shared_ptr<SituationReplayManagement::ForeignDataInterface> m_srpIfc { nullptr };
int m_scriptId { 0 };
QPointer<PresentationManagement::ScriptTimelineDialog> m_scriptDialog { nullptr };
LtsUtils::RequestHelper *m_requestHelper { nullptr };
};
} // namespace PresentationManagement
@@ -0,0 +1,265 @@
#include "pre_storyboard.h"
#include "ui_pre_storyboard.h"
#include "LtsPrinting/printing_base.h"
#include <LtsPrinting/printing.h>
#include "pre_script_dialog_select_handler.h"
#include <QDir>
#include <QLabel>
#include <QJsonArray>
#include <QTextTable>
#include <QFileDialog>
#include <QPrinter>
#include <QPainter>
#include <QPrintDialog>
#include <LtsSession/lts_session.h>
#include <LtsSession/lts_session_data.h>
#include <LtsWidgets/lts_base_layout_widget.h>
#include <LtsScriptTimeline/track_entry.h>
#include <ngltools/ngltools_dtg.h>
#include <Services/PresentationManagement/logger.h>
namespace PresentationManagement
{
StoryboardDialog::StoryboardDialog(
QWidget *parent, LtsSession::LtsSession *session, const QString &scriptName, const QString &scriptOwner,
const QString &maxTopicTimeString, qint64 lastModified, std::vector<TopicItem> topicItems
)
: LtsWidgets::LtsDialog(parent)
, m_session(session)
, m_scriptName(scriptName)
, m_scriptOwner(scriptOwner)
, m_maxTopicTimeString(maxTopicTimeString)
, m_lastModified(lastModified)
, m_isAlreadyTranslated(false)
, m_ui(new Ui::StoryboardDialog)
, m_logger(getLogger(QStringLiteral("StoryboardDialog")))
{
setOnlineHelpFilename(QStringLiteral("Script_Editor-Storyboard"));
m_ui->setupUi(this);
m_ui->tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::Fixed);
m_ui->tableWidget->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch);
m_ui->tableWidget->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
m_buttonBox->connectButton(LtsWidgets::DialogButton::Print, this, &StoryboardDialog::onPrint);
onColumnWidthChanged();
connect(m_ui->tableWidget->horizontalHeader(), &QHeaderView::sectionResized, this, &StoryboardDialog::onColumnWidthChanged);
fillStoryboard(std::move(topicItems));
}
StoryboardDialog::~StoryboardDialog()
{
delete m_ui;
}
///////////////////////////////////////////////////////////////////////////////
// PUBLIC
void StoryboardDialog::retranslate()
{
translate(m_ui, tr("Script Storyboard"));
if (m_isAlreadyTranslated)
return;
m_isAlreadyTranslated = true;
}
void StoryboardDialog::setScriptData(const QString &scriptName, const QString &scriptOwner, const QString &maxTopicTimeString, qint64 lastModified)
{
m_scriptName = scriptName;
m_scriptOwner = scriptOwner;
m_maxTopicTimeString = maxTopicTimeString;
m_lastModified = lastModified;
}
/**
* @brief
* @details Fill the Storyboard with Data from Model, Sort the vector by time of topic
* @param topicItems
*/
void StoryboardDialog::fillStoryboard(std::vector<TopicItem> topicItems)
{
m_ui->tableWidget->clear();
m_ui->tableWidget->clearContents();
m_ui->tableWidget->setRowCount(0);
m_topicItems.clear();
m_topicItems.reserve(topicItems.size());
std::vector<TopicEditorBase::TopicData> dataVector;
dataVector.reserve(topicItems.size());
for (TopicItem &topicItem : topicItems)
m_topicItems.push_back(std::move(topicItem));
for (TopicItem &topicItem : m_topicItems)
dataVector.push_back(topicItem.topicData());
std::sort(dataVector.begin(), dataVector.end(), [](const TopicEditorBase::TopicData &leftValue, const TopicEditorBase::TopicData &rightValue)
{
if (leftValue.startTime == rightValue.startTime)
{
if (leftValue.channel == QStringLiteral("Audio"))
return true;
else if (leftValue.channel == QStringLiteral("Replay") && rightValue.channel == QStringLiteral("Audio"))
return false;
else if (leftValue.channel == QStringLiteral("Screen 1") && (rightValue.channel == QStringLiteral("Replay") || rightValue.channel == QStringLiteral("Audio")))
return false;
else if (leftValue.channel == QStringLiteral("Screen 2") && (rightValue.channel == QStringLiteral("Replay") || rightValue.channel == QStringLiteral("Audio") || rightValue.channel == QStringLiteral("Screen 1")))
return false;
else
return true;
}
return leftValue.startTime < rightValue.startTime;
});
int topicCounter = 1;
for (TopicEditorBase::TopicData &data : dataVector)
{
if (!data.parentChannel.isEmpty() || data.state != true || data.channel.isEmpty() || data.topicType == TopicEditorBase::TopicType::Invalid)
continue;
QString firstColumnText = QStringLiteral("<big><b>[ %1 ]</b></big><br>%2").arg(topicCounter).arg(QTime::fromMSecsSinceStartOfDay(
((dataVector.at(dataVector.size()-1).startTime * 1000) + (dataVector.at(dataVector.size()-1).duration * 1000)) -
(data.startTime * 1000)).toString());
QString secondColumnText = QStringLiteral("<big><b>%1</b></big>").arg(TopicEditorBase::topicTypeString(data.topicType));
QString thirdColumnText = QStringLiteral("<big><b>&#8203;</b></big>"); // Creates empty line with the same height than the other columns
QJsonObject jsonObjectProperties;
QJsonArray jsonArrayProperties;
if (!data.properties.value(QStringLiteral("Sequences")).toArray().isEmpty())
jsonArrayProperties = data.properties.value(QStringLiteral("Sequences")).toArray();
else
jsonObjectProperties = data.properties;
secondColumnText += QStringLiteral("<br>- ") + tr("Description");
thirdColumnText += QStringLiteral("<br>%1").arg(data.description);
if (jsonArrayProperties.isEmpty())
{
if (jsonObjectProperties.value(QStringLiteral("start")).toDouble() > 0)
{
secondColumnText += QStringLiteral("<br>- ") + tr("Start");
thirdColumnText += QStringLiteral("<br>%1").arg(LtsTimeline::DateTime::toString(jsonObjectProperties.value(QStringLiteral("start")).toDouble() * 1000, LtsTimeline::DateTime::DTG));
}
if (data.duration > 0)
{
secondColumnText += QStringLiteral("<br>- ") + tr("Duration");
thirdColumnText += QStringLiteral("<br>%1").arg(QTime::fromMSecsSinceStartOfDay(data.duration * 1000).toString());
}
if (jsonObjectProperties.value(QStringLiteral("speed")).toDouble() > 0)
{
secondColumnText += QStringLiteral("<br>- ") + tr("Speed");
thirdColumnText += QStringLiteral("<br>%1").arg(jsonObjectProperties.value(QStringLiteral("speed")).toDouble());
}
}
else
{
if (jsonArrayProperties.first().toObject().value(QStringLiteral("start")).toDouble() > 0)
{
secondColumnText += QStringLiteral("<br>- ") + tr("Start");
thirdColumnText += QStringLiteral("<br>%1").arg(LtsTimeline::DateTime::toString(jsonArrayProperties.first().toObject().value(QStringLiteral("start")).toDouble() * 1000, LtsTimeline::DateTime::DTG));
}
if (jsonArrayProperties.first().toObject().value(QStringLiteral("start")).toDouble() > 0)
{
secondColumnText += QStringLiteral("<br>- ") + tr("End");
thirdColumnText += QStringLiteral("<br>%1").arg(LtsTimeline::DateTime::toString(jsonArrayProperties.last().toObject().value(QStringLiteral("start")).toDouble() * 1000 + jsonArrayProperties.last().toObject().value(QStringLiteral("duration")).toDouble() * 1000 ,LtsTimeline::DateTime::DTG ));
}
if (data.duration > 0)
{
secondColumnText += QStringLiteral("<br>- ") + tr("Duration");
thirdColumnText += QStringLiteral("<br>%1").arg(QTime::fromMSecsSinceStartOfDay(data.duration * 1000).toString());
}
if (jsonArrayProperties.size() > 1)
{
QJsonValue tempProperty;
for(QJsonValue property : jsonArrayProperties)
{
if (tempProperty.toObject().value(QStringLiteral("speed")).isNull())
tempProperty = property;
else
if (tempProperty != property)
{
secondColumnText += QStringLiteral("<br>- ") + tr("Speed");
thirdColumnText += QStringLiteral("<br>") + tr("variable");
break;
}
}
}
else
{
secondColumnText += QStringLiteral("<br>- ") + tr("Speed");
thirdColumnText += QStringLiteral("<br>%1").arg(jsonArrayProperties.first().toObject().value(QStringLiteral("speed")).toDouble());
}
}
m_ui->tableWidget->insertRow(topicCounter-1);
QTextEdit *firstColumnItem = new QTextEdit();
firstColumnItem->setReadOnly(true);
firstColumnItem->setStyleSheet(QStringLiteral("border : 0"));
firstColumnItem->setWordWrapMode(QTextOption::NoWrap);
firstColumnItem->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
firstColumnItem->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
firstColumnItem->setText(firstColumnText);
firstColumnItem->setAlignment(Qt::AlignTop);
m_ui->tableWidget->setCellWidget(topicCounter-1, 0, firstColumnItem);
QTextEdit *secondColumnItem = new QTextEdit();
secondColumnItem->setReadOnly(true);
secondColumnItem->setStyleSheet(QStringLiteral("border : 0"));
secondColumnItem->setWordWrapMode(QTextOption::NoWrap);
secondColumnItem->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
secondColumnItem->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
secondColumnItem->setText(secondColumnText);
secondColumnItem->setAlignment(Qt::AlignTop);
m_ui->tableWidget->setCellWidget(topicCounter-1, 1, secondColumnItem);
QTextEdit *thirdColumnItem = new QTextEdit;
thirdColumnItem->setReadOnly(true);
thirdColumnItem->setStyleSheet(QStringLiteral("border : 0"));
thirdColumnItem->setWordWrapMode(QTextOption::NoWrap);
thirdColumnItem->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
thirdColumnItem->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
thirdColumnItem->setText(thirdColumnText);
thirdColumnItem->setAlignment(Qt::AlignTop);
m_ui->tableWidget->setCellWidget(topicCounter-1, 2, thirdColumnItem);
++topicCounter;
}
m_ui->tableWidget->resizeRowsToContents();
}
///////////////////////////////////////////////////////////////////////////////
// PUBLIC SLOTS
/**
* @brief
* @details Print Storyboard with own printing Method
*/
void StoryboardDialog::onPrint()
{
QString headingHtml = (QStringLiteral("<H1>"));
headingHtml.append (tr("Scriptname:"));
headingHtml.append(m_scriptName);
headingHtml.append(QStringLiteral("</H1>"));
headingHtml.append(QStringLiteral("<H3>"));
headingHtml.append(tr("Script Owner: "));
headingHtml.append(m_scriptOwner);
headingHtml.append(QStringLiteral("</H3>"));
headingHtml.append(QStringLiteral("<H3>"));
headingHtml.append(tr("Last modified: "));
headingHtml.append(DTG::toStringMSec(m_lastModified));
headingHtml.append(QStringLiteral("<br>"));
headingHtml.append(tr("Duration: "));
headingHtml.append(m_maxTopicTimeString);
headingHtml.append(QStringLiteral("</H3><br>"));
LtsPrinting::printStoryboard(m_session, this, m_ui->tableWidget, headingHtml);
}
void StoryboardDialog::onColumnWidthChanged()
{
m_ui->tableWidget->setColumnWidth(0,this->width() * 0.25);
m_ui->tableWidget->setColumnWidth(1,this->width() * 0.40);
}
} // namespace PresentationManagement
@@ -0,0 +1,60 @@
#pragma once
#include <LtsScriptTimeline/track_entry.h>
#include <LtsSession/lts_session.h>
#include <LtsTimeline/item.h>
#include <LtsWidgets/lts_dialog.h>
#include "pre_topic_item.h"
#include <QTableWidget>
#include <QVector>
namespace Ui
{
class StoryboardDialog;
}
namespace PresentationManagement
{
class TopicItem;
class StoryboardDialog : public LtsWidgets::LtsDialog
{
Q_OBJECT
public:
StoryboardDialog(
QWidget *parent, LtsSession::LtsSession *session, const QString &scriptName, const QString &scriptOwner,
const QString &maxTopicTimeString, qint64 lastModified, std::vector<TopicItem> topicItems
);
~StoryboardDialog();
void retranslate() override final;
void setScriptData(
const QString &scriptName, const QString &scriptOwner, const QString &maxTopicTimeString, qint64 lastModified
);
void fillStoryboard(std::vector<TopicItem> topicItems);
public slots:
void onPrint();
void onColumnWidthChanged();
private:
LtsSession::LtsSession *m_session;
std::vector<TopicItem> m_topicItems;
QString m_scriptName;
QString m_scriptOwner;
QString m_maxTopicTimeString;
qint64 m_lastModified { 0 };
bool m_isAlreadyTranslated { false };
Ui::StoryboardDialog *m_ui { nullptr };
NGL::INGLLogger &m_logger;
};
} // namespace PresentationManagement
@@ -0,0 +1,109 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>StoryboardDialog</class>
<widget class="QWidget" name="StoryboardDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>707</width>
<height>464</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string notr="true">Script Editor</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="0">
<property name="spacing">
<number>9</number>
</property>
<property name="leftMargin">
<number>9</number>
</property>
<property name="topMargin">
<number>9</number>
</property>
<property name="rightMargin">
<number>9</number>
</property>
<property name="bottomMargin">
<number>9</number>
</property>
<item>
<widget class="QTableWidget" name="tableWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustIgnored</enum>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="textElideMode">
<enum>Qt::ElideNone</enum>
</property>
<property name="showGrid">
<bool>false</bool>
</property>
<property name="wordWrap">
<bool>false</bool>
</property>
<property name="rowCount">
<number>0</number>
</property>
<property name="columnCount">
<number>3</number>
</property>
<attribute name="horizontalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderMinimumSectionSize">
<number>100</number>
</attribute>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>120</number>
</attribute>
<attribute name="horizontalHeaderHighlightSections">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderMinimumSectionSize">
<number>0</number>
</attribute>
<attribute name="verticalHeaderDefaultSectionSize">
<number>150</number>
</attribute>
<attribute name="verticalHeaderHighlightSections">
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderStretchLastSection">
<bool>false</bool>
</attribute>
<column/>
<column/>
<column/>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
@@ -0,0 +1,71 @@
/**
* @RDE_HEADER@
* Dialog for the Topic Designer
* $Id$
* $HeadURL$
* $Author$
*
* Created by Mike Wille, SLSE, 2017-06-09
*/
#include "pre_topic_designer_dialog.h"
#include "ui_pre_topic_designer_dialog.h"
namespace PresentationManagement
{
TopicDesignerDialog::TopicDesignerDialog(QWidget *parent)
: LtsDialog(parent)
, m_ui(new Ui::TopicDesignerDialog)
{
setOnlineHelpFilename(QStringLiteral("Script_Editor-Designer"));
m_ui->setupUi(this);
setFocusPolicy(Qt::StrongFocus);
connect(m_ui->widget, &TopicDesigner::signalTopicSelected,
this, &TopicDesignerDialog::signalTopicSelected);
connect(m_ui->widget, &TopicDesigner::signalDeleteTrackEntry,
this, &TopicDesignerDialog::signalDeleteTrackEntry);
connect(m_ui->widget, &TopicDesigner::signalUnselect,
this, &TopicDesignerDialog::signalUnselect);
connect(m_ui->widget, &TopicDesigner::signalChangesMade,
this, &LtsWidgets::LtsDialog::signalChangesMade);
// connect(m_ui->pbToFront, &QPushButton::clicked,
// m_ui->widget, &TopicDesigner::to)
}
void TopicDesignerDialog::retranslate()
{
translate(m_ui, tr("Topic Designer"));
}
TopicDesignerDialog::~TopicDesignerDialog()
{
delete m_ui;
}
QVector<LtsScriptTimeline::TrackEntry *> TopicDesignerDialog::exportTopicList() const
{
return m_ui->widget->exportTopicList();
}
void TopicDesignerDialog::selectTopic(int topicId)
{
m_ui->widget->selectTopic(topicId);
}
void TopicDesignerDialog::loadTopicList(const QVector<LtsScriptTimeline::TrackEntry *> &topicsList)
{
m_ui->widget->loadTopicList(topicsList);
}
} // namespace PresentationManagement
@@ -0,0 +1,57 @@
/**
* @RDE_HEADER@
* Dialog for the Topic Designer
* $Id$
* $HeadURL$
* $Author$
*
* Created by Mike Wille, SLSE, 2017-06-09
*/
#pragma once
#include <LtsWidgets/lts_dialog.h>
#include <QVector>
namespace Ui
{
class TopicDesignerDialog;
}
namespace LtsScriptTimeline
{
class TrackEntry;
}
namespace PresentationManagement
{
class TopicDesignerDialog : public LtsWidgets::LtsDialog
{
public:
Q_OBJECT
public:
explicit TopicDesignerDialog(QWidget *parent = nullptr);
~TopicDesignerDialog();
void loadTopicList(const QVector<LtsScriptTimeline::TrackEntry *> &topicsList);
QVector<LtsScriptTimeline::TrackEntry *> exportTopicList() const;
void selectTopic(int topicId);
signals:
void signalTopicSelected(int topicId);
void signalDeleteTrackEntry(int topicId);
void signalUnselect();
private:
void retranslate() override;
Ui::TopicDesignerDialog *m_ui;
};
} // namespace PresentationManagement
@@ -0,0 +1,122 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TopicDesignerDialog</class>
<widget class="QWidget" name="TopicDesignerDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>600</width>
<height>338</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string notr="true">Script Editor</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>9</number>
</property>
<property name="topMargin">
<number>9</number>
</property>
<property name="rightMargin">
<number>9</number>
</property>
<property name="bottomMargin">
<number>9</number>
</property>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string notr="true">Some text</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QPushButton" name="pbToFront">
<property name="text">
<string>To Front</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pbIncIdx">
<property name="text">
<string>Inc Idx</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pbDecIdx">
<property name="text">
<string>Dec Idx</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pbToBack">
<property name="text">
<string>To Back</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="PresentationManagement::TopicDesigner" name="widget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>300</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>PresentationManagement::TopicDesigner</class>
<extends>QWidget</extends>
<header>topic_designer.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
@@ -0,0 +1,213 @@
#include "pre_topic_item.h"
#include "pre_script_model.h"
#include <PresentationManagement/pre.hpp>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QPainter>
#include <QStyleOptionViewItem>
#include <LtsTimeline/timeline_utils.h>
#include <QDebug>
namespace PresentationManagement
{
int TopicItem::s_gradientSize = 20;
const QString TopicItem::s_sequencesKey = QStringLiteral("Sequences");
const QString TopicItem::s_posKey = QStringLiteral("pos");
const QString TopicItem::s_startKey = QStringLiteral("start");
const QString TopicItem::s_durationKey = QStringLiteral("duration");
const QString TopicItem::s_speedKey = QStringLiteral("speed");
TopicItem::TopicItem(const TopicEditorBase::TopicData &data)
: LtsTimeline::Item(static_cast<qint64>(data.startTime) * 1000, static_cast<qint64>(data.duration) * 1000, topicTypeColor(data.topicType), true)
, m_topicType(data.topicType)
, m_topicData(data)
{
sortSequences();
}
TopicItem::TopicItem(const PresentationContent &content)
: LtsTimeline::Item(static_cast<qint64>(content.startTime()) * 1000, static_cast<qint64>(content.duration()) * 1000, TopicEditorBase::topicTypeColor(TopicEditorBase::topicTypeFromString(QString::fromStdString(content.presentationTopicType()))), true)
, m_topicType(TopicEditorBase::topicTypeFromString(QString::fromStdString(content.presentationTopicType())))
{
m_topicData.id = content.id();
m_topicData.topicType = m_topicType;
m_topicData.startTime = m_startTime / 1000;
m_topicData.duration = m_duration / 1000;
m_topicData.state = true;
m_topicData.index = content.index();
m_topicData.description = QString::fromStdString(content.description());
m_topicData.channel = QString::fromStdString(content.channel());
m_topicData.properties = QJsonDocument::fromJson(QByteArray(content.attributes().c_str(), content.attributes().size())).object();
m_topicData.x = content.x();
m_topicData.y = content.y();
m_topicData.w = content.width();
m_topicData.h = content.height();
sortSequences();
}
TopicItem::TopicItem(
qint64 startTime, int duration, TopicEditorBase::TopicType topicType, const QString &description,
const QJsonObject &properties, const QString &channel, const QString &parentChannel
)
: LtsTimeline::Item(startTime, duration, topicTypeColor(topicType))
, m_topicType(topicType)
{
m_topicData.topicType = m_topicType;
m_topicData.startTime = m_startTime / 1000;
m_topicData.duration = m_duration / 1000;
m_topicData.description = description;
m_topicData.channel = channel;
m_topicData.parentChannel = parentChannel;
m_topicData.properties = properties;
sortSequences();
}
TopicItem::~TopicItem()
{
delete m_replayInterface;
}
///////////////////////////////////////////////////////////////////////////////
// PUBLIC
TopicItem TopicItem::copy() const
{
return TopicItem(m_topicData);
}
qint64 TopicItem::replayTime(qint64 time) const
{
if (m_startTime > time || time > m_startTime + m_duration)
return -1;
if (ScriptModel::hasReplayTime(m_topicType))
{
if (m_topicData.properties.contains(s_sequencesKey))
{
QJsonArray sequences = m_topicData.properties.value(s_sequencesKey).toArray({});
if (sequences.isEmpty())
return -1;
qint64 relTime = time - m_startTime;
qint64 relSequenceStart = 0;
for (const QJsonValue &sequence : qAsConst(sequences))
{
QJsonObject obj = sequence.toObject();
qint64 start = obj.value(s_startKey).toDouble();
double speed = obj.value(s_speedKey).toDouble() / 100.;
int duration = speed * obj.value(s_durationKey).toInt();
if (relTime <= relSequenceStart + speed * duration)
return start + speed * (relTime - relSequenceStart);
relSequenceStart += speed * duration;
}
}
else
{
qint64 start = static_cast<qint64>(m_topicData.properties.value(QStringLiteral("start")).toDouble(-1));
if (start < 0)
return -1;
qint64 end = static_cast<qint64>(m_topicData.properties.value(QStringLiteral("end")).toDouble(-1));
if (end < 0)
return -1;
return start + (end - start) * (time - m_startTime) / m_duration;
}
}
return -1;
}
QJsonObject TopicItem::toJson() const
{
return QJsonObject({{QStringLiteral("topicId"), id()},
{QStringLiteral("startTime"), m_startTime / 1000},
{QStringLiteral("length"), m_duration / 1000},
{QStringLiteral("type"), static_cast<int>(m_topicType)},
{QStringLiteral("x"), m_topicData.x},
{QStringLiteral("y"), m_topicData.y},
{QStringLiteral("w"), m_topicData.w},
{QStringLiteral("h"), m_topicData.h},
{QStringLiteral("detailData"), m_topicData.properties},
{QStringLiteral("channelName"), m_topicData.channel},
{QStringLiteral("description"), m_topicData.description},
{QStringLiteral("filterParentChannel"), m_topicData.parentChannel}
});
}
PresentationContent TopicItem::toPresentationContent(int scriptId) const
{
PresentationManagement::PresentationContent content;
content.id(id());
content.presentationId(scriptId);
content.presentationTopicType(topicTypeString(m_topicType).toStdString());
content.channel(m_topicData.channel.toStdString());
content.startTime(m_startTime / 1000);
content.duration(m_duration / 1000);
const QByteArray attributes = QJsonDocument(m_topicData.properties).toJson(QJsonDocument::Compact);
content.attributes(attributes.data());
content.description(m_topicData.description.toStdString());
content.x(m_topicData.x);
content.y(m_topicData.y);
content.height(m_topicData.h);
content.width(m_topicData.w);
return content;
}
void TopicItem::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, bool highlight) const
{
painter->save();
LtsTimeline::Item::paint(painter, option, index, highlight);
if (option.rect.width() >= 20)
{
QRect boundingRect = option.rect.adjusted(1, 1, -1, -1);
QRect labelRect = boundingRect.adjusted(3, 16, 0, 0);
painter->drawText(labelRect, Qt::AlignLeft, description(), &boundingRect);
int penWidth = highlight ? 5 : 1;
QRect gradientRect = QRect(
option.rect.right() - s_gradientSize, option.rect.top() + penWidth, s_gradientSize, option.rect.height() - 2 * penWidth
);
QLinearGradient gradient(gradientRect.bottomRight(), gradientRect.bottomLeft());
gradient.setColorAt(0, QColor(m_color.red(), m_color.green(), m_color.blue(), 255));
gradient.setColorAt(1, QColor(m_color.red(), m_color.green(), m_color.blue(), 0));
painter->fillRect(gradientRect, gradient);
}
painter->restore();
}
///////////////////////////////////////////////////////////////////////////////
// PRIVATE
void TopicItem::sortSequences()
{
if (!m_topicData.properties.contains(s_sequencesKey))
return;
QJsonArray sequences = m_topicData.properties.value(s_sequencesKey).toArray({});
if (sequences.isEmpty())
return;
QVariantList variantList = sequences.toVariantList();
std::sort(variantList.begin(), variantList.end(), [](const QVariant &left, const QVariant &right)
{
return left.toJsonObject().value(s_posKey).toInt() <= right.toJsonObject().value(s_posKey).toInt();
});
m_topicData.properties[s_sequencesKey] = QJsonValue::fromVariant(variantList);
}
} // namespace PresentationManagement
@@ -0,0 +1,100 @@
#pragma once
#include "pre_utils.h"
#include <LtsScriptTimeline/track_entry.h>
#include <LtsTimeline/item.h>
#include <PluginInterfaces/topic_replay_interface.h>
#include <TopicEditorBase/topic_editor_utils.h>
#include <QWidget>
namespace PresentationManagement
{
class PresentationContent;
class TopicItem : public LtsTimeline::Item
{
friend class ScriptModel;
public:
static int s_gradientSize;
static const QString s_sequencesKey;
static const QString s_posKey;
static const QString s_startKey;
static const QString s_durationKey;
static const QString s_speedKey;
explicit TopicItem(const TopicEditorBase::TopicData &data);
explicit TopicItem(const PresentationContent &content);
TopicItem(TopicItem &&) = default;
explicit TopicItem(
qint64 startTime, int duration, TopicEditorBase::TopicType topicType, const QString &description,
const QJsonObject &properties, const QString &channel, const QString &parentChannel = {}
);
~TopicItem();
TopicItem copy() const;
TopicReplay::Interface *replayInterface() { return m_replayInterface; }
void setReplayInterface(TopicReplay::Interface *replayInterface) { m_replayInterface = replayInterface; }
bool hasReplayInterface() const { return bool(m_replayInterface); }
QWidget *replayWidget() { return m_replayWidget; }
void setReplayWidget(QWidget *widget) { m_replayWidget = widget; }
bool hasReplayWidget() const { return bool(m_replayWidget); }
TopicEditorBase::TopicType topicType() const { return m_topicType; }
TopicEditorBase::TopicData &topicData() { return m_topicData; }
const TopicEditorBase::TopicData &topicData() const { return m_topicData; }
qint64 replayTime(qint64 time) const;
int topicId() const { return m_topicData.id; }
void setTopicId(int id) { m_topicData.id = id; }
QString description() const { return m_topicData.description; }
void setDescription(const QString &description) { m_topicData.description = description; }
QString channel() const { return m_topicData.channel; }
void setChannel(const QString &channelName) { m_topicData.channel = channelName; }
QString parentChannel() const { return m_topicData.parentChannel; }
void setParentChannel(const QString &channelName) { m_topicData.parentChannel = channelName; }
QJsonObject properties() const { return m_topicData.properties; }
void setProperties(const QJsonObject &properties) { m_topicData.properties = properties; }
bool state() const { return m_topicData.state; }
void setState(bool state) { m_topicData.state = state; }
QJsonObject toJson() const;
PresentationContent toPresentationContent(int scriptId = -1) const;
void paint(
QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, bool highlight = false
) const override;
private:
void sortSequences();
private:
TopicReplay::Interface *m_replayInterface { nullptr }; // NOTE: because of this raw pointer, we can't follow the rule-of-zero here - but because the pointer address just needs to be copied, defaulted move constructor is enough
QWidget *m_replayWidget { nullptr };
TopicEditorBase::TopicType m_topicType;
TopicEditorBase::TopicData m_topicData;
};
typedef std::unique_ptr<TopicItem> TopicItemPtr;
typedef std::vector<TopicItemPtr> TopicItems;
typedef std::reference_wrapper<const TopicItem /* TODO: make Item, also! */> TopicItemPtrRef;
//typedef std::reference_wrapper<TopicItem> TopicItemPtrNCVRef;
typedef std::reference_wrapper<TopicItemPtr> TopicItemPtrNCVRef;
typedef std::vector<TopicItemPtrNCVRef> TopicItemPtrNCVRefs;
typedef std::vector<TopicItemPtrRef> TopicItemPtrRefs;
} // namespace PresentationManagement
@@ -0,0 +1,308 @@
/**
* @RDE_HEADER@
* class for recording a single topic on a screen
* $Id:
* $HeadURL:
* $Author:
* Created by Hannes Fritsche, SLTE, 2023-11-06
*/
#include "pre_topic_recorder.h"
#include "pre_screen.h"
#include <LtsUtilsCore/await.h>
#include <QFile>
#include <QThread>
#ifdef Q_OS_UNIX
#define _compiler_warning(x) _Pragma(#x)
#define compiler_warning(x) _compiler_warning(GCC warning x)
#else
#define compiler_warning(x)
#endif
namespace PresentationManagement
{
static QString s_container(L("mp4"));
static QString s_program(L("ffmpeg"));
TopicRecorder::TopicRecorder(const int topicId, const QString &topicName, const Screen *screen, qint32 offset,
qint64 startTime, qint64 stopTime, const double topicX, const double topicY,
const double topicW, const double topicH, QObject *parent)
: QObject(parent)
, m_recordingProcess(new QProcess(this))
, m_startTime(startTime)
, m_stopTime(stopTime)
, m_topicName(topicName)
, m_topicId(topicId)
, m_length((m_stopTime - (m_startTime - offset)) / 1000)
, m_logger(getLogger("TopicRecorder"))
{
const auto screenGlobalPos = screen->mapToGlobal({0,0});
const auto screenGeo = screen->geometry();
const double screenX = screenGlobalPos.x() + screenGeo.x();
const double screenY = screenGlobalPos.y() + screenGeo.y();
const double screenW = screenGeo.width();
const double screenH = screenGeo.height();
const bool fullscreen = (topicW < 0.0002 || topicH < 0.0002); // if w or h are too small, record the whole screen...
const QPoint globalPos(fullscreen ? screenX : screenX + (screenW * topicX),
fullscreen ? screenY : screenY + (screenH * topicY));
const qint32 inputX = globalPos.x();
const qint32 inputY = globalPos.y();
const qint32 inputW = (qint32)(fullscreen ? screenW : screenW * topicW);
const qint32 inputH = (qint32)(fullscreen ? screenH : screenH * topicH);
ERROR_LOG(m_logger) << "TopicRecorder: fullscreen? " << fullscreen;
ERROR_LOG(m_logger) << "TopicRecorder: topic - x: " << topicX << ", y: " << topicY << ", w: " << topicW << ", h: " << topicH;
ERROR_LOG(m_logger) << "TopicRecorder: screen - x: " << screenX << ", y: " << screenY << ", w: " << screenW << ", h: " << screenH;
ERROR_LOG(m_logger) << "TopicRecorder: input - x: " << inputX << ", y: " << inputY << ", w: " << inputW << ", h: " << inputH;
if (!m_tmpRecordingFile.open()) // create file
throw std::runtime_error(L("Failed to open temporary recording file: %1").arg(m_tmpRecordingFile.errorString()).toStdString());
m_recordingProcess->setProgram(s_program);
m_recordingProcess->setArguments(L("-hide_banner -f x11grab -s %1x%2 -i :0.0+%3,%4 -c:v libx264 -y -map 0 -movflags +faststart -f %5 %6").arg(inputW).arg(inputH).arg(inputX).arg(inputY).arg(s_container).arg(m_tmpRecordingFile.fileName()).split(L(" ")));
/*DEBUG*/ERROR_LOG(m_logger) << "args: " << m_recordingProcess->arguments().join(L(" "));
connect(m_recordingProcess, &QProcess::readyReadStandardOutput, [this]() { /*DEBUG*/ERROR_LOG(m_logger) << "REC [ ]: " << QString::fromUtf8(m_recordingProcess->readAllStandardOutput()); });
connect(m_recordingProcess, &QProcess::readyReadStandardError, [this]() { /*DEBUG*/ERROR_LOG(m_logger) << "REC [!]: " << QString::fromUtf8(m_recordingProcess->readAllStandardError()); });
connect(m_recordingProcess, &QProcess::started, [this]() { /*DEBUG*/ERROR_LOG(m_logger) << "REC started successfully!"; });
if (!m_tmpCuttingFile.open()) // create file
throw std::runtime_error(L("Failed to open temporary cutting file: %1").arg(m_tmpCuttingFile.errorString()).toStdString());
connect(m_recordingProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
this, &TopicRecorder::onRecordingProcessFinished);
connect(m_recordingProcess, &QProcess::errorOccurred,
this, QOverload<QProcess::ProcessError>::of(&TopicRecorder::onExportFailed));
}
TopicRecorder::~TopicRecorder()
{
if (m_recordingProcess->state() != QProcess::NotRunning)
m_recordingProcess->kill();
//m_recordingProcess->waitForFinished();
// if (m_cuttingProcess->state() == QProcess::Running)
// m_cuttingProcess->terminate();
// m_cuttingProcess->waitForFinished();
}
void TopicRecorder::cut()
{
auto *cuttingProcess = new QProcess(this);
cuttingProcess->setProgram(s_program);
// cut with only 1 thread: cuttingProcess->setArguments(L("-threads 1 -hide_banner -y -sseof -%1 -threads 1 -i %2 -c:v libx264 -x264-params threads=1 -f %3 %4").arg(m_length).arg(m_tmpRecordingFile.fileName(), s_container, m_tmpCuttingFile.fileName()).split(L(" ")));
cuttingProcess->setArguments(L("-hide_banner -y -sseof -%1 -i %2 -c:v libx264 -f %3 %4").arg(m_length).arg(m_tmpRecordingFile.fileName(), s_container, m_tmpCuttingFile.fileName()).split(L(" ")));
connect(cuttingProcess, &QProcess::readyReadStandardOutput, this, [this, cuttingProcess]() { /*DEBUG*/ERROR_LOG(m_logger) << "CUT [ ]: " << QString::fromUtf8(cuttingProcess->readAllStandardOutput()); });
connect(cuttingProcess, &QProcess::readyReadStandardError, this, [this, cuttingProcess]() { /*DEBUG*/ERROR_LOG(m_logger) << "CUT [!]: " << QString::fromUtf8(cuttingProcess->readAllStandardError()); });
connect(cuttingProcess, &QProcess::started, this, [this]() { /*DEBUG*/ERROR_LOG(m_logger) << "CUT started successfully!"; });
ERROR_LOG(m_logger) << "start cut";
cuttingProcess->start();
// if (m_recordingProcess->state() == QProcess::NotRunning)
// {
// return;
// }
try
{
LtsUtils::await(
cuttingProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), true,
cuttingProcess, &QProcess::errorOccurred, false,
this, &TopicRecorder::cancel, false
);
}
catch (...)
{
ERROR_LOG(m_logger) << "cut failed (error) with exitCode=" << cuttingProcess->exitCode() << " and exitStatus=" << (cuttingProcess->exitStatus() == QProcess::ExitStatus::CrashExit ? "Crash" : "Normal Exit");
m_failed = true;
cuttingProcess->deleteLater();
return;
}
if (cuttingProcess->exitCode() != 0)
{
ERROR_LOG(m_logger) << "cut failed with non-zero exitCode=" << cuttingProcess->exitCode() << " and exitStatus=" << (cuttingProcess->exitStatus() == QProcess::ExitStatus::CrashExit ? "Crash" : "Normal Exit");
m_failed = true;
cuttingProcess->deleteLater();
return;
}
ERROR_LOG(m_logger) << "cut done";
cuttingProcess->deleteLater();
}
void TopicRecorder::save(const QString &saveToPath) noexcept(false)
{
const QString newFilename(L("%1/%2.%3").arg(saveToPath).arg(m_topicId).arg(s_container));
if (!m_tmpCuttingFile.rename(newFilename))
{
throw L("Failed to move recording from '%1' to '%2' ('%3')").arg(m_tmpCuttingFile.fileName(), saveToPath, newFilename);
}
m_tmpCuttingFile.setAutoRemove(false); // keep file
}
//void TopicRecorder::saveRecording(const QString &saveToPath)
//{
// const QString newFilename(L("%1/%2.%3").arg(saveToPath).arg(m_topicId).arg(s_container));
// if (!m_tmpCuttingFile.rename(newFilename))
// {
// throw L("Failed to move recording from '%1' to '%2' ('%3')").arg(m_tmpCuttingFile.fileName(), saveToPath, newFilename);
// return;
// }
// m_tmpCuttingFile.setAutoRemove(false); // keep file
//}
void TopicRecorder::onExportFailed(const QProcess::ProcessError error)
{
m_failed = true;
onExportFailed(L("Process: '%1' - Process error: %2").arg(processToString(static_cast<QProcess*>(sender()))).arg((int)error));
}
void TopicRecorder::onExportFailed(const QString &errorMessage)
{
ERROR_LOG(m_logger) << "Export failed for topic '" << m_topicName << "': " << errorMessage;
emit exportFailed(errorMessage);
}
void TopicRecorder::killAndClear()
{
switch (m_recordingState)
{
case RecordingState::Done:
// m_cuttingProcess->kill();
Q_FALLTHROUGH();
case RecordingState::InProgress:
m_recordingProcess->kill();
return;
case RecordingState::Waiting:
default:
return;
}
}
void TopicRecorder::waitForFinishedRecording()
{
if (m_recordingProcess->state() == QProcess::NotRunning)
{
return;
}
try
{
LtsUtils::await(
m_recordingProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), true,
m_recordingProcess, &QProcess::errorOccurred, false,
this, &TopicRecorder::cancel, false
);
}
catch (...)
{
ERROR_LOG(m_logger) << "waitForFinishedRecording: catch!";
}
ERROR_LOG(m_logger) << "waitForFinishedRecording: done waiting!";
}
void TopicRecorder::onNewTime(qint64 time)
{
ERROR_LOG(m_logger) << "onNewTime: " << time << ", startTime: " << m_startTime;
switch (m_recordingState)
{
case RecordingState::Waiting:
if (time < m_startTime)
return;
ERROR_LOG(m_logger) << "onNewTime: started at " << time << " while startTime is " << m_startTime;
/*DEBUG*/ERROR_LOG(m_logger) << "Starting \"" << processToString(m_recordingProcess) << "\"";
m_recordingProcess->start();
m_recordingState = RecordingState::InProgress;
return;
case RecordingState::InProgress:
if (time < m_stopTime)
return;
/*DEBUG*/ERROR_LOG(m_logger) << "Stopping recording for " << m_topicName;
m_recordingProcess->terminate();
m_recordingState = RecordingState::Done;
return;
case RecordingState::Done:
default:
return;
}
}
void TopicRecorder::onRecordingProcessFinished(int exitCode, QProcess::ExitStatus exitStatus)
{
m_recordingState = RecordingState::Done;
/*DEBUG*/ERROR_LOG(m_logger) << "Recording finished for " << m_topicName << ", with exitCode=" << exitCode << " and exitStatus=" << exitStatus;
// if (exitStatus != QProcess::ExitStatus::NormalExit)
// {
// onExportFailed(L("Recording process crashed!"));
// return;
// }
// if (exitCode != 0)
// {
// onExportFailed(L("Recording process exited with exitCode %1").arg(exitCode));
// return;
// }
// /*DEBUG*/ERROR_LOG(m_logger) << "Starting \"" << processToString(m_cuttingProcess) << "\"";
// m_cuttingProcess->start();
}
//void TopicRecorder::onCuttingProcessFinished(int exitCode, QProcess::ExitStatus exitStatus)
//{
// /*DEBUG*/ERROR_LOG(m_logger) << "Cutting finished for " << m_topicName << ", with exitCode=" << exitCode << " and exitStatus=" << exitStatus;
//// if (exitCode != 0)
//// {
//// onExportFailed(L("Cutting process exited with exitCode %1").arg(exitCode));
//// return;
//// }
//}
QString TopicRecorder::processToString(QProcess *process)
{
return L("%1 %2").arg(process->program(), process->arguments().join(L(" ")));
}
} // namespace PresentationManagement
@@ -0,0 +1,93 @@
/**
* @RDE_HEADER@
* class for recording a single topic on a screen
* $Id:
* $HeadURL:
* $Author:
* Created by Hannes Fritsche, SLTE, 2023-11-06
*/
#pragma once
#include <QObject>
#include <QTemporaryFile>
#include <QProcess>
#include <Services/PresentationManagement/logger.h>
class Screen;
namespace PresentationManagement
{
class TopicItem;
class TopicRecorder : public QObject
{
Q_OBJECT
public: // methods
TopicRecorder(const int topicId, const QString &topicName, const Screen *screen, qint32 offset,
qint64 startTime, qint64 stopTime, const double topicX, const double topicY,
const double topicW, const double topicH, QObject *parent = nullptr);
~TopicRecorder();
void cut();
void save(const QString &saveToPath) noexcept(false);
void killAndClear();
void waitForFinishedRecording();
// void saveRecording(const QString &saveToPath);
bool isFailed() const { return m_failed; }
const QString &topicName() const { return m_topicName; }
int topicId() const { return m_topicId; }
public Q_SLOTS:
void onNewTime(qint64 time);
private: // other
enum class RecordingState
{
Waiting,
InProgress,
Done
};
signals:
void exportFailed(const QString &errorMessage);
void cancel();
private Q_SLOTS:
void onRecordingProcessFinished(int exitCode, QProcess::ExitStatus exitStatus);
// void onCuttingProcessFinished(int exitCode, QProcess::ExitStatus exitStatus);
void onExportFailed(const QProcess::ProcessError error);
void onExportFailed(const QString &errorMessage);
private: // methods
static QString processToString(QProcess *process);
private: // attributes
QProcess *m_recordingProcess { nullptr };
// QProcess *m_cuttingProcess { nullptr };
bool m_hasRecordingDone { false };
bool m_isWorking { false };
const qint64 m_startTime;
const qint64 m_stopTime;
const QString m_topicName;
QTemporaryFile m_tmpRecordingFile;
QTemporaryFile m_tmpCuttingFile;
RecordingState m_recordingState { RecordingState::Waiting };
const int m_topicId;
bool m_failed { false };
qint32 m_length;
LOGGER_T m_logger;
}; // class TopicRecorder
} // namespace PresentationManagement
@@ -0,0 +1,122 @@
/**
* @RDE_HEADER@
* class for recording a single topic on a screen
* $Id:
* $HeadURL:
* $Author:
* Created by Hannes Fritsche, SLTE, 2023-11-06
*/
#include "pre_topic_shotter.h"
#include "pre_screen.h"
#include <QFile>
#include <QThread>
#include <QScreen>
#include <QTimer>
namespace PresentationManagement
{
TopicShotter::TopicShotter(const int topicId, const QString &topicName, const Screen *screen, qint32 offset,
qint64 startTime, qint64 stopTime, const double topicX, const double topicY,
const double topicW, const double topicH, const bool isFilterTopic, const QString &desc, QObject *parent)
: QObject(parent)
, m_startTime(startTime)
, m_topicName(topicName)
, m_topicId(topicId)
, m_screen(screen)
, m_topicX(topicX)
, m_topicY(topicY)
, m_topicW(topicW)
, m_topicH(topicH)
, m_desc(desc)
, m_isFilterTopic(isFilterTopic)
, m_logger(getLogger("TopicShotter"))
{}
TopicShotter::~TopicShotter()
{}
void TopicShotter::saveRecording(const QString &saveToPath)
{
const QString newFilename(QStringLiteral("%1/%2.png").arg(saveToPath).arg(m_topicId));
if (!m_tmpFile.rename(newFilename))
{
throw QStringLiteral("Failed to move screenshot from '%1' to '%2' ('%3')").arg(m_tmpFile.fileName(), saveToPath, newFilename);
return;
}
m_tmpFile.setAutoRemove(false); // keep file
}
void TopicShotter::onExportFailed(const QString &errorMessage)
{
ERROR_LOG(m_logger) << "Export failed for topic '" << m_topicName << "': " << errorMessage;
emit exportFailed(errorMessage);
}
void TopicShotter::onNewTime(qint64 time)
{
// filter topics start a second later (loading issues), so start time needs to be adjusted for them:
const qint64 filterTopicDelay = (m_isFilterTopic ? 1000 : 0);
const qint64 startTime = m_startTime + filterTopicDelay + 1000 /* + 1000 because onNewTime is triggered *before* all the playTopic calls >_> */;
DEBUG_LOG(m_logger) << "onNewTime: " << time << ", startTime: " << startTime << ", isFilterTopic? " << m_isFilterTopic << ", desc: " << m_desc;
if (time < startTime)
return;
if (isStarted)
return;
isStarted = true;
DEBUG_LOG(m_logger) << "onNewTime: started at " << time << " while startTime is " << startTime;
if (!m_tmpFile.open()) // create file
{
onExportFailed(QStringLiteral("Can't create temporary file for screenshot at %1: %2").arg(m_tmpFile.fileName(), m_tmpFile.errorString()));
return;
}
const auto geo = m_screen->geometry();
const double screenW = geo.width();
const double screenH = geo.height();
const qint32 inputX = geo.x() + (screenW * m_topicX);
const qint32 inputY = geo.y() + (screenH * m_topicY);
const qint32 inputW = (qint32)(screenW * m_topicW);
const qint32 inputH = (qint32)(screenH * m_topicH);
QScreen *_screen = m_screen->window()->screen();
if (!_screen)
{
onExportFailed(QStringLiteral("Can't get system window of screen!"));
return;
}
// defered start of grabWindow as the to-be-shotted widget needs to initialize first
auto *t = new QTimer(this);
t->setInterval(750);
t->setSingleShot(true);
t->start();
connect(t, &QTimer::timeout, this, [this, inputX, inputY, inputW, inputH, _screen]() // =>
{
const WId winId = m_screen->winId();
DEBUG_LOG(m_logger) << "## CHEESE! ## grabWindow: x: " << inputX << ", y: " << inputY << ", w: " << inputW << ", h: " << inputH << ", name: " << m_topicName << ", id: " << m_topicId << ", desc: " << m_desc;
const QPixmap pix = _screen->grabWindow(winId, inputX, inputY, inputW, inputH); // NOTE: LOCAL position for grabWindow!
if (!pix.save(&m_tmpFile, "PNG"))
{
onExportFailed(QStringLiteral("Can't save screenshot to file: %1").arg(m_tmpFile.errorString()));
return;
}
});
}
} // namespace PresentationManagement
@@ -0,0 +1,67 @@
/**
* @RDE_HEADER@
* class for recording a single topic on a screen
* $Id:
* $HeadURL:
* $Author:
* Created by Hannes Fritsche, SLTE, 2023-11-06
*/
#pragma once
#include <QObject>
#include <QTemporaryFile>
#include <QProcess>
#include <Services/PresentationManagement/logger.h>
class Screen;
namespace PresentationManagement
{
class TopicItem;
class TopicShotter : public QObject
{
Q_OBJECT
public: // methods
TopicShotter(const int topicId, const QString &topicName, const Screen *screen, qint32 offset,
qint64 startTime, qint64 stopTime, const double topicX, const double topicY,
const double topicW, const double topicH, const bool isFilterTopic,
const QString &desc, QObject *parent = nullptr);
~TopicShotter();
void saveRecording(const QString &saveToPath);
bool isFailed() const { return m_failed; }
const QString &topicName() const { return m_topicName; }
int topicId() const { return m_topicId; }
public Q_SLOTS:
void onNewTime(qint64 time);
signals:
void exportFailed(const QString &errorMessage);
private Q_SLOTS:
void onExportFailed(const QString &errorMessage);
private: // attributes
const qint64 m_startTime;
const QString m_topicName;
QTemporaryFile m_tmpFile;
const int m_topicId;
bool m_failed { false };
const Screen *m_screen;
const double m_topicX;
const double m_topicY;
const double m_topicW;
const double m_topicH;
bool isStarted { false };
const bool m_isFilterTopic;
QString m_desc;
LOGGER_T m_logger;
}; // class TopicShotter
} // namespace PresentationManagement
@@ -0,0 +1,26 @@
#include "pre_topic_track.h"
namespace PresentationManagement
{
TopicTrack::TopicTrack(const QString &text, const QString &internalName, TrackType trackType)
: LtsTimeline::Track(text)
, m_trackType(trackType)
, m_internalName(internalName)
{}
///////////////////////////////////////////////////////////////////////////////
// PUBLIC
qint64 TopicTrack::firstTopicStart() const
{
return items().empty() ? -1 : items().front()->startTime();
}
qint64 TopicTrack::lastTopicEnd() const
{
return items().empty() ? -1 : items().back()->startTime() + items().back()->duration();
}
} // namespace PresentationManagement
@@ -0,0 +1,31 @@
#pragma once
#include "pre_utils.h"
#include <LtsTimeline/track.h>
namespace PresentationManagement
{
class TopicTrack : public LtsTimeline::Track
{
public:
TopicTrack(const QString &text, const QString &internalName, TrackType trackType);
TrackType trackType() const { return m_trackType; }
QString internalName() const { return m_internalName; }
void setInternalName(const QString &name) { m_internalName = name; }
qint64 firstTopicStart() const;
qint64 lastTopicEnd() const;
private:
TrackType m_trackType;
QString m_internalName;
};
} // namespace PresentationManagement
@@ -0,0 +1,30 @@
#include "pre_utils.h"
namespace PresentationManagement
{
QString trackTypeString(TrackType trackType)
{
switch (trackType)
{
case TrackType::Audio:
return QStringLiteral("Audio");
case TrackType::Replay:
return QStringLiteral("Playback");
case TrackType::Screen:
return QStringLiteral("Screen");
case TrackType::Filter:
return QStringLiteral("Filter");
}
Q_UNREACHABLE();
return {};
}
std::ostream& operator<<(std::ostream& stream, TrackType trackType)
{
stream << "TrackType::" << trackTypeString(trackType).toStdString().c_str();
return stream;
}
}
@@ -0,0 +1,22 @@
#pragma once
#include <QVector>
namespace PresentationManagement
{
enum class TrackType
{
Audio,
Replay,
Screen,
Filter
};
typedef QVector<TrackType> TrackTypes;
QString trackTypeString(TrackType trackType);
std::ostream& operator<<(std::ostream& stream, TrackType trackType);
} // PresentationManagement