Change SWT tree node text foreground when highlighted - java

I’m pretty new in SWT/JFace technology and I’ve found a problem that it’s driving me crazy. In an Eclipse RCP application I have a view where I’ve placed a SWT tree with a JFace TreeViewer which provides the labels and the icons by means of a label provider. By requirements of the customer the background colour of the tree is dark blue and the font colour is white. This combination of colours results in a bad visualization of a node’s text when the node is selected, the text does not fit the tree region and we place the mouse pointer over the node. Somehow a “native highlighting” appears. This can be shown in the following image.
On the other side, this problem does not happen when the node where we place the mouse over is not selected. The highlighting changes the colour of the font to make it more visible. This can be shown in the following image.
After doing some research I’ve found that by adding a listener for the SWT.EraseItem event I am able to modify the background’s colour of a selected node and then disable the selection. This allows me to define my own selection background style and also disable the SWT.SELECTED flag of the event.detail in order to force the OS to highlight as the node is not selected.
private final class EraseItemListener implements Listener {
public void handleEvent(Event event) {
// Only perform the node highlight when it is selected.
if ((event.detail & SWT.SELECTED) == SWT.SELECTED) {
// Modify background, emulate Windows highlighting.
...
// Set Windows that we do not want to draw this item as a selection (we have already highlighted the item in our way).
event.detail &= ~SWT.SELECTED;
}
}
}
This “solution” can be reasonable. The main drawbacks I see is that my selection style only fits for the Windows 7 default visual themes. For those “Windows classic” or “High contrast” I’ll get visualization problems. Moreover (and this is the most annoying issue), the fact of adding a listener for the SWT.EraseItem (even without code to handle the event) produces two new problems.
This makes either SWT or JFace to draw the icon of the tree node in
the wrong place as you can see in the following image.
The highlight of the tree’s root node is completely wrong. As you
can see in the following image, the node seems to be highlighted in
2 different ways and the icon is repeated.
My questions are basically two.
Do you think there is an easier solution for the main problem? What
I would like is to show a selected node (the one of the first image)
in the same way as in the second image. I would like to change the
foreground colour of the selected node to make it more visible.
In case of using the SWT.EraseItem approach, is there any way of
showing the icons in the correct location? Is this behaviour a known
bug?
Thanks in advance.

