Word wrapping inside a TitledPane that is inside VBox - scala

I have TitledPanes which contain large amounts of text. The TitledPanes are put inside of a VBox to lay them out vertically. But, when placed in a VBox, the TitlePane's width becomes the full width of the text instead of wrapping the text. How do I make it so that TitlePane's width is that of the available area, wrapping it's content, if necessary?
In this example, the text wrapping works as intended, but there's no VBox, so you can't have more than one TitledPane.
package nimrandsLibrary.fantasyCraft.characterBuilder
import javafx.scene.control._
import javafx.scene.layout._
import javafx.scene._
class TiltedTextExample extends javafx.application.Application {
def start(stage : javafx.stage.Stage) {
val titledPane = new TitledPane()
titledPane.setText("Expand me!")
val label = new Label("Some really long text here. Some really long text here. Some really long text here. Some really long text here. Some really long text here. Some really long text here. Some really long text here. Some really long text here. Some really long text here. Some really long text here. Some really long text here. Some really long text here. Some really long text here. Some really long text here. Some really long text here.")
label.setWrapText(true)
titledPane.setContent(label)
stage.setScene(new Scene(titledPane, 300, 300))
stage.show()
}
}
object TiltedTextExample extends App {
javafx.application.Application.launch(classOf[TiltedTextExample])
}
In this example, the TitledPane is placed inside a VBox so that multiple TitlePanes can be added and stacked vertically. Inexplicably, this breaks the word wrapping behavior.
package nimrandsLibrary.fantasyCraft.characterBuilder
import javafx.scene.control._
import javafx.scene.layout._
import javafx.scene._
class TiltedTextExample extends javafx.application.Application {
def start(stage : javafx.stage.Stage) {
val titledPane = new TitledPane()
titledPane.setText("Expand me!")
val label = new Label("Some really long text here. Some really long text here. Some really long text here. Some really long text here. Some really long text here. Some really long text here. Some really long text here. Some really long text here. Some really long text here. Some really long text here. Some really long text here. Some really long text here. Some really long text here. Some really long text here. Some really long text here.")
label.setWrapText(true)
titledPane.setContent(label)
val vBox = new VBox()
vBox.getChildren().add(titledPane)
stage.setScene(new Scene(vBox, 300, 300))
stage.show()
}
}
object TiltedTextExample extends App {
javafx.application.Application.launch(classOf[TiltedTextExample])
}

