apalomer
apalomer

Reputation: 1925

QFont error fitting text into specific height

I need to draw a scale onto the foreground of a QGraphicsView. For that, I create a class that inherits from it and reimplement drawForeground (it could also be done using a custom graphics item with a high z value but to make sure that nothing is drawn on it I decided that drawForeground is a better solution). In this method, I draw the scale (rectangle with black and white boxes) as I want and it has the desired behavior. However, the part of computing the size of the label that I need to show on my scale is not working. That labels of my scale should always have the same height with respect to the screen. This means that, as we zoom in and out, the font size needs to be computed to fit a rectangle that is always 10 pixels height. Computing the text height is copied from here

Here is my class implementation:

Header

#ifndef CUSTOMGRAPHICSVIEW_H
#define CUSTOMGRAPHICSVIEW_H

#include <QGraphicsView>

class CustomGraphicsView : public QGraphicsView
{
  Q_OBJECT
public:
  CustomGraphicsView(QWidget* parent = nullptr);

protected:
  virtual void wheelEvent(QWheelEvent* event) override;

  virtual void drawForeground(QPainter* painter, const QRectF& rect);

  void scalePainterFontSizeToFit(QPainter* painter, float heightToFitIn);

  virtual void contextMenuEvent(QContextMenuEvent* event);

  QAction* action_show_text_;
};

#endif  // CUSTOMGRAPHICSVIEW_H

Cpp

#include "customgraphicsview.h"

#include <QAction>
#include <QDebug>
#include <QMenu>
#include <QWheelEvent>

#include <math.h>

CustomGraphicsView::CustomGraphicsView(QWidget* parent) : QGraphicsView(parent)
{
  setScene(new QGraphicsScene);
  action_show_text_ = new QAction("Show text");
  action_show_text_->setCheckable(true);
  action_show_text_->setChecked(false);
}

void CustomGraphicsView::drawForeground(QPainter* painter, const QRectF& rect)
{
  // Select scale
  std::vector<double> steps = { 0.1, 0.15, 0.25, 0.5, 1, 2, 5, 10, 15, 25, 50 };
  qDebug() << "Rect: " << rect.topLeft() << "->" << rect.bottomRight();
  int target_y = 10;
  double step0 = (rect.bottom() - rect.top()) / target_y;
  double step = step0;
  double min_d = 1000000;
  for (size_t i = 0; i < steps.size(); i++)
  {
    if (fabs(steps[i] - step0) < min_d)
    {
      step = steps[i];
      min_d = fabs(steps[i] - step0);
    }
  }
  qDebug() << "Step: " << step;

  // Bottom right scale bar corner
  QPointF aux = mapToScene(QPoint(10, 10)) - rect.topLeft();
  QPointF br = mapToScene(mapFromScene(rect.bottomRight()) - QPoint(10, 10));

  // Draw outside rectangle
  painter->setPen(QPen(Qt::black, step * 0.01, Qt::SolidLine, Qt::SquareCap));
  painter->drawRect(QRectF(br - QPointF(4 * step, aux.y()), br));

  // Draw left half
  painter->fillRect(QRectF(br - QPointF(4 * step, aux.y()), br - QPointF(3 * step, aux.y() / 2)), Qt::black);
  painter->fillRect(QRectF(br - QPointF(4 * step, aux.y() / 2), br - QPointF(3 * step, 0)), Qt::white);

  // Draw right half
  painter->fillRect(QRectF(br - QPointF(3 * step, aux.y()), br - QPointF(2 * step, aux.y() / 2)), Qt::white);
  painter->fillRect(QRectF(br - QPointF(3 * step, aux.y() / 2), br - QPointF(2 * step, 0)), Qt::black);
  painter->fillRect(QRectF(br - QPointF(2 * step, aux.y()), br - QPointF(0, aux.y() / 2)), Qt::black);
  painter->fillRect(QRectF(br - QPointF(2 * step, aux.y() / 2), br), Qt::white);

  // Add texts
  if (action_show_text_->isChecked())
  {
    QRectF rect_text(br - QPointF(5 * step, aux.y() * 2.1), br - QPointF(3 * step, aux.y() * 1.1));
    scalePainterFontSizeToFit(painter, rect_text.height());
    painter->drawText(rect_text, Qt::AlignCenter, QString::number(0));
    rect_text = QRectF(br - QPointF(4 * step, aux.y() * 2.1), br - QPointF(2 * step, aux.y() * 1.1));
    painter->drawText(rect_text, Qt::AlignCenter, QString::number(step));
    rect_text = QRectF(br - QPointF(3 * step, aux.y() * 2.1), br - QPointF(1 * step, aux.y() * 1.1));
    painter->drawText(rect_text, Qt::AlignCenter, QString::number(2 * step));
    rect_text = QRectF(br - QPointF(1 * step, aux.y() * 2.1), br - QPointF(-1 * step, aux.y() * 1.1));
    painter->drawText(rect_text, Qt::AlignCenter, QString::number(4 * step));
  }
}

