462 lines
15 KiB
C++
462 lines
15 KiB
C++
/*
|
|
* ModelRailroadTimetablePlanner
|
|
* Copyright 2016-2023, Filippo Gentile
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "backgroundhelper.h"
|
|
|
|
#include "app/session.h"
|
|
|
|
#include "graph/model/linegraphscene.h"
|
|
#include "utils/jobcategorystrings.h"
|
|
|
|
#include <QPainter>
|
|
#include "utils/font_utils.h"
|
|
|
|
#include <QtMath>
|
|
|
|
#include <QDebug>
|
|
|
|
void BackgroundHelper::drawHourPanel(QPainter *painter, const QRectF &rect)
|
|
{
|
|
// TODO: settings
|
|
QFont hourTextFont;
|
|
setFontPointSizeDPI(hourTextFont, 15, painter);
|
|
|
|
QPen hourTextPen(AppSettings.getHourTextColor());
|
|
|
|
const int vertOffset = Session->vertOffset;
|
|
const int hourOffset = Session->hourOffset;
|
|
|
|
painter->setFont(hourTextFont);
|
|
painter->setPen(hourTextPen);
|
|
|
|
// qDebug() << "Drawing hours..." << rect << scroll;
|
|
const QString fmt(QStringLiteral("%1:00"));
|
|
|
|
const qreal top = rect.top() - vertOffset;
|
|
const qreal bottom = rect.bottom();
|
|
|
|
int h = qFloor(top / hourOffset);
|
|
if (h < 0)
|
|
h = 0;
|
|
|
|
QRectF labelRect = rect;
|
|
labelRect.setWidth(labelRect.width() * 0.9);
|
|
labelRect.setHeight(hourOffset);
|
|
labelRect.moveTop(h * hourOffset + vertOffset - hourOffset / 2);
|
|
|
|
for (; h <= 24 && labelRect.top() <= bottom; h++)
|
|
{
|
|
// qDebug() << "Y:" << y << fmt.arg(h);
|
|
painter->drawText(labelRect, fmt.arg(h), QTextOption(Qt::AlignVCenter | Qt::AlignRight));
|
|
labelRect.moveTop(labelRect.top() + hourOffset);
|
|
}
|
|
}
|
|
|
|
void BackgroundHelper::drawBackgroundHourLines(QPainter *painter, const QRectF &rect)
|
|
{
|
|
const double horizOffset = Session->horizOffset;
|
|
const double vertOffset = Session->vertOffset;
|
|
const double hourOffset = Session->hourOffset;
|
|
|
|
QPen hourLinePen(AppSettings.getHourLineColor(), AppSettings.getHourLineWidth());
|
|
|
|
const qreal x1 = qMax(qreal(horizOffset), rect.left());
|
|
const qreal x2 = rect.right();
|
|
const qreal t = qMax(rect.top(), vertOffset);
|
|
const qreal b = rect.bottom();
|
|
|
|
if (x1 > x2 || b < vertOffset || t > b)
|
|
return;
|
|
|
|
int firstH = qCeil((t - vertOffset) / hourOffset);
|
|
int lastH = qFloor((b - vertOffset) / hourOffset);
|
|
|
|
if (firstH > 24 || lastH < 0)
|
|
return;
|
|
|
|
if (firstH < 0)
|
|
firstH = 0;
|
|
if (lastH > 24)
|
|
lastH = 24;
|
|
|
|
const int n = lastH - firstH + 1;
|
|
if (n <= 0)
|
|
return;
|
|
|
|
qreal y = vertOffset + firstH * hourOffset;
|
|
|
|
QLineF *arr = new QLineF[n];
|
|
for (int i = 0; i < n; i++)
|
|
{
|
|
arr[i] = QLineF(x1, y, x2, y);
|
|
y += hourOffset;
|
|
}
|
|
|
|
painter->setPen(hourLinePen);
|
|
painter->drawLines(arr, n);
|
|
delete[] arr;
|
|
}
|
|
|
|
void BackgroundHelper::drawStationHeader(QPainter *painter, LineGraphScene *scene,
|
|
const QRectF &rect)
|
|
{
|
|
QFont stationFont;
|
|
stationFont.setBold(true);
|
|
setFontPointSizeDPI(stationFont, 25, painter);
|
|
|
|
QPen stationPen(AppSettings.getStationTextColor());
|
|
|
|
QFont platfBoldFont = stationFont;
|
|
setFontPointSizeDPI(platfBoldFont, 16, painter);
|
|
|
|
QFont platfNormalFont = platfBoldFont;
|
|
platfNormalFont.setBold(false);
|
|
|
|
QPen electricPlatfPen(Qt::blue);
|
|
QPen nonElectricPlatfPen(Qt::black);
|
|
|
|
const qreal platformOffset = Session->platformOffset;
|
|
const int stationOffset = Session->stationOffset;
|
|
|
|
// On left go back by half station offset to center station label
|
|
// and center platform label by going a back of half platformOffset
|
|
const int leftOffset = -stationOffset / 2 - platformOffset / 2;
|
|
|
|
const double margin = stationOffset * 0.1;
|
|
|
|
QRectF r = rect;
|
|
|
|
for (auto st : qAsConst(scene->stations))
|
|
{
|
|
const double left = st.xPos + leftOffset;
|
|
const double right = left + st.platforms.count() * platformOffset + stationOffset;
|
|
|
|
if (right < r.left() || left >= r.right())
|
|
continue; // Skip station, it's not visible
|
|
|
|
QRectF labelRect = r;
|
|
labelRect.setLeft(left + margin);
|
|
labelRect.setRight(right - margin);
|
|
labelRect.setBottom(r.bottom() - r.height() / 3);
|
|
|
|
painter->setPen(stationPen);
|
|
painter->setFont(stationFont);
|
|
painter->drawText(labelRect, Qt::AlignVCenter | Qt::AlignCenter, st.stationName);
|
|
|
|
labelRect = r;
|
|
labelRect.setTop(r.top() + r.height() * 2 / 3);
|
|
|
|
// Go to start of station (first platform)
|
|
// We need to compensate the half stationOffset used to center station label
|
|
double xPos = left + stationOffset / 2;
|
|
labelRect.setWidth(platformOffset);
|
|
for (const StationGraphObject::PlatformGraph &platf : qAsConst(st.platforms))
|
|
{
|
|
if (platf.platformType.testFlag(utils::StationTrackType::Electrified))
|
|
painter->setPen(electricPlatfPen);
|
|
else
|
|
painter->setPen(nonElectricPlatfPen);
|
|
|
|
if (platf.platformType.testFlag(utils::StationTrackType::Through))
|
|
painter->setFont(platfBoldFont);
|
|
else
|
|
painter->setFont(platfNormalFont);
|
|
|
|
labelRect.moveLeft(xPos);
|
|
|
|
painter->drawText(labelRect, Qt::AlignCenter, platf.platformName);
|
|
|
|
xPos += platformOffset;
|
|
}
|
|
}
|
|
}
|
|
|
|
void BackgroundHelper::drawStations(QPainter *painter, LineGraphScene *scene, const QRectF &rect)
|
|
{
|
|
const QRgb white = qRgb(255, 255, 255);
|
|
|
|
// const int horizOffset = Session->horizOffset;
|
|
const int vertOffset = Session->vertOffset;
|
|
// const int stationOffset = Session->stationOffset;
|
|
const double platfOffset = Session->platformOffset;
|
|
const int lastY = vertOffset + Session->hourOffset * 24 + 10;
|
|
|
|
const int width = AppSettings.getPlatformLineWidth();
|
|
const QColor mainPlatfColor = AppSettings.getMainPlatfColor();
|
|
|
|
QPen platfPen(mainPlatfColor, width);
|
|
|
|
QPointF top(0, vertOffset);
|
|
QPointF bottom(0, lastY);
|
|
|
|
for (const StationGraphObject &st : qAsConst(scene->stations))
|
|
{
|
|
const double left = st.xPos;
|
|
const double right = left + st.platforms.count() * platfOffset;
|
|
|
|
if (left > rect.right() || right < rect.left())
|
|
continue; // Skip station, it's not visible
|
|
|
|
top.rx() = bottom.rx() = st.xPos;
|
|
|
|
for (const StationGraphObject::PlatformGraph &platf : st.platforms)
|
|
{
|
|
if (platf.color == white)
|
|
platfPen.setColor(mainPlatfColor);
|
|
else
|
|
platfPen.setColor(platf.color);
|
|
|
|
painter->setPen(platfPen);
|
|
|
|
painter->drawLine(top, bottom);
|
|
|
|
top.rx() += platfOffset;
|
|
bottom.rx() += platfOffset;
|
|
}
|
|
}
|
|
}
|
|
|
|
void BackgroundHelper::drawJobStops(QPainter *painter, LineGraphScene *scene, const QRectF &rect,
|
|
bool drawSelection)
|
|
{
|
|
const double platfOffset = Session->platformOffset;
|
|
const double stationOffset = Session->stationOffset;
|
|
|
|
QFont jobNameFont;
|
|
setFontPointSizeDPI(jobNameFont, 20, painter);
|
|
painter->setFont(jobNameFont);
|
|
|
|
QPen jobPen;
|
|
jobPen.setWidth(AppSettings.getJobLineWidth());
|
|
jobPen.setCapStyle(Qt::RoundCap);
|
|
jobPen.setJoinStyle(Qt::RoundJoin);
|
|
|
|
QPen selectedJobPen;
|
|
|
|
const JobStopEntry selectedJob = scene->getSelectedJob();
|
|
if (drawSelection && selectedJob.jobId)
|
|
{
|
|
selectedJobPen.setWidthF(jobPen.widthF() * SelectedJobWidthFactor);
|
|
selectedJobPen.setCapStyle(Qt::RoundCap);
|
|
selectedJobPen.setJoinStyle(Qt::RoundJoin);
|
|
|
|
QColor color = Session->colorForCat(selectedJob.category);
|
|
color.setAlpha(SelectedJobAlphaFactor);
|
|
selectedJobPen.setColor(color);
|
|
}
|
|
|
|
QPointF top;
|
|
QPointF bottom;
|
|
|
|
JobCategory lastJobCategory = JobCategory::NCategories;
|
|
QTextOption textOption(Qt::AlignTop | Qt::AlignLeft);
|
|
|
|
for (const StationGraphObject &st : qAsConst(scene->stations))
|
|
{
|
|
const double left = st.xPos;
|
|
const double right = left + st.platforms.count() * platfOffset;
|
|
|
|
// Set a maximum right edge to Job labels
|
|
// This allows to determine if they have to be drawn
|
|
const double maxJobLabelX = right + stationOffset;
|
|
|
|
if (left > rect.right() || maxJobLabelX < rect.left())
|
|
continue; // Skip station, it's not visible
|
|
|
|
top.rx() = bottom.rx() = st.xPos;
|
|
|
|
for (const StationGraphObject::PlatformGraph &platf : st.platforms)
|
|
{
|
|
for (const StationGraphObject::JobStopGraph &jobStop : platf.jobStops)
|
|
{
|
|
// NOTE: departure comes AFTER arrival in time, opposite than job segment
|
|
if (jobStop.arrivalY > rect.bottom() || jobStop.departureY < rect.top())
|
|
continue; // Skip, job not visible
|
|
|
|
top.setY(jobStop.arrivalY);
|
|
bottom.setY(jobStop.departureY);
|
|
|
|
const bool nullStopDuration = qFuzzyCompare(top.y(), bottom.y());
|
|
|
|
if (drawSelection && selectedJob.jobId == jobStop.stop.jobId)
|
|
{
|
|
// Draw selection around segment
|
|
painter->setPen(selectedJobPen);
|
|
|
|
if (nullStopDuration)
|
|
painter->drawPoint(top);
|
|
else
|
|
painter->drawLine(top, bottom);
|
|
|
|
// Reset pen
|
|
painter->setPen(jobPen);
|
|
}
|
|
|
|
if (lastJobCategory != jobStop.stop.category)
|
|
{
|
|
QColor color = Session->colorForCat(jobStop.stop.category);
|
|
jobPen.setColor(color);
|
|
painter->setPen(jobPen);
|
|
lastJobCategory = jobStop.stop.category;
|
|
}
|
|
|
|
if (nullStopDuration)
|
|
painter->drawPoint(top);
|
|
else
|
|
painter->drawLine(top, bottom);
|
|
|
|
if (jobStop.drawLabel)
|
|
{
|
|
const QString jobName =
|
|
JobCategoryName::jobName(jobStop.stop.jobId, jobStop.stop.category);
|
|
|
|
// Put label a bit to the left in respect to the stop arrival point
|
|
// Calculate width so it doesn't go after maxJobLabelX
|
|
const qreal topWithMargin = top.x() + platfOffset / 2;
|
|
QRectF r(topWithMargin, top.y(), maxJobLabelX - topWithMargin, 25);
|
|
painter->drawText(r, jobName, textOption);
|
|
}
|
|
}
|
|
|
|
top.rx() += platfOffset;
|
|
bottom.rx() += platfOffset;
|
|
}
|
|
}
|
|
}
|
|
|
|
void BackgroundHelper::drawJobSegments(QPainter *painter, LineGraphScene *scene, const QRectF &rect,
|
|
bool drawSelection)
|
|
{
|
|
const double stationOffset = Session->stationOffset;
|
|
|
|
QFont jobNameFont;
|
|
setFontPointSizeDPI(jobNameFont, 20, painter);
|
|
painter->setFont(jobNameFont);
|
|
|
|
QColor textBackground(Qt::white);
|
|
textBackground.setAlpha(100);
|
|
|
|
QPen jobPen;
|
|
jobPen.setWidth(AppSettings.getJobLineWidth());
|
|
jobPen.setCapStyle(Qt::RoundCap);
|
|
jobPen.setJoinStyle(Qt::RoundJoin);
|
|
|
|
QPen selectedJobPen;
|
|
|
|
const JobStopEntry selectedJob = scene->getSelectedJob();
|
|
if (drawSelection && selectedJob.jobId)
|
|
{
|
|
selectedJobPen.setWidthF(jobPen.widthF() * SelectedJobWidthFactor);
|
|
selectedJobPen.setCapStyle(Qt::RoundCap);
|
|
selectedJobPen.setJoinStyle(Qt::RoundJoin);
|
|
|
|
QColor color = Session->colorForCat(selectedJob.category);
|
|
color.setAlpha(SelectedJobAlphaFactor);
|
|
selectedJobPen.setColor(color);
|
|
}
|
|
|
|
JobCategory lastJobCategory = JobCategory::NCategories;
|
|
QTextOption textOption(Qt::AlignCenter);
|
|
|
|
// Iterate until one but last
|
|
// This way we can always acces next station
|
|
for (int i = 0; i < scene->stationPositions.size() - 1; i++)
|
|
{
|
|
const LineGraphScene::StationPosEntry &stPos = scene->stationPositions.at(i);
|
|
|
|
const double left = stPos.xPos;
|
|
double right = 0;
|
|
|
|
if (i < scene->stationPositions.size() - 2)
|
|
{
|
|
const LineGraphScene::StationPosEntry &afterNextPos = scene->stationPositions.at(i + 2);
|
|
right = afterNextPos.xPos - stationOffset;
|
|
}
|
|
else
|
|
{
|
|
right = rect.right(); // Last station, use all space on right side
|
|
}
|
|
|
|
if (left > rect.right() || right < rect.left())
|
|
continue; // Skip station, it's not visible
|
|
|
|
for (const LineGraphScene::JobSegmentGraph &job : stPos.nextSegmentJobGraphs)
|
|
{
|
|
// NOTE: departure comes BEFORE arrival in time, opposite than job stop
|
|
if (job.fromDeparture.y() > rect.bottom() || job.toArrival.y() < rect.top())
|
|
continue; // Skip, job not visible
|
|
|
|
const QLineF line(job.fromDeparture, job.toArrival);
|
|
|
|
if (drawSelection && selectedJob.jobId == job.jobId)
|
|
{
|
|
// Draw selection around segment
|
|
painter->setPen(selectedJobPen);
|
|
painter->drawLine(line);
|
|
|
|
// Reset pen
|
|
painter->setPen(jobPen);
|
|
}
|
|
|
|
if (lastJobCategory != job.category)
|
|
{
|
|
QColor color = Session->colorForCat(job.category);
|
|
jobPen.setColor(color);
|
|
painter->setPen(jobPen);
|
|
lastJobCategory = job.category;
|
|
}
|
|
|
|
painter->drawLine(line);
|
|
|
|
const QString jobName = JobCategoryName::jobName(job.jobId, job.category);
|
|
|
|
// Save old transformation to reset it after drawing text
|
|
const QTransform oldTransf = painter->transform();
|
|
|
|
// Move to line center, it will be rotation pivot
|
|
painter->translate(line.center());
|
|
|
|
// Rotate by line angle
|
|
qreal angle = line.angle();
|
|
if (job.fromDeparture.x() > job.toArrival.x())
|
|
angle += 180.0; // Prevent flipping text
|
|
|
|
painter->rotate(-angle); // minus because QPainter wants clockwise angle
|
|
|
|
const double lineLength = line.length();
|
|
QRectF textRect(-lineLength / 2, -30, lineLength, 25);
|
|
|
|
// Try to avoid overlapping text of crossing jobs, move text towards arrival
|
|
if (job.toArrival.x() > job.fromDeparture.x())
|
|
textRect.moveLeft(textRect.left() + lineLength / 5);
|
|
else
|
|
textRect.moveLeft(textRect.left() - lineLength / 5);
|
|
|
|
textRect = painter->boundingRect(textRect, jobName, textOption);
|
|
|
|
// Draw a semi transparent background to ease text reading
|
|
painter->fillRect(textRect, textBackground);
|
|
painter->drawText(textRect, jobName, textOption);
|
|
|
|
// Reset to old transformation
|
|
painter->setTransform(oldTransf);
|
|
}
|
|
}
|
|
}
|