diff --git a/app/common/html.cpp b/app/common/html.cpp
index 1b58418fa..d9ad84a28 100644
--- a/app/common/html.cpp
+++ b/app/common/html.cpp
@@ -142,7 +142,7 @@ void Html::WriteBlock(QXmlStreamWriter *writer, const QTextBlock &block)
if (!(fmt.alignment() & Qt::AlignLeft)) {
if (fmt.alignment() & Qt::AlignRight) {
writer->writeAttribute(QStringLiteral("align"), QStringLiteral("right"));
- } else if (fmt.alignment() & Qt::AlignCenter) {
+ } else if (fmt.alignment() & Qt::AlignHCenter) {
writer->writeAttribute(QStringLiteral("align"), QStringLiteral("center"));
} else if (fmt.alignment() & Qt::AlignJustify) {
writer->writeAttribute(QStringLiteral("align"), QStringLiteral("justify"));
@@ -161,7 +161,7 @@ void Html::WriteBlock(QXmlStreamWriter *writer, const QTextBlock &block)
WriteCSSProperty(&style, QStringLiteral("line-height"), QStringLiteral("%1%").arg(fmt.lineHeight()));
}
- //WriteCharFormat(&style, block.charFormat());
+ WriteCharFormat(&style, block.charFormat());
if (!style.isEmpty()) {
writer->writeAttribute(QStringLiteral("style"), style);
@@ -169,12 +169,7 @@ void Html::WriteBlock(QXmlStreamWriter *writer, const QTextBlock &block)
auto it = block.begin();
- if (it == block.end()) {
- // FIXME: Might not be necessary with our custom HTML implementation
- QString s;
- s.append(QChar::Nbsp);
- writer->writeCharacters(s);
- } else {
+ if (it != block.end()) {
for (; it!=block.end(); it++) {
WriteFragment(writer, it.fragment());
}
@@ -374,7 +369,7 @@ QTextBlockFormat Html::ReadBlockFormat(const QXmlStreamAttributes &attributes)
if (StrEquals(attr.value(), QStringLiteral("right"))) {
block_fmt.setAlignment(Qt::AlignRight);
} else if (StrEquals(attr.value(), QStringLiteral("center"))) {
- block_fmt.setAlignment(Qt::AlignCenter);
+ block_fmt.setAlignment(Qt::AlignHCenter);
} else if (StrEquals(attr.value(), QStringLiteral("justify"))) {
block_fmt.setAlignment(Qt::AlignJustify);
}
diff --git a/app/common/qtutils.cpp b/app/common/qtutils.cpp
index 5ad608d3f..46ee40172 100644
--- a/app/common/qtutils.cpp
+++ b/app/common/qtutils.cpp
@@ -43,7 +43,7 @@ QFrame *QtUtils::CreateHorizontalLine()
QFrame *QtUtils::CreateVerticalLine()
{
QFrame *l = CreateHorizontalLine();
- l->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding);
+ l->setFrameShape(QFrame::VLine);
return l;
}
diff --git a/app/node/generator/text/textv3.cpp b/app/node/generator/text/textv3.cpp
index dd8bf791c..a723e67aa 100644
--- a/app/node/generator/text/textv3.cpp
+++ b/app/node/generator/text/textv3.cpp
@@ -25,7 +25,9 @@
#include
#include "common/html.h"
+#include "core.h"
#include "node/project/project.h"
+#include "widget/nodeparamview/nodeparamviewundo.h"
namespace olive {
@@ -43,14 +45,15 @@ const QString TextGeneratorV3::kUseArgsInput = QStringLiteral("use_args_in");
const QString TextGeneratorV3::kArgsInput = QStringLiteral("args_in");
TextGeneratorV3::TextGeneratorV3() :
- ShapeNodeBase(false)
+ ShapeNodeBase(false),
+ dont_emit_valign_(false)
{
AddInput(kTextInput, NodeValue::kText, QStringLiteral("%1
").arg(tr("Sample Text")));
SetInputProperty(kTextInput, QStringLiteral("vieweronly"), true);
SetStandardValue(kSizeInput, QVector2D(400, 300));
- AddInput(kVerticalAlignmentInput, NodeValue::kCombo);
+ AddInput(kVerticalAlignmentInput, NodeValue::kCombo, InputFlags(kInputFlagHidden | kInputFlagStatic));
AddInput(kUseArgsInput, NodeValue::kBoolean, true, InputFlags(kInputFlagHidden | kInputFlagStatic));
@@ -180,6 +183,33 @@ void TextGeneratorV3::UpdateGizmoPositions(const NodeValueRow &row, const NodeGl
text_gizmo_->SetHtml(row[kTextInput].toString());
}
+Qt::Alignment TextGeneratorV3::GetQtAlignmentFromOurs(VerticalAlignment v)
+{
+ switch (v) {
+ case kVAlignTop:
+ return Qt::AlignTop;
+ case kVAlignMiddle:
+ return Qt::AlignVCenter;
+ case kVAlignBottom:
+ return Qt::AlignBottom;
+ }
+ return Qt::Alignment();
+}
+
+TextGeneratorV3::VerticalAlignment TextGeneratorV3::GetOurAlignmentFromQts(Qt::Alignment v)
+{
+ switch (v) {
+ case Qt::AlignTop:
+ return kVAlignTop;
+ case Qt::AlignVCenter:
+ return kVAlignMiddle;
+ case Qt::AlignBottom:
+ return kVAlignBottom;
+ }
+
+ return kVAlignTop;
+}
+
QString TextGeneratorV3::FormatString(const QString &input, const QStringList &args)
{
QString output;
@@ -218,14 +248,32 @@ QString TextGeneratorV3::FormatString(const QString &input, const QStringList &a
return output;
}
+void TextGeneratorV3::InputValueChangedEvent(const QString &input, int element)
+{
+ if (input == kVerticalAlignmentInput && !dont_emit_valign_) {
+ text_gizmo_->SetVerticalAlignment(GetQtAlignmentFromOurs(GetVerticalAlignment()));
+ }
+
+ super::InputValueChangedEvent(input, element);
+}
+
void TextGeneratorV3::GizmoActivated()
{
SetStandardValue(kUseArgsInput, false);
+ connect(text_gizmo_, &TextGizmo::VerticalAlignmentChanged, this, &TextGeneratorV3::SetVerticalAlignmentUndoable);
+ dont_emit_valign_ = true;
}
void TextGeneratorV3::GizmoDeactivated()
{
SetStandardValue(kUseArgsInput, true);
+ disconnect(text_gizmo_, &TextGizmo::VerticalAlignmentChanged, this, &TextGeneratorV3::SetVerticalAlignmentUndoable);
+ dont_emit_valign_ = true;
+}
+
+void TextGeneratorV3::SetVerticalAlignmentUndoable(Qt::Alignment a)
+{
+ Core::instance()->undo_stack()->push(new NodeParamSetStandardValueCommand(NodeInput(this, kVerticalAlignmentInput), GetOurAlignmentFromQts(a)));
}
}
diff --git a/app/node/generator/text/textv3.h b/app/node/generator/text/textv3.h
index a815fbb43..b7d6a5674 100644
--- a/app/node/generator/text/textv3.h
+++ b/app/node/generator/text/textv3.h
@@ -54,6 +54,14 @@ public:
kVAlignBottom
};
+ VerticalAlignment GetVerticalAlignment() const
+ {
+ return static_cast(GetStandardValue(kVerticalAlignmentInput).toInt());
+ }
+
+ static Qt::Alignment GetQtAlignmentFromOurs(VerticalAlignment v);
+ static VerticalAlignment GetOurAlignmentFromQts(Qt::Alignment v);
+
static const QString kTextInput;
static const QString kVerticalAlignmentInput;
static const QString kUseArgsInput;
@@ -61,12 +69,18 @@ public:
static QString FormatString(const QString &input, const QStringList &args);
+protected:
+ virtual void InputValueChangedEvent(const QString &input, int element) override;
+
private:
TextGizmo *text_gizmo_;
+ bool dont_emit_valign_;
+
private slots:
void GizmoActivated();
void GizmoDeactivated();
+ void SetVerticalAlignmentUndoable(Qt::Alignment a);
};
diff --git a/app/node/gizmo/text.cpp b/app/node/gizmo/text.cpp
index 6e2b97c5b..6d6816948 100644
--- a/app/node/gizmo/text.cpp
+++ b/app/node/gizmo/text.cpp
@@ -26,7 +26,8 @@
namespace olive {
TextGizmo::TextGizmo(QObject *parent)
- : NodeGizmo{parent}
+ : NodeGizmo{parent},
+ valign_(Qt::AlignTop)
{
}
diff --git a/app/node/gizmo/text.h b/app/node/gizmo/text.h
index 30ea62f7f..5c3218671 100644
--- a/app/node/gizmo/text.h
+++ b/app/node/gizmo/text.h
@@ -42,9 +42,21 @@ public:
void UpdateInputHtml(const QString &s, const rational &time);
+ Qt::Alignment GetVerticalAlignment() const
+ {
+ return valign_;
+ }
+
+ void SetVerticalAlignment(Qt::Alignment va)
+ {
+ valign_ = va;
+ emit VerticalAlignmentChanged(valign_);
+ }
+
signals:
void Activated();
void Deactivated();
+ void VerticalAlignmentChanged(Qt::Alignment va);
private:
QRectF rect_;
@@ -53,6 +65,8 @@ private:
NodeKeyframeTrackReference input_;
+ Qt::Alignment valign_;
+
};
}
diff --git a/app/panel/audiomonitor/audiomonitor.cpp b/app/panel/audiomonitor/audiomonitor.cpp
index 2b2df6a28..3bf6a734b 100644
--- a/app/panel/audiomonitor/audiomonitor.cpp
+++ b/app/panel/audiomonitor/audiomonitor.cpp
@@ -31,24 +31,11 @@ AudioMonitorPanel::AudioMonitorPanel(QWidget *parent) :
{
audio_monitor_ = new AudioMonitor();
- audio_monitor_->installEventFilter(this);
-
- setWidget(QWidget::createWindowContainer(audio_monitor_));
+ setWidget(audio_monitor_);
Retranslate();
}
-bool AudioMonitorPanel::eventFilter(QObject *o, QEvent *e)
-{
- if (o == audio_monitor_ && e->type() == QEvent::FocusIn) {
- // HACK: QWindow focus isn't accounted for in QApplication::focusChanged, so we handle it
- // manually here.
- PanelManager::instance()->FocusChanged(nullptr, this);
- }
-
- return super::eventFilter(o, e);
-}
-
void AudioMonitorPanel::Retranslate()
{
SetTitle(tr("Audio Monitor"));
diff --git a/app/panel/audiomonitor/audiomonitor.h b/app/panel/audiomonitor/audiomonitor.h
index b9d7b5e04..2c76d60da 100644
--- a/app/panel/audiomonitor/audiomonitor.h
+++ b/app/panel/audiomonitor/audiomonitor.h
@@ -45,8 +45,6 @@ public:
audio_monitor_->SetParams(params);
}
- virtual bool eventFilter(QObject *o, QEvent *e) override;
-
private:
virtual void Retranslate() override;
diff --git a/app/panel/panelmanager.cpp b/app/panel/panelmanager.cpp
index 04943235f..8bf6dfe44 100644
--- a/app/panel/panelmanager.cpp
+++ b/app/panel/panelmanager.cpp
@@ -28,7 +28,8 @@ PanelManager* PanelManager::instance_ = nullptr;
PanelManager::PanelManager(QObject *parent) :
QObject(parent),
- locked_(false)
+ locked_(false),
+ suppress_changed_signal_(false)
{
}
@@ -165,7 +166,9 @@ void PanelManager::FocusChanged(QWidget *old, QWidget *now)
focus_history_.move(panel_index, 0);
}
- emit FocusedPanelChanged(panel_cast_test);
+ if (!suppress_changed_signal_) {
+ emit FocusedPanelChanged(panel_cast_test);
+ }
}
break;
diff --git a/app/panel/panelmanager.h b/app/panel/panelmanager.h
index 23feeb02f..45639b7e8 100644
--- a/app/panel/panelmanager.h
+++ b/app/panel/panelmanager.h
@@ -122,6 +122,11 @@ public:
*/
void UnregisterPanel(PanelWidget *panel);
+ void SetSuppressChangedSignal(bool e)
+ {
+ suppress_changed_signal_ = e;
+ }
+
public slots:
/**
* @brief Connect this to a QApplication's SIGNAL(focusChanged())
@@ -157,6 +162,8 @@ private:
*/
static PanelManager* instance_;
+ bool suppress_changed_signal_;
+
};
template
diff --git a/app/panel/project/project.cpp b/app/panel/project/project.cpp
index b1ecf1f59..d97eb7bd3 100644
--- a/app/panel/project/project.cpp
+++ b/app/panel/project/project.cpp
@@ -174,7 +174,10 @@ void ProjectPanel::ItemDoubleClickSlot(Node *item)
Core::instance()->DialogImportShow();
} else if (dynamic_cast(item)) {
// Open this footage in a FootageViewer
- PanelManager::instance()->MostRecentlyFocused()->ConnectViewerNode(static_cast(item));
+ auto panel = PanelManager::instance()->MostRecentlyFocused();
+ panel->ConnectViewerNode(static_cast(item));
+ panel->raise();
+ panel->setFocus();
} else if (dynamic_cast(item)) {
// Open this sequence in the Timeline
Core::instance()->main_window()->OpenSequence(static_cast(item));
diff --git a/app/panel/viewer/viewerbase.cpp b/app/panel/viewer/viewerbase.cpp
index 7076ae510..6c90506b6 100644
--- a/app/panel/viewer/viewerbase.cpp
+++ b/app/panel/viewer/viewerbase.cpp
@@ -27,6 +27,7 @@ namespace olive {
ViewerPanelBase::ViewerPanelBase(const QString& object_name, QWidget *parent) :
TimeBasedPanel(object_name, parent)
{
+ connect(PanelManager::instance(), &PanelManager::FocusedPanelChanged, this, &ViewerPanelBase::FocusedPanelChanged);
}
void ViewerPanelBase::PlayPause()
@@ -101,4 +102,12 @@ void ViewerPanelBase::SetViewerWidget(ViewerWidget *vw)
SetTimeBasedWidget(vw);
}
+void ViewerPanelBase::FocusedPanelChanged(PanelWidget *panel)
+{
+ auto vw = static_cast(GetTimeBasedWidget());
+ if (vw->IsPlaying() && panel != this) {
+ vw->Pause();
+ }
+}
+
}
diff --git a/app/panel/viewer/viewerbase.h b/app/panel/viewer/viewerbase.h
index e5fe7cdff..1ca7c06ff 100644
--- a/app/panel/viewer/viewerbase.h
+++ b/app/panel/viewer/viewerbase.h
@@ -93,6 +93,9 @@ signals:
protected:
void SetViewerWidget(ViewerWidget *vw);
+private slots:
+ void FocusedPanelChanged(PanelWidget *panel);
+
};
}
diff --git a/app/ui/icons/icons.cpp b/app/ui/icons/icons.cpp
index 85288cb6b..3f4fdaa95 100644
--- a/app/ui/icons/icons.cpp
+++ b/app/ui/icons/icons.cpp
@@ -75,6 +75,9 @@ QIcon icon::TextAlignLeft;
QIcon icon::TextAlignRight;
QIcon icon::TextAlignCenter;
QIcon icon::TextAlignJustify;
+QIcon icon::TextAlignTop;
+QIcon icon::TextAlignBottom;
+QIcon icon::TextAlignMiddle;
QIcon icon::Snapping;
QIcon icon::ZoomIn;
QIcon icon::ZoomOut;
@@ -145,6 +148,9 @@ void icon::LoadAll(const QString& theme)
TextAlignRight = Create(theme, "align-right");
TextAlignCenter = Create(theme, "align-center");
TextAlignJustify = Create(theme, "align-justify-all");
+ TextAlignTop = Create(theme, "align-left");
+ TextAlignBottom = Create(theme, "align-right");
+ TextAlignMiddle = Create(theme, "align-center");
Snapping = Create(theme, "magnet");
ZoomIn = Create(theme, "zoomin");
diff --git a/app/ui/icons/icons.h b/app/ui/icons/icons.h
index 83a9f18dc..a93ed9483 100644
--- a/app/ui/icons/icons.h
+++ b/app/ui/icons/icons.h
@@ -85,6 +85,9 @@ extern QIcon TextAlignLeft;
extern QIcon TextAlignRight;
extern QIcon TextAlignCenter;
extern QIcon TextAlignJustify;
+extern QIcon TextAlignTop;
+extern QIcon TextAlignBottom;
+extern QIcon TextAlignMiddle;
// Miscellaneous Icons
extern QIcon Snapping;
diff --git a/app/widget/audiomonitor/audiomonitor.h b/app/widget/audiomonitor/audiomonitor.h
index 1d58b1d72..aaa5e6a5a 100644
--- a/app/widget/audiomonitor/audiomonitor.h
+++ b/app/widget/audiomonitor/audiomonitor.h
@@ -22,7 +22,7 @@
#define AUDIOMONITORWIDGET_H
#include
-#include
+#include
#include
#include "audio/audiovisualwaveform.h"
@@ -32,7 +32,7 @@
namespace olive {
-class AudioMonitor : public QOpenGLWindow
+class AudioMonitor : public QOpenGLWidget
{
Q_OBJECT
public:
diff --git a/app/widget/timebased/timebasedwidget.cpp b/app/widget/timebased/timebasedwidget.cpp
index 03f0111c9..a4beb3bfa 100644
--- a/app/widget/timebased/timebasedwidget.cpp
+++ b/app/widget/timebased/timebasedwidget.cpp
@@ -741,7 +741,7 @@ void TimeBasedWidget::GoToOut()
void TimeBasedWidget::DeleteSelected()
{
- if (ruler_->underMouse()) {
+ if (ruler_->HasItemsSelected()) {
ruler_->DeleteSelected();
}
}
diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp
index 8b59f7fe1..6a3311304 100644
--- a/app/widget/timelinewidget/timelinewidget.cpp
+++ b/app/widget/timelinewidget/timelinewidget.cpp
@@ -442,7 +442,7 @@ void TimelineWidget::ReplaceBlocksWithGaps(const QVector &blocks,
void TimelineWidget::DeleteSelected(bool ripple)
{
- if (ruler()->hasFocus()) {
+ if (ruler()->HasItemsSelected()) {
ruler()->DeleteSelected();
return;
}
diff --git a/app/widget/timeruler/seekablewidget.h b/app/widget/timeruler/seekablewidget.h
index fadf13492..ef90f872e 100644
--- a/app/widget/timeruler/seekablewidget.h
+++ b/app/widget/timeruler/seekablewidget.h
@@ -65,6 +65,11 @@ public:
void SeekToScenePoint(qreal scene);
+ bool HasItemsSelected() const
+ {
+ return !selection_manager_.GetSelectedObjects().empty();
+ }
+
const std::vector &GetSelectedMarkers() const
{
return selection_manager_.GetSelectedObjects();
diff --git a/app/widget/viewer/footageviewer.cpp b/app/widget/viewer/footageviewer.cpp
index 16c9b2d4c..b643f6740 100644
--- a/app/widget/viewer/footageviewer.cpp
+++ b/app/widget/viewer/footageviewer.cpp
@@ -60,6 +60,7 @@ void FootageViewerWidget::ConnectNodeEvent(ViewerOutput *n)
{
super::ConnectNodeEvent(n);
+ IgnoreNextScrubEvent();
SetTime(cached_timestamps_.value(n, 0));
}
@@ -70,6 +71,7 @@ void FootageViewerWidget::DisconnectNodeEvent(ViewerOutput *n)
super::DisconnectNodeEvent(n);
+ IgnoreNextScrubEvent();
SetTime(0);
}
diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp
index fbf507768..cf12ad0cd 100644
--- a/app/widget/viewer/viewer.cpp
+++ b/app/widget/viewer/viewer.cpp
@@ -73,7 +73,8 @@ ViewerWidget::ViewerWidget(QWidget *parent) :
recording_(false),
first_requeue_watcher_(nullptr),
enable_audio_scrubbing_(true),
- waveform_mode_(kWFAutomatic)
+ waveform_mode_(kWFAutomatic),
+ ignore_scrub_(0)
{
// Set up main layout
QVBoxLayout* layout = new QVBoxLayout(this);
@@ -85,7 +86,6 @@ ViewerWidget::ViewerWidget(QWidget *parent) :
layout->addWidget(sizer_);
display_widget_ = new ViewerDisplayWidget();
- display_widget_->setAcceptDrops(true);
display_widget_->SetShowWidgetBackground(true);
playback_devices_.append(display_widget_);
connect(display_widget_, &ViewerDisplayWidget::customContextMenuRequested, this, &ViewerWidget::ShowContextMenu);
@@ -147,8 +147,6 @@ ViewerWidget::ViewerWidget(QWidget *parent) :
instances_.append(this);
- setAcceptDrops(true);
-
auto_cacher_ = new PreviewAutoCacher(this);
connect(display_widget_, &ViewerDisplayWidget::ColorProcessorChanged, auto_cacher_, &PreviewAutoCacher::SetDisplayColorProcessor);
@@ -260,6 +258,7 @@ void ViewerWidget::DisconnectNodeEvent(ViewerOutput *n)
disconnect(n, &ViewerOutput::TextureInputChanged, this, &ViewerWidget::UpdateWaveformViewFromMode);
CloseAudioProcessor();
+ audio_scrub_watchers_.clear();
SetDisplayImage(QVariant());
@@ -485,7 +484,7 @@ void ViewerWidget::DisarmRecording()
void ViewerWidget::UpdateAudioProcessor()
{
if (GetConnectedNode()) {
- audio_processor_.Close();
+ CloseAudioProcessor();
AudioParams ap = GetConnectedNode()->GetAudioParams();
AudioParams packed(OLIVE_CONFIG("AudioOutputSampleRate").toInt(),
@@ -679,30 +678,33 @@ void ViewerWidget::ReceivedAudioBufferForPlayback()
void ViewerWidget::ReceivedAudioBufferForScrubbing()
{
- // NOTE: Might be good to organize a queue for this in the event that audio takes a long time to
- // keep the scrubbed chunks ordered, similar to the playback_queue_ or audio_playback_queue_
-
RenderTicketWatcher *watcher = static_cast(sender());
- if (watcher->HasResult()) {
- SampleBuffer samples = watcher->Get().value();
- if (samples.is_allocated()) {
- if (samples.audio_params().channel_count() > 0) {
- AudioProcessor::Buffer buf;
- int r = audio_processor_.Convert(samples.to_raw_ptrs().data(), samples.sample_count(), &buf);
+ while (!audio_scrub_watchers_.empty() && audio_scrub_watchers_.front() != watcher) {
+ audio_scrub_watchers_.pop_front();
+ }
- if (r >= 0) {
- if (!buf.empty()) {
- QString error;
- const QByteArray &packed = buf.at(0);
- AudioManager::instance()->ClearBufferedOutput();
- if (!AudioManager::instance()->PushToOutput(audio_processor_.to(), packed, &error)) {
- Core::instance()->ShowStatusBarMessage(tr("Audio scrubbing failed: %1").arg(error));
+ if (!audio_scrub_watchers_.empty()) {
+ if (watcher->HasResult()) {
+ SampleBuffer samples = watcher->Get().value();
+ if (samples.is_allocated()) {
+ if (samples.audio_params().channel_count() > 0) {
+ AudioProcessor::Buffer buf;
+ int r = audio_processor_.Convert(samples.to_raw_ptrs().data(), samples.sample_count(), &buf);
+
+ if (r >= 0) {
+ if (!buf.empty()) {
+ QString error;
+ const QByteArray &packed = buf.at(0);
+ AudioManager::instance()->ClearBufferedOutput();
+ if (!AudioManager::instance()->PushToOutput(audio_processor_.to(), packed, &error)) {
+ Core::instance()->ShowStatusBarMessage(tr("Audio scrubbing failed: %1").arg(error));
+ }
+ AudioMonitor::PushSampleBufferOnAll(samples);
}
- AudioMonitor::PushSampleBufferOnAll(samples);
+ } else {
+ qCritical() << "Failed to process audio for scrubbing:" << r;
}
- } else {
- qCritical() << "Failed to process audio for scrubbing:" << r;
}
}
}
@@ -932,16 +934,23 @@ void ViewerWidget::PauseInternal()
void ViewerWidget::PushScrubbedAudio()
{
if (!IsPlaying() && GetConnectedNode() && OLIVE_CONFIG("AudioScrubbing").toBool() && enable_audio_scrubbing_) {
- // Get audio src device from renderer
- const AudioParams& params = GetConnectedNode()->GetAudioParams();
+ if (ignore_scrub_ > 0) {
+ ignore_scrub_--;
+ }
- if (params.is_valid()) {
- // NOTE: Hardcoded scrubbing interval (20ms)
- rational interval = rational(20, 1000);
+ if (ignore_scrub_ == 0) {
+ // Get audio src device from renderer
+ const AudioParams& params = GetConnectedNode()->audio_playback_cache()->GetParameters();
- RenderTicketWatcher *watcher = new RenderTicketWatcher();
- connect(watcher, &RenderTicketWatcher::Finished, this, &ViewerWidget::ReceivedAudioBufferForScrubbing);
- watcher->SetTicket(auto_cacher_->GetRangeOfAudio(TimeRange(GetTime(), GetTime() + interval)));
+ if (params.is_valid()) {
+ // NOTE: Hardcoded scrubbing interval (20ms)
+ rational interval = rational(20, 1000);
+
+ RenderTicketWatcher *watcher = new RenderTicketWatcher();
+ connect(watcher, &RenderTicketWatcher::Finished, this, &ViewerWidget::ReceivedAudioBufferForScrubbing);
+ audio_scrub_watchers_.push_back(watcher);
+ watcher->SetTicket(auto_cacher_->GetRangeOfAudio(TimeRange(GetTime(), GetTime() + interval)));
+ }
}
}
}
diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h
index 88be45e86..e4aaa6f0e 100644
--- a/app/widget/viewer/viewer.h
+++ b/app/widget/viewer/viewer.h
@@ -177,6 +177,11 @@ protected:
return display_widget_;
}
+ void IgnoreNextScrubEvent()
+ {
+ ignore_scrub_++;
+ }
+
private:
int64_t GetTimestamp() const
{
@@ -279,6 +284,8 @@ private:
static QVector instances_;
+ std::list audio_scrub_watchers_;
+
bool record_armed_;
bool recording_;
TimelineWidget *recording_callback_;
@@ -295,6 +302,8 @@ private:
QVector dry_run_watchers_;
+ int ignore_scrub_;
+
private slots:
void PlaybackTimerUpdate();
diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp
index ec23372ad..68a212bcd 100644
--- a/app/widget/viewer/viewerdisplay.cpp
+++ b/app/widget/viewer/viewerdisplay.cpp
@@ -44,7 +44,7 @@
#include "node/gizmo/point.h"
#include "node/gizmo/polygon.h"
#include "node/gizmo/screen.h"
-#include "viewertexteditor.h"
+#include "window/mainwindow/mainwindow.h"
namespace olive {
@@ -67,7 +67,8 @@ ViewerDisplayWidget::ViewerDisplayWidget(QWidget *parent) :
playback_speed_(0),
push_mode_(kPushNull),
add_band_(false),
- queue_starved_(false)
+ queue_starved_(false),
+ text_edit_(nullptr)
{
connect(Core::instance(), &Core::ToolChanged, this, &ViewerDisplayWidget::ToolChanged);
@@ -76,6 +77,8 @@ ViewerDisplayWidget::ViewerDisplayWidget(QWidget *parent) :
const int kFrameRateAverageCount = 8;
frame_rate_averages_.resize(kFrameRateAverageCount);
+
+ inner_widget()->setAcceptDrops(true);
}
void ViewerDisplayWidget::SetMatrixTranslate(const QMatrix4x4 &mat)
@@ -240,48 +243,110 @@ void ViewerDisplayWidget::IncrementSkippedFrames()
bool ViewerDisplayWidget::eventFilter(QObject *o, QEvent *e)
{
- if (o != this->inner_widget()) {
- return super::eventFilter(o, e);
- }
-
- switch (e->type()) {
- case QEvent::MouseButtonPress:
- {
- QMouseEvent *mouse = static_cast(e);
- if (!(mouse->flags() & Qt::MouseEventCreatedDoubleClick)) {
- if (OnMousePress(mouse)) {
+ if (o == this->inner_widget()) {
+ switch (e->type()) {
+ case QEvent::MouseButtonPress:
+ {
+ QMouseEvent *mouse = static_cast(e);
+ if (!(mouse->flags() & Qt::MouseEventCreatedDoubleClick)) {
+ if (OnMousePress(mouse)) {
+ return true;
+ }
+ }
+ break;
+ }
+ case QEvent::MouseMove:
+ EmitColorAtCursor(static_cast(e));
+ if (OnMouseMove(static_cast(e))) {
return true;
}
+ break;
+ case QEvent::MouseButtonRelease:
+ if (OnMouseRelease(static_cast(e))) {
+ return true;
+ }
+ break;
+ case QEvent::MouseButtonDblClick:
+ if (OnMouseDoubleClick(static_cast(e))) {
+ return true;
+ }
+ break;
+ case QEvent::ShortcutOverride:
+ case QEvent::KeyPress:
+ if (OnKeyPress(static_cast(e))) {
+ return true;
+ }
+ break;
+ case QEvent::KeyRelease:
+ if (OnKeyRelease(static_cast(e))) {
+ return true;
+ }
+ break;
+ case QEvent::DragEnter:
+ {
+ auto drag_enter = static_cast(e);
+ if (text_edit_) {
+ ForwardDragEventToTextEdit(drag_enter);
+ } else {
+ emit DragEntered(drag_enter);
+ }
+
+ if (drag_enter->isAccepted()) {
+ return true;
+ }
+ break;
}
- break;
- }
- case QEvent::MouseMove:
- EmitColorAtCursor(static_cast(e));
- if (OnMouseMove(static_cast(e))) {
+ case QEvent::DragMove:
+ {
+ auto drag_move = static_cast(e);
+ if (text_edit_) {
+ ForwardDragEventToTextEdit(drag_move);
+ }
+
+ if (drag_move->isAccepted()) {
+ return true;
+ }
+ break;
+ }
+ case QEvent::DragLeave:
+ {
+ auto drag_leave = static_cast(e);
+ if (text_edit_) {
+ ForwardDragEventToTextEdit(drag_leave);
+ } else {
+ emit DragLeft(drag_leave);
+ }
+
+ if (drag_leave->isAccepted()) {
+ return true;
+ }
+ break;
+ }
+ case QEvent::Drop:
+ {
+ auto drop = static_cast(e);
+ if (text_edit_) {
+ ForwardDragEventToTextEdit(drop);
+ } else {
+ emit Dropped(drop);
+ }
+
+ if (drop->isAccepted()) {
+ return true;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ } else if (o == text_edit_) {
+ switch (e->type()) {
+ case QEvent::Paint:
+ update();
return true;
+ default:
+ break;
}
- break;
- case QEvent::MouseButtonRelease:
- if (OnMouseRelease(static_cast(e))) {
- return true;
- }
- break;
- case QEvent::MouseButtonDblClick:
- if (OnMouseDoubleClick(static_cast(e))) {
- return true;
- }
- break;
- case QEvent::DragEnter:
- emit DragEntered(static_cast(e));
- break;
- case QEvent::DragLeave:
- emit DragLeft(static_cast(e));
- break;
- case QEvent::Drop:
- emit Dropped(static_cast(e));
- break;
- default:
- break;
}
return super::eventFilter(o, e);
@@ -385,6 +450,16 @@ void ViewerDisplayWidget::OnPaint()
gizmo->Draw(&p);
}
}
+
+ if (text_edit_) {
+ QPixmap pm(text_edit_->width(), text_edit_->height());
+ pm.fill(Qt::transparent);
+
+ QPainter pixp(&pm);
+ text_edit_->Paint(&pixp, active_text_gizmo_->GetVerticalAlignment());
+
+ p.drawPixmap(text_edit_pos_, pm);
+ }
}
// Draw action/title safe areas
@@ -622,99 +697,95 @@ NodeGizmo *ViewerDisplayWidget::TryGizmoPress(const NodeValueRow &row, const QPo
void ViewerDisplayWidget::OpenTextGizmo(TextGizmo *text, QMouseEvent *event)
{
- QTransform gizmo_transform = GenerateDisplayTransform();
-
- // Create popup container for text and toolbar
- auto popup = new QWidget(this);
- popup->setWindowFlags(Qt::Popup | Qt::FramelessWindowHint);
- popup->setAttribute(Qt::WA_DeleteOnClose);
- popup->setAttribute(Qt::WA_TranslucentBackground);
+ active_text_gizmo_ = text;
+ text_transform_ = GenerateGizmoTransform();
+ text_transform_inverted_ = text_transform_.inverted();
// Create text editor
- ViewerTextEditor *text_edit = new ViewerTextEditor(gizmo_transform.m11(), popup);
- Html::HtmlToDoc(text_edit->document(), text->GetHtml());
- text_edit->setProperty("gizmo", reinterpret_cast(text));
- connect(text_edit, &ViewerTextEditor::textChanged, this, &ViewerDisplayWidget::TextEditChanged);
- connect(text_edit, &ViewerTextEditor::destroyed, this, &ViewerDisplayWidget::TextEditDestroyed);
+ text_edit_ = new ViewerTextEditor(text_transform_.m11(), this);
+ // Set text editor's gizmo property for later use
+ text_edit_->setProperty("gizmo", reinterpret_cast(text));
+
+ // Install ourselves as event filter so we can receive the text editor's paint events
+ text_edit_->installEventFilter(this);
+
+ // Disable focus on text editor
+ text_edit_->setFocusPolicy(Qt::NoFocus);
+
+ // Disable mouse events on text editor
+ text_edit_->setAttribute(Qt::WA_TransparentForMouseEvents);
+
+ // "Show" text editor so that it throws paint events, even though its paint event is disabled
+ text_edit_->show();
+
+ // Convert HTML to Qt document
+ Html::HtmlToDoc(text_edit_->document(), text->GetHtml());
+
+ // Connect text change event to propagate back to node
+ connect(text_edit_, &ViewerTextEditor::textChanged, this, &ViewerDisplayWidget::TextEditChanged);
+
+ // Connect destroyed signal to cleanup after destruction
+ connect(text_edit_, &ViewerTextEditor::destroyed, this, &ViewerDisplayWidget::TextEditDestroyed);
+
+ // Set text editor's size to logical size
+ QRectF text_rect = text->GetRect();
+ text_edit_pos_ = text_rect.topLeft();
+ text_edit_->setGeometry(text_rect.toRect());
+
+ // Emit text gizmo activation signal
emit text->Activated();
- // Get on screen text rect (this will be the text editor's global geometry)
- QRect global_text_area = gizmo_transform.map(text->GetRect()).boundingRect().toRect();
- global_text_area = QRect(mapToGlobal(global_text_area.topLeft()), mapToGlobal(global_text_area.bottomRight()));
-
- QRect global_popup_area = global_text_area;
-
// Create toolbar
- ViewerTextEditorToolBar *toolbar = new ViewerTextEditorToolBar(popup);
- text_edit->ConnectToolBar(toolbar);
+ text_toolbar_ = new ViewerTextEditorToolBar(text_edit_);
+ text_toolbar_->setWindowFlags(Qt::Window | Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint);
+ connect(text_toolbar_, &ViewerTextEditorToolBar::VerticalAlignmentChanged, text, &TextGizmo::SetVerticalAlignment);
+ connect(text, &TextGizmo::VerticalAlignmentChanged, text_toolbar_, &ViewerTextEditorToolBar::SetVerticalAlignment);
+ text_toolbar_->SetVerticalAlignment(text->GetVerticalAlignment());
+ text_edit_->ConnectToolBar(text_toolbar_);
- // Work out which corner of the text editor to anchor the toolbar to based on screen limitations
- bool top = true;
- bool left = true;
- for (QScreen *screen : qApp->screens()) {
- // Look for screen that contains text area
- if (screen->geometry().contains(global_text_area)) {
- if (global_text_area.left() + toolbar->width() > screen->geometry().right()) {
- left = false;
- }
- if (global_text_area.top() - toolbar->height() < screen->geometry().top()) {
- top = false;
- }
- break;
- }
- }
-
-
- QPoint toolbar_pos;
-
- if (top) {
- global_popup_area.adjust(0, -toolbar->height(), 0, 0);
- toolbar_pos.setY(0);
- } else {
- global_popup_area.adjust(0, 0, 0, toolbar->height());
- toolbar_pos.setY(global_text_area.height());
- }
-
- if (toolbar->width() > global_popup_area.width()) {
- int diff = toolbar->width() - global_popup_area.width();
- if (left) {
- global_popup_area.adjust(0, 0, diff, 0);
+ QPoint toolbar_pos = mapToGlobal(text_transform_.map(text_edit_pos_).toPoint());
+ if (QScreen *screen = qApp->screenAt(toolbar_pos)) {
+ // Determine whether to anchor to the top of the rect of the bottom
+ if (toolbar_pos.y() - text_toolbar_->height() >= screen->geometry().top()) {
+ toolbar_pos.setY(toolbar_pos.y() - text_toolbar_->height());
} else {
- global_popup_area.adjust(-diff, 0, 0, 0);
+ toolbar_pos.setY(toolbar_pos.y() + text_transform_.map(text_rect).boundingRect().height());
+ }
+
+ // Clamp X
+ if (toolbar_pos.x() + text_toolbar_->width() > screen->geometry().right()) {
+ toolbar_pos.setX(screen->geometry().right() - text_toolbar_->width());
+ }
+
+ // Clamp Y
+ if (toolbar_pos.y() + text_toolbar_->height() > screen->geometry().bottom()) {
+ toolbar_pos.setY(screen->geometry().bottom() - text_toolbar_->height());
}
- toolbar_pos.setX(0);
} else {
- if (left) {
- toolbar_pos.setX(0);
- } else {
- toolbar_pos.setX(global_popup_area.width() - toolbar->width());
- }
+ // Fallback
+ toolbar_pos.setY(toolbar_pos.y() - text_toolbar_->height());
}
- toolbar->move(toolbar_pos);
+ text_toolbar_->move(toolbar_pos);
+ text_toolbar_->show();
- popup->setGeometry(global_popup_area);
+ // Allow widget to take keyboard focus
+ inner_widget()->setFocusPolicy(Qt::StrongFocus);
+ inner_widget()->setMouseTracking(true);
- text_edit->setGeometry(QRect(text_edit->mapFromGlobal(global_text_area.topLeft()), text_edit->mapFromGlobal(global_text_area.bottomRight())));
+ connect(qApp, &QApplication::focusChanged, this, &ViewerDisplayWidget::FocusChanged);
- popup->show();
-
- // Store click pos from event so we can use it later to set the initial text cursor position
- QPoint click_pos;
+ // Start text cursor where the user clicked
if (event) {
- click_pos = event->globalPos();
+ QPoint click_pos = text_transform_inverted_.map(event->pos()) - text_edit_pos_.toPoint();
+ text_edit_->setTextCursor(text_edit_->cursorForPosition(click_pos));
}
- // Ensure text edit is actually focused rather than the toolbar
- connect(toolbar, &ViewerTextEditorToolBar::FirstPaint, this, [text_edit, click_pos]{
- // Grab focus back from the toolbar
- text_edit->setFocus();
-
- // Start text cursor where the user clicked
- if (!click_pos.isNull()) {
- text_edit->setTextCursor(text_edit->cursorForPosition(text_edit->mapFromGlobal(click_pos)));
- }
+ // Grab focus back from the toolbar
+ connect(text_toolbar_, &ViewerTextEditorToolBar::FirstPaint, this, [this]{
+ Core::instance()->main_window()->activateWindow();
+ inner_widget()->setFocus();
});
}
@@ -726,10 +797,14 @@ bool ViewerDisplayWidget::OnMousePress(QMouseEvent *event)
hand_last_drag_pos_ = event->pos();
hand_dragging_ = true;
emit HandDragStarted();
- setCursor(Qt::ClosedHandCursor);
+ inner_widget()->setCursor(Qt::ClosedHandCursor);
return true;
+ } else if (text_edit_) {
+
+ return ForwardMouseEventToTextEdit(event, true);
+
} else if (event->button() == Qt::LeftButton) {
if (Core::instance()->tool() == Tool::kAdd
@@ -775,6 +850,19 @@ bool ViewerDisplayWidget::OnMouseMove(QMouseEvent *event)
return true;
+ } else if (text_edit_) {
+
+ if (event->buttons() == Qt::NoButton) {
+ QPointF mapped = text_transform_inverted_.map(event->pos()) - text_edit_pos_;
+ if (mapped.x() >= 0 && mapped.y() >= 0 && mapped.x() < text_edit_->width() && mapped.y() < text_edit_->height()) {
+ inner_widget()->setCursor(Qt::IBeamCursor);
+ } else {
+ inner_widget()->unsetCursor();
+ }
+ }
+
+ return ForwardMouseEventToTextEdit(event);
+
} else if (add_band_) {
add_band_end_ = event->pos();
@@ -832,6 +920,10 @@ bool ViewerDisplayWidget::OnMouseRelease(QMouseEvent *e)
return true;
+ } else if (text_edit_) {
+
+ return ForwardMouseEventToTextEdit(e);
+
} else if (add_band_) {
QRect band_rect = QRect(add_band_start_, add_band_end_).normalized();
@@ -865,7 +957,9 @@ bool ViewerDisplayWidget::OnMouseRelease(QMouseEvent *e)
bool ViewerDisplayWidget::OnMouseDoubleClick(QMouseEvent *event)
{
- if (event->button() == Qt::LeftButton && gizmos_) {
+ if (text_edit_) {
+ return ForwardMouseEventToTextEdit(event);
+ } else if (event->button() == Qt::LeftButton && gizmos_) {
QPointF ptr = TransformViewerSpaceToBufferSpace(event->pos());
foreach (NodeGizmo *g, gizmos_->GetGizmos()) {
if (TextGizmo *text = dynamic_cast(g)) {
@@ -880,6 +974,27 @@ bool ViewerDisplayWidget::OnMouseDoubleClick(QMouseEvent *event)
return false;
}
+bool ViewerDisplayWidget::OnKeyPress(QKeyEvent *e)
+{
+ if (text_edit_) {
+ if (e->key() == Qt::Key_Escape) {
+ CloseTextEditor();
+ return true;
+ } else {
+ return ForwardEventToTextEdit(e);
+ }
+ }
+ return false;
+}
+
+bool ViewerDisplayWidget::OnKeyRelease(QKeyEvent *e)
+{
+ if (text_edit_) {
+ return ForwardEventToTextEdit(e);
+ }
+ return false;
+}
+
void ViewerDisplayWidget::EmitColorAtCursor(QMouseEvent *e)
{
// Do this no matter what, emits signal to any pixel samplers
@@ -987,6 +1102,89 @@ void ViewerDisplayWidget::DrawSubtitleTracks()
}
}
+template
+void ViewerDisplayWidget::ForwardDragEventToTextEdit(T *e)
+{
+ // HACK: Absolutely filthy hack. We need to be able to transform the mouse coordinates for our
+ // proxied QTextEdit, however unlike QMouseEvents, Qt's drag events don't allow modifying
+ // the position after construction. Unhelpfully, Qt also explicitly forbids users creating
+ // their own drag events because they "rely on Qt's internal state". So in order to forward
+ // drag events, we defy this by creating our own events, but DON'T process them through Qt's
+ // event queue and instead just send them directly to the widget (requiring its protected
+ // drag events to be made public). That way Qt stays happy, because as far as it's
+ // concerned it's only interfacing with this widget, and the QTextEdit gets to receive
+ // transformed events. It's a terrible hack, but seems to work.
+
+ if constexpr (std::is_same_v) {
+ text_edit_->dragLeaveEvent(e);
+ } else {
+ T relay(AdjustPosByVAlign(GetVirtualPosForTextEdit(e->posF())).toPoint(),
+ e->possibleActions(),
+ e->mimeData(),
+ e->mouseButtons(),
+ e->keyboardModifiers());
+
+ if (e->type() == QEvent::DragEnter) {
+ text_edit_->dragEnterEvent(static_cast(&relay));
+ } else if (e->type() == QEvent::DragMove) {
+ text_edit_->dragMoveEvent(static_cast(&relay));
+ } else if (e->type() == QEvent::Drop) {
+ text_edit_->dropEvent(&relay);
+ }
+
+ if (relay.isAccepted()) {
+ e->accept();
+ }
+ }
+}
+
+bool ViewerDisplayWidget::ForwardMouseEventToTextEdit(QMouseEvent *event, bool check_if_outside)
+{
+ // Transform screen mouse coords to world mouse coords
+ QPointF local_pos = GetVirtualPosForTextEdit(event->localPos());
+
+ if (check_if_outside) {
+ if (local_pos.x() < 0 || local_pos.x() >= text_edit_->width() || local_pos.y() < 0 || local_pos.y() >= text_edit_->height()) {
+ CloseTextEditor();
+ return true;
+ }
+ }
+
+ local_pos = AdjustPosByVAlign(local_pos);
+
+ event->setLocalPos(local_pos);
+ return ForwardEventToTextEdit(event);
+}
+
+bool ViewerDisplayWidget::ForwardEventToTextEdit(QEvent *event)
+{
+ qApp->sendEvent(text_edit_->viewport(), event);
+ return event->isAccepted();
+}
+
+QPointF ViewerDisplayWidget::AdjustPosByVAlign(QPointF p)
+{
+ switch (active_text_gizmo_->GetVerticalAlignment()) {
+ case Qt::AlignTop:
+ // Do nothing
+ break;
+ case Qt::AlignVCenter:
+ p.setY(p.y() - text_edit_->height()/2 + text_edit_->document()->size().height()/2);
+ break;
+ case Qt::AlignBottom:
+ p.setY(p.y() - text_edit_->height() + text_edit_->document()->size().height());
+ break;
+ }
+
+ return p;
+}
+
+void ViewerDisplayWidget::CloseTextEditor()
+{
+ text_edit_->deleteLater();
+ text_edit_ = nullptr;
+}
+
void ViewerDisplayWidget::SetShowFPS(bool e)
{
show_fps_ = e;
@@ -1098,6 +1296,12 @@ void ViewerDisplayWidget::TextEditDestroyed()
{
TextGizmo *gizmo = reinterpret_cast(sender()->property("gizmo").value());
emit gizmo->Deactivated();
+ text_edit_ = nullptr;
+ text_toolbar_ = nullptr;
+ inner_widget()->setMouseTracking(false);
+ inner_widget()->setFocusPolicy(Qt::NoFocus);
+ UpdateCursor();
+ disconnect(qApp, &QApplication::focusChanged, this, &ViewerDisplayWidget::FocusChanged);
}
void ViewerDisplayWidget::SubtitlesChanged(const TimeRange &r)
@@ -1107,4 +1311,27 @@ void ViewerDisplayWidget::SubtitlesChanged(const TimeRange &r)
}
}
+void ViewerDisplayWidget::FocusChanged(QWidget *old, QWidget *now)
+{
+ if (!now) {
+ // Ignore this
+ return;
+ }
+
+ bool unfocused = true;
+
+ while (now) {
+ if (now == text_toolbar_ || now == this) {
+ unfocused = false;
+ break;
+ } else {
+ now = now->parentWidget();
+ }
+ }
+
+ if (unfocused) {
+ CloseTextEditor();
+ }
+}
+
}
diff --git a/app/widget/viewer/viewerdisplay.h b/app/widget/viewer/viewerdisplay.h
index 430f11b54..ab39a2b10 100644
--- a/app/widget/viewer/viewerdisplay.h
+++ b/app/widget/viewer/viewerdisplay.h
@@ -34,6 +34,7 @@
#include "viewerplaybacktimer.h"
#include "viewerqueue.h"
#include "viewersafemargininfo.h"
+#include "viewertexteditor.h"
#include "widget/manageddisplay/manageddisplay.h"
#include "widget/timetarget/timetarget.h"
@@ -245,6 +246,12 @@ private:
QTransform GenerateDisplayTransform();
QTransform GenerateGizmoTransform(NodeTraverser >, const TimeRange &range);
+ QTransform GenerateGizmoTransform()
+ {
+ NodeTraverser t;
+ t.SetCacheVideoParams(gizmo_params_);
+ return GenerateGizmoTransform(t, GenerateGizmoTime());
+ }
TimeRange GenerateGizmoTime()
{
@@ -261,10 +268,28 @@ private:
bool OnMouseRelease(QMouseEvent *e);
bool OnMouseDoubleClick(QMouseEvent *e);
+ bool OnKeyPress(QKeyEvent *e);
+ bool OnKeyRelease(QKeyEvent *e);
+
void EmitColorAtCursor(QMouseEvent* e);
void DrawSubtitleTracks();
+ QPointF GetVirtualPosForTextEdit(const QPointF &p)
+ {
+ return text_transform_inverted_.map(p) - text_edit_pos_;
+ }
+
+ template
+ void ForwardDragEventToTextEdit(T *event);
+
+ bool ForwardMouseEventToTextEdit(QMouseEvent *event, bool check_if_outside = false);
+ bool ForwardEventToTextEdit(QEvent *event);
+
+ QPointF AdjustPosByVAlign(QPointF p);
+
+ void CloseTextEditor();
+
/**
* @brief Internal reference to the OpenGL texture to draw. Set in SetTexture() and used in paintGL().
*/
@@ -377,6 +402,13 @@ private:
bool queue_starved_;
+ TextGizmo *active_text_gizmo_;
+ QPointF text_edit_pos_;
+ ViewerTextEditor *text_edit_;
+ ViewerTextEditorToolBar *text_toolbar_;
+ QTransform text_transform_;
+ QTransform text_transform_inverted_;
+
private slots:
void UpdateFromQueue();
@@ -385,6 +417,8 @@ private slots:
void SubtitlesChanged(const TimeRange &r);
+ void FocusChanged(QWidget *old, QWidget *now);
+
};
diff --git a/app/widget/viewer/viewertexteditor.cpp b/app/widget/viewer/viewertexteditor.cpp
index c7cc4efb6..99da81967 100644
--- a/app/widget/viewer/viewertexteditor.cpp
+++ b/app/widget/viewer/viewertexteditor.cpp
@@ -27,6 +27,7 @@
#include
#include
#include
+#include
#include "common/qtutils.h"
#include "ui/icons/icons.h"
@@ -49,6 +50,9 @@ ViewerTextEditor::ViewerTextEditor(double scale, QWidget *parent) :
document()->setDefaultStyleSheet(QStringLiteral("body { color: white; }"));
+ // Ensure cursor is visible at this scale
+ setCursorWidth(std::ceil(1.0 / scale));
+
viewport()->setAutoFillBackground(false);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
@@ -58,7 +62,7 @@ ViewerTextEditor::ViewerTextEditor(double scale, QWidget *parent) :
// Force DPI to the same one that we're using in the actual render
dpi_force_ = QImage(1, 1, QImage::Format_RGBA8888_Premultiplied);
- const int dpm = 3780 * scale;
+ const int dpm = 3780;
dpi_force_.setDotsPerMeterX(dpm);
dpi_force_.setDotsPerMeterY(dpm);
document()->documentLayout()->setPaintDevice(&dpi_force_);
@@ -93,16 +97,13 @@ void ViewerTextEditor::ConnectToolBar(ViewerTextEditorToolBar *toolbar)
toolbars_.append(toolbar);
}
-void ViewerTextEditor::paintEvent(QPaintEvent *e)
+void ViewerTextEditor::Paint(QPainter *p, Qt::Alignment valign)
{
- QPainter p(this->viewport());
-
QAbstractTextDocumentLayout::PaintContext ctx;
- QRect r = e->rect();
- if (r.isValid())
- p.setClipRect(r, Qt::IntersectClip);
- ctx.clip = r;
+ QRect clip = this->rect();
+ p->setClipRect(clip, Qt::IntersectClip);
+ ctx.clip = clip;
ctx.cursorPosition = this->textCursor().position();
@@ -124,9 +125,29 @@ void ViewerTextEditor::paintEvent(QPaintEvent *e)
ctx.selections.append(selection);
}
- if (transparent_clone_) {
- transparent_clone_->documentLayout()->draw(&p, ctx);
+ switch (valign) {
+ case Qt::AlignTop:
+ // Do nothing
+ break;
+ case Qt::AlignVCenter:
+ p->translate(0, clip.height()/2-document()->size().height()/2);
+ break;
+ case Qt::AlignBottom:
+ p->translate(0, clip.height()-document()->size().height());
+ break;
}
+
+ const bool use_transparent_clone = true;
+ if (transparent_clone_ && use_transparent_clone) {
+ transparent_clone_->documentLayout()->draw(p, ctx);
+ } else {
+ document()->documentLayout()->draw(p, ctx);
+ }
+}
+
+void ViewerTextEditor::paintEvent(QPaintEvent *e)
+{
+ // Disable painting
}
void ViewerTextEditor::UpdateToolBar(ViewerTextEditorToolBar *toolbar, const QTextCharFormat &f, const QTextBlockFormat &b, Qt::Alignment alignment)
@@ -292,6 +313,7 @@ void ViewerTextEditor::DocumentChanged()
delete transparent_clone_;
transparent_clone_ = document()->clone(this);
transparent_clone_->documentLayout()->setPaintDevice(&dpi_force_);
+ transparent_clone_->documentLayout()->setProperty("cursorWidth", document()->documentLayout()->property("cursorWidth"));
QTextCursor cursor(transparent_clone_);
cursor.select(QTextCursor::Document);
@@ -304,132 +326,159 @@ void ViewerTextEditor::DocumentChanged()
ViewerTextEditorToolBar::ViewerTextEditorToolBar(QWidget *parent) :
QWidget(parent),
painted_(false),
- drag_enabled_(false)
+ drag_enabled_(true)
{
QVBoxLayout *outer_layout = new QVBoxLayout(this);
- outer_layout->setSpacing(0);
- QHBoxLayout *basic_layout = new QHBoxLayout();
- outer_layout->addLayout(basic_layout);
+ const int advanced_slider_width = QtUtils::QFontMetricsWidth(fontMetrics(), QStringLiteral("9999.9%"));
- int advanced_slider_width = QtUtils::QFontMetricsWidth(fontMetrics(), QStringLiteral("9999.9%"));
+ {
+ QHBoxLayout *row_layout = new QHBoxLayout();
+ row_layout->setSpacing(0);
+ outer_layout->addLayout(row_layout);
- font_combo_ = new QFontComboBox();
- connect(font_combo_, &QFontComboBox::currentTextChanged, this, &ViewerTextEditorToolBar::UpdateFontStyleListAndEmitFamilyChanged);
- basic_layout->addWidget(font_combo_);
+ font_combo_ = new QFontComboBox();
+ connect(font_combo_, &QFontComboBox::currentTextChanged, this, &ViewerTextEditorToolBar::UpdateFontStyleListAndEmitFamilyChanged);
+ row_layout->addWidget(font_combo_);
- font_sz_slider_ = new FloatSlider();
- font_sz_slider_->SetMinimum(0.1);
- font_sz_slider_->SetMaximum(9999.9);
- font_sz_slider_->SetDecimalPlaces(1);
- font_sz_slider_->SetAlignment(Qt::AlignCenter);
- font_sz_slider_->setFixedWidth(advanced_slider_width);
- connect(font_sz_slider_, &FloatSlider::ValueChanged, this, &ViewerTextEditorToolBar::SizeChanged);
- font_sz_slider_->SetLadderElementCount(2);
- basic_layout->addWidget(font_sz_slider_);
+ font_sz_slider_ = new FloatSlider();
+ font_sz_slider_->SetMinimum(0.1);
+ font_sz_slider_->SetMaximum(9999.9);
+ font_sz_slider_->SetDecimalPlaces(1);
+ font_sz_slider_->SetAlignment(Qt::AlignCenter);
+ font_sz_slider_->setFixedWidth(advanced_slider_width);
+ connect(font_sz_slider_, &FloatSlider::ValueChanged, this, &ViewerTextEditorToolBar::SizeChanged);
+ font_sz_slider_->SetLadderElementCount(2);
+ row_layout->addWidget(font_sz_slider_);
- style_combo_ = new QComboBox();
- connect(style_combo_, &QComboBox::currentTextChanged, this, &ViewerTextEditorToolBar::StyleChanged);
- basic_layout->addWidget(style_combo_);
+ style_combo_ = new QComboBox();
+ connect(style_combo_, &QComboBox::currentTextChanged, this, &ViewerTextEditorToolBar::StyleChanged);
+ row_layout->addWidget(style_combo_);
- underline_btn_ = new QPushButton();
- connect(underline_btn_, &QPushButton::clicked, this, &ViewerTextEditorToolBar::UnderlineChanged);
- underline_btn_->setCheckable(true);
- underline_btn_->setIcon(icon::TextUnderline);
- basic_layout->addWidget(underline_btn_);
+ underline_btn_ = new QPushButton();
+ connect(underline_btn_, &QPushButton::clicked, this, &ViewerTextEditorToolBar::UnderlineChanged);
+ underline_btn_->setCheckable(true);
+ underline_btn_->setIcon(icon::TextUnderline);
+ row_layout->addWidget(underline_btn_);
- strikethrough_btn_ = new QPushButton();
- connect(strikethrough_btn_, &QPushButton::clicked, this, &ViewerTextEditorToolBar::StrikethroughChanged);
- strikethrough_btn_->setCheckable(true);
- strikethrough_btn_->setIcon(icon::TextStrikethrough);
- basic_layout->addWidget(strikethrough_btn_);
+ strikethrough_btn_ = new QPushButton();
+ connect(strikethrough_btn_, &QPushButton::clicked, this, &ViewerTextEditorToolBar::StrikethroughChanged);
+ strikethrough_btn_->setCheckable(true);
+ strikethrough_btn_->setIcon(icon::TextStrikethrough);
+ row_layout->addWidget(strikethrough_btn_);
- basic_layout->addWidget(QtUtils::CreateVerticalLine());
+ AddSpacer(row_layout);
- align_left_btn_ = new QPushButton();
- align_left_btn_->setCheckable(true);
- align_left_btn_->setIcon(icon::TextAlignLeft);
- connect(align_left_btn_, &QPushButton::clicked, this, [this]{emit AlignmentChanged(Qt::AlignLeft);});
- basic_layout->addWidget(align_left_btn_);
+ color_btn_ = new QPushButton();
+ color_btn_->setAutoFillBackground(true);
+ connect(color_btn_, &QPushButton::clicked, this, [this]{
+ QColor c = color_btn_->property("color").value();
- align_center_btn_ = new QPushButton();
- align_center_btn_->setCheckable(true);
- align_center_btn_->setIcon(icon::TextAlignCenter);
- connect(align_center_btn_, &QPushButton::clicked, this, [this]{emit AlignmentChanged(Qt::AlignCenter);});
- basic_layout->addWidget(align_center_btn_);
+ QColorDialog cd(c, this);
+ if (cd.exec() == QDialog::Accepted) {
+ c = cd.selectedColor();
+ SetColor(c);
+ emit ColorChanged(c);
+ }
+ });
+ row_layout->addWidget(color_btn_);
- align_right_btn_ = new QPushButton();
- align_right_btn_->setCheckable(true);
- align_right_btn_->setIcon(icon::TextAlignRight);
- connect(align_right_btn_, &QPushButton::clicked, this, [this]{emit AlignmentChanged(Qt::AlignRight);});
- basic_layout->addWidget(align_right_btn_);
+ row_layout->addStretch();
+ }
- align_justify_btn_ = new QPushButton();
- align_justify_btn_->setCheckable(true);
- align_justify_btn_->setIcon(icon::TextAlignJustify);
- connect(align_justify_btn_, &QPushButton::clicked, this, [this]{emit AlignmentChanged(Qt::AlignJustify);});
- basic_layout->addWidget(align_justify_btn_);
+ {
+ QHBoxLayout *row_layout = new QHBoxLayout();
+ row_layout->setSpacing(0);
+ outer_layout->addLayout(row_layout);
- basic_layout->addWidget(QtUtils::CreateVerticalLine());
+ align_left_btn_ = new QPushButton();
+ align_left_btn_->setCheckable(true);
+ align_left_btn_->setIcon(icon::TextAlignLeft);
+ connect(align_left_btn_, &QPushButton::clicked, this, [this]{emit AlignmentChanged(Qt::AlignLeft);});
+ row_layout->addWidget(align_left_btn_);
- color_btn_ = new QPushButton();
- color_btn_->setAutoFillBackground(true);
- connect(color_btn_, &QPushButton::clicked, this, [this]{
- QColor c = color_btn_->property("color").value();
+ align_center_btn_ = new QPushButton();
+ align_center_btn_->setCheckable(true);
+ align_center_btn_->setIcon(icon::TextAlignCenter);
+ connect(align_center_btn_, &QPushButton::clicked, this, [this]{emit AlignmentChanged(Qt::AlignHCenter);});
+ row_layout->addWidget(align_center_btn_);
- QColorDialog cd(c, this);
- if (cd.exec() == QDialog::Accepted) {
- c = cd.selectedColor();
- SetColor(c);
- emit ColorChanged(c);
- }
- });
- basic_layout->addWidget(color_btn_);
+ align_right_btn_ = new QPushButton();
+ align_right_btn_->setCheckable(true);
+ align_right_btn_->setIcon(icon::TextAlignRight);
+ connect(align_right_btn_, &QPushButton::clicked, this, [this]{emit AlignmentChanged(Qt::AlignRight);});
+ row_layout->addWidget(align_right_btn_);
- basic_layout->addStretch();
+ align_justify_btn_ = new QPushButton();
+ align_justify_btn_->setCheckable(true);
+ align_justify_btn_->setIcon(icon::TextAlignJustify);
+ connect(align_justify_btn_, &QPushButton::clicked, this, [this]{emit AlignmentChanged(Qt::AlignJustify);});
+ row_layout->addWidget(align_justify_btn_);
- QHBoxLayout *advanced_layout = new QHBoxLayout();
- outer_layout->addLayout(advanced_layout);
+ AddSpacer(row_layout);
- advanced_layout->addWidget(new QLabel(tr("Stretch: "))); // FIXME: Procure icon
+ align_top_btn_ = new QPushButton();
+ align_top_btn_->setCheckable(true);
+ align_top_btn_->setIcon(icon::TextAlignTop);
+ connect(align_top_btn_, &QPushButton::clicked, this, [this]{emit VerticalAlignmentChanged(Qt::AlignTop);});
+ row_layout->addWidget(align_top_btn_);
- stretch_slider_ = new IntegerSlider();
- stretch_slider_->SetMinimum(0);
- stretch_slider_->SetDefaultValue(100);
- stretch_slider_->setFixedWidth(advanced_slider_width);
- stretch_slider_->SetFormat(tr("%1%"));
- connect(stretch_slider_, &IntegerSlider::ValueChanged, this, &ViewerTextEditorToolBar::StretchChanged);
- advanced_layout->addWidget(stretch_slider_);
+ align_middle_btn_ = new QPushButton();
+ align_middle_btn_->setCheckable(true);
+ align_middle_btn_->setIcon(icon::TextAlignMiddle);
+ connect(align_middle_btn_, &QPushButton::clicked, this, [this]{emit VerticalAlignmentChanged(Qt::AlignVCenter);});
+ row_layout->addWidget(align_middle_btn_);
- advanced_layout->addWidget(new QLabel(tr("Kerning: "))); // FIXME: Procure icon
+ align_bottom_btn_ = new QPushButton();
+ align_bottom_btn_->setCheckable(true);
+ align_bottom_btn_->setIcon(icon::TextAlignBottom);
+ connect(align_bottom_btn_, &QPushButton::clicked, this, [this]{emit VerticalAlignmentChanged(Qt::AlignBottom);});
+ row_layout->addWidget(align_bottom_btn_);
- kerning_slider_ = new FloatSlider();
- kerning_slider_->SetMinimum(0);
- kerning_slider_->SetDefaultValue(100);
- kerning_slider_->SetDecimalPlaces(1);
- kerning_slider_->setFixedWidth(advanced_slider_width);
- kerning_slider_->SetFormat(tr("%1%"));
- connect(kerning_slider_, &FloatSlider::ValueChanged, this, &ViewerTextEditorToolBar::KerningChanged);
- advanced_layout->addWidget(kerning_slider_);
+ AddSpacer(row_layout);
- advanced_layout->addWidget(new QLabel(tr("Line Height: "))); // FIXME: Procure icon
+ small_caps_btn_ = new QPushButton();
+ small_caps_btn_->setIcon(icon::TextSmallCaps);
+ small_caps_btn_->setCheckable(true);
+ connect(small_caps_btn_, &QPushButton::clicked, this, &ViewerTextEditorToolBar::SmallCapsChanged);
+ row_layout->addWidget(small_caps_btn_);
- line_height_slider_ = new FloatSlider();
- line_height_slider_->SetMinimum(0);
- line_height_slider_->SetDefaultValue(100);
- line_height_slider_->SetDecimalPlaces(1);
- line_height_slider_->setFixedWidth(advanced_slider_width);
- line_height_slider_->SetFormat(tr("%1%"));
- connect(line_height_slider_, &FloatSlider::ValueChanged, this, &ViewerTextEditorToolBar::LineHeightChanged);
- advanced_layout->addWidget(line_height_slider_);
+ AddSpacer(row_layout);
- small_caps_btn_ = new QPushButton();
- small_caps_btn_->setIcon(icon::TextSmallCaps);
- small_caps_btn_->setCheckable(true);
- connect(small_caps_btn_, &QPushButton::clicked, this, &ViewerTextEditorToolBar::SmallCapsChanged);
- advanced_layout->addWidget(small_caps_btn_);
+ row_layout->addWidget(new QLabel(tr("Stretch: "))); // FIXME: Procure icon
- advanced_layout->addStretch();
+ stretch_slider_ = new IntegerSlider();
+ stretch_slider_->SetMinimum(0);
+ stretch_slider_->SetDefaultValue(100);
+ stretch_slider_->setFixedWidth(advanced_slider_width);
+ stretch_slider_->SetFormat(tr("%1%"));
+ connect(stretch_slider_, &IntegerSlider::ValueChanged, this, &ViewerTextEditorToolBar::StretchChanged);
+ row_layout->addWidget(stretch_slider_);
+
+ row_layout->addWidget(new QLabel(tr("Kerning: "))); // FIXME: Procure icon
+
+ kerning_slider_ = new FloatSlider();
+ kerning_slider_->SetMinimum(0);
+ kerning_slider_->SetDefaultValue(100);
+ kerning_slider_->SetDecimalPlaces(1);
+ kerning_slider_->setFixedWidth(advanced_slider_width);
+ kerning_slider_->SetFormat(tr("%1%"));
+ connect(kerning_slider_, &FloatSlider::ValueChanged, this, &ViewerTextEditorToolBar::KerningChanged);
+ row_layout->addWidget(kerning_slider_);
+
+ row_layout->addWidget(new QLabel(tr("Line Height: "))); // FIXME: Procure icon
+
+ line_height_slider_ = new FloatSlider();
+ line_height_slider_->SetMinimum(0);
+ line_height_slider_->SetDefaultValue(100);
+ line_height_slider_->SetDecimalPlaces(1);
+ line_height_slider_->setFixedWidth(advanced_slider_width);
+ line_height_slider_->SetFormat(tr("%1%"));
+ connect(line_height_slider_, &FloatSlider::ValueChanged, this, &ViewerTextEditorToolBar::LineHeightChanged);
+ row_layout->addWidget(line_height_slider_);
+
+ row_layout->addStretch();
+ }
setAutoFillBackground(true);
@@ -439,11 +488,18 @@ ViewerTextEditorToolBar::ViewerTextEditorToolBar(QWidget *parent) :
void ViewerTextEditorToolBar::SetAlignment(Qt::Alignment a)
{
align_left_btn_->setChecked(a == Qt::AlignLeft);
- align_center_btn_->setChecked(a == Qt::AlignCenter);
+ align_center_btn_->setChecked(a == Qt::AlignHCenter);
align_right_btn_->setChecked(a == Qt::AlignRight);
align_justify_btn_->setChecked(a == Qt::AlignJustify);
}
+void ViewerTextEditorToolBar::SetVerticalAlignment(Qt::Alignment a)
+{
+ align_top_btn_->setChecked(a == Qt::AlignTop);
+ align_middle_btn_->setChecked(a == Qt::AlignVCenter);
+ align_bottom_btn_->setChecked(a == Qt::AlignBottom);
+}
+
void ViewerTextEditorToolBar::SetColor(const QColor &c)
{
color_btn_->setProperty("color", c);
@@ -464,6 +520,20 @@ void ViewerTextEditorToolBar::paintEvent(QPaintEvent *event)
QWidget::paintEvent(event);
}
+void ViewerTextEditorToolBar::AddSpacer(QLayout *l)
+{
+ const int spacing = this->fontMetrics().height()/4;
+ QWidget *a = new QWidget();
+ a->setFixedSize(spacing, 1);
+ l->addWidget(a);
+
+ l->addWidget(QtUtils::CreateVerticalLine());
+
+ QWidget *b = new QWidget();
+ b->setFixedSize(spacing, 1);
+ l->addWidget(b);
+}
+
void ViewerTextEditorToolBar::UpdateFontStyleList(const QString &family)
{
QString temp = style_combo_->currentText();
diff --git a/app/widget/viewer/viewertexteditor.h b/app/widget/viewer/viewertexteditor.h
index 33b640b76..8bef901b9 100644
--- a/app/widget/viewer/viewertexteditor.h
+++ b/app/widget/viewer/viewertexteditor.h
@@ -68,6 +68,7 @@ public slots:
void SetUnderline(bool e) { underline_btn_->setChecked(e); }
void SetStrikethrough(bool e) { strikethrough_btn_->setChecked(e); }
void SetAlignment(Qt::Alignment a);
+ void SetVerticalAlignment(Qt::Alignment a);
void SetColor(const QColor &c);
void SetSmallCaps(bool e) { small_caps_btn_->setChecked(e); }
void SetStretch(int i) { stretch_slider_->SetValue(i); }
@@ -81,6 +82,7 @@ signals:
void UnderlineChanged(bool e);
void StrikethroughChanged(bool e);
void AlignmentChanged(Qt::Alignment alignment);
+ void VerticalAlignmentChanged(Qt::Alignment alignment);
void ColorChanged(const QColor &c);
void SmallCapsChanged(bool e);
void StretchChanged(int i);
@@ -101,6 +103,8 @@ protected:
virtual void paintEvent(QPaintEvent *event) override;
private:
+ void AddSpacer(QLayout *l);
+
QPoint drag_anchor_;
QFontComboBox *font_combo_;
@@ -117,6 +121,10 @@ private:
QPushButton *align_right_btn_;
QPushButton *align_justify_btn_;
+ QPushButton *align_top_btn_;
+ QPushButton *align_middle_btn_;
+ QPushButton *align_bottom_btn_;
+
IntegerSlider *stretch_slider_;
FloatSlider *kerning_slider_;
FloatSlider *line_height_slider_;
@@ -145,6 +153,13 @@ public:
void SetListenToFocusEvents(bool e) { listen_to_focus_events_ = e; }
+ void Paint(QPainter *p, Qt::Alignment valign);
+
+ virtual void dragEnterEvent(QDragEnterEvent *e) override { return QTextEdit::dragEnterEvent(e); }
+ virtual void dragMoveEvent(QDragMoveEvent *e) override { return QTextEdit::dragMoveEvent(e); }
+ virtual void dragLeaveEvent(QDragLeaveEvent *e) override { return QTextEdit::dragLeaveEvent(e); }
+ virtual void dropEvent(QDropEvent *e) override { return QTextEdit::dropEvent(e); }
+
protected:
virtual void paintEvent(QPaintEvent *event) override;
diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp
index d80e3fad5..5b76ed1b0 100644
--- a/app/window/mainwindow/mainwindow.cpp
+++ b/app/window/mainwindow/mainwindow.cpp
@@ -314,9 +314,18 @@ void MainWindow::ToggleMaximizedPanel()
}
}
} else {
+ // Preserve currently focused panel
+ auto currently_focused_panel = PanelManager::instance()->CurrentlyFocused(false);
+
// Assume we are currently maximized, restore the state
+ PanelManager::instance()->SetSuppressChangedSignal(true);
restoreState(premaximized_state_);
premaximized_state_.clear();
+
+ currently_focused_panel->raise();
+ currently_focused_panel->setFocus();
+
+ PanelManager::instance()->SetSuppressChangedSignal(false);
}
}