I can find neither a solution nor whether it is even possible to easily transform a Composite in SWT.
I'm trying to implement zooming in/out with CTRL+SCROLL over a Composite. What I found thus far is adding a PaintListener to a composite, like this:
this.viewingFrame = new Shell(new Display());
this.viewingFrame.setLayout(new GridLayout(2, false));
GridDataFactory.fillDefaults().grab(true, true).applyTo(viewingFrame);
c1 = new Composite(viewingFrame, SWT.NONE);
GridLayoutFactory.swtDefaults().applyTo(c1);
GridDataFactory.swtDefaults().align(SWT.FILL, SWT.FILL).grab(true, true).applyTo(c1);
c1.addPaintListener(new PaintListener() {
#Override
public void paintControl(PaintEvent e) {
Transform trans = new Transform(e.display);
e.gc.setAdvanced(true); // After this line a call to e.gc.getAdvance() returns true
trans.scale(1.5f, 1.5f);
e.gc.setTransform(trans);
trans.dispose();
}
});
// Of course there is more code. I have a KTable displaying a 2D map. I also tried adding the PaintListener to the shell, the composite c1 and the KTable. Same result.
However this doesn't work. While debugging I could see that the listener is called but there is no visible effect.
I'm a novice with regard to SWT but I learn fast so I'm willing to consider complex solutions.
The short answer is: scaling controls in SWT is not supported (directly). The Transform applies only to the drawing operations that are done on the GC for which it was created. It does not affect the controls that are contained in the Composite.
To emulate scaling to a cretain degree, you can change the font size of the container whose content should scale (i.e. the Shell or a Composite) and then re-layout. The font size is inherited by contained controls unless a font was explicitly set. In the latter case you would have to change the size of these fonts as well.
If you want the Shell to grow/shrink with its content you will have to pack() it. Otherwise you likely need to place a ScrolledComposite within the Shell that contains all the actual content.
BTW use the Canvas widget instead of a Composite if you actually want to draw something.
Related
I have a C++/Qt background and am now learning Java and SWT GUI programming. I need to draw some primitive shapes (circles, rectangles, etc.) on an image. The shapes need to move on the image (ie. change its position in relation to the background, imagine a real-time updating map). In Qt this could be easily done by using QGraphicsScene and QGraphicsItems but how to achieve this in SWT? I have tried to use Composite with Label which is close to what I need but I haven't found a way to add primitive shapes to the Composite.
The Canvas control is specifically for drawing on. Basic drawing operations are in the GC class.
Canvas drawWidget = new Canvas(composite, SWT.NONE);
drawWidget.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
drawWidget.addPaintListener(new PaintListener() {
#Override
public void paintControl(final PaintEvent e) {
Rectangle r = drawWidget.getClientArea();
e.gc.drawOval(0, 0, r.width - 1, r.height - 1);
}
});
Call the Canvas redraw method to request that the control be redrawn. This will call the paint listener again.
Other Eclipse packages such as Eclipse GEF provide much more sophisticated drawing APIs.
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();
}
Before any one suggests HTML, I explain later why thats not an option here. I have a table that contains a column with text cells in it. I need to be able to highlight some of the text in each cell. So for example if the cell contained "cat foo dog"... I might want to highlight foo.
My current method is to use a custom TableCellRenderer that puts html into a JLabel component that gets rendered and for a time it was good. Then I noticed that when the text in the cell became too long to fit in the column width it just truncated the text without the normal "..." that happens normally in this case. Thus users didnt know there was more text they were not seeing. The other problem was that if the original text itself contained HTML, which in my case it does at times, the cell would not render correctly. I know I could just escape the html but I would still have the prevous problem.
If I use a component other than a jlabel then it makes my table's cells look strange. Does any one have any suggestions? Thanks
Well, here is a solution.
In short, you can subclass JLabel to draw the highlight manually. Override the paintComponent method to do the actual drawing and use FontMetrics to calculate where the highlighted region should be drawn.
Here is that answer in excruciating detail:
Basically, you can make a subclass of JLabel that can highlight stuff. I would do that like this; you may want to do it somewhat differently:
Add a method that tells the label which part to highlight. This could be something like this, assuming you just need one highlighted region:
public void highlightRegion(int start, int end) {
// Set some field to tell you where the highlight starts and ends...
}
If you need multiple regions, just have an ArrayList instead of a simple field. A method for dehighlighting would probably be useful too.
Now, you need to override the paintComponent method of JLabel. Here you need to do several discrete steps, which you may want to organize in different methods or something. For simplicity, I'll put it all in the paint method.
#Override
protected void paintComponent(Graphics g) {
...
First, you need to figure out the physical dimensions of the highlight, which you can do using the nice FontMetrics class. Create the FontMetrics class for the Font you're using.
FontMetrics metrics = new FontMetrics(getFont());
Now you can get all the information you need to create a rectangle that will be the highlight. You'll need the starting position, the height and the width. To get this, you'll need two substrings of the JLabel's text as follows:
String start = getText().substring(0, startOfHighlight);
String text = getText().substring(startOfHighlight, endOfHighlight);
//You may also need to account for some offsets here:
int startX = metrics.stringWidth(start);
int startY = 0; //You probably have some vertical offset to add here.
int length = metrics.stringWidth(text);
int height = metrics.getHeight();
Now you can draw the highlighted region before drawing the rest of the label:
g.fillRect(startX, startY, length, height);
super.paintComponent(g);
}
Of course, if you want the highlight to span multiple rows, that will require more work.
If you were wondering, I have actually written something like this before. On a whim, I decided to write my own text area type component from a JPanel, and this was basically the way I handled highlighting. Reinventing the wheel may be stupid in an actual project, but it does teach you random stuff that may come in useful...
Can't resist to throw the SwingX renderer decoration mechanism into the ring: its way to solve the requirement is to implement a Highlighter doing it. Which in fact is already done (though not yet in the officially supported) but hidden in the SwingLabs-Demos project, named X/MatchingTextHighlighter (you would need both) and recently fixed to cope with icons, RToL-ComponentOrientation, alignment, ellipses ..
Thats a great answer, and probably the best solution. But an alternative that some might find simpler is to use a JTextfield instead of a JLabel for rendering then you can use JTextfields highlighting capabilities i.e
void highlightWhitespaceText(JTextField text)
{
text.setHighlighter(AbstractTableCellRenderer.defaultHighlighter);
try
{
Matcher m = AbstractTableCellRenderer.whitespaceStartPattern.matcher(text.getText());
if (m.matches())
{
text.getHighlighter().addHighlight(m.start(1), m.end(1), AbstractTableCellRenderer.highlightPainter);
}
m = AbstractTableCellRenderer.whitespaceEndPattern.matcher(text.getText());
if (m.matches())
{
text.getHighlighter().addHighlight(m.start(1), m.end(1), AbstractTableCellRenderer.highlightPainter);
}
}
catch (BadLocationException ble)
{
//
}
}
You can change the properties of a JTextfield so it looks like a jLabel in other respects.
I am looking for ways to zoom in a Java Swing application. That means that I would like to resize all components in a given JPanel by a given factor as if I would take an screenshot of the UI and just applied an "Image scale" operation. The font size as well as the size of checkboxes, textboxes, cursors etc. has to be adjusted.
It is possible to scale a component by applying transforms to a graphics object:
protected Graphics getComponentGraphics(Graphics g) {
Graphics2D g2d=(Graphics2D)g;
g2d.scale(2, 2);
return super.getComponentGraphics(g2d);
}
That works as long as you don't care about self-updating components. If you have a textbox in your application this approach ceases to work since the textbox updates itself every second to show the (blinking) cursor. And since it doesn't use the modified graphics object this time the component appears at the old location. Is there a possibility to change a components graphics object permanently? There is also a problem with the mouse click event handlers.
The other possibility would be to resize all child components of the JPanel (setPreferredSize) to a new size. That doesn't work for checkboxes since the displayed picture of the checkbox doesn't change its size.
I also thought of programming my own layout manager but I don't think that this will work since layout managers only change the position (and size) of objects but are not able to zoom into checkboxes (see previous paragraph). Or am I wrong with this hypothesis?
Do you have any ideas how one could achieve a zoomable Swing GUI without programming custom components? I looked for rotatable user interfaces because the problem seems familiar but I also didn't find any satisfying solution to this problem.
Thanks for your help,
Chris
You could give a try to the JXLayer library.
There are several tools in it, which could help you to make a zoom. Check the examples shown here. I would recommend you to read more about the TransformUI, from this library. From the example, it seems like it could help solving your problem.
Scaling the view is easy enough; transforming mouse coordinates is only slightly more difficult. Here's an elementary example. I'd keep JComponents out, although it might make sense to develop an analogous ScaledComponent that knows about the geometry. That's where #Gnoupi's suggestion of using a library comes in.
hey you can try this if you want to zoom a image like any other image viewer the use a JPanel draw an image using drawImage() method now create a button and when you click the button increase the size of the panel on the frame it appears as if the image is being viewed in Zoom
You might find Piccolo2D.java API useful: http://code.google.com/p/piccolo2d/
It is very simple.
It touts in particular its smooth zooming. You essentially make a "canvas" that can contain various elements, and can then zoom by just holding right-click and panning the mouse back and forth.
I worked on a team that used it to create this: http://sourceforge.net/apps/mediawiki/guitar/index.php?title=WebGuitar#EFG.2FGUI_Visualizer
The nodes you see there are clickable links themselves.
Since Java 9, there are VM arguments (actually meant to be used for high dpi scaling) that can render a application with a higher scaling factor:
java -Dsun.java2d.uiScale=2.0 -jar MyApplication.jar
Or:
java -Dsun.java2d.win.uiScaleX=2.0 -Dsun.java2d.win.uiScaleY=2.0 -jar MyApplication.jar
We are currently using Eclipse Draw2D/GEF for an information page describing a process in our application. This basically consists of a matrix of large squares, each containing a matrix of smaller squares. We originally had all the squares as GEF objects, but because of the large volume of them being shown, we found that this did not scale very well and the view took a very long time to open. We then changed it so that only the large squares are Figures and we then draw the smaller squares using the graphic in paintFigure.
The problem that we are running into is that we still want the tooltip to change depending on which small square you are hovering over. I tried to do this by adding a mouseMotionListener and setting the tooltip, through setTooltip, depending on where the mouse currently is. The problem is that once the tooltip is displayed, it does not change any more when setTooltip is called.
Does any one know of an alternative way of doing this? Is there a way of getting the viewpart's PopupHelper and using that? Any help would be appreciated.
Thanks
Hmnn.. interesting problem. Since you paint your own Grid within the Figure, I would think that you have two options.
Try posting SWT events to fool Eclipse. I'd try a focus lost followed by a focused gained, to trigger tooltip machinery, at which point you could get the coordinates and display the appropriate contents.
Don't use the Figure#getTooltip strategy at all. Just show your own composite.
To dynamically change the tooltip, you can hold an instance of the tooltip Figure in your parent Figure. In the constructor of the parent Figure, create a new tooltip Figure (e.g. a Label) and use setToolTip() method to set the tooltip Figure to parent Figure.
When data model is changed, the updated tooltip text/icon can be set to the tooltip Figure. Then you just call setToolTip(tooltipFigure) method again.
You can have a method like:
protected Label toolTipLabel;
protected void updateToolTip(String text, Image icon){
toolTipLabel.setText(text);
toolTipLabel.setIcon(icon);
setToolTip(toolTipLabel);
}
The updateToolTip() method can be invoked in parent Figure's conturctor to initialize the tooltip. And this method can be invoked each time after the data model is changed.
I encountered the same problem in my code and solved it with that method. In my code, I invoked the updateToolTip() in the parentFigure.paintFigure() method.