void CustomGraphicsView::scalePainterFontSizeToFit(QPainter* painter, float heightToFitIn)
{
  float oldFontSize, newFontSize, oldHeight;
  QFont r_font = painter->font();

  // Init
  oldFontSize = r_font.pointSizeF();

  // Loop
  for (int i = 0; i < 3; i++)
  {
    qDebug() << i << "a";
    oldHeight = painter->fontMetrics().boundingRect('D').height();
    qDebug() << i << "b";
    newFontSize = (heightToFitIn / oldHeight) * oldFontSize;
    qDebug() << i << "c";
    r_font.setPointSizeF(newFontSize);
    qDebug() << i << "d";
    painter->setFont(r_font);
    qDebug() << i << "e";
    oldFontSize = newFontSize;
    qDebug() << "OldFontSize=" << oldFontSize << "HtoFitIn=" << heightToFitIn << "  fontHeight=" << oldHeight
             << "newFontSize=" << newFontSize;
  }

  // End
  r_font.setPointSizeF(newFontSize);
  painter->setFont(r_font);
}

void CustomGraphicsView::wheelEvent(QWheelEvent* event)
{
  // if ctrl pressed, use original functionality
  if (event->modifiers() & Qt::ControlModifier)
    QGraphicsView::wheelEvent(event);
  // otherwise, do yours
  else
  {
    setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
    if (event->delta() > 0)
    {
      scale(1.1, 1.1);
    }
    else
    {
      scale(0.9, 0.9);
    }
  }
}

void CustomGraphicsView::contextMenuEvent(QContextMenuEvent* event)
{
  // Create menu
  QMenu menu(this);

  // Add Fit in view
  menu.addAction(action_show_text_);

  // Exectue menu
  menu.exec(event->globalPos());
}

The text constant height more or less works although it is not always the exact same height and some times it appears cropped. Moreover, the most important error is that, after a certain zoom, the one that sets setPointSizeF to 0.36127, the applications freezes at the line oldHeight = painter->fontMetrics().boundingRect('D').height(); (seen from the qDebug messages).

My questions are:

  1. How can I force the height of my text to be constant so the user doesn't see a text size change as he/she zooms in/out?
  2. Why does oldHeight = painter->fontMetrics().boundingRect('D').height(); freezes my application after a certain font point size is set? How can I solve this?

To test the sample code you have to show the scale labels by activating them in the context menu from the left click on the graphics view.

Upvotes: 0

Views: 252

Answers (1)

Dimitry Ernot
Dimitry Ernot

Reputation: 6584

When the function drawForeground is called, the scale is already applied to your painter.

You can remove it calling painter->resetMatix() (but, it will remove all transformations including rotation and shear. Maybe, You should recompute a new matrix without the scale factor).

The text will always be painted with the same height. But, your painter will draw the text at the "real" position. You have to apply the transformation to your QRect to fix it:

painter->save(); // Save the transformation

QTransform matrix(painter->transform()); // Get the current matrix containing the scale factor

painter->resetMatrix(); // Remove transformations

QRectF rect_text(br - QPointF(5 * step, aux.y() * 2.1), br - QPointF(3 * step, aux.y() * 1.1));
rect_text = matrix.mapRect(rect_text); // Get the position of rect_text with the right scale

painter->drawText(rect_text, Qt::AlignCenter, QString::number(0));

painter->restore(); // Reset the transformation

Upvotes: 1

Related Questions