If you use a label provider based on StyledCellLabelProvider (perhaps one based on DelegatingStyledCellLabelProvider) you can specify COLORS_ON_SELECTION to retain normal colors on selection.
If that is not good enough (don't have Windows here to test) you can override the paint method - again you can try using event.detail &= ~SWT.SELECTED to suppress the normal selection handling or even handle the paint yourself.

Just stumbled across this old post and realized it matches a bug that I opened recently on eclipse.
My workaround to have the text in the correct color was to paint over the text with a different color using a PaintListener (registered in the tree with SWT.PaintItem):
private static class TreePaintListener implements Listener {
#Override
public void handleEvent(Event event) {
boolean isSelected = (event.detail & SWT.SELECTED) != 0;
if (isSelected && event.item instanceof TreeItem) {
TreeItem treeItem = (TreeItem) event.item;
Tree parent = treeItem.getParent();
GC gc = event.gc;
Color foreground = null/* Some Color */;
gc.setForeground(foreground);
Rectangle imageBounds = treeItem.getImageBounds(0);
Rectangle textBounds = treeItem.getTextBounds(0);
Point stringExtent = gc.stringExtent(treeItem.getText());
int offsetX = imageBounds.width != 0 ? imageBounds.height - imageBounds.width : 0;
int offsetY = (textBounds.height - stringExtent.y) / 2;
int x = textBounds.x + offsetX;
int y = textBounds.y + offsetY;
if (gc.getClipping().contains(x, y)) {
gc.drawString(treeItem.getText(), x, y);
}
}
}
}
But it still misbehaves when there are multiple columns defined (only the first one is painted over), and depending on the operating system and DPI the calculations might be a bit off.

Related

Get color of pixel for JavaFX window

Note: I'm using TornadoFX and kotlin, but it is based of JavaFX with some kotlin additions, which is why I'm mentioning JavaFX instead, since it seems more related to JavaFX than TornadoFX.
I'm trying to get the color of a specific location on the JavaFX window (the scene).
The reason is because for my 2D game, I'm trying to build a map. For example, if I touch black, then stop moving in that direction (so the border). Or if it's red, then lose a life (obstacle). Rather than hardcoding that (I've got no idea how I'd be able to do that, since I don't want the map to just be a square), I'm trying to get the pixels and get the color.
Note that since this is part of the hit detection system, it'll be ran 100+ a second, so I'll need a solution that doesn't take too much time.
Also, note that I'm not trying to get a pixel from an image, but the window that the user sees. (Just clarifying so that someone doesn't misunderstand)
EDIT: I just realized that I could maybe use the image and get the color from that... although if I zoom in the image to make the map larger.. that part confuses me of how I could do it then.
You can use Robot API to do this, to get the colour of the current mouse position you can do this
int xValue = MouseInfo.getPointerInfo().getLocation().x;
int yValue = MouseInfo.getPointerInfo().getLocation().y;
Robot robot = new Robot();
Color color = robot.getPixelColor(xValue, yValue);
To get the current position when the cursor is moved you need to use the setOnMouseMoved listener
yourViewNode.setOnMouseMoved(event -> {
int xValue = MouseInfo.getPointerInfo().getLocation().x;
int yValue = MouseInfo.getPointerInfo().getLocation().y;
Robot robot = new Robot();
Color color = robot.getPixelColor(xValue, yValue);
});
Then you can compare the color and check with it, if you want the color when the user clicks only you need to listen for the right or left click then use the same code in the listener to get the color at this moment
I have used this solution in my bot creator project to provide tools that can take action depend on the current position color

Expandable component to hide or unhide another component

I am wondering if there is a Swing-component which is able to be expanded, so that I am able to hide or unhide something like a menu.
As an example something similar can be found in MS Outlook:
This is the default look, where all mail folders are unhidden. But clicking on the little arrow (circled red) hides that view:
I would like to have something similar in my Java-GUI to do the same, while the included component should be hidden by default. I am not sure what component should be under that expandable "tab", but right now I am thinking about a JTree.
This is what I am generally trying. But if you want a bonus cooky, you could consider the requirement that this expandable menu has to expand in a flowing, smooth animation, instead of being hidden or unhidden instantly. The latter can be found in TeamViewer for example. There you have a menu bar on top, which can be hidden or unhidden, while it's going up and down in a smooth animation.
Example, TeamViewer:
EDIT
First I tried the JSplitPane, but moving all existing components to fit the split pane schema was not a solution I would prefer. Instead I was looking for something more independent.
The next thing I tried was using Swing Timer to expand the width of the JFrame using its setBounds-method. It works exactly the way I want when it comes to toggling additional space for a menu. The JFrame gets bigger or smaller while the resizing process is animated. But I can see two disadvantages of this approach:
The animation is kind of slow and not perfectly smooth. I removed the delay. It is quite OK so far, but a more smoother solution is preferred here. But I can totally live with it how it is currently.
A big disadvantage is that the increasing of the size leaves black spaces between the old and the new width for half of a second. If anyone knows how to avoid that, I would have my perfect solution to this problem.
To make it clearer what I mean with "black spaces", see:
Now you can see that black area. Like I said, it only remains for half of a second or even less. With Swing Timer I added 100 pixels to the width of the JFrame. The higher the value I add to the width, the higher the black area. If the JFrame's width is completely resized, everything is in the correct color again.
So does anyone know why this happens? Is this hardware related or is it just simply a standard behavior of Java or Swing? Does anyone know solutions or workarounds for this?
See splitpane.
For example
JSplitPane mainSplitPanel = new JSplitPane();
mainSplitPanel.setDividerLocation(650);
mainSplitPanel.setOneTouchExpandable(true);
For samples click here
The solution which fit the best for me can be found in the edited part of my question. I found a good combination of delay time and frame resizing which appeared smooth enough (1 millesecond delay and increasing the width with 45 pixels). The issue with the black frame is not problematical anymore. Now the black screen is even shorter in its duration, and if the user waits around 2 seconds, the black area won't be displayed (visibly) at all. In that case it's OK for me, because the user should spend some seconds after expanding anyways.
For everyone who wants to know more about this black area while resizing JFrames, see here.
The code of the solution I described in my edited question:
final Timer timer = new Timer(1, null);
timer.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent evt)
{
double width = myFrame.getBounds().getWidth();
if(isExpanded == false)
width += FRAME_PIXEL_CHANGE;
else
width -= FRAME_PIXEL_CHANGE;
if(myFrame.getBounds().getWidth() >= FRAME_SIZE_EXPANDED && isExpanded == false)
{
myFrame.setBounds(FRAME_X, FRAME_Y, FRAME_SIZE_EXPANDED, FRAME_HEIGTH);
btnExpand.setIcon(new ImageIcon(GUI.class.getResource("/img/close.png")));
timer.stop();
isExpanded = true;
}
else if(myFrame.getBounds().getWidth() <= FRAME_SIZE_REGULAR && isExpanded == true)
{
myFrame.setBounds(FRAME_X, FRAME_Y, FRAME_SIZE_REGULAR, FRAME_HEIGTH);
btnExpand.setIcon(new ImageIcon(GUIMain.class.getResource("/img/expand.png")));
timer.stop();
isExpanded = false;
}
else
{
myFrame.setBounds(FRAME_X, FRAME_Y, (int) width, (int) FRAME_HEIGTH);
btnExpand.setBounds((int) (width-36), 246, 36, 36);
}
}
});
return timer;

