Community
Participate
Working Groups
Build ID: M20070212-1330 More information: TextFlow visualizes a truncated TextFragmentBox by appending "..." to it. This happens on a line-by-line basis -- whenever a line does not fit entirely into the bounding box, the ellipsis is drawn. It does not visualize truncated text in the vertical direction, though. E.g. if the last line of text is clipped by the bounding box, TextFlow happily paints the upper lines as is, without a hint that the last line is omitted. I propose adding the "..." also if one or more lines are left out. If you agree with me that this would be useful, I would attach a patch to TextFlow.paintFigure(), that basically checks if the "next" line would be visible before painting the "current" line. If it wouldn't be visible, then the "..." will be drawn for the current line.
The Block's FlowContext would have to indicate how much vertical space it is allowed to have. Once you know that, TextFlow could decide to just stop generating fragments and truncate the last one before it exceeds the available height. Determining if a fragment would be visible to the user is too expensive and has a vague definition.
(In reply to comment #1) > Determining if a fragment would be visible to the user is too expensive and has > a vague definition. In my case it was sufficient to use the clipping area of the Graphics object, but I agree that giving that information to the FlowContext would be better.
Created attachment 74560 [details] Cherie's fix I have attached a patch with my fix. The patch includes duplicate Draw2D examples to test this. I don't plan on committing these, but will create one example to test this. How it works: After the FlowPage finishes the layout() it checks if the size of its blockbox is greater than the recommended size it has been given and if so sets the point at which to truncate (leaving enough space for the truncation decoration). When each TextFlow paints its fragments, it does not paint a line of text if the text will not completely fix above the truncation point. When the FlowPage paints, it paints the truncation decoration (an arrow for now) if required. FlowFigure • Added a shouldTruncateVertically () method that clients can set to get the truncation behavior. • Added a new method getTruncationPoint() which returns the point where this figure is to be truncated. This method returns null if there is to be no truncation. The truncation point is set in the postValidate() method after the FlowPage has layed out. • Paints a little triangle if it is truncated (i.e. the preferred size is bigger than the actual size). This triangle will most likely change. I need to ask the icon creation people for some help. TextFlow • Modified the paint method so that any fragments that extend into the area beyond the truncation point will not be painted. FlowPage and BlockFlowLayout were also modified so that a layout will occur if the height of the figure changes. Previously, a layout only occurred if the width changed. Although I have decided to go with the approach of not painting text below the truncation point instead of not laying out figures below the truncation point, it is possible that a client could have a scenario where the layout is affected which is why I think the change in height should trigger a re-layout. I had a hard time understanding what the pageWidth and recommendedWidth are (especially since the method called getPageWidth() returns the recommendedWidth. The attached patch has changed these to be pageSize and recommendedSize already, but I would also like to rename them. • I would like to rename pageSize to preferredSize. It is only represented internally and represents the preferred full-figure size. • I would like to rename the method getPageSize() (which gets the recommendedSize) to be called getRecommendedSize().
Randy, can you review this when you have time?
Created attachment 74568 [details] Correct a small error in FlowPage I noticed that when I paste text in the cell editor in a truncated label on the logic diagram that the truncation decoration doesn't show up afterwards. I need to trigger the postValidate() call somehow after the text editor appears since it using the same FlowPage (untruncated) in the text cell editor. I'll have to think about this.
Hi Randy. There are probably still a couple bugs with this one, but I would like to get your opinion on the general approach before spending more time on it. Thanks!
Sorry for the delay. Here are some comments... (I haven't applied the patch yet because I haven't moved my projects to point to the new CVS root) It seems like FlowPage is going to paint a triangle at the bottom if its block is larger than its client area. This sounds like a easy approach that communicates that some information is not visible. It seems like this decoration would even work without all of the other changes. Also, it could be done in a subclass without changing FlowPage. The only behavior that I see as a result of the remaining changes is that text in a partially visible line doesn't paint. I think this step could be avoided. You could let the text be painted and then paint over it with the background color before painting the decoration (kind of like the palette's scrollbar buttons). Or you could extend TextFlow so that it clips the graphics before calling super.paint(g). IMO, the overall behavior seems too specific to belong in the base text package and doesn't handle some text constructs, like Inline and TextFlow figures with borders, blocks which paint, or blocks with relative coordinate systems. Handling these cases wouldn't be worth the effort when a domain-specific solution like painting a simple decoration on a [custom] FlowPage exists.
Created attachment 77662 [details] Patch to expose API. Thanks for your feedback, Randy. It seems to be rather difficult to create a generic solution for this, so I guess I will add this behavior in GMF to our particular label figure which contains one TextFlow. In this case, I would rather extend the TextFlow paintFigure() method to avoid having the lines half painted. In order to do this, I need access to some methods that are called in the TextFlow paintFigure() method. I have attached a patch that shows methods I would like to make API.
The way TextFlow paints each substring currently is: paintText(g, draw, frag.getX(), frag.getBaseline() - getAscent(), frag.getBidiLevel()); getAscent() could be replaced with frag.getAscent() also: bottom = frag.getBaseline() + frag.getDescent() These existing methods are already API. Is it necessary to determine truncation using the line root instead of the text fragments? Making getLargestSubstringConfinedTo public seems safe.
I would like to stop painting the text when I hit a text fragment that won't be completely shown and I would like to change the text in the previous fragment to add "...". In order to do this, I need to override the paintFigure() method which means essentially copying the existing code there which accesses the private methods because I can't call super.paintFigure() method if I want to truncate the text before painting. I was also using getLineRoot().getVisibleBottom() to see if the text will be cut off.
(In reply to comment #10) FWIW, we're in exactly the same situation, with a TextFlow subclass and a copy of paintFigure(), calling all those private methods through reflection. An improvement here would be very welcome.
> I was also using getLineRoot().getVisibleBottom() to see if the text will be > cut off. Unless you have a border, it should be the case that: frag.getLineRoot().getVisibleBottom() == frag.getBaseline() + frag.getDescent();
> (In reply to comment #10) > FWIW, we're in exactly the same situation, with a TextFlow subclass and a copy > of paintFigure(), calling all those private methods through reflection. An > improvement here would be very welcome. Can you describe what you're trying to do in the subclass?
(In reply to comment #13) > Can you describe what you're trying to do in the subclass? We're adding an ellipsis to the last visible line of a vertically truncated multiline textflow. We also try to merge as much of the first invisible line into the last visible line before adding the ellipsis.
The code I am adding to the subclass is not really the problem, it is the fact that I have to copy the code from TextFlow.paintFigure(). Here is my subclass code. The section in surrounded in ///////// is the part I added. protected void paintFigure(Graphics g) { TextFragmentBox frag; g.getClip(Rectangle.SINGLETON); int yStart = Rectangle.SINGLETON.y; int yEnd = Rectangle.SINGLETON.bottom(); int maxHeight = getParent().getClientArea().bottom(); for (int i = 0; i < getFragments().size(); i++) { frag = (TextFragmentBox) getFragments().get(i); if (frag.offset == -1) continue; // Loop until first visible fragment if (yStart > frag.getLineRoot().getVisibleBottom() + 1)// The + 1 // is for // disabled // text continue; // Break loop at first non-visible fragment if (yEnd < frag.getLineRoot().getVisibleTop()) break; String draw = getBidiSubstring(frag, i); ///////////////////////////////////////////// // If the next fragment will not be completely visible, then // truncate this fragment. boolean truncate = frag.isTruncated(); if (i + 1 < getFragments().size() && maxHeight < ((TextFragmentBox) getFragments().get(i + 1)) .getLineRoot().getVisibleBottom()){ draw = truncateText(draw); truncate = true; // increment the counter so no further fragments will be processed i = getFragments().size(); } if (truncate) draw += ELLIPSIS; ///////////////////////////////////////////// if (!isEnabled()) { Color cachedfgColor = g.getForegroundColor(); g.setForegroundColor(ColorConstants.buttonLightest); paintText(g, draw, frag.getX() + 1, frag.getBaseline() - getAscent() + 1, frag.getBidiLevel()); g.setForegroundColor(ColorConstants.buttonDarker); paintText(g, draw, frag.getX(), frag.getBaseline() - getAscent(), frag.getBidiLevel()); g.setForegroundColor(cachedfgColor); } else { paintText(g, draw, frag.getX(), frag.getBaseline() - getAscent(), frag.getBidiLevel()); } } I tried getting rid of the calls to frag.getLineRoot().getVisibleBottom() and frag.getLineRoot().getVisibleTop() and using the fragment's baseline, ascent, and descent and that worked fine. So, now all I need to expose is: - change getBidiSubstring(TextFragmentBox box, int index) to protected - change paintText(Graphics g, String draw, int x, int y, int bidiLevel) to protected - change static variable ELLIPSIS to public (I want to use this elsewhere as well).
Created attachment 78760 [details] final patch Updated patch code reviewed by Anthony.
Committed to HEAD. Part of the fix was commited as part of Bug 194278