I have to delve deep (looking through the JavaFX code itself), but I found a workable, but not ideal, solution.
The problem basically boiled down to the fact that the TitledPane was not coded to support a horizontal content bias (where its min/preferred height is calculated based on its available width). So, even though its content, the Label, supports this functionality, its rendered mote by the TitledPane, which calculates its minimum and preferred dimensions without the consideration of the other, in which case the Label control simply requests a width and height necessary to display all its text on one line.
To overrride this behavior, I had to override three functions, two in the TitledPane itself, and one in the TitlePane's default skin (which actually does the dimension calculations for TitledPane). First, I override the getContentBias return HORIZONTAL. Then, I override the getMinWidth to return 0. Finally, I update the skin's computePrefHeight function to take into account the available width when its asking its children how much height they need.
Here is the code:
package nimrandsLibrary.fantasyCraft.characterBuilder
import javafx.scene.control._
import javafx.scene.layout._
import javafx.scene._
class TiltedTextExample extends javafx.application.Application {
private def createTitledPane(title: String) = {
val titledPane = new HorizontalContentBiasTitledPane()
titledPane.setText(title)
val label = new Label("Some really long text here. Some really long text here. Some really long text here. Some really long text here. Some really long text here. Some really long text here. Some really long text here. Some really long text here. Some really long text here. Some really long text here. Some really long text here. Some really long text here. Some really long text here. Some really long text here. Some really long text here.")
label.setWrapText(true)
titledPane.setContent(label)
titledPane.setExpanded(false)
titledPane
}
def start(stage: javafx.stage.Stage) {
val vBox = new VBox()
vBox.getChildren().add(createTitledPane("Expand Me #1"))
vBox.getChildren().add(createTitledPane("Expand Me #2"))
vBox.getChildren().add(createTitledPane("Expand Me #3"))
stage.setScene(new Scene(vBox, 300, 300))
stage.show()
}
}
object TiltedTextExample extends App {
javafx.application.Application.launch(classOf[TiltedTextExample])
}
class HorizontalContentBiasTitledPane extends javafx.scene.control.TitledPane {
override def getContentBias() = javafx.geometry.Orientation.HORIZONTAL
override def computeMinWidth(height: Double) = 0
this.setSkin(new com.sun.javafx.scene.control.skin.TitledPaneSkin(this) {
private val getTransitionMethod = classOf[com.sun.javafx.scene.control.skin.TitledPaneSkin].getDeclaredMethod("getTransition")
getTransitionMethod.setAccessible(true)
override protected def computePrefHeight(width: Double) = {
val contentContainer = getChildren().get(0)
val titleRegion = getChildren().get(1)
val headerHeight = Math.max(com.sun.javafx.scene.control.skin.TitledPaneSkin.MIN_HEADER_HEIGHT, snapSize(titleRegion.prefHeight(-1)));
var contentHeight = 0.0;
if (getSkinnable().getParent() != null && getSkinnable().getParent().isInstanceOf[com.sun.javafx.scene.control.skin.AccordionSkin]) {
contentHeight = contentContainer.prefHeight(width);
} else {
contentHeight = contentContainer.prefHeight(width) * getTransitionMethod.invoke(this).asInstanceOf[java.lang.Double].toDouble
}
headerHeight + snapSize(contentHeight) + snapSpace(getInsets().getTop()) + snapSpace(getInsets().getBottom());
}
})
}
This works for my use case, but there are some limitations, some of which can be worked around, and some that cannot.
This code is brittle, as it depends on specific implementation details of Java FX, especially the part where I have to call a private method on TitlePane's default skin. I don't see any easy way to fix this, other than to re-implement TitlePane's default skin in its enteritety (which I guess isn't too difficult, since the code is available).
Clamping the minWidth of the TitlePane to 0 it potentially problematic. A more robust algorithm might be necessary, depending on the use case.
Similarly, hard-coding title pane's content bias to HORIZONTAL isn't ideal. A more robust solution would be to use the content bias of its content, and make the dimension calculations of the control work based on that content bias.
Despite the limitations, this code seems to work for me, and I think the modifications to make it more robust are fairly straightforward. However, if anyone has a less drastic solution, or can improve on mine, please contribute.

Use Text instead of label. Example is as below,
#Override
public void start(Stage primaryStage) {
StackPane root = new StackPane();
primaryStage.setScene(new Scene(root, 300, 250));
primaryStage.setTitle("Hello World!");
VBox vBox = new VBox();
TitledPane tPane = new TitledPane();
tPane.setText("expand me");
tPane.setPrefWidth(root.getWidth());
Text text = new Text();
text.setText("Some really long text here. Some really long text here. Some really long text here. Some really long text here. Some really long text here. Some really long text here. Some really long text here. Some really long text here. Some really long text here. Some really long text here. Some really long text here. Some really long text here. Some really long text here. Some really long text here. Some really long text here.");
text.setWrappingWidth(tPane.getPrefWidth());
tPane.setContent(text);
vBox.getChildren().add(tPane);
root.getChildren().add(vBox);
primaryStage.show();
}
The titlepane of javafx will take the maximum width of its children.
Result:

Related

ProgressBar framerate drops when Label draws on top