Generate Java Swing UI Touch Screen Interaction Heat Maps

I have created an experiment to evaluate various cursors( Bubble Cursor, Area Cursor). Although I have generated the response time and accuracy for each interaction I was interested in knowing user touch patterns and visualize it in heat maps. I could find several API's for iphone, javascript, etc. Since Java Swing package isn't meant to be for touch screen, I couldn't find a way to generate them. Only way I could thing of is to find the touch point pixels and then use some visual tools to generate heatmaps in x-y plane. Any better solution is much appreciated.
Starting from this example, I added the following code to the mouse handler and dragged the mouse diagonally across the image to get the effect pictured below. For simplicity, I used darker() on each pass, but a transformation in HSB space may also be appealing, for example.
#Override
public void mouseMoved(MouseEvent e) {
…
Color color = new Color(img.getRGB(x, y));
int c = color.darker().getRGB();
img.setRGB(x, y, c);
repaint();
…
}
Alternatively, look at using JFreeChart with an XYBubbleRenderer or an XYShapeRenderer, seen here. Add a ChartMouseListener to highlight the selected bubble, as shown here for a StackedXYBarRenderer.
Also consider the prefuse visualization library. As the mouse moves over each entry in prefuse.demos.Congress, illustrated below, the highlight, tooltip and detail text change.

computing JavaFX animation values without knowing layout

