QFontMetrics returns inaccurate results

4k views Asked by At

I have a custom delegate in my QTableWidget to hightlight matches if a user searches something. Unfortunately the rectangle position does often not really fit This happens on some characters or phrases or depending on the number of matches or the size of the leading string. I can't find something specific causing this. Here is one example: Example.

This is my paint routine (a bit messy from all the trial and error trying to fix the issue):

void custom_delegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const{

    const QTableWidget* table_widget = qobject_cast<const QTableWidget*>(qstyleoption_cast<const QStyleOptionViewItemV3*>(&option)->widget);
    const int cell_width = table_widget->columnWidth(index.column());

    // basic table cell rectangle
    QRect rect_a = option.rect;

    // adjust rectangle to match text begin
    QStyle* style;
    if(table_widget != 0){
        style = table_widget->style();
    }else{
        style = QApplication::style();
    }
    const int text_horizontal_margin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, table_widget) + 1;
    QRect rect_b = rect_a.adjusted(text_horizontal_margin, 0, -text_horizontal_margin, 0);

    // adjust rectangle to match text height
    QFont cell_font = index.model()->data(index, Qt::FontRole).value<QFont>();
    cell_font.setPointSize(9);
    QFontMetrics fm(cell_font);
    const int height = fm.height();

    rect_b.setY(rect_a.y() + (rect_a.height() - height)/2);
    rect_b.setHeight(height);

    // displayed text
    std::string cell_text = qstrtostr(fm.elidedText(index.model()->data(index, Qt::DisplayRole).toString(),Qt::ElideRight,rect_a.width()));
    int found_pos = find_ci(cell_text, this->filter_string, 0);
    int old_pos = 0;
    int found_width = 0;
    QRect rect_c = rect_b;

    // find occurence of filter string in cell_text
    while(found_pos != std::string::npos){

        std::string front = cell_text.substr(0, found_pos);
        rect_c.setX(rect_b.x() + fm.tightBoundingRect(QString::fromStdString(front)).width());
        rect_c.setWidth(fm.width(QString::fromStdString(cell_text.substr(found_pos, this->filter_string.size()))));
        painter->fillRect(rect_c, Qt::yellow);
        old_pos = found_pos+1;
        found_pos = find_ci(cell_text, this->filter_string, old_pos);
    }
}

Notes: filter_string is the string searched for, find_ci is just a wrapper for std::string::find including case-insensitivity but not important here as this test case is fully lower-case and I use std::string for non-qt stuff.

Edit: For the width calculation I tried fm.tightBoundingRect().width(), fm.boundingRect.width() and fm.width() with different but never correct results.

I use Qt 5.2

1

There are 1 answers

0
BartoszKP On

In my case I got the desired result with the following hack:

auto initialRect = fm.boundingRect(text);
auto improvedRect = fm.boundingRect(initialRect, 0, text);

It's not entirely clear why the other overload of boundingRect returns the correct result, but it may be just accidental, because as the documentation states:

The bounding rectangle returned by this function is somewhat larger than that calculated by the simpler boundingRect() function. This function uses the maximum left and right font bearings as is necessary for multi-line text to align correctly. Also, fontHeight() and lineSpacing() are used to calculate the height, rather than individual character heights.

The width method you propose also will return larger result, but it does not seem correct, as it should be used only when you need a position for a next word:

[...] width() returns the distance to where the next string should be drawn.

Also, sometimes it matters whether you pass the result of painter.device() to QFontMetrics constructor.