I'd not normally ask for help here, but I'm stumped - this bug is the strangest thing I've seen in a long time.
https://gfycat.com/FluidFrigidEastsiberianlaika
I've got a simple UI object called GhostProgressBar that extends ScalaFX.StackPane and gives it two children - a ProgressBar and a Label. I noticed after adding it to some other UI screens that my framerate had plummeted, to a point where the UI was painfully unusuable.
The code for this is super simple:
import scalafx.geometry.Pos
import scalafx.scene.control.{Label, ProgressBar}
import scalafx.scene.layout.StackPane
class GhostProgressBar extends StackPane {
alignment = Pos.Center
val bar = new ProgressBar() {
prefWidth = Integer.MAX_VALUE
}
val text = new Label() {
id = "ProgressBarText"
text = "PERFORMANCE TESTING"
}
children = List(bar, text)
}
In the GIF I'm using it inside a VBox that's the center element of a regular BorderPane - nothing strange or atypical.
From the behaviour I've observed, I think it's an issue with text being drawn over the bar of the ProgressBar. Just now I've done some more debugging, and my suspicions that it was related to the styling of the text were confirmed.
This is the styling that's on the text in the GIF.
#ProgressBarText {
-fx-text-fill: #dddddd;
-fx-font-weight: bold;
}
#ProgressBarText .text {
-fx-stroke: #333333;
-fx-stroke-width: 1px;
-fx-stroke-type: outside;
}
When I remove that styling, the framerate doesn't drop when the bar hits the text.
What I can't figure out is why this is happening? Anyone got any ideas? I have no idea if it's a Scala thing, or a ScalaFX thing, whether or not it's reproducible with the same stuff in a JavaFX context.
Help would be appreciated.
EDIT: I was asked for versions, here we go:
Scala version: 2.12.7
ScalaFX version: 8.0.102-R11
JDK version: 1.8.0_181
JavaFX version: unknown, I'm not familiar with ScalaFX's internals and I'm not using JavaFX directly.
EDIT 2: I was asked to try the same screen elements, but using JavaFX elements instead of ScalaFX ones. Here's the code I used, the outcome was the same - whenever the outlined text was over the progress bar's bar, the framerate dropped.
import javafx.geometry.Pos
import javafx.scene.control.{Label, ProgressBar}
import javafx.scene.layout.StackPane
class JavaFXGhostProgressBar extends StackPane {
this.setAlignment(Pos.CENTER)
val bar = new ProgressBar()
bar.setMaxWidth(Double.MaxValue)
val text = new Label()
text.textProperty().setValue("PERFORMANCE TESTING")
text.idProperty().setValue("OutlineProgressBarText")
this.getChildren.addAll(bar, text)
}
I couldn't find out what version of JavaFX I used here; IntelliJ was weirdly inconsiderate in not telling me. I couldn't find it in my external libraries list, either.

Gtk (mm) limit width of combobox