As succinctly as I can manage: Given that I need the layout information of a node (the actual height/width of a node as rendered) to compute my animation, how can I get that information before javafx draws a frame with it?
A little bit longer explanation:
I've got a TreeItem that has child items appearing in it (at the front). What I'd like to have is an animation to cause all existing children to slide down to make room for the new item which would slide in. Each child tree-items contents are different and only known at run-time, meaning the height of each child tree item cannot be expressed as a constant.
This got me writing code along these lines:
groupController.groupTreeItem.getChildren().addListener(
new ListChangeListener<TreeItem<Node>>() {
#Override public void onChanged(Change<? extends TreeItem<Node>> c) {
while(c.next()){
if ( ! c.wasAdded()){
continue;
}
TreeItem newItem = c.getAddedSublist().get(0)
new Timeline(
new KeyFrame(
seconds(0),
new KeyValue(view.translateYProperty(), -1 * newItem.getHeight())
),
new KeyFrame(
seconds(1),
new KeyValue(view.translateYProperty(), 0)
)
);
}
}
}
);
the issue here is that as when a treeItem is added to another, its components aren't laid out by the time the invalidation event is fired, meaning newItem.view.getHeight() returns 0.
My next thought was to then have the animation performed as a reaction to both a change in the list content and a sequential change to the height property, (which got me to write some really hideous code that I'd rather not share --listeners adding listeners is not something I really want to write). This almost works, except that javaFX will draw a single frame with the height property set but without the animations translation applied. I could hack down this road further and try to work something out with opacity being toggled and jobs being enqueued for later, but I figured that this would be the path to madness.
I'm wondering if there's some pseudo-class foo or some clever use of a layout property I could use to help me here. I've been poking around at various combinations of various properties, and haven't gotten anywhere. It seems that as soon as the component has a height, it is rendered, regardless of any listeners you put in or around that height assignment.
Any help much appreciated!
have you tried, overriding this
#Override
protected void updateBounds() {
super.updateBounds();
}

Force a UI update

I have a piece of code designed to take a screenshot of a node in JavaFX:
public BufferedImage getSnapshot(final Node... hideNodes) {
Window window = getScene().getWindow();
Bounds b = localToScene(getBoundsInLocal());
int x = (int) Math.round(window.getX() + getScene().getX() + b.getMinX());
int y = (int) Math.round(window.getY() + getScene().getY() + b.getMinY());
int w = (int) Math.round(b.getWidth());
int h = (int) Math.round(b.getHeight());
try {
Robot robot = new Robot();
for(Node node : hideNodes) {
node.setOpacity(0);
node.getParent().requestLayout();
}
BufferedImage image = robot.createScreenCapture(new java.awt.Rectangle(x, y, w, h));
for(Node node : hideNodes) {
node.setOpacity(1);
node.getParent().requestLayout();
}
return image;
}
catch(AWTException ex) {
return null;
}
}
It has a twist, and that is it should hide the given nodes before taking the screenshot (in case they overlap with the node, which in some cases is definite.)
However, I'm stuck finding a way to force a redraw to include the opacity change before taking the screenshot - the only reference I found was to requestLayout(), but no joy there.
What method(s) should I call to force and wait for a redraw to complete?
I find your code quite strange:
Why use node.setOpacity(0) to make it invisible, rather than node.setVisible(false)?
Why return an AWT BufferedImage rather than a JavaFX Image?
Why use a robot to capture of the screen rather than taking a snapshot of the scene?
Why mix Swing and JavaFX and end up having to worry about rendering order?
Perhaps there are reasons for these things which I don't understand, but I'd just do it this way:
public Image takeSnapshot(Scene scene, final Node... hideNodes) {
for (Node node: hideNodes) node.setVisible(false);
Image image = scene.snapshot(null);
for (Node node: hideNodes) node.setVisible(true);
return image;
}
I created a small sample app which uses the above routine.
The primary window includes a group with a circle and a rectangle. When a snapshot command is issued, the rectangle is hidden in the primary, a snapshot of the primary is taken, then the rectangle is made visible in the primary again.
To answer your question's title about forcing a UI update - you can't really. The JavaFX application thread and JavaFX rendering thread are to be treated as two separate things. What you need to do is run your processing on the JavaFX application thread, seed control back to the JavaFX system, wait for it to do it's rendering, then examine the results. The scene.snapshot method will take care of that synchronization for you so you don't need to worry about it.
If, for whatever reason, scene.snapshot won't work for you and you wanted to maintain something similar to your original strategy, then what you would do is:
Issue some update commands (e.g. setting node opacity to 0) on the JavaFX application thread.
Issue a Platform.runLater call and take your robotic snapshot in the runLater body.
Once the snapshot has really been taken (notification in some awt callback), issue another Platform.runLater command to get back on the JavaFX application thread.
Back in the JavaFX application thread, issue some more update commands (e.g. setting node opacity back to 1).
This should work as it will allow the JavaFX system to perform another pulse which performs a rendering layout of the screen with the opacity changes before your robot actually takes the snapshot. An alternate mechanism is to use a JavaFX AnimationTimer which will provide you with a callback whenever a JavaFX pulse occurs. Maintaining proper synchronization of all of this between the AWT and JavaFX threads, would be annoying.

Categories

Resources