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); } }