Because I use Comboboxes that may contain text entries of very long size,
which leads to the combobox increasing its width far beyond reasonable size,
I am trying to give a maximum width to the combobox.
If I am doing this like this:
class MyCombo : public Gtk::ComboBox {
private:
CellRendererText render;
public:
MyCombo() {
render.property_width_chars() = 10;
render.property_ellipsize() = Pango::ELLIPSIZE_END;
pack_start(render, true);
}
};
The result will be an empty cell of the desired width, which seems logical since I did not specify which column to show. But how can I do this with that attempt? Using pack_start will just bypass the renderer...
Another approach is this one:
class MyCombo : public Gtk::ComboBox {
private:
CellRendererText render;
public:
MyCombo() {
pack_start(render, true);
set_cell_data_func(render, sigc::mem_fun(*this, &MyCombo::render_iter));
}
void render_iter(const TreeModel::const_iterator& iter) {
Glib::ustring data = get_string_from_iter(iter);
int desired_width_chars = 10; //for example
render.property_text() = ellipsize_string(data, desired_width_chars);
}
};
Using that approach, it works, but the text in the popup (what opens up when u click the combobox) is also shortened which is not what I want (obviously the user should be able to read the whole string and I dont care about the popup widht.)
Can you please help me with this? I would be happy for any advice/alternative solutions.
Regards tagelicht
NOTE: set_wrap_width is a function that wraps the total number of entries in the combo box over a number of columns specified; it does not answer the question.
Using set_wrap_width(1) | Using set_wrap_width(5)
Following Noup's answer as a guide I managed to get the below code; which directly answers the question and its requirements (C++/Gtkmm).
// Get the first cell renderer of the ComboBox.
auto v_cellRenderer = (Gtk::CellRendererText*)v_comboBox.get_first_cell();
// Probably obsolete; Sets character width to 1.
v_cellRenderer->property_width_chars() = 1;
// Sets the ellipses ("...") to be at the end, where text overflows.
// See Pango::ELLIPSIZE enum for other values.
v_cellRenderer->property_ellipsize() = Pango::ELLIPSIZE_END;
// Sets the size of the box, change this to suit your needs.
// -1 sets it to automatic scaling: (width, height).
v_cellRenderer->set_fixed_size(200, -1);
Result (image):
Result of code
BE AWARE: Depending on where you perform the above code; either all the cells will be the same size, or just the box itself (intended).
From experimenting, I've found:
In the parent object constructor: All cell sizes are the same.
In a separate function: Only the first cell (the box) is affected.
I'd recommend you put the code in a function that's connected to the comboBox's changed signal, such as:
v_comboBox.signal_changed().connect(sigc::mem_fun(*this, &YourClass::comboBox_changedFunction));
This may be what you are looking for:
cell_renderer_text.set_wrap_width(10)
This is for Python, but you get the idea :-)
Unfortunately, the documentation is scarce. I found this by poking around in Anjuta/Glade.
Edit:
the docs are here. They are not overly helpful, but they do exist.
As an alternative, the following works for me without having to set wrap_width nor to subclass ComboBox (in Gtk#):
ComboBoxText cb = new ComboBoxText();
cb.Hexpand = true; //If there's available space, we use it
CellRendererText renderer = (cb.Cells[0] as CellRendererText); //Get the ComboBoxText only renderer
renderer.WidthChars = 20; //Always show at least 20 chars
renderer.Ellipsize = Pango.EllipsizeMode.End;
Note: I'm using Expand to use space if it's available. If you just want to keep the combo box on a fixed width, just remove that bit.

TextMarginFinder to verify printability

I am attempting to use TextMarginFinder to prove that odd and even pages back up correctly when printing. I have based my code on:
http://itextpdf.com/examples/iia.php?id=280
The issue I have is that on odd pages I am looking for the box to be aligned to the left showing a 1CM back margin for example, and on an even page I would expect the page box to be aligned to the right also showing a 1CM back margin. Even in the example above this is not the case, but when printed the text does back up perfectly because the Trim Box conforms.
In summary I believe on certain PDF files the TextMarginFinder is incorrectly locating the text width, usually on Even pages. This is evident by the width being greater than the actual text. This is usually the case if there are slug marks outside of the Media Box area.
In the PDF the OP pointed to (margins.pdf from the iText samples themselves) indeed the box is not flush with the text:
If you look into the PDF Content, though, you'll see that many of the lines have a trailing space character, e.g. the first line:
(s I have worn out since I started my ) Tj
These trailing space characters are part of the text and, therefore, the box does not flush with the visible text but it does with the text including such space characters.
If you want to ignore such space characters, you can try doing so by filtering such trailing spaces (or for the sake of simplicity all spaces) before they get fed into the TextMarginFinder. To do this I'd explode the TextRenderInfo instances character-wise and then filter those which trim to empty strings.
A helper class to explode the render info objects:
import com.itextpdf.text.pdf.parser.ImageRenderInfo;
import com.itextpdf.text.pdf.parser.RenderListener;
import com.itextpdf.text.pdf.parser.TextRenderInfo;
public class TextRenderInfoSplitter implements RenderListener
{
public TextRenderInfoSplitter(RenderListener strategy) {
this.strategy = strategy;
}
public void renderText(TextRenderInfo renderInfo) {
for (TextRenderInfo info : renderInfo.getCharacterRenderInfos()) {
strategy.renderText(info);
}
}
public void beginTextBlock() {
strategy.beginTextBlock();
}
public void endTextBlock() {
strategy.endTextBlock();
}
public void renderImage(ImageRenderInfo renderInfo) {
strategy.renderImage(renderInfo);
}
final RenderListener strategy;
}
Using this helper you can update the iText sample like this:
RenderFilter spaceFilter = new RenderFilter() {
public boolean allowText(TextRenderInfo renderInfo) {
return renderInfo != null && renderInfo.getText().trim().length() > 0;
}
};
PdfReader reader = new PdfReader(src);
PdfReaderContentParser parser = new PdfReaderContentParser(reader);
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(RESULT));
for (int i = 1; i <= reader.getNumberOfPages(); i++) {
TextMarginFinder finder = new TextMarginFinder();
FilteredRenderListener filtered = new FilteredRenderListener(finder, spaceFilter);
parser.processContent(i, new TextRenderInfoSplitter(filtered));
PdfContentByte cb = stamper.getOverContent(i);
cb.rectangle(finder.getLlx(), finder.getLly(), finder.getWidth(), finder.getHeight());
cb.stroke();
}
stamper.close();
reader.close();
The result:
In case of slug area text etc you might want to filter more, e.g. anything outside the crop box.
Beware, though, there might be fonts in which the space character is not invisible, e.g. a font of boxed characters. Taking the spaces out of the equation in that case would be wrong.

