I have an SWT Tree in my application that contains an infinite data structure. Upon expanding an item, I generate its children. On Windows though, users can press "*", triggering an "expand all descendants" action, and my application hangs.
There are two acceptable behaviors for me when the user presses "*":
Expand all children of the selected element, but only to the next level
Do nothing
In either case, I will still need to be able to expand items as deep as required (by clicking on the [+] icon, or by pressing "+"), so limiting the tree depth is not a solution. Is there another way that I can achieve either of the above without modifying SWT classes?
I got this far -- maybe it helps someone:
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.*;
import org.eclipse.swt.layout.*;
import org.eclipse.swt.widgets.*;
public class SWTInfiniteTree {
private boolean expanding;
public SWTInfiniteTree() {
Display display = new Display();
Shell shell = new Shell(display);
shell.setLayout(new GridLayout());
Tree tree = new Tree(shell, SWT.BORDER);
tree.setLayoutData(new GridData(GridData.FILL_BOTH));
createItem(tree, "ITEM1");
createItem(tree, "ITEM2");
createItem(tree, "ITEM3");
tree.addTreeListener(new TreeListener() {
#Override
public void treeExpanded(TreeEvent e) {
TreeItem parent = (TreeItem) e.item;
if (expanding) {
e.doit = false;
} else {
expanding = true;
parent.removeAll();
createItem(parent, ".1");
createItem(parent, ".2");
createItem(parent, ".3");
}
}
#Override
public void treeCollapsed(TreeEvent e) {
}
});
tree.addKeyListener(new KeyListener() {
#Override
public void keyReleased(KeyEvent e) {
expanding = false;
}
#Override
public void keyPressed(KeyEvent e) {
}
});
tree.addMouseListener(new MouseListener() {
#Override
public void mouseUp(MouseEvent e) {
expanding = false;
}
#Override
public void mouseDown(MouseEvent e) {
}
#Override
public void mouseDoubleClick(MouseEvent e) {
expanding = false;
}
});
shell.setSize(300, 200);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
private TreeItem createItem(Widget parent, String text) {
TreeItem item;
if (parent instanceof Tree) {
item = new TreeItem((Tree) parent, SWT.NULL);
item.setText(text);
} else {
item = new TreeItem((TreeItem) parent, SWT.NULL);
item.setText(((TreeItem) parent).getText() + text);
}
// So that we have a [+] icon
item.setItemCount(1);
return item;
}
public static void main(String[] args) {
new SWTInfiniteTree();
}
}
What it does is it expands the first element, and then goes into "won't expand more" mode, which is lifted whenever a key or the mouse button is released. However, for some reason it will expand my freshly generated items.
I hope someone has a better solution.
Related
I have this JFace dialog:
setShellStyle(SWT.APPLICATION_MODAL | SWT.CLOSE);
setBlockOnOpen(false);
Is there a way to make it close by clicking somewhere outside the dialog?
Maybe something like listening for a click event on the whole screen and detecting if it's outside the dialog, and then closing.
You can attach an SWT.Deactivate listener to the underlying Shell of the dialog.
To attach the listener, you could override Window::configureShell like this:
#Override
protected void configureShell(Shell shell) {
super.configureShell(shell);
shell.addListener(SWT.Deactivate, event -> shell.close());
}
And here a standalone SWT example to illustrate the bare mechanism:
Display display = new Display();
Shell parentShell = new Shell(display);
parentShell.setSize(500, 500);
parentShell.open();
Shell shell = new Shell(parentShell);
shell.addListener(SWT.Deactivate, event -> shell.close());
shell.setSize(300, 300);
shell.setText("Closes on Deactivate");
shell.open();
while (!parentShell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
With the Dialog being modal I believe that causes some challenges using the Shell of the base application to listen for MouseEvents because the Dialog intercepts them.
If you're not opposed to using an additional library you could consider using JNativeHook to listen for global mouse click events. This would allow you to listen for a click anywhere on the computer and close the dialog if the click occurred outside the dialog bounds, if that's what you're looking for.
For example:
GlobalScreen.addNativeMouseListener(new NativeMouseInputAdapter() {
public void nativeMouseClicked(final NativeMouseEvent nativeMouseEvent) {
display.syncExec(new Runnable() {
public void run() {
if (dialog.getShell() == null || dialog.getShell().isDisposed()) {
return;
}
// Close the dialog if there is a mouse click outside the bounds of the dialog
if (!dialog.getShell().getBounds().contains(awtToSwtPoint(nativeMouseEvent.getPoint()))) {
dialog.close();
}
}
});
}
});
Other than that, I'm not aware of a way to listen to mouse clicks that are outside of the base application / anywhere on the screen.
Full example:
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.jnativehook.GlobalScreen;
import org.jnativehook.NativeHookException;
import org.jnativehook.mouse.NativeMouseEvent;
import org.jnativehook.mouse.NativeMouseInputAdapter;
public class DialogCloseTest {
private final Display display;
private final Shell shell;
public DialogCloseTest() {
display = new Display();
shell = new Shell(display);
shell.setSize(450, 450);
final Dialog dialog = new MyDialog(shell);
dialog.open();
registerNativeHook();
GlobalScreen.addNativeMouseListener(new NativeMouseInputAdapter() {
public void nativeMouseClicked(final NativeMouseEvent nativeMouseEvent) {
display.syncExec(new Runnable() {
public void run() {
if (dialog.getShell() == null || dialog.getShell().isDisposed()) {
return;
}
// Close the dialog if there is a mouse click outside the bounds of the dialog
if (!dialog.getShell().getBounds().contains(awtToSwtPoint(nativeMouseEvent.getPoint()))) {
dialog.close();
}
}
});
}
});
}
private org.eclipse.swt.graphics.Point awtToSwtPoint(final java.awt.Point point) {
return new org.eclipse.swt.graphics.Point(point.x, point.y);
}
private static void registerNativeHook() {
try {
GlobalScreen.registerNativeHook();
} catch (NativeHookException ex) {
System.err.println("There was a problem registering the native hook.");
System.err.println(ex.getMessage());
System.exit(1);
}
}
private static void unregisterNativeHook() {
try {
GlobalScreen.unregisterNativeHook();
} catch (NativeHookException e) {
System.err.println("There was a problem unregistering the native hook.");
System.err.println(e.getMessage());
}
}
private static class MyDialog extends Dialog {
MyDialog(final Shell parent) {
super(parent);
}
#Override
protected void configureShell(final Shell shell) {
super.configureShell(shell);
setShellStyle(SWT.APPLICATION_MODAL | SWT.CLOSE);
setBlockOnOpen(false);
}
}
public void run() {
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
unregisterNativeHook();
}
public static void main(String... args) {
new DialogCloseTest().run();
}
}
Note: This will close the Dialog even if it is not visible (eg. if you alt-tab away), so you could add some logic to check whether the dialog is visible as well, if you would like)
Here is my full coding.I have Two class firstone MyDateTime and Second one is Employee.
i have included currently working coding of mine.For the EmployeePart class,AbstractEditorPart is our own parent class Which is extended
public class MyDateTime extends DateTime{
public DateTime(Composite parent, int style)
{
super(parent, style);
}
public Date getValue()
{
Date date = new Date(getYear(), getMonth(), getDay());
return date;
}
}
public Class EmployeePart extends AbstractEditorPart(
private MyDateTime currentDate;
public void createBody(Composite parent){
currentDate=Util.createDateChooserCombo(parent, toolkit, "Date:", 2);
}
public void save(Employee input){
return null;
}
}
}
Turns out to be a little more complicated than I first thought.
One solution is to define a TabList for the Composite that contains your Widgets.
This way you can first define in which order you want them to be traversed.
Then add a Listener to each of the Widgets you want to traverse. This Listener will determine the next item in the TabList and force the focus on this item when either Tab or Enter is pressed.
Here is some example code:
public static void main(String[] args)
{
Display display = Display.getDefault();
Shell shell = new Shell(display);
shell.setLayout(new FillLayout());
final Composite content = new Composite(shell, SWT.NONE);
content.setLayout(new FillLayout());
Text first = new Text(content, SWT.BORDER);
Text second = new Text(content, SWT.BORDER);
content.setTabList(new Control[] {first, second});
Listener enterListener = new Listener()
{
#Override
public void handleEvent(Event event)
{
/* Is it a traverse via Tab or Enter? */
if(event.keyCode == SWT.CR || event.keyCode == SWT.TRAVERSE_RETURN || event.keyCode == SWT.TRAVERSE_TAB_NEXT)
{
/* Get source of event */
Widget source = event.widget;
/* Get traverse order of content composite */
Control[] tabList = content.getTabList();
/* Try to find current position in the tab list */
for(int i = 0; i < tabList.length; i++)
{
if(source.equals(tabList[i]))
{
/* Get the next item in the tab list */
Control nextControl = tabList[(i + 1) % tabList.length];
/* And force the focus on this item */
nextControl.setFocus();
nextControl.forceFocus();
return;
}
}
}
}
};
first.addListener(SWT.KeyUp, enterListener);
second.addListener(SWT.KeyUp, enterListener);
shell.pack();
shell.open();
while (!shell.isDisposed())
{
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
}
I wanted to have a Text widget that could display a message in it when the user has not entered a value into the field yet. I extended composite and essentially wrapped a text field in it. Added a focus listener to remove the message on focus, and to replace the message when the focus is lost if the field is empty. That all works as expected.
The issue I am having is I wanted the prompt to be styled differently when it is placed in the text field. The font does not seem to be being used initially. Once the field has had focus and loses focus it looks correct.
For example this is how it looks when it initially loads:
And this is how it should look on initial load and how it looks after having and lost focus:
It gets a little stranger, as when I run this inside a simple shell, it works how it should. When I run it as an Eclipse application is when it does not get styled correctly.
Here is the code for my composite:
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Text;
/**
* The <code>PromptingTextInput</code> component is a small enhancement to
* standard <code>Text</code>. It adds the ability to specify a prompt value
* that displays when the text is empty and the field does not have focus.
*/
public class PromptingTextInput extends Composite {
private String prompt;
private Text input;
private boolean textEmpty;
Font emptyFont;
Font inputFont;
public PromptingTextInput(String prompt, Composite parent, int style, boolean passwordField) {
super(parent, style);
this.prompt = prompt;
setLayout(new FillLayout());
this.textEmpty = true;
this.input = new Text(this, (passwordField ? SWT.PASSWORD : SWT.NONE));
setEmptyInputStyle();
this.input.setText(this.prompt);
this.input.addFocusListener(new FocusAdapter() {
public void focusGained(FocusEvent e) {
PromptingTextInput.this.focusGained();
}
public void focusLost(FocusEvent e) {
PromptingTextInput.this.focusLost();
}
});
addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent e) {
disposeFonts();
}
});
}
protected void focusGained() {
if (this.textEmpty) {
this.input.setText("");
setInputStyle();
}
}
protected void focusLost() {
if (input.getText() == null || input.getText().trim().length() == 0) {
this.input.setText(this.prompt);
setEmptyInputStyle();
this.textEmpty = true;
} else {
this.textEmpty = false;
}
}
protected void setInputStyle() {
if (this.inputFont == null){
this.inputFont = new Font(Display.getCurrent(), "Verdana", 8, SWT.DEFAULT);
}
this.input.setFont(this.inputFont);
this.input.setForeground(Display.getCurrent().getSystemColor(SWT.COLOR_BLACK));
}
protected void setEmptyInputStyle() {
if (this.emptyFont == null){
this.emptyFont = new Font(Display.getCurrent(), "Verdana", 6, SWT.ITALIC);
}
this.input.setFont(this.emptyFont);
this.input.setForeground(Display.getCurrent().getSystemColor(SWT.COLOR_GRAY));
}
public String getPrompt() {
return prompt;
}
public void setPrompt(String prompt) {
this.prompt = prompt;
if(!this.input.isFocusControl()){
this.input.setText(this.prompt);
setEmptyInputStyle();
}
}
public Text getInput() {
return input;
}
public boolean isTextEmpty() {
return textEmpty;
}
public String getText() {
return this.input.getText();
}
public void addModifyListener (ModifyListener listener) {
this.input.addModifyListener(listener);
}
public void disposeFonts(){
if (this.inputFont != null){
this.inputFont.dispose();
}
if (this.emptyFont != null){
this.emptyFont.dispose();
}
}
}
UPDATE: As Baz has shown this is not an issue in Indigo and only seems to be an issue with an E4 app in Juno.
Building upon sambi's answer, I got the following working. Maybe this works in Juno:
private static Font italic;
public static void main(String[] args) {
final Display display = new Display();
Shell shell = new Shell(display);
shell.setLayout(new GridLayout(1,false));
italic = new Font(Display.getCurrent(), "Verdana", 6, SWT.ITALIC);
final Text text = new Text(shell, SWT.BORDER);
text.addListener(SWT.Paint, new Listener() {
#Override
public void handleEvent(Event event) {
if(text.getText().length() < 1 && !text.isFocusControl())
{
GC gc = event.gc;
gc.setFont(italic);
gc.setForeground(display.getSystemColor(SWT.COLOR_GRAY));
Point size = text.computeSize(SWT.DEFAULT, SWT.DEFAULT);
/* Strangely the y positioning doesn't work correctly */
//gc.drawText("Please enter text", 1, (size.y / 2) - (italic.getFontData()[0].getHeight() / 2));
gc.drawText("Please enter text", 1, 4);
}
}
});
text.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true));
Button button = new Button(shell, SWT.PUSH);
button.setText("Dummy");
button.forceFocus();
shell.setSize(200, 100);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
italic.dispose();
display.dispose();
}
Without focus and empty text:
With focus or text:
You could use Text.setMessage (String message). The problem with this is you will not be able to customize much with font size and fore ground...etc.
To customize it, you can actually draw message with in the bounds of Text.
Consider the following Java (SWT) code:
private static ComboViewer createViewer(final Shell shell) {
final ComboViewer v = new ComboViewer(shell, SWT.DROP_DOWN);
v.setLabelProvider(new LabelProvider());
v.setContentProvider(new ArrayContentProvider());
v.setInput(new String[]{"value 1", "value 2"});
return v;
}
public static void main(final String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setSize(200, 60);
shell.setLayout(new GridLayout());
final ComboViewer v = createViewer(shell);
// This wires up the userSelectedSomething method correctly
v.addSelectionChangedListener(new ISelectionChangedListener() {
#Override
public void selectionChanged(final SelectionChangedEvent event) {
userSelectedSomething();
}
});
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
public static void userSelectedSomething() {
// This should be called *only if* the user selected from the drop-down
}
public static void userTypedSomething() {
// This should be called *only if* the user typed in the combo
}
I want to call the userTypedSomething method only if the user typed into the combo (and not when they selected from the drop-down). What listener should I add to achieve this? Adding a modify listener to the combo viewer with v.getCombo().addModifyListener(...) is no good as this is triggered for both typing and selection from the combo.
private static ComboViewer createViewer(final Shell shell) {
final ComboViewer v = new ComboViewer(shell, SWT.DROP_DOWN);
v.setLabelProvider(new LabelProvider());
v.setContentProvider(new ArrayContentProvider());
v.setInput(new String[]{"value 1", "value 2"});
return v;
}
private static boolean userTyped;
private static int index = -1;
public static void main(final String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setSize(200, 60);
shell.setLayout(new GridLayout());
final ComboViewer v = createViewer(shell);
/*
* invoked multiple times when combo selection happens
* invoked once when user types
*/
v.getCombo().addVerifyListener(new VerifyListener() {
#Override
public void verifyText(VerifyEvent e) {
userTyped = (e.keyCode != 0);
}
});
v.getCombo().addModifyListener(new ModifyListener() {
#Override
public void modifyText(ModifyEvent e) {
Combo c = (Combo)e.widget;
if(userTyped || index == c.getSelectionIndex() || c.getSelectionIndex() == -1)
{
userTypedOrEditedSomething();
}
index = c.getSelectionIndex();
}
});
// This wires up the userSelectedSomething method correctly
v.addSelectionChangedListener(new ISelectionChangedListener() {
#Override
public void selectionChanged(final SelectionChangedEvent event) {
userSelectedSomething();
}
});
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
public static void userSelectedSomething() {
// This should be called *only if* the user selected from the drop-down
System.out.println("User selected");
}
public static void userTypedOrEditedSomething() {
// This should be called *only if* the user typed in the combo
System.out.println("User typed or edited");
}
I would suggest you to use Verify event instead Key UP as you might endup handling lot of things (arrow keys, magic keys...etc). Verify is also Key Event but it filter out ALT,CNTRL,SHIFT combination. When user types just check for keycode!=0.
As you pointed out, when you use CNTRL+V ,Right click Menu paste....combo doesn't consider it as key event but it fires verify event to make sure the clipboard text is valid for combo or not. I think this is how it should work as Menu item selection and Key event on combo are different things.
you can always monitor all key events for special actions like copy/paste/delete.
the above sample code should be able to perform what you are looking for.
Since you want to listen to keyboard input, I would suggest listening to SWT.KeyUp.
This should be a good starting point:
public static void main(String[] args) {
final Display display = new Display();
final Shell shell = new Shell(display);
shell.setLayout(new FillLayout());
final Combo combo = new Combo(shell, SWT.NONE);
combo.add("First");
combo.add("Second");
combo.addListener(SWT.Selection, new Listener() {
#Override
public void handleEvent(Event arg0) {
System.out.println("Selected: " + combo.getItem(combo.getSelectionIndex()));
}
});
combo.addListener(SWT.KeyUp, new Listener() {
#Override
public void handleEvent(Event arg0) {
System.out.println("Typed");
}
});
shell.pack();
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
I have an SWT Composite that I need to pass to some other code which will add children to it at will. Is there any way to be notified that a child of the composite received and lost focus?
Just to make sure it's clear, I cannot add listeners to each child, because I'm not in charge of creating those controls. A child could be added at any time.
As noted by Favonius, you can hook layout events like SWT.Resize to determine when you're being painted and recompute your child hierarchy, adding listeners as appropriate. Another option is simply to listen for all focus events and only pay attention to those that are for controls that you're interested in.
Displays have filters which, like listeners, are notified of events, however filters differ in that they are run before listeners, they have the opportunity to cancel events, and they are notified for all of a type of event on the entire Display.
You could thus use a Filter to examine all focus events and determine if it's one that you're interested in. For example:
public class MyControl extends Composite
{
private final Listener focusListener;
public MyControl(final Composite parent, final int style)
{
/* initialize the control... */
focusListener = new Listener()
{
public void handleEvent(Event event)
{
if (!(event.widget instanceof Control))
{
return;
}
boolean isOurChild = false;
for (Control c = (Control) event.widget; c != null; c = c.getParent())
{
if (c == container)
{
isOurChild = true;
break;
}
}
if (isOurChild)
{
System.out.println("Our child is " + (event.type == SWT.FocusIn ? "focused" : "unfocused"));
}
}
};
getDisplay().addFilter(SWT.FocusIn, focusListener);
getDisplay().addFilter(SWT.FocusOut, focusListener);
addDisposeListener(new DisposeListener()
{
public void widgetDisposed(DisposeEvent e)
{
getDisplay().removeFilter(SWT.FocusIn, focusListener);
getDisplay().removeFilter(SWT.FocusOut, focusListener);
}
});
}
}
Do note the javadoc for Display's warnings about using filters:
They should generally be avoided for performance, debugging and code maintenance reasons.
Obviously you're looking at performance trade-offs in either solution - depending on what type of application you're delivering and your users' workflow, it may make more sense to add focus listeners when you resize, or it may make more sense to simply listen to all focus events and ignore the ones you're not interested in.
Have you checked this link: SWT: notify a composite that it has a new child
As per the proposed solution in the above link the only possible solution is to use the resize event. Based on that see the following code, which adds a focus listener on all the immediate child nodes. Though the solution itself is not very elegant.
Test Code
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
public class SWTApplication
{
public static void main(String[] args) {
new SWTApplication().initSystem("Children Notification");
}
private Display display;
private Shell shell;
public void initSystem(String windowName)
{
display = new Display();
shell = new Shell(display);
shell.setText(windowName);
shell.setLayout(new GridLayout(6, true));
shell.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
final TestFocusListener listener = new TestFocusListener();
shell.addControlListener(new ControlListener() {
public void controlResized(ControlEvent e) {
if(e.getSource() instanceof Shell)
{
Shell s = (Shell)e.getSource();
Control[] children = s.getChildren();
for (int i = 0; i < children.length; i++)
{
Control c = children[i];
c.removeFocusListener(listener);
c.addFocusListener(listener);
}
}
}
public void controlMoved(ControlEvent e) {
}
});
createControls();
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
}
private void createControls()
{
String[] name = {"a", "b", "c", "d", "e", "f"};
for(int i=0; i<6; i++)
{
Button button = new Button(shell, SWT.PUSH);
button.setText(name[i] + " button");
button.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
shell.setSize(shell.computeSize(SWT.DEFAULT, SWT.DEFAULT));
}
}
class TestFocusListener implements FocusListener
{
public void focusGained(FocusEvent e) {
Object src = e.getSource();
if(src instanceof Button)
{
System.out.println("Focus gained: " + ((Button)src).getText());
}
}
public void focusLost(FocusEvent e) {
Object src = e.getSource();
if(src instanceof Button)
{
System.out.println("Focus lost: " + ((Button)src).getText());
}
}
}
}