Is there any way to force the SWT button text to span two lines, either manually in the SWT code or using the WindowBuilder GUI?
I have this:
I want something like this (digitally altered image to achieve this):
Is it possible? How?
I was able to get the multi-line button text work, by adding SWT.WRAP as a style for the button and by having '\n' inserted in the text to be set as the button label.
....
Button check = new Button(composite, SWT.WRAP | SWT.PUSH );
check.setText("Multi\n Line\n Button \n Text");
....
Even though the button.setText(..) java doc says that the button label must not contain new line chars, it works when SWT.WRAP is added as a style('\n' were ignored when SWT.WRAP was not set).
Just thought this could be useful.
Instead of diving into native code, I would do this by creating a custom control. Extend composite, use a label for the multi-line text and add some decoration with 2D drawing. There are methods in graphic context for rounded rectangles and gradient painting. It may not look exactly like a native widget, but, in my opinion, better than using JNI.
The following code does the trick:
final int style = OS.GetWindowLong(button.handle, OS.GWL_STYLE);
OS.SetWindowLong(button.handle, OS.GWL_STYLE, style | OS.BS_MULTILINE);
button.setText("line 1\nline 2");
You just import org.eclipse.swt.internal.win32.OS and that's it. Of course, that class, and the handle field inside button are not part of SWT API, so your code is no longer portable. But the functions are defined in Windows API, so you don't have to worry too much they will change in future versions of SWT.
Be aware that after this change, computeSize() no longer works well.
Edit: full class where I take care of computeSize() and GWL style
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.internal.win32.OS;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
public class MultilineButton extends Button {
public MultilineButton(Composite parent, int style) {
super(parent, style);
final int gwlStyle = OS.GetWindowLong(this.handle, OS.GWL_STYLE);
OS.SetWindowLong(this.handle, OS.GWL_STYLE, gwlStyle | OS.BS_MULTILINE);
}
#Override
protected void checkSubclass() {
}
#Override
public Point computeSize(int wHint, int hHint, boolean changed) {
final Point size = super.computeSize(wHint, hHint, changed);
final GC gc = new GC(this);
final String multiLineText = this.getText();
final Point multiLineTextSize = gc.textExtent(multiLineText, SWT.DRAW_DELIMITER);
final String flatText = multiLineText.replace('\n', ' ');
final Point flatTextSize = gc.textExtent(flatText);
gc.dispose();
size.x -= flatTextSize.x - multiLineTextSize.x;
size.y += multiLineTextSize.y - flatTextSize.y;
return size;
}
}
I would try setting the button text to a string with a '\n' character where you want the line break to happen.
there's actually a super easy way in swing, not in SWT. I misread the question when I first posted this answer, but since I found my way here whilst searching for a swing solution, I'll leave it up for others.
button.setText("<html>My<br>Text<br>");
many swing components render HTML.
On checking the date of posting, not sure if this is a relevant answer to a question that old, but I found this while trying to solve this problem so I thought I'd share. Cheers.
The only way I can think of doing this, is through JNI/JNA. Take note that if you go down this road, you'll be breaking Java's platform independence by calling platform-specific native functions.
For Windows, take a look at the SetWindowText API call. Supposing you have code similar to:
Button btn = new Button(shell, SWT.PUSH);
btn.setText("Hello world!");
You can obtain the handle via btn.handle. With a bit of JNA magic something along these lines might be possible:
final User32 lib = (User32) Native.loadLibrary("user32", User32.class);
final HWND hWnd = new W32API.HWND(new W32API.UINT_PTR(btn.handle)
.toPointer());
final String text = "Hello\nworld!"; // or "Hello\r\nworld!"?
lib.SetWindowText(hWnd, text.toCharArray());
I couldn't actually get this to work (it gives a java.lang.UnsatisfiedLinkError on SetWindowText() which I couldn't sort out), but this might point you in the right direction.
Related
I have a simple GUI that has a jTextField that waits for the user to put in something. After a button is clicked, the program:
reads the input, saves it in a String variable;
opens a new GUI (that is in a separate class file), which contains an empty jLabel, and passes the String variable to it, changing the jLabel text to it.
The problem is that no matter how hard I try to reconfigure the code, adding things like repaint(), revalidate(), etc., the jLabel in the second GUI stays empty. Using a System.out.println(jLabel.getText()) reveals that the text value is indeed changed, but not displayed. How do I "refresh" this jLabel, so it'd show what I want it to? I'm aware I could add an event, though I don't want the user to click anything to refresh the GUI, the values should be there as it's initiated. I've read trough several similar posts, but found that the solutions don't work for me.
The code of first GUI's button click event:
private void sbuttonActionPerformed(java.awt.event.ActionEvent evt) {
errortext.setText("");
Search = sfield.getText();
Transl = hashes.find(Search);
if (Transl.equals("0")) errortext.setText("Word not found in database.");
else {
ws.run(Search, Transl); // <- this opens the second GUI, with two String parameters I want to display in the second GUI;
}
}
The code of the second GUI (activeword and translation are the jLabels that are giving me trouble.):
public void run(String Search, String Transl) {
WordScreen init = new WordScreen(); //initialise the second GUI;
init.setVisible(true);
activeword.setText(Search);
translation.setText(Transl);
}
Any reply is very welcome! Please ask me for more information about the code if necessary, I will make sure to reply as soon as possible!
Best solution: change WordScreen's constructor to accept the two Strings of interest:
From this:
public void run(String Search, String Transl) {
WordScreen init = new WordScreen(); //initialise the second GUI;
init.setVisible(true);
activeword.setText(Search);
translation.setText(Transl);
}
to this:
public void run(String search, String transl) {
WordScreen init = new WordScreen(search, transl);
init.setVisible(true);
}
Then in the WordScreen constructor use those Strings where needed:
public WordScreen(String search, String transl) {
JLabel someLabel = new JLabel(search);
JLabel otherLabel = new JLabel(transl);
// put them where needed
}
Note that I cannot create a comprehensive answer without your posting a decent MRE
As an aside, you will want to learn and use Java naming conventions. Variable names should all begin with a lower letter while class names with an upper case letter. Learning this and following this will allow us to better understand your code, and would allow you to better understand the code of others.
I made a grid of buttons in JavaFX.
When I resize the window with the grid inside, the buttons resize within the grid as well.
The problem is that the text on those buttons doesn't resize along with them: it stays the same size all the time, so when the buttons grow big, there's a lot of empty space on a button and then a tiny little text in the middle, which looks terrible.
I would rather like the text to automatically resize along with these buttons to fit the empty space, so that when the entire user interface gets bigger, the text on the buttons gets bigger as well.
How can I accomplish that?
I tried setting the -fx-font-size in the CSS stylesheet to percentage values, but it doesn't seem to work the same way as for websites: the text doesn't scale as a percentage of its container, but as a percentage of some predefined text size.
Edit
This is not a duplicate! Stop marking each question out there as duplicate! If it has been answered, I wouldn't have asked it in the first place!
From what I see, the first of those threads was about a situation where someone wanted to set the size/style of the text for newly-created buttons to account for the current size of their container etc. This is not what I need, because I want the buttons which has been already created as well to automatically resize their texts when these buttons resize inside their container in some way.
The other thread was about scaling the text along with the root container / window with a preset font size. This is also different from what I need, because I don't want the text to be scaled with the window, but with the sizes of the buttons themselves. And it has to be scaled in a certain way: to always fit the size of the button. You know: the text stays the same, but stretches so that it always fits the inside of the button (with a little padding, not a huge empty area around the text).
It is the button's size which is to determine the size of the text on it, not the window or container or something else, and it needs to be done automatically by the button itself (either the built-in one or a subclassed one), not manually by its encompassing container iterating over all these buttons and updating their text's sizes (which would be dumb way to do it).
This is, liked the linked questions, something of a hack: but consider scaling the text node inside the button instead of changing the font size. This seems to work ok:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.stage.Stage;
public class ScaledButtons extends Application {
#Override
public void start(Stage primaryStage) {
GridPane root = new GridPane();
root.setHgap(5);
root.setVgap(5);
for (int i = 1; i <= 9 ; i++) {
root.add(createScaledButton(Integer.toString(i)), (i-1) % 3, (i-1) / 3);
}
root.add(createScaledButton("#"), 0, 3);
root.add(createScaledButton("0"), 1, 3);
root.add(createScaledButton("*"), 2, 3);
primaryStage.setScene(new Scene(root, 250, 400));
primaryStage.show();
}
private Button createScaledButton(String text) {
Button button = new Button(text);
GridPane.setFillHeight(button, true);
GridPane.setFillWidth(button, true);
GridPane.setHgrow(button, Priority.ALWAYS);
GridPane.setVgrow(button, Priority.ALWAYS);
button.layoutBoundsProperty().addListener((obs, oldBounds, newBounds) ->
scaleButton(button));
button.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
return button ;
}
private void scaleButton(Button button) {
double w = button.getWidth();
double h = button.getHeight();
double bw = button.prefWidth(-1);
double bh = button.prefHeight(-1);
if (w == 0 || h == 0 || bw == 0 || bh == 0) return ;
double hScale = w / bw ;
double vScale = h / bw ;
double scale = Math.min(hScale, vScale);
button.lookup(".text").setScaleX(scale);
button.lookup(".text").setScaleY(scale);
}
public static void main(String[] args) {
launch(args);
}
}
An alternate approach to get a similar effect could be to subclass com.sun.javafx.scene.control.skin.ButtonSkin and override the layoutLabelInArea(double x, double y, double w, double h, Pos alignment) method from the skin's parent (LabeledSkinBase). You can then explicitly assign the updated skin to your button (either via CSS or via Java API calls).
Doing so would requires the subclassing of com.sun APIs which could change without notice in subsequent JavaFX releases. Also layoutLabelInArea is reasonably complex in its operation so changing the layout logic could be a little tricky. Certainly, James's suggestion of applying a text rescaling operation based upon a listener to the layout bounds property is simpler in this particular case.
I'm not necessarily advocating this approach, just providing a route to something that you could create that would satisfy your goal of: "It is the button's size which is to determine the size of the text on it, not the window or container or something else, and it needs to be done automatically by the button itself".
I'm developing my own text-like JComponent. It isn't a subclass of JTextComponent, because it isn't using a Document as the model. I'd still like to support the standard mnemonics of cut/copy/paste, but I know that the keystrokes depend on the platform.
Ultimately, I'll let the user edit the keybindings themselves, but for now, I'd like to at least default to something that is sensible.
Is it possible to get it from the LookAndFeel somehow? Or do I need to detect the platform myself and just have a mapping per platform?
I'm using Java 8 if that makes a difference.
There is no LAF property that I'm aware of for this purpose.
However you might be able to use the information from the InputMap of the LAF.
The following works for Windows 8:
import java.awt.*;
import javax.swing.*;
public class PlatformMnemonics
{
public static void main(String[] args)
{
KeyStroke copyKeyStroke = null;
KeyStroke cutKeyStroke = null;
KeyStroke pasteKeyStroke = null;
InputMap im = (InputMap) UIManager.get("TextField.focusInputMap");
for (KeyStroke keyStroke: im.keys())
{
boolean upperCase = Character.isUpperCase( keyStroke.getKeyCode() );
if ( upperCase )
{
String actionMapKey = im.get( keyStroke ).toString();
if ("copy-to-clipboard".equals(actionMapKey))
copyKeyStroke = keyStroke;
else if ("cut-to-clipboard".equals(actionMapKey))
cutKeyStroke = keyStroke;
else if ("paste-from-clipboard".equals(actionMapKey))
pasteKeyStroke = keyStroke;
}
}
System.out.println("Copy KeyStroke: " + copyKeyStroke);
System.out.println("Cut KeyStroke: " + cutKeyStroke);
System.out.println("Paste KeyStroke: " + pasteKeyStroke);
}
}
Note there are actually 3 bindings on Windows for each Action as you can see in the Key Bindings programs that displays all the key bindings for every Swing component. I just displayed the binding I think you are interested in.
So, the closest I found to do what I want is the OS X Integration for Java page from Apple. There is a method on the Toolkit class getMenuShortcutKeyMask(), which will help support what I want.
For example, the following will get the right keystroke for "meta-v" on mac, and "ctrl-v" on windows/linux.
KeyStroke.getKeyStroke(KeyEvent.VK_V,
Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())
I'm trying to find a way to change the line spacing in a JTextArea component.
A little bit of searching always seems to reveal the same answer: "Use a JTextPane instead and then call setParagraphAttributes".
But I'm wondering whether it's possible to achieve this with just a JTextArea by, for example, messing with the font.
With the deriveFont(...) method, it's possible to change the tracking and the kerning of the font, i.e. the horizontal spacing between characters, but I haven't been able to find a way to change the vertical spacing (maybe ascent, descent, leading). Am I missing something there?
As camickr pointed out, JTextArea does not provide a way to change the line height directly. It simply uses the font height provided by the corresponding FontMetrics.
But, this leaves a point of attack open using the following helper-class:
public class FontMetricsWrapper extends FontMetrics {
private final FontMetrics target;
public FontMetricsWrapper(FontMetrics target) {
super(target.getFont());
this.target = target;
}
#Override
public int bytesWidth(byte[] data, int off, int len) {
return target.bytesWidth(data, off, len);
}
#Override
public int charWidth(char ch) {
return target.charWidth(ch);
}
#Override
public int charWidth(int codePoint) {
return target.charWidth(codePoint);
}
// ... tons more #Override's, all of the same form:
//
// #Override
// public baz foo(bar, ...) {
// return target.foo(bar, ...);
// }
}
Then, it becomes possible to create the JTextArea like this:
JTextArea myTextArea = new JTextArea("Some text") {
#Override
public FontMetrics getFontMetrics(Font font) {
return new FontMetricsWrapper(super.getFontMetrics(font)) {
#Override
public int getHeight() {
return 10; // Gives line height in pixels
}
};
}
};
This is definitely not the cleanest solution and is merely meant as proof of concept. For example, one issue is that getFontMetrics(...) is called quite often, and, in the given example, creates a new instance of the wrapper class each time. So, at the very least, a HashMap that caches the created FontMetricsWrapper for each given font would be in order...
But, what I was really hoping for was a way to play with the Font or maybe the associated FontRenderContext passed into the JTextArea to modify the line height. For example, is there some way to influence font's reported ascent, descent, and leading values? Seems strange that you can change pretty much any other aspect of the font's appearance, except this one...
What's wrong with light-weight coding?
Nothing, you should use the simplest component for the job. But if the simple component doesn't support a requirement it is usually because the requirement is more complex and you need a more complex component to implement the functionality. Rarely would it be as simple at setting a property of a class.
Sometimes I'm simply interested in learning something new and figuring out what's possible.
Swing text components use a View to paint the text. It is the view's responsibility to format and position the text. So each view determines when to wrap and where to position the next line.
In the case of a JTextArea it uses either a Plainview or a WrappedPlanView. For the Plainview the painting code is:
drawLine(line, g, x, y);
y += fontHeight;
where the fontHeight is determined by using the FontMetrics.getHeight() method.
So the value is basically hard coded in the View. You could always provide a custom View for your text area, but overriding a View is generally not an easy task.
I'm developing a an eclipse plugin that uses an SWT interface. I need to display text, and within that text there needs to be links. The only two widgets that I've found that will allow me to include clickable links in text are Link and Browser. Browser, however, is overkill for my needs, and I couldn't properly customize the look of it. This only leaves the Link widget.
The problem is I need the Link widget to inherit a gradient from the Composite in which it is in. It does this correctly, only when it is resized or scrolled the Link component flickers. The Link is the only component in which I have seen this effect.
In an attempt to fix this I've tried manipulating other components into having clickable links, but I haven't found a good solution yet.
Is there anyway to fix the flickering effect on the Link, or is there a different component which would support links?
Thanks,
Brian
After spending the day working on this, I came up with a workaround. I created a Composite for the text area. For each word that isn't part of a url,got its own label. For links, each letter got its own label. Then the labels for the url characters got a listener to launch a browser. Using this method provided the Link functionality, handled resizing properly, and has no flicker.
Have you tried passing SWT.NO_BACKGROUND to your Link widget? It might get a little strange... and you may have to do a little more work to get the gui drawing properly, but that would be my first guess.
Other than that, here's my Quick n' dirty implementation of a link inside of a StyledText. You will need to fill in for changing the cursor (if that's something you want), as well as coming up with a good "text to link" mapping scheme.
The only thing is I'm not sure if StyledText will inherit your background... give it a shot.
public class StyledTextExample {
public static void main(String [] args) {
// create the widget's shell
Shell shell = new Shell();
shell.setLayout(new FillLayout());
shell.setSize(200, 100);
Display display = shell.getDisplay();
// create the styled text widget
final StyledText widget = new StyledText(shell, SWT.NONE);
String text = "This is the StyledText widget.";
widget.setText(text);
widget.setEditable(false);
final StyleRange hyperlinkStyle = new StyleRange();
String linkWord = "StyledText";
hyperlinkStyle.start = text.indexOf(linkWord);
hyperlinkStyle.length = linkWord.length();
hyperlinkStyle.fontStyle = SWT.BOLD;
hyperlinkStyle.foreground = display.getSystemColor(SWT.COLOR_BLUE);
widget.setStyleRange(hyperlinkStyle);
widget.addMouseListener(new MouseAdapter() {
public void mouseUp(MouseEvent arg0) {
Point clickPoint = new Point(arg0.x, arg0.y);
try {
int offset = widget.getOffsetAtLocation(clickPoint);
if (widget.getStyleRangeAtOffset(offset) != null) {
System.out.println("link");
}
} catch (IllegalArgumentException e) {
//ignore, clicked out of text range.
}
}});
shell.open();
while (!shell.isDisposed())
if (!display.readAndDispatch()) display.sleep();
}
}