/* * Copyright (C) 2015 Open Source Robotics Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ #include "gazebo/common/Console.hh" #include "gazebo/common/Time.hh" #include "gazebo/transport/Node.hh" #include "gazebo/gui/Actions.hh" #include "gazebo/gui/LogPlayWidget.hh" #include "gazebo/gui/LogPlayWidgetPrivate.hh" using namespace gazebo; using namespace gui; ///////////////////////////////////////////////// LogPlayWidget::LogPlayWidget(QWidget *_parent) : QWidget(_parent), dataPtr(new LogPlayWidgetPrivate) { this->setObjectName("logPlayWidget"); this->dataPtr->timePanel = dynamic_cast(_parent); QSize bigSize(70, 70); QSize bigIconSize(40, 40); // Empty space on the left QWidget *leftSpacer = new QWidget(); leftSpacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // Play this->dataPtr->playButton = new QToolButton(this); this->SetupButton(this->dataPtr->playButton, ":/images/log_play.png", false); connect(this->dataPtr->playButton, SIGNAL(clicked()), this, SLOT(OnPlay())); connect(this, SIGNAL(ShowPlay()), this->dataPtr->playButton, SLOT(show())); connect(this, SIGNAL(HidePlay()), this->dataPtr->playButton, SLOT(hide())); // Pause this->dataPtr->pauseButton = new QToolButton(this); this->SetupButton(this->dataPtr->pauseButton, ":/images/log_pause.png", false); connect(this->dataPtr->pauseButton, SIGNAL(clicked()), this, SLOT(OnPause())); connect(this, SIGNAL(ShowPause()), this->dataPtr->pauseButton, SLOT(show())); connect(this, SIGNAL(HidePause()), this->dataPtr->pauseButton, SLOT(hide())); // Step forward this->dataPtr->stepForwardButton = new QToolButton(this); this->SetupButton(this->dataPtr->stepForwardButton, ":/images/log_step_forward.png", true); connect(this->dataPtr->stepForwardButton, SIGNAL(clicked()), this, SLOT(OnStepForward())); // Step back this->dataPtr->stepBackButton = new QToolButton(this); this->SetupButton(this->dataPtr->stepBackButton, ":/images/log_step_back.png", true); connect(this->dataPtr->stepBackButton, SIGNAL(clicked()), this, SLOT(OnStepBack())); // Rewind this->dataPtr->rewindButton = new QToolButton(this); this->SetupButton(this->dataPtr->rewindButton, ":/images/log_rewind.png", true); connect(this->dataPtr->rewindButton, SIGNAL(clicked()), this, SLOT(OnRewind())); // Forward this->dataPtr->forwardButton = new QToolButton(this); this->SetupButton(this->dataPtr->forwardButton, ":/images/log_forward.png", true); connect(this->dataPtr->forwardButton, SIGNAL(clicked()), this, SLOT(OnForward())); // Step size QLabel *stepLabel = new QLabel("Step: "); this->dataPtr->stepSpin = new QSpinBox(); this->dataPtr->stepSpin->setMaximumWidth(30); this->dataPtr->stepSpin->setValue(1); this->dataPtr->stepSpin->setRange(1, 10000); QHBoxLayout *stepLayout = new QHBoxLayout(); stepLayout->addWidget(stepLabel); stepLayout->addWidget(this->dataPtr->stepSpin); stepLayout->setAlignment(stepLabel, Qt::AlignRight); stepLayout->setAlignment(this->dataPtr->stepSpin, Qt::AlignLeft); // Play layout QHBoxLayout *playLayout = new QHBoxLayout(); playLayout->addWidget(this->dataPtr->rewindButton); playLayout->addWidget(this->dataPtr->stepBackButton); playLayout->addWidget(this->dataPtr->playButton); playLayout->addWidget(this->dataPtr->pauseButton); playLayout->addWidget(this->dataPtr->stepForwardButton); playLayout->addWidget(this->dataPtr->forwardButton); // Controls layout QVBoxLayout *controlsLayout = new QVBoxLayout(); controlsLayout->addLayout(playLayout); controlsLayout->addLayout(stepLayout); // View this->dataPtr->view = new LogPlayView(this); connect(this, SIGNAL(SetCurrentTime(common::Time)), this->dataPtr->view, SLOT(SetCurrentTime(common::Time))); connect(this, SIGNAL(SetStartTime(common::Time)), this->dataPtr->view, SLOT(SetStartTime(common::Time))); connect(this, SIGNAL(SetEndTime(common::Time)), this->dataPtr->view, SLOT(SetEndTime(common::Time))); // Current time fields // Day edit this->dataPtr->currentDayEdit = new QLineEdit(); this->dataPtr->currentDayEdit->setAlignment(Qt::AlignRight); this->dataPtr->currentDayEdit->setValidator(new QIntValidator(0, 99)); this->dataPtr->currentDayEdit->setMaximumWidth(25); this->dataPtr->currentDayEdit->setText("00"); connect(this->dataPtr->currentDayEdit, SIGNAL(editingFinished()), this, SLOT(OnCurrentTime())); connect(this, SIGNAL(SetCurrentDays(const QString &)), this->dataPtr->currentDayEdit, SLOT(setText(const QString &))); // Hour edit this->dataPtr->currentHourEdit = new QLineEdit(); this->dataPtr->currentHourEdit->setAlignment(Qt::AlignRight); this->dataPtr->currentHourEdit->setValidator(new QIntValidator(0, 23)); this->dataPtr->currentHourEdit->setMaximumWidth(25); this->dataPtr->currentHourEdit->setText("00"); connect(this->dataPtr->currentHourEdit, SIGNAL(editingFinished()), this, SLOT(OnCurrentTime())); connect(this, SIGNAL(SetCurrentHours(const QString &)), this->dataPtr->currentHourEdit, SLOT(setText(const QString &))); // Minute edit this->dataPtr->currentMinuteEdit = new QLineEdit(); this->dataPtr->currentMinuteEdit->setAlignment(Qt::AlignRight); this->dataPtr->currentMinuteEdit->setValidator(new QIntValidator(0, 59)); this->dataPtr->currentMinuteEdit->setMaximumWidth(25); this->dataPtr->currentMinuteEdit->setText("00"); connect(this->dataPtr->currentMinuteEdit, SIGNAL(editingFinished()), this, SLOT(OnCurrentTime())); connect(this, SIGNAL(SetCurrentMinutes(const QString &)), this->dataPtr->currentMinuteEdit, SLOT(setText(const QString &))); // Second edit this->dataPtr->currentSecondEdit = new QLineEdit(); this->dataPtr->currentSecondEdit->setAlignment(Qt::AlignRight); this->dataPtr->currentSecondEdit->setValidator( new QDoubleValidator(0, 59.999, 3)); this->dataPtr->currentSecondEdit->setMaximumWidth(60); this->dataPtr->currentSecondEdit->setText("00.000"); connect(this->dataPtr->currentSecondEdit, SIGNAL(editingFinished()), this, SLOT(OnCurrentTime())); connect(this, SIGNAL(SetCurrentSeconds(const QString &)), this->dataPtr->currentSecondEdit, SLOT(setText(const QString &))); // Labels this->dataPtr->dayLabel = new QLabel("d"); this->dataPtr->hourLabel = new QLabel("h"); this->dataPtr->hourSeparator = new QLabel(":"); std::vector currentTimeLabels; currentTimeLabels.push_back(this->dataPtr->dayLabel); currentTimeLabels.push_back(this->dataPtr->hourLabel); currentTimeLabels.push_back(new QLabel("min")); currentTimeLabels.push_back(new QLabel("s")); // End time QLabel *endTime = new QLabel(); endTime->setMaximumHeight(10); connect(this, SIGNAL(SetEndTime(const QString &)), endTime, SLOT(setText(const QString &))); auto timeLayout = new QGridLayout(); timeLayout->setContentsMargins(0, 0, 0, 0); timeLayout->addWidget(this->dataPtr->currentDayEdit, 0, 0); timeLayout->addWidget(this->dataPtr->currentHourEdit, 0, 1); timeLayout->addWidget(this->dataPtr->hourSeparator, 0, 2); timeLayout->addWidget(this->dataPtr->currentMinuteEdit, 0, 3); timeLayout->addWidget(new QLabel(":"), 0, 4); timeLayout->addWidget(this->dataPtr->currentSecondEdit, 0, 5); timeLayout->addWidget(currentTimeLabels[0], 1, 0); timeLayout->addWidget(currentTimeLabels[1], 1, 1); timeLayout->addWidget(currentTimeLabels[2], 1, 3); timeLayout->addWidget(currentTimeLabels[3], 1, 5); timeLayout->addWidget(endTime, 2, 0, 1, 6); for (auto label : currentTimeLabels) { label->setStyleSheet("QLabel{font-size:11px; color:#444444}"); label->setMaximumHeight(10); timeLayout->setAlignment(label, Qt::AlignRight); } timeLayout->setAlignment(endTime, Qt::AlignRight); auto timeWidget = new QWidget(); timeWidget->setLayout(timeLayout); timeWidget->setMaximumHeight(50); timeWidget->setMaximumWidth(200); // Empty space on the right QWidget *rightSpacer = new QWidget(); rightSpacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // Main layout QHBoxLayout *mainLayout = new QHBoxLayout; mainLayout->addWidget(leftSpacer); mainLayout->addLayout(controlsLayout); mainLayout->addWidget(this->dataPtr->view); mainLayout->addWidget(timeWidget); mainLayout->addWidget(rightSpacer); this->setLayout(mainLayout); mainLayout->setAlignment(controlsLayout, Qt::AlignRight); mainLayout->setAlignment(timeLayout, Qt::AlignLeft); this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); this->layout()->setContentsMargins(0, 0, 0, 0); // Transport this->dataPtr->node = transport::NodePtr(new transport::Node()); this->dataPtr->node->TryInit(common::Time::Maximum()); this->dataPtr->logPlaybackControlPub = this->dataPtr->node-> Advertise("~/playback_control"); } ///////////////////////////////////////////////// LogPlayWidget::~LogPlayWidget() { delete this->dataPtr; this->dataPtr = NULL; } ///////////////////////////////////////////////// void LogPlayWidget::SetupButton(QToolButton *_button, QString _icon, bool _isSmall) { QPixmap pixmap(_icon); QPixmap disabledPixmap(pixmap.size()); disabledPixmap.fill(Qt::transparent); QPainter p(&disabledPixmap); p.setOpacity(0.4); p.drawPixmap(0, 0, pixmap); QIcon icon(pixmap); icon.addPixmap(disabledPixmap, QIcon::Disabled); QSize buttonSize; QSize iconSize; if (_isSmall) { buttonSize = QSize(50, 50); iconSize = QSize(30, 30); } else { buttonSize = QSize(70, 70); iconSize = QSize(40, 40); } _button->setFixedSize(buttonSize); _button->setCheckable(false); _button->setEnabled(false); _button->setIcon(icon); _button->setIconSize(iconSize); _button->setStyleSheet( QString("border-radius: %1px").arg(buttonSize.width()/2-2)); } ///////////////////////////////////////////////// bool LogPlayWidget::IsPaused() const { return this->dataPtr->paused; } ///////////////////////////////////////////////// void LogPlayWidget::SetPaused(const bool _paused) { this->dataPtr->paused = _paused; if (_paused) { emit ShowPlay(); emit HidePause(); // Check if there are pending steps and publish now that it's paused if (this->dataPtr->pendingStep != 0) { this->PublishMultistep(this->dataPtr->pendingStep); this->dataPtr->pendingStep = 0; } } else { emit HidePlay(); emit ShowPause(); } } ///////////////////////////////////////////////// void LogPlayWidget::OnPlay() { msgs::LogPlaybackControl msg; msg.set_pause(false); this->dataPtr->logPlaybackControlPub->Publish(msg); } ///////////////////////////////////////////////// void LogPlayWidget::OnPause() { msgs::LogPlaybackControl msg; msg.set_pause(true); this->dataPtr->logPlaybackControlPub->Publish(msg); } ///////////////////////////////////////////////// void LogPlayWidget::OnStepForward() { if (this->dataPtr->paused) { this->PublishMultistep(this->dataPtr->stepSpin->value()); } // Only step after it's paused, to sync with server else { this->OnPause(); this->dataPtr->pendingStep += this->dataPtr->stepSpin->value(); } } ///////////////////////////////////////////////// void LogPlayWidget::OnStepBack() { if (this->dataPtr->paused) { this->PublishMultistep(-this->dataPtr->stepSpin->value()); } // Only step after it's paused, to sync with server else { this->OnPause(); this->dataPtr->pendingStep += -this->dataPtr->stepSpin->value(); } } ///////////////////////////////////////////////// void LogPlayWidget::OnRewind() { msgs::LogPlaybackControl msg; msg.set_rewind(true); this->dataPtr->logPlaybackControlPub->Publish(msg); } ///////////////////////////////////////////////// void LogPlayWidget::OnForward() { msgs::LogPlaybackControl msg; msg.set_forward(true); this->dataPtr->logPlaybackControlPub->Publish(msg); } ///////////////////////////////////////////////// void LogPlayWidget::OnSeek(const common::Time &_time) { msgs::LogPlaybackControl msg; msgs::Set(msg.mutable_seek(), _time); this->dataPtr->logPlaybackControlPub->Publish(msg); } ///////////////////////////////////////////////// void LogPlayWidget::OnCurrentTime() { auto day = this->dataPtr->currentDayEdit->text().toInt(); auto hour = this->dataPtr->currentHourEdit->text().toInt(); auto min = this->dataPtr->currentMinuteEdit->text().toInt(); auto sec = this->dataPtr->currentSecondEdit->text().toDouble(); auto time = common::Time(24*60*60*day + 60*60*hour + 60*min + sec); this->OnSeek(time); this->dataPtr->currentDayEdit->clearFocus(); this->dataPtr->currentHourEdit->clearFocus(); this->dataPtr->currentMinuteEdit->clearFocus(); this->dataPtr->currentSecondEdit->clearFocus(); } ///////////////////////////////////////////////// void LogPlayWidget::EmitSetCurrentTime(const common::Time &_time) { // Make sure it's within limits common::Time time = std::max(_time, this->dataPtr->startTime); if (this->dataPtr->endTime != common::Time::Zero) time = std::min(time, this->dataPtr->endTime); this->dataPtr->currentTime = time; // Enable/disable buttons this->dataPtr->stepBackButton->setEnabled(time != this->dataPtr->startTime); this->dataPtr->rewindButton->setEnabled(time != this->dataPtr->startTime); this->dataPtr->stepForwardButton->setEnabled(time != this->dataPtr->endTime); this->dataPtr->forwardButton->setEnabled(time != this->dataPtr->endTime); this->dataPtr->pauseButton->setEnabled(time != this->dataPtr->endTime); this->dataPtr->playButton->setEnabled(time != this->dataPtr->endTime); // Update current time line edit if the user is not editing it if (!(this->dataPtr->currentDayEdit->hasFocus() || this->dataPtr->currentHourEdit->hasFocus() || this->dataPtr->currentMinuteEdit->hasFocus() || this->dataPtr->currentSecondEdit->hasFocus())) { // milliseconds double msec = time.nsec / common::Time::nsInMs; // seconds double s = time.sec; int seconds = msec / 1000; msec -= seconds * 1000; s += seconds; // days unsigned int day = s / 86400; s -= day * 86400; // hours unsigned int hour = s / 3600; s -= hour * 3600; // minutes unsigned int min = s / 60; s -= min * 60; this->SetCurrentDays(QString::number(day)); this->SetCurrentHours(QString::number(hour)); this->SetCurrentMinutes(QString::number(min)); this->SetCurrentSeconds(QString::number(s + msec / 1000.0, 'f', 3)); } // Update current time item in view this->SetCurrentTime(time); } ///////////////////////////////////////////////// void LogPlayWidget::EmitSetStartTime(const common::Time &_time) { if (this->dataPtr->endTime != common::Time::Zero && _time >= this->dataPtr->endTime) { gzwarn << "Start time [" << _time << "] after end time [" << this->dataPtr->endTime << "]. Not updating." << std::endl; return; } this->dataPtr->startTime = _time; // Update start time in view this->SetStartTime(this->dataPtr->startTime); // Keep current time within bounds if (this->dataPtr->startTime > this->dataPtr->currentTime) this->EmitSetCurrentTime(this->dataPtr->startTime); } ///////////////////////////////////////////////// void LogPlayWidget::EmitSetEndTime(const common::Time &_time) { if (_time <= this->dataPtr->startTime) { gzwarn << "End time [" << _time << "] before start time [" << this->dataPtr->startTime << "]. Not updating." << std::endl; return; } this->dataPtr->endTime = _time; // Use shorter string if less than 1h if (_time < common::Time::Hour) this->dataPtr->lessThan1h = true; // Update end time label std::string timeString; if (this->dataPtr->lessThan1h) { timeString = _time.FormattedString(common::Time::FormatOption::MINUTES); } else { timeString = _time.FormattedString(); } timeString = "/ " + timeString; this->SetEndTime(QString::fromStdString(timeString)); // Update end time in view this->SetEndTime(_time); // Keep current time within bounds if (this->dataPtr->endTime < this->dataPtr->currentTime) this->EmitSetCurrentTime(this->dataPtr->endTime); // Hide unecessary widgets this->dataPtr->currentDayEdit->setVisible(!this->dataPtr->lessThan1h); this->dataPtr->currentHourEdit->setVisible(!this->dataPtr->lessThan1h); this->dataPtr->dayLabel->setVisible(!this->dataPtr->lessThan1h); this->dataPtr->hourLabel->setVisible(!this->dataPtr->lessThan1h); this->dataPtr->hourSeparator->setVisible(!this->dataPtr->lessThan1h); } ///////////////////////////////////////////////// void LogPlayWidget::PublishMultistep(const int _step) { msgs::LogPlaybackControl msg; msg.set_multi_step(_step); this->dataPtr->logPlaybackControlPub->Publish(msg); } ///////////////////////////////////////////////// LogPlayView::LogPlayView(LogPlayWidget *_parent) : QGraphicsView(_parent), dataPtr(new LogPlayViewPrivate) { this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); this->dataPtr->sceneWidth = 1000; this->dataPtr->sceneHeight = 120; this->dataPtr->margin = 50; QGraphicsScene *graphicsScene = new QGraphicsScene(); graphicsScene->setBackgroundBrush(QColor(128, 128, 128)); this->setScene(graphicsScene); this->setMinimumWidth(this->dataPtr->sceneHeight); this->setViewportUpdateMode(QGraphicsView::FullViewportUpdate); this->setStyleSheet("QGraphicsView{border-style: none;}"); this->setSceneRect(0, 0, this->dataPtr->sceneWidth, this->dataPtr->sceneHeight); // Time line QGraphicsLineItem *line = new QGraphicsLineItem(this->dataPtr->margin, this->dataPtr->sceneHeight/2, this->dataPtr->sceneWidth - this->dataPtr->margin, this->dataPtr->sceneHeight/2); line->setPen(QPen(QColor(50, 50, 50, 255), 2)); graphicsScene->addItem(line); // Current time item this->dataPtr->currentTimeItem = new CurrentTimeItem(); this->dataPtr->currentTimeItem->setPos(this->dataPtr->margin, this->dataPtr->sceneHeight/2); graphicsScene->addItem(this->dataPtr->currentTimeItem); this->dataPtr->startTimeSet = false; this->dataPtr->endTimeSet = false; // Send controls to parent LogPlayWidget *widget = qobject_cast(_parent); if (!widget) return; connect(this, SIGNAL(Seek(const common::Time &)), widget, SLOT(OnSeek(const common::Time &))); } ///////////////////////////////////////////////// void LogPlayView::SetCurrentTime(const common::Time &_time) { if (this->dataPtr->currentTimeItem->isSelected()) return; common::Time totalTime = this->dataPtr->endTime - this->dataPtr->startTime; if (totalTime == common::Time::Zero) return; double relPos = ((_time - this->dataPtr->startTime) / totalTime).Double(); this->dataPtr->currentTimeItem->setPos(this->dataPtr->margin + (this->dataPtr->sceneWidth - 2 * this->dataPtr->margin)*relPos, this->dataPtr->sceneHeight/2); } ///////////////////////////////////////////////// void LogPlayView::SetStartTime(const common::Time &_time) { this->dataPtr->startTime = _time; this->dataPtr->startTimeSet = true; } ///////////////////////////////////////////////// void LogPlayView::SetEndTime(const common::Time &_time) { this->dataPtr->endTime = _time; this->dataPtr->endTimeSet = true; if (this->dataPtr->startTimeSet) this->DrawTimeline(); } ///////////////////////////////////////////////// void LogPlayView::DrawTimeline() { if (this->dataPtr->timelineDrawn || !this->dataPtr->startTimeSet || !this->dataPtr->endTimeSet) return; common::Time totalTime = this->dataPtr->endTime - this->dataPtr->startTime; if (totalTime == common::Time::Zero) return; // Aim for this number, but some samples might be added/removed int intervals = 10; // All ticks are shifted from a round number of seconds (no msec or nsec) common::Time roundStartTime = common::Time(this->dataPtr->startTime.sec, 0); // Time between ticks (round seconds) common::Time interval = totalTime/intervals; interval.nsec = 0; if (interval == common::Time::Zero) interval = common::Time::Second; // Time line int tickHeight = 15; common::Time tickTime = this->dataPtr->startTime; int i = 0; while (tickTime >= this->dataPtr->startTime && tickTime < this->dataPtr->endTime) { // Intermediate samples have a round number if (i != 0) { tickTime = roundStartTime + interval * i; } // If first interval too close, shift by 1s common::Time endSpace = interval * 0.9; if (tickTime != this->dataPtr->startTime && tickTime < this->dataPtr->startTime + endSpace) { roundStartTime += common::Time::Second; tickTime = roundStartTime + interval * i; } // If last interval too close, skip to end if (tickTime > this->dataPtr->endTime - endSpace) { tickTime = this->dataPtr->endTime; } ++i; // Relative position double relPos = ((tickTime - this->dataPtr->startTime) / totalTime) .Double(); // Tick vertical line QGraphicsLineItem *tick = new QGraphicsLineItem(0, -tickHeight, 0, 0); tick->setPos(this->dataPtr->margin + (this->dataPtr->sceneWidth - 2 * this->dataPtr->margin)*relPos, this->dataPtr->sceneHeight/2); tick->setPen(QPen(QColor(50, 50, 50, 255), 2)); this->scene()->addItem(tick); // Text std::string timeText; if (tickTime == this->dataPtr->startTime || tickTime == this->dataPtr->endTime) { timeText = tickTime.FormattedString(common::Time::FormatOption::MINUTES); } else { timeText = tickTime.FormattedString(common::Time::FormatOption::MINUTES, common::Time::FormatOption::SECONDS); } QGraphicsSimpleTextItem *tickText = new QGraphicsSimpleTextItem( QString::fromStdString(timeText)); tickText->setBrush(QBrush(QColor(50, 50, 50, 255))); tickText->setPos( this->dataPtr->margin - tickText->boundingRect().width()*0.5 + (this->dataPtr->sceneWidth - 2 * this->dataPtr->margin)*relPos, this->dataPtr->sceneHeight/2 - 3 * tickHeight); this->scene()->addItem(tickText); } this->dataPtr->timelineDrawn = true; } ///////////////////////////////////////////////// void LogPlayView::mousePressEvent(QMouseEvent *_event) { QGraphicsItem *mouseItem = this->scene()->itemAt(this->mapToScene(_event->pos())); if (mouseItem == this->dataPtr->currentTimeItem) { QApplication::setOverrideCursor(QCursor(Qt::ClosedHandCursor)); mouseItem->setSelected(true); } } ///////////////////////////////////////////////// void LogPlayView::mouseMoveEvent(QMouseEvent *_event) { // If nothing is selected if (this->scene()->selectedItems().isEmpty()) { QGraphicsItem *mouseItem = this->scene()->itemAt(this->mapToScene(_event->pos())); // Change cursor when hovering over current time item if (mouseItem == this->dataPtr->currentTimeItem) QApplication::setOverrideCursor(QCursor(Qt::OpenHandCursor)); else QApplication::setOverrideCursor(QCursor(Qt::ArrowCursor)); } // If dragging current time item, keep it within bounds if (this->dataPtr->currentTimeItem->isSelected()) { QPointF newPos(this->mapToScene(_event->pos())); if (newPos.x() < this->dataPtr->margin) newPos.setX(this->dataPtr->margin); else if (newPos.x() > (this->dataPtr->sceneWidth - this->dataPtr->margin)) newPos.setX(this->dataPtr->sceneWidth - this->dataPtr->margin); newPos.setY(this->dataPtr->sceneHeight/2); this->dataPtr->currentTimeItem->setPos(newPos); } } ///////////////////////////////////////////////// void LogPlayView::mouseReleaseEvent(QMouseEvent */*_event*/) { // Send time seek if releasing current time item if (this->dataPtr->currentTimeItem->isSelected()) { double relPos = (this->dataPtr->currentTimeItem->pos().x() - this->dataPtr->margin) / (this->dataPtr->sceneWidth - 2 * this->dataPtr->margin); common::Time totalTime = this->dataPtr->endTime - this->dataPtr->startTime; common::Time seekTime = (totalTime * relPos) + this->dataPtr->startTime; this->Seek(seekTime); } this->scene()->clearSelection(); QApplication::setOverrideCursor(QCursor(Qt::ArrowCursor)); } ///////////////////////////////////////////////// CurrentTimeItem::CurrentTimeItem() { this->setEnabled(true); this->setRect(-8, -25, 16, 50); this->setZValue(10); this->setAcceptHoverEvents(true); this->setAcceptedMouseButtons(Qt::LeftButton); this->setFlag(QGraphicsItem::ItemIsSelectable); this->setFlag(QGraphicsItem::ItemIsMovable); this->setFlag(QGraphicsItem::ItemSendsScenePositionChanges); this->setCursor(Qt::SizeAllCursor); } ///////////////////////////////////////////////// void CurrentTimeItem::paint(QPainter *_painter, const QStyleOptionGraphicsItem */*_option*/, QWidget */*_widget*/) { int lineHeight = 50; int lineWidth = 3; // Vertical line QLineF vLine(-lineWidth/10.0, -lineHeight/2.0, -lineWidth/10.0, +lineHeight/2.0); QPen linePen; linePen.setColor(QColor(50, 50, 50, 255)); linePen.setWidth(lineWidth); _painter->setPen(linePen); _painter->drawLine(vLine); // Triangle QVector trianglePts; trianglePts.push_back(QPointF(-8, -lineHeight/2 - 1)); trianglePts.push_back(QPointF(8, -lineHeight/2 - 1)); trianglePts.push_back(QPointF(0, -lineHeight/2 + 10)); QPolygonF triangle(trianglePts); QPen whitePen(Qt::white, 0); QPen orangePen(QColor(245, 129, 19, 255), 0); QBrush whiteBrush(Qt::white); QBrush orangeBrush(QColor(245, 129, 19, 255)); if (this->isSelected()) { _painter->setPen(whitePen); _painter->setBrush(whiteBrush); } else { _painter->setPen(orangePen); _painter->setBrush(orangeBrush); } _painter->drawPolygon(triangle); }