[Users] [Bug 2577] Focus rectangle on folder list and message list headings doesn't get properly ...
Michael Shell
list1 at michaelshell.org
Tue Feb 21 18:25:32 CET 2012
Regarding bug 2577:
http://www.thewildbeast.co.uk/claws-mail/bugzilla/show_bug.cgi?id=2577
I encountered this one with Claws 3.8.0 and got curious and did a little
checking into it. I don't think it is cairo bug, but rather is a
real problem in Claws, although the versions of the various graphics
libraries a user is running and their specific X setup may affect
whether they see horizontal or vertical lines, or no rendering
artifacts at all, because we are dealing with 1/2 pixel issues.
On my Linux from Scratch system, on which I see only the horizontal line
problem, I have very recent versions of the gtk+ support libraries:
glib-2.31.16
pixman-0.24.4
cairo-1.10.2
pango-1.29.5
ATK-2.3.3
gdk-pixbuf-2.25.2
gtk-2.24.10
As pezcurrel did a great job pointing out back in January, the problem
code is found around line 933 in src/gtk/gtkcmctree.c:
/* draw focus rectangle */
if (clist->focus_row == row &&
gtk_widget_get_can_focus (widget) && gtk_widget_has_focus (widget))
{
if (!area || gdk_rectangle_intersect (area, &row_rectangle,
&intersect_rectangle))
{
cairo_set_line_width(cr, 1.0);
cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
gdk_cairo_set_source_color(cr, &style->fg[GTK_STATE_NORMAL]);
cairo_rectangle(cr, row_rectangle.x, row_rectangle.y,
row_rectangle.width + 1,
row_rectangle.height);
cairo_stroke(cr);
}
}
which has been redone in Claws 3.80 to use more modern cario-based gtk+
drawing over the older deprecated gdk interface. One aspect of the
problem is mentioned in the cairo FAQ under "How do I draw a sharp,
single-pixel-wide line?":
http://www.cairographics.org/FAQ/
Cairo draws lines *between* pixels, so a "one pixel" width stroke will
touch half of the pixels on either side of the path resulting in a
*two* pixel wide line. In Claws, the bottom (actual) two pixel wide line
will be 1 pixel outside the normally updated row region and will result
in phantom *horizontal* lines that persist until the screen region is
redrawn - such as when another window passes over.
So, with cairo, to draw a rectangle within the boundaries of another,
one must use some offsets:
x_origin + 1/2 line_width, y_origin + 1/2 line_width
with relative lengths:
x_width - linewidth, y_height - linewidth
which gives us:
cairo_rectangle(cr, row_rectangle.x + 0.5, row_rectangle.y + 0.5,
row_rectangle.width - 1,
row_rectangle.height - 1);
Which should fix the *horizontal* line problem, but this will not fix
the *vertical* line problem, and even probably will cause it to appear
where it did not before because the rectangle side edges will now always
be "properly" within the enclosing viewing rectangle.
The *vertical* line problem, which can happen when the Claws window is
made too narrow to display the message headings creating a scroll bar,
is caused by the fact that the left and right edges of the focus
rectangle, if they are rendered to either side of the message text (as
seems to be the design intent), will become part of the text and will
scroll with the message heading text and then another new focus
rectangle will get drawn with each scroll that will create yet another
set of vertical lines and so on and so on. These little lines will
persist until the window is redrawn such as when another window is made
active and then Claws is made active again. i.e., the focus rectangle
side edges are placed to either side of the text scroll *view port*
rather than only at the ends of the entire row to be highlighted.
Possible solutions here include:
1. forcing a redraw of the message text around the focus rectangle
side edges in the row that is in focus *during* a scroll and
before redrawing the focus rectangle (to erase the old edges);
2. drawing the focus rectangle edge(s) only at the actual end(s) of the
row (not the ends of the narrower scroll view port) when they are
visible. This may be preferable to #1 because text is never "cut"
by the side edges of the focus rectangle and there is less load
on the system due to less redrawing. But, one has to know when one
or both of these edges must be drawn, and so the focus rectangle
must be constructed out of individual lines so that only the
portions that are wanted will be drawn;
3. doing away with the rectangle side edges altogether and just using
the top and bottom focus "lines".
I don't have enough gtk/cairo knowledge to do #1 or #2, but #3 can be
done like this:
/* draw focus lines */
if (clist->focus_row == row &&
gtk_widget_get_can_focus (widget) && gtk_widget_has_focus (widget))
{
if (!area || gdk_rectangle_intersect (area, &row_rectangle,
&intersect_rectangle))
{
cairo_set_line_width(cr, 1.0);
cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
gdk_cairo_set_source_color(cr, &style->fg[GTK_STATE_NORMAL]);
cairo_move_to (cr, row_rectangle.x, row_rectangle.y + 0.5);
cairo_line_to (cr, row_rectangle.x + row_rectangle.width, row_rectangle.y + 0.5);
cairo_move_to (cr, row_rectangle.x, row_rectangle.y + row_rectangle.height - 0.5);
cairo_line_to (cr, row_rectangle.x + row_rectangle.width, row_rectangle.y + row_rectangle.height - 0.5);
cairo_stroke(cr);
}
}
AFAIK, we don't need to use offset stuff like row_rectangle.x + 0.5
because cairo won't stroke pixels (just) beyond the end limits of the
stroke path (it's *pixels* along, perpendicular to, the stoke path that
are tricky with "off by 1/2 line width errors"). I *think*.
To me, a 1 pixel line seems to be a bit narrow. The focus rectangle is
tough to see in any case if the text is black and the highlight color
is dark blue - it is most easily seen on messages that have been set to
a color label of red.
So, here is my 2 pixel wide version:
/* draw focus lines */
if (clist->focus_row == row &&
gtk_widget_get_can_focus (widget) && gtk_widget_has_focus (widget))
{
if (!area || gdk_rectangle_intersect (area, &row_rectangle,
&intersect_rectangle))
{
cairo_set_line_width(cr, 2.0);
cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
gdk_cairo_set_source_color(cr, &style->fg[GTK_STATE_NORMAL]);
cairo_move_to (cr, row_rectangle.x, row_rectangle.y + 1);
cairo_line_to (cr, row_rectangle.x + row_rectangle.width, row_rectangle.y + 1);
cairo_move_to (cr, row_rectangle.x, row_rectangle.y + row_rectangle.height - 1);
cairo_line_to (cr, row_rectangle.x + row_rectangle.width, row_rectangle.y + row_rectangle.height - 1);
cairo_stroke(cr);
}
}
which works just fine for me.
Cheers,
Mike Shell
More information about the Users
mailing list