mirror of
https://github.com/subsurface/subsurface.git
synced 2025-02-19 22:16:15 +00:00
profile: convert the "ruler item" to qt-quick
Code is mostly based on the "tooltip item". The dragging code was slightly reworked to be more logical. A "disk item" was added for the handles. Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
This commit is contained in:
parent
b167e130a4
commit
ea0085fef6
14 changed files with 313 additions and 228 deletions
|
@ -1,182 +1,184 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include "profile-widget/ruleritem.h"
|
||||
#include "ruleritem.h"
|
||||
#include "profilescene.h"
|
||||
#include "profileview.h"
|
||||
#include "zvalues.h"
|
||||
#include "core/settings/qPrefTechnicalDetails.h"
|
||||
|
||||
#include "core/profile.h"
|
||||
|
||||
#include <QFont>
|
||||
#include <QFontMetrics>
|
||||
#include <QGraphicsScene>
|
||||
#include <QGraphicsSceneEvent>
|
||||
#include <QGraphicsView>
|
||||
#include <QApplication>
|
||||
|
||||
RulerNodeItem2::RulerNodeItem2() :
|
||||
pInfo(NULL),
|
||||
idx(-1),
|
||||
ruler(NULL),
|
||||
timeAxis(NULL),
|
||||
depthAxis(NULL)
|
||||
static QColor handleBorderColor(Qt::red);
|
||||
static QColor handleColor(0xff, 0, 0, 0x80);
|
||||
static constexpr double handleRadius = 8.0;
|
||||
|
||||
static QColor lineColor(Qt::black);
|
||||
static constexpr double lineWidth = 1.0;
|
||||
|
||||
static constexpr int tooltipBorder = 1;
|
||||
static constexpr double tooltipBorderRadius = 2.0; // Radius of rounded corners
|
||||
static QColor tooltipBorderColor(Qt::black);
|
||||
static QColor tooltipColor(0xff, 0xff, 0xff, 190);
|
||||
static QColor tooltipFontColor(Qt::black);
|
||||
|
||||
class RulerItemHandle : public ChartDiskItem
|
||||
{
|
||||
setRect(-8, -8, 16, 16);
|
||||
setBrush(QColor(0xff, 0, 0, 127));
|
||||
setPen(QColor(Qt::red));
|
||||
setFlag(ItemIsMovable);
|
||||
setFlag(ItemSendsGeometryChanges);
|
||||
setFlag(ItemIgnoresTransformations);
|
||||
public:
|
||||
ProfileView &profileView;
|
||||
double xpos;
|
||||
// The first argument is passed twice, to avoid an downcast. Yes, that's silly.
|
||||
RulerItemHandle(ChartView &view, ProfileView &profileView, double dpr) :
|
||||
ChartDiskItem(view, ProfileZValue::RulerItem,
|
||||
QPen(handleBorderColor, dpr),
|
||||
QBrush(handleColor),
|
||||
true),
|
||||
profileView(profileView),
|
||||
xpos(0.0)
|
||||
{
|
||||
}
|
||||
// The call chain here is weird: this calls into the ProfileScene, which then calls
|
||||
// back into the RulerItem. The reason is that the ProfileScene knows the current
|
||||
// dive etc. This seems more robust than storing the current dive in the subobject.
|
||||
void drag(QPointF pos) override
|
||||
{
|
||||
xpos = pos.x();
|
||||
profileView.rulerDragged();
|
||||
}
|
||||
};
|
||||
|
||||
// duplicate code in tooltipitem.cpp
|
||||
static QFont makeFont(double dpr)
|
||||
{
|
||||
QFont font(qApp->font());
|
||||
if (dpr != 1.0) {
|
||||
int pixelSize = font.pixelSize();
|
||||
if (pixelSize > 0) {
|
||||
pixelSize = lrint(static_cast<double>(pixelSize) * dpr);
|
||||
font.setPixelSize(pixelSize);
|
||||
} else {
|
||||
font.setPointSizeF(font.pointSizeF() * dpr);
|
||||
}
|
||||
}
|
||||
return font;
|
||||
}
|
||||
|
||||
void RulerNodeItem2::setPlotInfo(const plot_info &info)
|
||||
static ChartItemPtr<RulerItemHandle> makeHandle(ProfileView &view, double dpr)
|
||||
{
|
||||
pInfo = &info;
|
||||
idx = 0;
|
||||
auto res = view.createChartItem<RulerItemHandle>(view, dpr);
|
||||
res->resize(handleRadius * dpr);
|
||||
return res;
|
||||
}
|
||||
|
||||
void RulerNodeItem2::setRuler(RulerItem2 *r)
|
||||
RulerItem::RulerItem(ProfileView &view, double dpr) :
|
||||
line(view.createChartItem<ChartLineItem>(ProfileZValue::RulerItem,
|
||||
lineColor, lineWidth * dpr)),
|
||||
handle1(makeHandle(view, dpr)),
|
||||
handle2(makeHandle(view, dpr)),
|
||||
tooltip(view.createChartItem<AnimatedChartRectItem>(ProfileZValue::RulerItem,
|
||||
QPen(tooltipBorderColor, lrint(tooltipBorder * dpr)),
|
||||
QBrush(tooltipColor), tooltipBorderRadius * dpr,
|
||||
false)),
|
||||
font(makeFont(dpr)),
|
||||
fm(font),
|
||||
fontHeight(fm.height())
|
||||
{
|
||||
ruler = r;
|
||||
}
|
||||
|
||||
void RulerNodeItem2::recalculate()
|
||||
void RulerItem::setVisible(bool visible)
|
||||
{
|
||||
if (!pInfo || pInfo->nr <= 0)
|
||||
line->setVisible(visible);
|
||||
handle1->setVisible(visible);
|
||||
handle2->setVisible(visible);
|
||||
tooltip->setVisible(visible);
|
||||
}
|
||||
|
||||
// Binary search to find index at time stamp
|
||||
static int get_idx_at_time(const plot_info &info, int time)
|
||||
{
|
||||
auto entry = std::lower_bound(info.entry.begin(), info.entry.end(), time,
|
||||
[](const plot_data &d, int time)
|
||||
{ return d.sec < time; });
|
||||
return entry != info.entry.end() ? entry - info.entry.begin()
|
||||
: info.entry.size() - 1;
|
||||
}
|
||||
|
||||
void RulerItem::update(const dive *d, double dpr, const ProfileScene &scene, const plot_info &info, int animspeed)
|
||||
{
|
||||
if (info.nr == 0)
|
||||
return; // Nothing to display
|
||||
|
||||
auto [minX, maxX] = scene.minMaxX();
|
||||
auto [minY, maxY] = scene.minMaxY();
|
||||
double x1 = std::clamp(handle1->xpos, minX, maxX);
|
||||
double x2 = std::clamp(handle2->xpos, minX, maxX);
|
||||
|
||||
int time1 = lrint(scene.timeAt(QPointF(x1, 0.0)));
|
||||
int time2 = lrint(scene.timeAt(QPointF(x2, 0.0)));
|
||||
|
||||
int idx1 = get_idx_at_time(info, time1);
|
||||
int idx2 = get_idx_at_time(info, time2);
|
||||
|
||||
double y1 = scene.posAtDepth(info.entry[idx1].depth);
|
||||
double y2 = scene.posAtDepth(info.entry[idx2].depth);
|
||||
|
||||
QPointF pos1(x1, y1);
|
||||
QPointF pos2(x2, y2);
|
||||
line->setLine(pos1, pos2);
|
||||
handle1->setPos(pos1);
|
||||
handle2->setPos(pos2);
|
||||
|
||||
if (idx1 == idx2) {
|
||||
tooltip->setVisible(false);
|
||||
return;
|
||||
|
||||
const struct plot_data &last = pInfo->entry[pInfo->nr - 1];
|
||||
if (x() < 0) {
|
||||
setPos(0, y());
|
||||
} else if (x() > timeAxis->posAtValue(last.sec)) {
|
||||
setPos(timeAxis->posAtValue(last.sec), depthAxis->posAtValue(last.depth));
|
||||
} else {
|
||||
idx = 0;
|
||||
while (idx < pInfo->nr && timeAxis->posAtValue(pInfo->entry[idx].sec) < x())
|
||||
++idx;
|
||||
const struct plot_data &data = pInfo->entry[idx];
|
||||
setPos(timeAxis->posAtValue(data.sec), depthAxis->posAtValue(data.depth));
|
||||
}
|
||||
}
|
||||
tooltip->setVisible(true);
|
||||
|
||||
void RulerNodeItem2::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
qreal x = event->scenePos().x();
|
||||
if (x < 0.0)
|
||||
x = 0.0;
|
||||
setPos(x, event->scenePos().y());
|
||||
recalculate();
|
||||
ruler->recalculate();
|
||||
}
|
||||
auto lines = compare_samples(d, info, idx1, idx2, 1);
|
||||
|
||||
RulerItem2::RulerItem2() : pInfo(NULL),
|
||||
source(new RulerNodeItem2()),
|
||||
dest(new RulerNodeItem2()),
|
||||
timeAxis(NULL),
|
||||
depthAxis(NULL),
|
||||
textItemBack(new QGraphicsRectItem(this)),
|
||||
textItem(new QGraphicsSimpleTextItem(this))
|
||||
{
|
||||
source->setRuler(this);
|
||||
dest->setRuler(this);
|
||||
textItem->setFlag(QGraphicsItem::ItemIgnoresTransformations);
|
||||
textItemBack->setBrush(QColor(0xff, 0xff, 0xff, 190));
|
||||
textItemBack->setPen(QColor(Qt::white));
|
||||
textItemBack->setFlag(QGraphicsItem::ItemIgnoresTransformations);
|
||||
setPen(QPen(QColor(Qt::black), 0.0));
|
||||
connect(qPrefTechnicalDetails::instance(), &qPrefTechnicalDetails::rulergraphChanged, this, &RulerItem2::settingsChanged);
|
||||
}
|
||||
|
||||
void RulerItem2::settingsChanged(bool value)
|
||||
{
|
||||
//ProfileWidget2 *profWidget = NULL;
|
||||
//if (scene() && scene()->views().count())
|
||||
//profWidget = qobject_cast<ProfileWidget2 *>(scene()->views().first());
|
||||
|
||||
//setVisible( (profWidget && profWidget->currentState == ProfileWidget2::PROFILE) ? value : false);
|
||||
}
|
||||
|
||||
void RulerItem2::recalculate()
|
||||
{
|
||||
QPointF tmp;
|
||||
QFont font;
|
||||
QFontMetrics fm(font);
|
||||
|
||||
if (timeAxis == NULL || depthAxis == NULL || !pInfo || pInfo->nr == 0)
|
||||
return;
|
||||
|
||||
prepareGeometryChange();
|
||||
startPoint = mapFromItem(source, 0, 0);
|
||||
endPoint = mapFromItem(dest, 0, 0);
|
||||
|
||||
if (startPoint.x() > endPoint.x()) {
|
||||
tmp = endPoint;
|
||||
endPoint = startPoint;
|
||||
startPoint = tmp;
|
||||
double width = 0;
|
||||
// Turn strings into QString/width pairs and increase width if needed
|
||||
std::vector<std::pair<QString, int>> strings;
|
||||
strings.reserve(lines.size());
|
||||
for (auto &s_std: lines) {
|
||||
auto s = QString::fromStdString(s_std);
|
||||
int w = fm.size(Qt::TextSingleLine, s).width();
|
||||
width = std::max(width, static_cast<double>(w));
|
||||
strings.push_back(std::make_pair(s, w));
|
||||
}
|
||||
QLineF line(startPoint, endPoint);
|
||||
setLine(line);
|
||||
width += 6.0 * tooltipBorder * dpr;
|
||||
|
||||
QString text;
|
||||
for (const std::string &s: compare_samples(dive, *pInfo, source->idx, dest->idx, 1)) {
|
||||
if (!text.isEmpty())
|
||||
text += '\n';
|
||||
text += QString::fromStdString(s);
|
||||
double height = static_cast<double>(strings.size()) * fontHeight +
|
||||
4.0 * tooltipBorder * dpr;
|
||||
|
||||
QPixmap pixmap(lrint(width), lrint(height));
|
||||
pixmap.fill(Qt::transparent);
|
||||
QPainter painter(&pixmap);
|
||||
|
||||
painter.setFont(font);
|
||||
painter.setPen(QPen(tooltipFontColor)); // QPainter uses QPen to set text color!
|
||||
double x = 4.0 * tooltipBorder * dpr;
|
||||
double y = 2.0 * tooltipBorder * dpr;
|
||||
for (auto &[s,w]: strings) {
|
||||
QRectF rect(x, y, w, fontHeight);
|
||||
painter.drawText(rect, s);
|
||||
y += fontHeight;
|
||||
}
|
||||
|
||||
// draw text
|
||||
QGraphicsView *view = scene()->views().first();
|
||||
QPoint begin = view->mapFromScene(mapToScene(startPoint));
|
||||
textItem->setText(text);
|
||||
qreal tgtX = startPoint.x();
|
||||
const qreal diff = begin.x() + textItem->boundingRect().width();
|
||||
// clamp so that the text doesn't go out of the screen to the right
|
||||
if (diff > view->width()) {
|
||||
begin.setX(lrint(begin.x() - (diff - view->width())));
|
||||
tgtX = mapFromScene(view->mapToScene(begin)).x();
|
||||
}
|
||||
// always show the text bellow the lowest of the start and end points
|
||||
qreal tgtY = (startPoint.y() >= endPoint.y()) ? startPoint.y() : endPoint.y();
|
||||
// this isn't exactly optimal, since we want to scale the 1.0, 4.0 distances as well
|
||||
textItem->setPos(tgtX - 1.0, tgtY + 4.0);
|
||||
tooltip->setPixmap(pixmap, animspeed);
|
||||
|
||||
// setup the text background
|
||||
textItemBack->setVisible(startPoint.x() != endPoint.x());
|
||||
textItemBack->setPos(textItem->x(), textItem->y());
|
||||
textItemBack->setRect(0, 0, textItem->boundingRect().width(), textItem->boundingRect().height());
|
||||
if (pos2.x() < pos1.x())
|
||||
std::swap(pos1, pos2);
|
||||
double xpos = width < maxX - minX ?
|
||||
std::clamp(pos1.x() + handleRadius * dpr, 0.0, maxX - width) : 0.0;
|
||||
|
||||
double ypos = height < maxY - minY ?
|
||||
std::clamp(pos1.y() + handleRadius * dpr, 0.0, maxY - height) : 0.0;
|
||||
|
||||
tooltip->setPos(QPointF(xpos, ypos));
|
||||
}
|
||||
|
||||
RulerNodeItem2 *RulerItem2::sourceNode() const
|
||||
void RulerItem::anim(double progress)
|
||||
{
|
||||
return source;
|
||||
}
|
||||
|
||||
RulerNodeItem2 *RulerItem2::destNode() const
|
||||
{
|
||||
return dest;
|
||||
}
|
||||
|
||||
void RulerItem2::setPlotInfo(const struct dive *d, const plot_info &info)
|
||||
{
|
||||
dive = d;
|
||||
pInfo = &info;
|
||||
dest->setPlotInfo(info);
|
||||
source->setPlotInfo(info);
|
||||
dest->recalculate();
|
||||
source->recalculate();
|
||||
recalculate();
|
||||
}
|
||||
|
||||
void RulerItem2::setAxis(DiveCartesianAxis *time, DiveCartesianAxis *depth)
|
||||
{
|
||||
timeAxis = time;
|
||||
depthAxis = depth;
|
||||
dest->depthAxis = depth;
|
||||
dest->timeAxis = time;
|
||||
source->depthAxis = depth;
|
||||
source->timeAxis = time;
|
||||
recalculate();
|
||||
}
|
||||
|
||||
void RulerItem2::setVisible(bool visible)
|
||||
{
|
||||
QGraphicsLineItem::setVisible(visible);
|
||||
source->setVisible(visible);
|
||||
dest->setVisible(visible);
|
||||
tooltip->anim(progress);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue