A set of methods to render schematic and to convert between viewport,
contents and model coordinate systems.
@c Schematic object has @e contents and @e viewport –– they are inherited
from Q3ScrollView.
@e Contents is basically a canvas to draw something on it. Its size is not
fixed and may be changed anytime (@ref Q3ScrollView::resizeContents)
@e Viewport is the visible part of the contents and at the same time it's
the widget you actually see. Contents may be larger than the display and
viewport allows us to scroll the contents and to observe parts of it.
@e Model is not inhereted from Q3ScrollView and is not represented by any
widget, it is actually entirely abstract/logical entity. It describes some
abstract space where an "idea" of the schematic lives. All dimensions and
sizes in model are @e reference dimensions and sizes. For example, when
schematic has to be drawn in double scale (twice as large), model size
multiplied by two is the contents size for double-scale schematic. Want
to draw schematic in half-scale? Take model size and divide it by two,
that would be the contents size for half-scale schematic. In other words
contents is the @em projection of model and there is a connection between
them: @code <size in model> * <Scale> = <size on contents> @endcode
Because of this connection between model and contents every point
on contents has a counterpart in model and vise-versa.
Contents coordinates originate at top-left corner and grow to the right
and downwards. Model coordinates also originate at top-left corner,
but while contents top-left is always (0,0), model's top-left is not
fixed and changes as user scrolls or occupies space with schematic
elements. Think about model as of a rectangle somewhere in cartesian
coordinate system.
There is also viewport coordinates; they are relative to top left
corner of the viewport widget.
When you click on schematic, position of the click may be described in
- viewport coordinates (relative to top left corner of what you see)
- contents coordinates (relative to top left corner of contents, which
may not be visible at them moment)
- model coordinates
There is a set of methods to convert between types of coordinates.
The core of drawing a schematic –– or @e rendering –– is @ref Schematic::renderModel
method. Use it when you need to show display changes applied to schematic, like:
- schematic should be drawn in another scale
- model size has changed because the view has been scrolled
See method description for example usage.
*/
#include "mouseactions.h"
#include "qucsdoc.h"
#include "qucs.h"
#include "schematic.h"
QRect Schematic::modelRect()
{
return QRect{a_ViewX1, a_ViewY1, a_ViewX2 - a_ViewX1, a_ViewY2 - a_ViewY1};
}
QRect Schematic::viewportRect() {
return QRect{0, 0, viewport()->width(), viewport()->height()};
}
QPoint Schematic::modelToViewport(const QPoint& modelCoordinates)
{
return modelToContents(modelCoordinates) - QPoint{ contentsX(), contentsY() };
}
QPoint Schematic::viewportToModel(const QPoint& viewportCoordinates)
{
return contentsToModel(QPoint{ contentsX(), contentsY() } + viewportCoordinates);
}
QPoint Schematic::contentsToModel(const QPoint& coordinates)
{
QPoint modelCoords = coordinates / a_Scale;
modelCoords.setX(a_ViewX1 + modelCoords.x());
modelCoords.setY(a_ViewY1 + modelCoords.y());
return modelCoords;
}
QPoint Schematic::modelToContents(const QPoint& coordinates)
{
QPoint contentsCoords{coordinates.x() - a_ViewX1, coordinates.y() - a_ViewY1};
contentsCoords *= a_Scale;
return contentsCoords;
}
double Schematic::clipScale(double offeredScale)
{
if (offeredScale > a_maxScale) {
return a_maxScale;
} else if (offeredScale < a_minScale) {
return a_minScale;
} else {
return offeredScale;
}
}
bool Schematic::shouldRender(const double& newScale, const QRect& newModelBounds, const QPoint& toBeDisplayed, const QPoint& viewportCoords) {
const QRect currentModelBounds = modelRect();
const QPoint currenlyDisplayed = viewportToModel(viewportCoords);
return a_Scale != newScale || toBeDisplayed != currenlyDisplayed || currentModelBounds != newModelBounds;
}
double Schematic::renderModel(const double offeredScale, QRect newModel, const QPoint modelPoint, const QPoint viewportPoint)
{
assert(modelPoint.x() >= newModel.left() && modelPoint.x() <= newModel.right());
assert(modelPoint.y() >= newModel.top() && modelPoint.y() <= newModel.bottom());
assert(viewportPoint.x() >= 0 && viewportPoint.x() < viewport()->width());
assert(viewportPoint.y() >= 0 && viewportPoint.y() < viewport()->height());
const double newScale = clipScale(offeredScale);
if (!shouldRender(newScale, newModel, modelPoint, viewportPoint)) {
return a_Scale;
}
QSize viewportSizeOnModelPlane = viewportRect().size() / newScale;
QPoint vpTopLeftOnModelPlane{
modelPoint.x() - static_cast<int>(std::round(viewportPoint.x() / newScale)),
modelPoint.y() - static_cast<int>(std::round(viewportPoint.y() / newScale))
};
QRect viewportOnModelPlane{vpTopLeftOnModelPlane, viewportSizeOnModelPlane};
newModel |= viewportOnModelPlane;
a_ViewX1 = newModel.left();
a_ViewY1 = newModel.top();
a_ViewX2 = newModel.left() + newModel.width();
a_ViewY2 = newModel.top() + newModel.height();
a_Scale = newScale;
resizeContents(static_cast<int>(std::round(newModel.width() * a_Scale)),
static_cast<int>(std::round(newModel.height() * a_Scale)));
auto contentTopLeft = modelToContents(vpTopLeftOnModelPlane);
setContentsPos(contentTopLeft.x(), contentTopLeft.y());
viewport()->update();
return a_Scale;
}