I'm working on an application using SWT in Java, and I've encountered some wierd behavior from multiple StyledText widgets. It's fairly consistent: if there are more than one StyledText widget displayed in the window/view/editor at once, you can select whatever you want from each one individually, at the same time. In the screenshot, there are 4 separate widgets with 4 separate selections.
My expectation is that if I start selecting from one widget, whatever other widget that might already have a selection should then lose it, similarly to the behavior you would expect from a web browser; only one selection at a time.
I'd like to fix this problem, but I hope to avoid needing to make some manager that listens to every widget and turns off selections when a new one is made. Is there a better way to do this? (Also why is this happening in the first place?)
Why is it happening?
To best of my knowledge the behavior of a widget depends on its implementation i.e. whether it is a
native widget (like org.eclipse.swt.widgets.Text) or
custom widget (like org.eclipse.swt.custom.StyledText)
The difference lies in the handling of the mouse down or mouse up events.
For example,
In case of org.eclipse.swt.widgets.Text the left mouse down eventually translates to OS.SendMessage (hwnd, OS.WM_LBUTTONUP, wParam, lParam);
Whereas
org.eclipse.swt.custom.StyledText uses a mouse event handler and extra processing in the handleMouseDown(Event event) method. Most of the functionality or UI is done using custom draw/redraw/validate/invalidate/update methods.
To put it in a very crude win32 sdk way:
There are some controls provided by windows/win32 GDI
And, some are custom user drawn controls
See the below SWT code which uses text, styledtext, browser etc for testing. Also Note the browser control is not exactly a win32 control it is a wrapper control around Internet Explorer activex or mozilla's gecko engine, therefore its behavior is same as that of styled text.
Any possible solution ?
Well I can only think of borrowing SWT's styledtext code and then making a version which suits me. Or
As you have already mentioned, use some listener to reset all the other unfocused widgets (which even in my opinion is not a very clean solution).
Test Code & Output
Code:
import org.eclipse.swt.SWT;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.custom.CCombo;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
public class StyledTextTest {
private static Display display;
public static void main(String[] args)
{
display = new Display();
Shell shell = new Shell(display);
shell.setLayout(new GridLayout(2,true));
shell.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
createStyledText(shell);
createStyledText(shell);
createText(shell);
createText(shell);
createCombo(shell);
createCombo(shell);
createCustomCombo(shell);
createCustomCombo(shell);
createBrowser(shell);
createBrowser(shell);
shell.pack();
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
}
private static void createCustomCombo(Composite parent)
{
new Label(parent, SWT.NONE).setText("Custom Combo");
CCombo c = new CCombo(parent, SWT.DROP_DOWN);
c.setItems(new String[] {"test best", "best rest", "rest test"});
c.select(0);
c.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
}
private static void createCombo(Composite parent)
{
new Label(parent, SWT.NONE).setText("Combo");
Combo c = new Combo(parent, SWT.DROP_DOWN);
c.setItems(new String[] {"test best", "best rest", "rest test"});
c.select(0);
c.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
}
static void createBrowser(Composite parent)
{
new Label(parent, SWT.NONE).setText("Browser");
Browser browser = new Browser(parent, SWT.NONE);
browser.setText("<div>This is a test !!</div>");
browser.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
}
static void createText(Composite parent) {
new Label(parent, SWT.NONE).setText("Text");
final Text text = new Text(parent, SWT.BORDER);
text.setText("0123456789 ABCDEFGHIJKLM NOPQRSTUVWXYZ");
}
static void createStyledText(Composite parent)
{
new Label(parent, SWT.NONE).setText("Styled Text");
StyledText text = new StyledText (parent, SWT.BORDER|SWT.SINGLE);
text.setText("0123456789 ABCDEFGHIJKLM NOPQRSTUVWXYZ");
// make 0123456789 appear bold
StyleRange style1 = new StyleRange();
style1.start = 0;
style1.length = 10;
style1.fontStyle = SWT.BOLD;
text.setStyleRange(style1);
// make ABCDEFGHIJKLM have a red font
StyleRange style2 = new StyleRange();
style2.start = 11;
style2.length = 13;
style2.foreground = display.getSystemColor(SWT.COLOR_RED);
text.setStyleRange(style2);
// make NOPQRSTUVWXYZ have a blue background
StyleRange style3 = new StyleRange();
style3.start = 25;
style3.length = 13;
style3.fontStyle = SWT.BOLD | SWT.ITALIC;
text.setStyleRange(style3);
}
}
Related
I am using Java's SWT toolkit to create a GUI with text field inputs. These input fields require numerical input and have units assigned to them. I'm trying to create a fancy way to integrate units within the field as a fixed suffix to the text, such that the user can only edit the numerical part. I'd also like for the suffix to be greyed out so the user knows it is disabled - something like the following:
While searching, I saw some solutions with a mask formatter from Swing that might do the trick, but I'm sort of hoping there might be something default with SWT. Any suggestions on how to make this work?
The field is part of a a matrix, so I can't simply add the units to a header label. I suppose I could create another column after the text field that could provide units as a label, but I'm going for something more intuitive and aesthetic.
Any suggestions?
One option would be to group Text and Label widgets in the same composite, and set the text on the Label to the desired suffix:
The area to the left of the suffix is the single-line text field, which can be edited, and the suffix is a disabled Label.
public class TextWithSuffixExample {
public class TextWithSuffix {
public TextWithSuffix(final Composite parent) {
// The border gives the appearance of a single component
final Composite baseComposite = new Composite(parent, SWT.BORDER);
baseComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
final GridLayout baseCompositeGridLayout = new GridLayout(2, false);
baseCompositeGridLayout.marginHeight = 0;
baseCompositeGridLayout.marginWidth = 0;
baseComposite.setLayout(baseCompositeGridLayout);
// You can set the background color and force it on
// the children (the Text and Label objects) to add
// to the illusion of a single component
baseComposite.setBackground(new Color(parent.getDisplay(), new RGB(255, 255, 255)));
baseComposite.setBackgroundMode(SWT.INHERIT_FORCE);
final Text text = new Text(baseComposite, SWT.SINGLE | SWT.RIGHT);
text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
final Label label = new Label(baseComposite, SWT.NONE);
label.setEnabled(false);
label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, true));
label.setText("kg/m^3");
}
}
final Display display;
final Shell shell;
public TextWithSuffixExample() {
display = new Display();
shell = new Shell(display);
shell.setLayout(new GridLayout());
shell.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
new TextWithSuffix(shell);
}
public void run() {
shell.setSize(200, 100);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
public static void main(final String[] args) {
new TextWithSuffixExample().run();
}
}
How can I send a custom SWT key event such that it results in the typing of that exact literal character without any conversion?
We are making a custom on-screen graphical keyboard that allows for multiple possible layouts - QWERTY being the main one, but several others planned, including ones that visually simulate the iPhone keyboard layout or others that are non-standard relative to the PC.
I would like to be able to send any arbitrary character of my choosing as an event, such that it will result in the typing of that exact character. However, whenever I send an event with a given character, SWT seems to automatically interpret and convert it based on the system's keyboard settings and status, changing it to be capitalized/lowercase or using the shifted symbols depending on whether or not the shift key (the real one on the actual keyboard, for instance) is pressed. For instance, passing the character 'T' through a key event will result in either 't' or 'T' depending on the shift key. It performs similarly with the '4' character, doing either '4' or '$' based on shift.
This is problematic, as I wish to perform the shifting of characters manually, and indeed may not wish to use the same shift characters as the standard QWERTY keyboard in any given case (perhaps, for instance, I want '$' to be the shifted (or 'function' key) version of 'R' on a smaller, cellphone-like keyboard). How can I use these events such that it does not assume the usage of QWERTY keys?
The documentation for the 'character' field in the SWT events says the following:
the character represented by the key that was typed. This is the final
character that results after all modifiers have been applied. For
example, when the user types Ctrl+A, the character value is 0x01. It
is important that applications do not attempt to modify the character
value based on a stateMask (such as SWT.CTRL) or the resulting
character will not be correct.
This makes me believe that it should not be converting my character as it is doing, as I am commanding it to use my "final" result.
I have created a simple, stand-alone example to demonstrate my issue (please note that this is not how I am doing the actual Keyboard, and is only meant to highlight the key event issue):
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Button;
public class KeyboardIssueExample extends Shell {
private Text txtTextArea;
private Composite cmpButtons;
private Button btnBigA;
private Button btnBigS;
private Button btnLittleD;
private Button btnSix;
private Button btnSeven;
private Button btnDollah;
private Button btnStar;
private SelectionListener mainListener = new SelectionAdapter() {
#Override
public void widgetSelected(SelectionEvent selE) {
Button b = (Button)selE.widget;
char c = b.getText().charAt(0);
sendKey(c);
}
};
/**
* Launch the application.
* #param args
*/
public static void main(String args[]) {
try {
Display display = Display.getDefault();
KeyboardIssueExample shell = new KeyboardIssueExample(display);
shell.open();
shell.layout();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Create the shell.
* #param display
*/
public KeyboardIssueExample(Display display) {
super(display, SWT.SHELL_TRIM);
createContents();
}
/**
* Create contents of the shell.
*/
protected void createContents() {
setText("SWT Application");
setSize(450, 300);
setLayout(new GridLayout());
txtTextArea = new Text(this, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL | SWT.CANCEL | SWT.MULTI);
txtTextArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
cmpButtons = new Composite(this, SWT.NONE);
cmpButtons.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 1, 1));
GridLayout gl_cmpButtons = new GridLayout(7, false);
gl_cmpButtons.marginWidth = 0;
gl_cmpButtons.marginHeight = 0;
cmpButtons.setLayout(gl_cmpButtons);
btnBigA = new Button(cmpButtons, SWT.NONE);
btnBigA.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 1, 1));
btnBigA.setText("A");
btnBigS = new Button(cmpButtons, SWT.NONE);
btnBigS.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 1, 1));
btnBigS.setText("S");
btnLittleD = new Button(cmpButtons, SWT.NONE);
btnLittleD.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 1, 1));
btnLittleD.setText("d");
btnSix = new Button(cmpButtons, SWT.NONE);
btnSix.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 1, 1));
btnSix.setText("6");
btnSeven = new Button(cmpButtons, SWT.NONE);
btnSeven.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 1, 1));
btnSeven.setText("7");
btnDollah = new Button(cmpButtons, SWT.NONE);
btnDollah.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 1, 1));
btnDollah.setText("$");
btnStar = new Button(cmpButtons, SWT.NONE);
btnStar.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 1, 1));
btnStar.setText("*");
init();
}
private void init() {
btnBigA.addSelectionListener(mainListener);
btnBigS.addSelectionListener(mainListener);
btnLittleD.addSelectionListener(mainListener);
btnSix.addSelectionListener(mainListener);
btnSeven.addSelectionListener(mainListener);
btnDollah.addSelectionListener(mainListener);
btnStar.addSelectionListener(mainListener);
}
#Override
protected void checkSubclass() {
// Disable the check that prevents subclassing of SWT components
}
private void sendKey(char c) {
sendKeyEvent(c, SWT.KeyDown);
sendKeyEvent(c, SWT.KeyUp);
}
private void sendKeyEvent(char c, int eventType) {
txtTextArea.forceFocus();
Event event = new Event();
event.type = eventType;
event.character = c;
getDisplay().post(event);
}
}
As you will find, the key events do not respect my own custom usage of capitalization, and instead uses its own. How troublesome.
Looking at the source of Display.post for the Mac and for Windows there is a lot of code specific to the key up/down events. This includes calls to OS specific APIs to convert the character and key code you specify to the parameters required by the OS specific method used to generate the key stroke event.
For Windows this code calls VkKeyScan which according to the Microsoft documentation does look at the current keyboard state.
Display does have a postEvent(Event) method which adds to the event queue without changing anything, but this is not public so you would have to access it using reflection. Since it is not part of the official API it may change.
Edit:
There is actually an Eclipse bug report here for this. It has been open for 10 years so probably isn't going to be fixed!
Update:
There is also a Widget.notifyListeners(eventType, Event) method which could be used to send the key events to a specific control. You will have to create the Event and set the required fields.
I'm having a few troubles with the dialogs in the eclipse rcp. I wish to have a dialog which shows me a MasterDetailBlock to manage an arbitrary amount of entities shown in a table on the master part with their corresponding DetailPages shown in the detail part. As of now, this is done using a View, but a non-modal dialog seems more fitting for this.
At first, I tried the naive way of just taking the code from the view und put it into the dialog, with a few modifications due to the difference between view and dialog creation. However, most controls were missing. A search on Google, the eclipse forums and here on Stackoverflow did not bring a solution for this. After checking these sites for the solution, I tried to understand what's happening by stepping through the code with the debugger, but that didn't help me either.
The following Code should show a dialog containing section in which a button should be displayed. However, it doesn't:
protected Control createDialogArea(Composite parent) {
parent.setLayout(new GridLayout(1, true));
Section section = toolkit.createSection(parent, ExpandableComposite.EXPANDED | ExpandableComposite.TITLE_BAR);
section.setLayout(new GridLayout());
section.setText("Section");
section.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
Button test = toolkit.createButton(section, "test", SWT.PUSH);
test.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
test.setVisible(true);
section.computeSize(SWT.DEFAULT, SWT.DEFAULT);
return parent;
}
The result of this is:
However, as a MasterDetailBlock needs a form, I'll provide this Code as well:
protected Control createDialogArea(Composite parent) {
parent.setLayout(new GridLayout(1, true));
form = new ScrolledForm(parent);
form.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
form.getBody().setLayout(new FillLayout());
Composite formComposite = toolkit.createComposite(form.getBody());
formComposite.setLayout(new GridLayout(1,true));
Section section = toolkit.createSection(formComposite, ExpandableComposite.EXPANDED | ExpandableComposite.TITLE_BAR);
section.setLayout(new GridLayout());
section.setText("Section");
section.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
Button test = toolkit.createButton(section, "test", SWT.PUSH);
test.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
test.setVisible(true);
section.computeSize(SWT.DEFAULT, SWT.DEFAULT);
return parent;
}
Just a slight modification by adding a form to the dialog and everything goes on the form. However, the result looks like this:
I'm afraid I'm missing something obvious here. As I said, searching didn't bring any enlightment and stepping through the code didn't help either. My last resort "trying stuff to see what happens and try to understand that" didn't help much, as the results didn't change from the ones already posted.
So, do I miss something? (Which I think it is)
If you can provide me a link to show me what's wrong (or anything from your experience as well), I would appriciate that.
Thank you for your help.
There is a
section.setClient(test);
missing. Moreover, you should have this when using a Form and section is expandible:
protected final IExpansionListener expansionListener= new ExpansionAdapter()
{
/**
* {#inheritDoc}
*/
#Override
public void expansionStateChanged(ExpansionEvent e)
{
ScrolledForm form = ...; //your scrolled form goes here
form.layout(new Control[] {(ExpandableComposite) e.getSource()}, SWT.ALL | SWT.CHANGED);
form.reflow(true);
}
};
...
section.addExpansionListener(expansionListener);
Instead of the button test you should add a Composite which contains the button. Each section can have only one client.
In my SWT application i have certain components inside the SWT shell.
Now how can i auto re-size this components according to the size of display window.
Display display = new Display();
Shell shell = new Shell(display);
Group outerGroup,lowerGroup;
Text text;
public test1() {
GridLayout gridLayout = new GridLayout();
gridLayout.numColumns=1;
shell.setLayout(gridLayout);
outerGroup = new Group(shell, SWT.NONE);
GridData data = new GridData(1000,400);
data.verticalSpan = 2;
outerGroup.setLayoutData(data);
gridLayout = new GridLayout();
gridLayout.numColumns=2;
gridLayout.makeColumnsEqualWidth=true;
outerGroup.setLayout(gridLayout);
...
}
i.e when I decrease the size of window the components inside it should appear according to that.
That sounds suspiciously like you aren't using layouts.
The whole concept of layouts makes worrying about resizing needless. The layout will take care of the size of all of its components.
I would recommend to read the Eclipse article about layouts
Your code can easily be corrected. Don't set the size of individual components, the layout will determine their size. If you want the window to have a predefined size, set the shell's size:
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setLayout(new GridLayout(1, false));
Group outerGroup = new Group(shell, SWT.NONE);
// Tell the group to stretch in all directions
outerGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
outerGroup.setLayout(new GridLayout(2, true));
outerGroup.setText("Group");
Button left = new Button(outerGroup, SWT.PUSH);
left.setText("Left");
// Tell the button to stretch in all directions
left.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
Button right = new Button(outerGroup, SWT.PUSH);
right.setText("Right");
// Tell the button to stretch in all directions
right.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
shell.setSize(1000,400);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
}
Before resizing:
After resizing:
I am writing a application for manipulating a custom sqlite DB.
DB is quite complicated, it has tree like structure.
main
--->operator1
------>name
------>address
--->operator2
------>name
------>address
------>tariffs
---------->name
---------->price
I need to have something like a 'path' in order to easily browse through the table and edit stuff... My data is organized as SWT Table. I have SWT.MouseDoubleClick listener attached to it. I intent to 'step into' my operator data by double clicking on a particular table row. The problem is, how to get back to the 'main view', I need some sort of navigation for that purpose.
My current idea is to create a container and add necessary buttons into it. Similar to
nautilus
Notice, the path is created as consecutive buttons, aligned horizontally:
mentis -> Dropbox -> Photos
The big question is how to do that ;)
I am able to create a button and add it to may container, however this works only when application starts. I don't know how to add buttons to my container when the app is running.
In my main class I have sth like this:
Composite pathBarContainer = new Composite(shell, SWT.BORDER);
pathBarContainer.setSize(shell.getBounds().width, 20);
pathBarContainer.setLayout(new FillLayout(SWT.HORIZONTAL));
GridData gridData = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
gridData.horizontalSpan = 3;
pathBarContainer.setLayoutData(gridData);
pathBar = new PathBar(pathBarContainer, shell, contentProvider);
pathBar.getPathBar();
this is my PathBar class:
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Shell;
public class PathBar {
Composite parent;
Composite pathBar;
Shell mainShell;
ContentProvider contentProvider;
Button mainButton;
Button nextButton;
public PathBar(Composite parent_in, Shell mainShell_in, ContentProvider cp) {
parent = parent_in;
mainShell = mainShell_in;
contentProvider = cp;
pathBar = new Composite(parent, SWT.BORDER_DOT);
//pathBar.setSize(100, 300);
pathBar.setLayout(new GridLayout(10, true));
mainButton = new Button(pathBar, SWT.PUSH);
mainButton.setText("main");
}
public Composite getPathBar() {
return pathBar;
}
public void addMainButton() {
mainButton = new Button(pathBar, SWT.PUSH);
mainButton.setText("main");
pathBar.redraw();
//parent.redraw();
//mainShell.redraw();
}
public void addButton() {
nextButton = new Button(pathBar, SWT.PUSH);
nextButton.setText("sth");
pathBar.redraw();
parent.redraw();
System.out.println("addButton");
}
}
Methods addMainButton() and addButton() are supposed to be run from eventHandler... attached to my SWT table...
How to solve this ?
Pls help :)
You need to tell it to redo your layout after you add buttons.
pathBar.layout(true);