GWT: TabLayoutPanel with custom tabs does not display correctly

I have a TabLayoutPanel where I am putting custom widgets in for the tabs to be able to display some images next to the text. I originally worked with TabPanel and using custom HTML for the tab text, but custom tab widgets allows me to modify the image on the fly as needed.
My tab widget is essentially a HorizontalPanel, a number of small images, and a line of text. The problem I'm having is that the tab doesn't want to stick to the bottom of the tab bar like normal. The tab is getting positioned at the top of the space reserved for the tab bar, and there's a gap between it and the bottom of the tab bar. I uploaded an image of the problem to http://imgur.com/fkSHd.jpg.
Is there some style that I need to apply to custom widget tabs to make them appear correctly?
In my brief experience, the newer standards mode panels (they all end in "LayoutPanel") don't get along with the older ones (the ones that just end in "Panel"). So you might consider trying a DockLayoutPanel instead of the HorizontalPanel, and it may be more cooperative.
See https://developers.google.com/web-toolkit/doc/latest/DevGuideUiPanels, particularly the section called "What won't work in Standards Mode?":
HorizontalPanel is a bit trickier. In some cases, you can simply
replace it with a DockLayoutPanel, but that requires that you specify
its childrens' widths explicitly. The most common alternative is to
use FlowPanel, and to use the float: left; CSS property on its
children. And of course, you can continue to use HorizontalPanel
itself, as long as you take the caveats above into account.
After a bit more research, I found the answer here: https://groups.google.com/d/msg/google-web-toolkit/mq7BuDaTNgk/wLqPm5MQeicJ. I had to use InlineLabel or InlineHTML widgets instead of normal Label or HTML widgets. I've tested this solution and it does exactly what I want. I pasted the code of the class below for completeness. Note two things here:
The "float" attribute cannot be set on the last element (the InlineLabel) or the incorrect drawing condition occurs again.
The code could be cleaned up a bit further by having the class extend directly from FlowPanel instead of making it a composite containing a FlowPanel.
package com.whatever;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Style.Float;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.InlineLabel;
public class StatusTab extends Composite
{
public interface StatusImages extends ClientBundle
{
public static StatusImages instance = GWT.create(StatusImages.class);
#Source("images/status-green.png")
ImageResource green();
#Source("images/status-red.png")
ImageResource red();
}
private final ImageResource greenImage;
private final ImageResource redImage;
private final FlowPanel flowPanel;
public LinkStatusTab(String text, int numStatuses) {
greenImage = StatusImages.instance.green();
redImage = StatusImages.instance.red();
flowPanel = new FlowPanel();
initWidget(flowPanel);
for (int i = 0; i < numStatuses; i++)
{
Image statusImg = new Image(redImage);
statusImg.getElement().getStyle().setMarginRight(3, Unit.PX);
statusImg.getElement().getStyle().setFloat(Float.LEFT);
flowPanel.add(statusImg);
}
flowPanel.add(new InlineLabel(text));
}
/**
* Sets the image displayed for a specific status entry.
*/
public void setStatus(int which, boolean status)
{
Image image = (Image)flowPanel.getWidget(which);
if (status)
image.setResource(greenImage);
else
image.setResource(redImage);
}
}

How to wrap a long text which has no whitespace in it

final jLabel descLabel = new jLabel();
des.setWordWrap(true);
des.setWidth("200px");
descLabel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_LEFT);
tableDisplay.setWidget(row, 2, des);
I'm placing the label inside a FlexTable
This is how it looks in the label. It's exceeding beyond the width I have given for the label.
Appears odd. I mean to say that the long text without whitespace is not following the width I have given for the label. Tried giving this:
public class Jlabel extends Label{
public Jlabel () {
DOM.setStyleAttribute(this.getElement(), "word-wrap", "break-word");
}
wwwwwwwwwr ttttttttttt rrrrrrrrrrrrrrrrr yyyyyyyyyyyyyyyyr rriuoeggn ryyyyyy ryj klrtp;irptiml;rtkroitlrktrpotilr;gkawpeti;lrkgwptjkrotkw;'rtoi4p[tok
Browsers can only break words on whitespace characters. So you need to provide some in your long word.
One option is to insert ­ every 10 characters. This will (should) not display if the word fits into a line.