I'm working on setting new tab orders for multiple Panels accross an application. I want to create a new class that will allow me to handle to sorting much in the same way as the deprecated setNextFocusableComponent method would. I'm creating a class that extends the DefaultTreversalPolicyClass and overloads the getComponentBefore and getComponentAfter methods. On the panels themselves I want the implementation to be as simple as creating a new custom class (I'll call it CustomizedTabOrderPolicy) and then adding the components for which I want to specify the the tab order. I do this via a convenience method on the CustomizedTabOrderPolicy class which allows me to add the components for which I want to specify tab order "link" to a Hashtable, I then create a copy of the hastable in which the key and value are switched. I then use these hashtables to establish a forward and backward tab order "link" between the two components.
the following is my custom class:
import java.awt.Component;
import java.awt.Container;
import java.awt.DefaultFocusTraversalPolicy;
import java.util.Hashtable;
public class CustomizedTabOrderPolicy extends DefaultFocusTraversalPolicy {
private Hashtable<Component, Component> exceptionsMap = new Hashtable<Component, Component>();
private Hashtable<Component, Component> excpetionsMapReflection = new Hashtable<Component, Component>();
private Component defaultComponent;
private Component firstComponent;
private Component lastComponent;
public CustomizedTabOrderPolicy() {
}
#Override
public Component getComponentAfter(Container container, Component component) {
java.awt.Toolkit.getDefaultToolkit().beep();
if (exceptionsMap.containsKey(component)) {
return exceptionsMap.get(component);
} else {
return super.getComponentAfter(container, component);
}
}
#Override
public Component getComponentBefore(Container container, Component component) {
if (exceptionsMap.containsValue(component)) {
return excpetionsMapReflection.get(component);
} else {
return super.getComponentBefore(container, component);
}
}
#Override
public Component getDefaultComponent(Container container) {
if (this.defaultComponent != null) {
return this.defaultComponent;
}
return super.getDefaultComponent(container);
}
#Override
public Component getFirstComponent(Container container) {
if (this.firstComponent != null) {
return this.firstComponent;
}
return super.getFirstComponent(container);
}
#Override
public Component getLastComponent(Container container) {
if (this.lastComponent != null) {
return this.lastComponent;
}
return super.getLastComponent(container);
}
public void addNewException(Component origin, Component destination) {
exceptionsMap.put(origin, destination);
excpetionsMapReflection.put(destination, origin);
}
public void setDefaultComponent(Component defaultComponent) {
this.defaultComponent = defaultComponent;
}
public void setLastComponent(Component lastComponent) {
this.lastComponent = lastComponent;
}
public void setFirstComponent(Component firstComponent) {
this.firstComponent = firstComponent;
}
}
In order to implement this I am using the following on my JPanel:
CustomizedTabOrderPolicy customPolicy = new CustomizedTabOrderPolicy();
customPolicy.addNewException(<component1>, <component2>);
customPolicy.addNewException(<component3>, <component4>);
customPolicy.addNewException(<component5>, <component6>);
this.setFocusTraversalPolicy(customPolicy);
I don't get any compiler or runtime errors, but it seems that my CustomTabOrderPolicy is not being used at all by the JPanel. When I insert breakpoints into the getComponentBefore and getComponentAfter methods, they are never triggered. I am very new to working with Focus Traversal Policies so I'm wondering what I'm missing here...
Ok so what I should have done is inherited FocusTraversalPolicy and not DefaultFocusTraversalPolicy and passed in a copy of the original Policy to revert to when there are no "exceptions". I also need to set FocusCycleRoot to true on the panel in order for it to use my FocusTraversalPolicy. I now have it set up so that I have an instance of my CustomizedTabOrderPolicy on my AbstractJPanel base class so that all I need to call in the panel initialization is something like this:
this.setTabOrderEnabled(true);
this.getCustomTabOrder().addNewException(component1, component2);
this.getCustomTabOrder().addNewException(component2, component3);
this.getCustomTabOrder().addNewException(component3, component4);
I've also changed the class itself somewhat to make it more effective.
public class CustomizedTabOrderPolicy extends FocusTraversalPolicy {
private FocusTraversalPolicy oldPolicy;
private Hashtable<Component, Component> exceptionsMap = new Hashtable<Component, Component>();
private Hashtable<Component, Component> exceptionsMapReflection = new Hashtable<Component, Component>();
private Component defaultComponent;
private Component firstComponent;
private Component lastComponent;
public CustomizedTabOrderPolicy(FocusTraversalPolicy oldPolicy) {
this.oldPolicy = oldPolicy;
}
public Component getComponentAfter(Container container, Component component) {
if (component == this.getLastComponent(container)) {
if (isValid(this.getFirstComponent(container))) {
return this.getFirstComponent(container);
} else {
return this.getComponentAfter(container, this.getFirstComponent(container));
}
}
if (exceptionsMap.containsKey(component) && isValid(exceptionsMap.get(component))) {
return exceptionsMap.get(component);
} else {
return oldPolicy.getComponentAfter(container, component);
}
}
public Component getComponentBefore(Container container, Component component) {
if (component == this.getFirstComponent(container)) {
if (isValid(this.getLastComponent(container))) {
return this.getLastComponent(container);
} else {
return this.getComponentBefore(container, this.getLastComponent(container));
}
}
if (exceptionsMapReflection.containsKey(component) && isValid(exceptionsMapReflection.get(component))) {
return exceptionsMapReflection.get(component);
} else {
return oldPolicy.getComponentBefore(container, component);
}
}
public boolean isValid(Component component) {
if (component.isEnabled() && component.isVisible() && component.isFocusable()) {
return true;
} else {
return false;
}
}
public Component getDefaultComponent(Container container) {
if (this.defaultComponent != null) {
return this.defaultComponent;
}
return oldPolicy.getDefaultComponent(container);
}
public Component getFirstComponent(Container container) {
if (this.firstComponent != null) {
return this.firstComponent;
}
return oldPolicy.getFirstComponent(container);
}
public Component getLastComponent(Container container) {
if (this.lastComponent != null) {
return this.lastComponent;
} else {
return oldPolicy.getLastComponent(container);
}
}
public void addNewException(Component origin, Component destination) {
exceptionsMap.put(origin, destination);
exceptionsMapReflection.put(destination, origin);
}
public void setDefaultComponent(Component defaultComponent) {
this.defaultComponent = defaultComponent;
}
public void setLastComponent(Component lastComponent) {
this.lastComponent = lastComponent;
}
public void setFirstComponent(Component firstComponent) {
this.firstComponent = firstComponent;
}
}
efficient! I use this with a little modification to let it continue traversing when not isValid() (of course it should at least one isValid() component in your panel):
public Component getComponentAfter(Container container, Component component) {
if (component == this.getLastComponent(container)) {
if (isValid(this.getFirstComponent(container))) return this.getFirstComponent(container);
else return this.getComponentAfter(container, this.getFirstComponent(container));
}
if( exceptionsMap.containsKey(component) ) {
component = exceptionsMap.get(component);
if( isValid(component) ) return component;
return this.getComponentAfter(container, component);
}
return oldPolicy.getComponentAfter(container, component);
}
public Component getComponentBefore(Container container, Component component) {
if (component == this.getFirstComponent(container)) {
if (isValid(this.getLastComponent(container))) return this.getLastComponent(container);
else return this.getComponentBefore(container, this.getLastComponent(container));
}
if( exceptionsMapReflection.containsKey(component) ) {
component = exceptionsMapReflection.get(component);
if( isValid(component) ) return component;
return this.getComponentBefore(container, component);
}
return oldPolicy.getComponentBefore(container, component);
}
Related
I have a textual editor that extends AbstractTextEditor and I also have an Outline that needs to be saved when its content is modified by the user. I am currently using a Saveable which is added to the editor.
If the editor was marked as 'dirty' and it is saved, the Saveableis saved as well. However, if the Saveable's state changes to 'dirty', the * next to the file name does not appear. The save button in the top menu bar does show, but when I click it, nothing happens.
This is my implementation:
public class MyTextEditor extends AbstractTextEditor {
...
public void setOutlineSaveable(Saveable saveable) {
this.outlineSaveable = saveable;
ISaveablesLifecycleListener lifecycleListener = (ISaveablesLifecycleListener)getSite().getService(ISaveablesLifecycleListener.class);
lifecycleListener.handleLifecycleEvent( new SaveablesLifecycleEvent(this, SaveablesLifecycleEvent.POST_OPEN, new Saveable[] {saveable}, false));
}
#Override
public Saveable[] getSaveables() {
if(outlineSaveable != null) {
// copy Saveables from super.getSaveables() to a new array
Saveable[] superSaveables = super.getSaveables();
Saveable[] res = new Saveable[superSaveables.length + 1];
int i = 0;
for(; i < superSaveables.length; i++) {
res[i] = superSaveables[i];
}
res[i] = outlineSaveable;
return res;
}
else
return super.getSaveables();
}
public void saveableDirty() {
firePropertyChange(PROP_DIRTY);
}
}
My ContentOutlinePage:
public class GraphicalOutlinePage extends ContentOutlinePage {
...
private GraphicalOutlineSaveable saveable;
public Saveable getSaveable() {
return saveable;
}
class GraphicalOutlineSaveable extends Saveable {
private boolean dirty = false;
private IEditorPart editor;
public GraphicalOutlineSaveable(IEditorPart editor) {
this.editor = editor;
}
#Override
public void doSave(IProgressMonitor monitor) throws CoreException {
viewer.doSave(monitor);
dirty = false;
}
#Override
public boolean equals(Object obj) {
System.err.println("GraphicalOutline.GraphicalOutlineSaveable.equals");
return obj instanceof GraphicalOutlineSaveable && ((Saveable)obj).getName() == getName();
}
#Override
public ImageDescriptor getImageDescriptor() {
return editor.getEditorInput().getImageDescriptor();
}
#Override
public String getName() {
return "Graphical Outline: " + editor.getEditorInput().getName();
}
#Override
public String getToolTipText() {
return "";
}
#Override
public boolean isDirty() {
System.err.println("GraphicalOutlinePage.GraphicalOutlineSaveable.isDirty: " + dirty);
return dirty;
}
public void setDirty() {
System.err.println("GraphicalOutlinePage.GraphicalOutlineSaveable.setDirty");
dirty = true;
// notify text editor about property change
if(editor instanceof AbstractTextEditor) {
((MyTextEditor)editor).saveableDirty();
}
}
#Override
public int hashCode() {
return viewer.hashCode();
}
}
}
vieweris a GraphicalViewerdisplayed in the ContentOutlinePage.
Somewhere in another class, I then call:
textEditor.setSaveable(grOutlinePage.getSaveable());
You may need to override the main editor isDirty() method and test each of the Saveable objects dirty flags.
It seems that the handling of multiple Saveables is not done as cleanly is it might have been.
I have a ValueAwareEditor that contains a couple of sub editors:
Essentially, an OfferDto is composed of a TariffDto and a Commission. The Commission can be one of 4 sub-types, but there is only ever one. Usually this list of possible commissions inside the TariffDto will only contain one element, but it can sometimes contain two.
public class OfferDto
{
private TariffDto tariff;
// selected from the list in the tariff
private Commission commission;
}
public class TariffDto extends EntityDto
{
// omitted for brevity...
protected List<Commission> commissions = new ArrayList<Commission>();
}
When commissions contains more than one item, I want to display a dropdown with the two optiions, and add allow the user to choose between them, each time resetting the commission in the OfferDto and the CommissionEditor.
The problem is that, when call commission.setValue() for the second time, the editor does not change. What should I be doing here?
public class OfferEditor extends Composite implements ValueAwareEditor<OfferDto>
{
#UiField
TariffRenderer tariff;
#Ignore
#UiField
HTMLPanel panel;
#UiField
CommissionEditor commission;
#Override
public void setValue(final OfferDto value)
{
panel.clear();
List<Commission> commissions = value.getTariff().getCommissions();
if(commissions.size() == 1)
{
value.setCommission(commissions.get(0));
}
else
{
// multiple commissions
ValueListBox<Commission> dropdown = new ValueListBox<Commission>(new Renderer<Commission>()
{
#Override
public String render(Commission object)
{
return object == null ? "" : object.getName();
}
#Override
public void render(Commission object, Appendable appendable) throws IOException
{
appendable.append(render(object));
}
});
dropdown.setValue(value.getCommission());
dropdown.setAcceptableValues(commissions);
dropdown.addValueChangeHandler(new ValueChangeHandler<Commission>()
{
#Override
public void onValueChange(ValueChangeEvent<Commission> event)
{
Commission selected = event.getValue();
// this works, but the CommissionEditor that was first rendered remains
value.setCommission(selected);
}
});
panel.add(dropdown);
}
}
}
Currently, I am rendering the list of commissions in a ValueListBox, then when the value changes I am pushing that value to the OfferDto. The Commission seems to get set right, but the subEditor does not change.
Any help greatly appreciated.
EDIT:
CommissionEditor shows the relevant sub-editor depending on the type.
public class CommissionEditor extends Composite implements Editor<Commission>
{
private static CommissionEditorUiBinder uiBinder = GWT.create(CommissionEditorUiBinder.class);
interface CommissionEditorUiBinder extends UiBinder<Widget, CommissionEditor>
{
}
#UiField
Panel subEditorPanel;
public CommissionEditor()
{
initWidget(uiBinder.createAndBindUi(this));
}
#Ignore
final UnitRateCommissionEditor unitRateCommissionEditor = new UnitRateCommissionEditor();
#Path("")
final AbstractSubTypeEditor<Commission, UnitRateCommission, UnitRateCommissionEditor> unitRateCommissionEditorWrapper = new AbstractSubTypeEditor<Commission, UnitRateCommission, UnitRateCommissionEditor>(
unitRateCommissionEditor)
{
#Override
public void setValue(final Commission value)
{
if(value instanceof UnitRateCommission)
{
setValue(value, value instanceof UnitRateCommission);
System.out.println("UnitRateCommission setValue");
subEditorPanel.clear();
subEditorPanel.add(unitRateCommissionEditor);
}
}
};
#Ignore
final StandingChargeCommissionEditor standingChargeCommissionEditor = new StandingChargeCommissionEditor();
#Path("")
final AbstractSubTypeEditor<Commission, StandingChargeCommission, StandingChargeCommissionEditor> standingChargeCommissionEditorWrapper = new AbstractSubTypeEditor<Commission, StandingChargeCommission, StandingChargeCommissionEditor>(
standingChargeCommissionEditor)
{
#Override
public void setValue(final Commission value)
{
if(value instanceof StandingChargeCommission)
{
setValue(value, value instanceof StandingChargeCommission);
System.out.println("StandingChargeCommission setValue");
subEditorPanel.clear();
subEditorPanel.add(standingChargeCommissionEditor);
}
}
};
#Ignore
final PerMwhCommissionEditor perMwhCommissionEditor = new PerMwhCommissionEditor();
#Path("")
final AbstractSubTypeEditor<Commission, PerMwhCommission, PerMwhCommissionEditor> perMwhCommissionEditorWrapper = new AbstractSubTypeEditor<Commission, PerMwhCommission, PerMwhCommissionEditor>(
perMwhCommissionEditor)
{
#Override
public void setValue(final Commission value)
{
if(value instanceof PerMwhCommission)
{
setValue(value, value instanceof PerMwhCommission);
System.out.println("PerMwhCommission setValue");
subEditorPanel.clear();
subEditorPanel.add(perMwhCommissionEditor);
}
}
};
}
Possible Solution:
I changed OfferEditor as so:
public class OfferEditor extends Composite implements Editor<OfferDto>
{
#UiField
TariffRenderer tariff;
#Path("tariff.commissions")
#UiField
CommissionsEditor commission;
}
New editor CommissionsEditor is a CompositeEditor. It needs to take List tariff.commissions and set the chosen Commission into offer.commission:
public class CommissionsEditor extends Composite implements CompositeEditor<List<Commission>, Commission, CommissionEditor>
{
private static CommissionsEditorUiBinder uiBinder = GWT.create(CommissionsEditorUiBinder.class);
interface CommissionsEditorUiBinder extends UiBinder<Widget, CommissionsEditor>
{
}
private EditorChain<Commission, CommissionEditor> chain;
#UiField
FlowPanel dropdownPanel, subEditorPanel;
#Ignore
CommissionEditor subEditor;
public CommissionsEditor()
{
initWidget(uiBinder.createAndBindUi(this));
}
#Override
public void setValue(List<Commission> valueList)
{
// clear both panels
dropdownPanel.clear();
subEditorPanel.clear();
if(valueList.size() == 1)
{
// set the commission to the first in the list
Commission selected = valueList.get(0);
subEditor = new CommissionEditor();
subEditorPanel.add(subEditor);
chain.attach(selected, subEditor);
}
else if(valueList.size() > 1)
{
ValueListBox<Commission> dropdown = new ValueListBox<Commission>(new Renderer<Commission>()
{
#Override
public String render(Commission object)
{
return object == null ? "" : object.getName();
}
#Override
public void render(Commission object, Appendable appendable) throws IOException
{
appendable.append(render(object));
}
});
dropdownPanel.add(dropdown);
dropdown.setValue(valueList.get(0));
dropdown.setAcceptableValues(valueList);
dropdown.addValueChangeHandler(new ValueChangeHandler<Commission>()
{
#Override
public void onValueChange(ValueChangeEvent<Commission> event)
{
Commission selected = event.getValue();
subEditorPanel.clear();
CommissionEditor subEditor = new CommissionEditor();
subEditorPanel.add(subEditor);
chain.attach(selected, subEditor);
}
});
}
}
#Override
public void flush()
{
}
#Override
public void onPropertyChange(String... paths)
{
// TODO Auto-generated method stub
}
#Override
public void setDelegate(EditorDelegate<List<Commission>> delegate)
{
// TODO Auto-generated method stub
}
#Override
public CommissionEditor createEditorForTraversal()
{
return new CommissionEditor();
}
#Override
public String getPathElement(CommissionEditor subEditor)
{
return null;
}
#Override
public void setEditorChain(EditorChain<Commission, CommissionEditor> chain)
{
this.chain = chain;
}
}
When the CommissionsEditor renders the dropdown and onValueChange() is called, the new editor gets created, but the value for the commission never seems to get set.
For some reason the selected subEditor's value is not pushed into offer.setCommission(). I thought chain.attach() would perform this for me?
I've implemented a set of draggable tabs, following the form of this example:
How to implement draggable tab using Java Swing?
Everything appears to work as I desire, however,when I drag outside of the main panel, the desktop will become a valid drop target (the resulting drop is accepted and marked successful).
Is there a way to intercept this drop to react to dropping outside of our root pane? It's simple enough to detect, but it's not clear to me how to actually capture the drop before the outside world does.
By the time DragSourceListener's dragDropEnd is called, the drop is already executed and there doesn't appear to be a good way to end dragging in dragOver/Exit/Whatever.
Gee, it'd be nice if something like this worked:
#Override
public void dragOver(DragSourceDragEvent dragEvent)
{
DragEnabledTabTransferData data = getTabTransferData(dragEvent);
DragSourceContext dragSourceContext = dragEvent.getDragSourceContext();
if (data == null)
{
dragSourceContext.setCursor(DragSource.DefaultMoveNoDrop);
return;
}
if (!data.getTabbedPane().getRootPane().getBounds().contains(dragEvent.getLocation()))
{
dragSourceContext.dragDropEnd(new DragSourceDropEvent(dragSourceContext, 999, true));
}
}
Instead the drag continues dragging along. I do, however get a dragDropEnd for my troubles.
Any ideas? I'd be pretty sad to hear that the only solution would be to have some hidden maximized global pane that acted only as a drop target to capture out-of-window events.
Here is a working example. If you drag a tab out to, say, the desktop in Linux, it'll try to cast the transfer data into a Serializable and not be happy. The drag over I was playing with is commented with "This is where I'd assume we'd be able to intercept stuff" if you want to jump straight to what I'd pointed to above.
/** "Simple" example of DnD tabbed panes. Sourced from Eugene Yokota:
* http:stackoverflow.com/questions/60269/how-to-implement-draggable-tab-using-java-swing */
import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.dnd.*;
import javax.swing.*;
public class DnDTabbedPane extends JTabbedPane {
private static final String NAME = "TabTransferData";
private final DataFlavor FLAVOR = new DataFlavor(DataFlavor.javaJVMLocalObjectMimeType, NAME);
public DnDTabbedPane() {
super();
final DragSourceListener dsl = new DragSourceListener() {
public void dragEnter(DragSourceDragEvent e) {
e.getDragSourceContext().setCursor(DragSource.DefaultMoveDrop);
}
public void dragExit(DragSourceEvent e) {
e.getDragSourceContext().setCursor(DragSource.DefaultMoveNoDrop);
}
/**
* This is where I'd assume we'd be able to intercept stuff
* so drops don't happen where we don't want them to.
*/
public void dragOver(DragSourceDragEvent e) {
TabTransferData data = getTabTransferData(e);
if (data == null) {
e.getDragSourceContext().setCursor(DragSource.DefaultMoveNoDrop);
return;
}
//This is where I ended up robokilling the drag via hackery
e.getDragSourceContext().setCursor(DragSource.DefaultMoveDrop);
}
public void dragDropEnd(DragSourceDropEvent e) {}
public void dropActionChanged(DragSourceDragEvent e) {}
};
final DragGestureListener dgl = new DragGestureListener() {
public void dragGestureRecognized(DragGestureEvent e) {
Point tabPt = e.getDragOrigin();
int dragTabIndex = indexAtLocation(tabPt.x, tabPt.y);
if (dragTabIndex < 0) {
return;
}
e.startDrag(DragSource.DefaultMoveDrop,new TabTransferable(DnDTabbedPane.this, dragTabIndex), dsl);
}
};
new DropTarget(this, DnDConstants.ACTION_COPY_OR_MOVE, new CDropTargetListener(), true);
new DragSource().createDefaultDragGestureRecognizer(this, DnDConstants.ACTION_COPY_OR_MOVE, dgl);
}
private TabTransferData getTabTransferData(DropTargetDropEvent a_event) {
try {
return (TabTransferData) a_event.getTransferable().getTransferData(FLAVOR);
} catch (Exception e) {}
return null;
}
private TabTransferData getTabTransferData(DropTargetDragEvent a_event) {
try {
return (TabTransferData) a_event.getTransferable().getTransferData(FLAVOR);
} catch (Exception e) {}
return null;
}
private TabTransferData getTabTransferData(DragSourceDragEvent a_event) {
try {
return (TabTransferData) a_event.getDragSourceContext().getTransferable().getTransferData(FLAVOR);
} catch (Exception e) {}
return null;
}
class TabTransferable implements Transferable {
private TabTransferData m_data = null;
private DataFlavor[] flavors = {FLAVOR};
public TabTransferable(DnDTabbedPane a_tabbedPane, int a_tabIndex) {
m_data = new TabTransferData(DnDTabbedPane.this, a_tabIndex);
}
public Object getTransferData(DataFlavor flavor) {
return m_data;
}
public DataFlavor[] getTransferDataFlavors() {
return flavors;
}
public boolean isDataFlavorSupported(DataFlavor flavor) {
return flavor.getHumanPresentableName().equals(NAME);
}
}
class TabTransferData {
DnDTabbedPane m_tabbedPane = null;
int m_tabIndex = -1;
public TabTransferData(DnDTabbedPane a_tabbedPane, int a_tabIndex) {
m_tabbedPane = a_tabbedPane;
m_tabIndex = a_tabIndex;
}
}
class CDropTargetListener implements DropTargetListener {
public void dragEnter(DropTargetDragEvent e) {
if (isDragAcceptable(e)) {
e.acceptDrag(e.getDropAction());
} else {
e.rejectDrag();
}
}
public void drop(DropTargetDropEvent a_event) {
if (isDropAcceptable(a_event)) {
convertTab(getTabTransferData(a_event),
getTargetTabIndex(a_event.getLocation()));
a_event.dropComplete(true);
} else {
a_event.dropComplete(false);
}
}
private boolean isTransferableGood(Transferable t, DataFlavor flavor)
{
return t == null || t.isDataFlavorSupported(flavor);
}
private boolean isDataGood(TabTransferData data)
{
if (DnDTabbedPane.this == data.m_tabbedPane && data.m_tabIndex >= 0) {
return true;
}
return false;
}
public boolean isDragAcceptable(DropTargetDragEvent e) {
Transferable t = e.getTransferable();
if (!isTransferableGood(t, e.getCurrentDataFlavors()[0])) {
return false;
}
return isDataGood(getTabTransferData(e));
}
public boolean isDropAcceptable(DropTargetDropEvent e) {
Transferable t = e.getTransferable();
if (!isTransferableGood(t, e.getCurrentDataFlavors()[0])) {
return false;
}
return isDataGood(getTabTransferData(e));
}
public void dragExit(DropTargetEvent e) {}
public void dropActionChanged(DropTargetDragEvent e) {}
public void dragOver(final DropTargetDragEvent e) {}
}
private int getTargetTabIndex(Point a_point) {
for (int i = 0; i < getTabCount(); i++) {
Rectangle r = getBoundsAt(i);
r.setRect(r.x - r.width / 2, r.y, r.width, r.height);
if (r.contains(a_point)) {
return i;
}
}
return -1;
}
private void convertTab(TabTransferData a_data, int a_targetIndex) {
DnDTabbedPane source = a_data.m_tabbedPane;
int sourceIndex = a_data.m_tabIndex;
if (sourceIndex < 0) {
return;
}
Component cmp = source.getComponentAt(sourceIndex);
String str = source.getTitleAt(sourceIndex);
if (a_targetIndex < 0 || sourceIndex == a_targetIndex) {
return;
}
source.remove(sourceIndex);
if (a_targetIndex == getTabCount()) {
addTab(str, cmp);
} else if (sourceIndex > a_targetIndex) {
insertTab(str, null, cmp, null, a_targetIndex);
} else {
insertTab(str, null, cmp, null, a_targetIndex - 1);
}
}
public static void main(String[] args)
{
JFrame window = new JFrame();
DnDTabbedPane tabbedPane = new DnDTabbedPane();
for(int i=0; i< 5; i++)
{
tabbedPane.addTab("I'm tab "+i, new JLabel("I'm tab "+i));
}
window.add(tabbedPane);
window.setSize(400, 200);
window.setVisible(true);
}
}
Thus far, the best I can do is call something to this effect when we hop out of the parent.
Component rootPane = SwingUtilities.getRoot(component);
Rectangle bounds = rootPane.getBounds();
if (!bounds.contains(location))
{
Robot robot = null;
try
{
robot = new Robot();
} catch (AWTException e)
{
return;
}
robot.keyPress(KeyEvent.VK_ESCAPE);
robot.keyRelease(KeyEvent.VK_ESCAPE);
}
It's a total hack, and doesn't solve my issue. I'd like to intercept the final drop event, see if it was outside of the frame and spawn the tab in its own JFrame.
If I was using the NetBeans, MyDoggy, or Eclipse frameworks, I guess this would all be magically handled for me. Alas.
There is no Way to Cancel the Drag directly. see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4502185
I would prefer to show the User that Drop on Desktop is not allowed, by changing the Cursor.
Your DragSourceListener dsl has in the dragOver method a DragSourceDragEvent which tells you
that the target action is NONE over the Desktop.
Change to this:
public void dragOver(DragSourceDragEvent e) {
TabTransferData data = getTabTransferData(e);
if( data == null || e.getTargetActions() == DnDConstants.ACTION_NONE ) {
e.getDragSourceContext().setCursor( DragSource.DefaultMoveNoDrop );
return;
}
e.getDragSourceContext().setCursor( DragSource.DefaultMoveDrop);
}
If you really want to Cancel, than you have to use your ESC solution or something like that:
try {
new Robot().mouseRelease( InputEvent.BUTTON1_MASK ); // if Button1 was the only Button to start a Drag
} catch( AWTException e1 ) {
}
As confirmed by #oliholz, there just isn't a way to do it without having to force a cancel via a keystroke.
However, for my needs of creating a tear-off tab, I found that creating a floating pane that was, itself, a drop target listener felt like the cleanest solution:
package com.amish.whatever;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JLabel;
import javax.swing.JWindow;
import javax.swing.Timer;
public class TearAwayTab extends JWindow {
MousePoller mousePoller = new MousePoller();
public TearAwayTab() {
this.add(new JLabel("FLONT"));
this.pack();
new DropTarget(this, DnDConstants.ACTION_COPY_OR_MOVE, new EasyDropTarget(), true);
this.setVisible(false);
}
private void center(Point location)
{
Point center = new Point();
center.setLocation(location.x-this.getWidth()/2, location.y-this.getHeight()/2);
TearAwayTab.this.setLocation(center);
}
public void attach(Point location)
{
center(location);
mousePoller.start();
this.setVisible(true);
}
public void detach()
{
mousePoller.stop();
this.setVisible(false);
}
private int DELAY = 10;
private class MousePoller extends Timer{
public MousePoller(){
super(DELAY, new ActionListener() {
private Point lastPoint = MouseInfo.getPointerInfo().getLocation();
#Override
public void actionPerformed(ActionEvent e) {
Point point = MouseInfo.getPointerInfo().getLocation();
if (!point.equals(lastPoint)) {
center(point);
}
lastPoint = point;
}
});
}
}
private class EasyDropTarget implements DropTargetListener
{
#Override
public void dragEnter(DropTargetDragEvent dtde) {
dtde.acceptDrag(dtde.getDropAction());
}
#Override
public void dragOver(DropTargetDragEvent dtde) {}
#Override
public void dropActionChanged(DropTargetDragEvent dtde) {}
#Override
public void dragExit(DropTargetEvent dte) {}
#Override
public void drop(DropTargetDropEvent dtde) {
dtde.dropComplete(true);
detach();
System.out.println("DROP Intercepted");
}
}
}
The bit with the MousePoller works around scrubbing the mouse too fast for mouse listeners to reliably update the location. I'd tried with a motion listener and was able to escape the bounds of the floater quite easily.
Back in the first example, I now include the tear away tab as a private member of the tabbed pane, and call attach and detach when exiting or entering my drop areas:
final DragSourceListener dsl = new DragSourceListener() {
public void dragEnter(DragSourceDragEvent e) {
e.getDragSourceContext().setCursor(DragSource.DefaultMoveDrop);
Rectangle bounds = SwingUtilities.getRoot(DnDTabbedPane.this).getBounds();
if(bounds.contains(e.getLocation())){
tearTab.detach();
}
}
public void dragExit(DragSourceEvent e) {
e.getDragSourceContext().setCursor(DragSource.DefaultMoveNoDrop);
tearTab.attach(e.getLocation());
}
...
This also has the added benefit of preserving the DnD operation in the case of dragging out, and then back in.
Thanks for the input. If you have any other ideas/comments, I'm all ears.
This doesn't directly relate to tabs, but one way to stop drags from being able to be dragged to the desktop is to wrap whatever you're dragging in a custom wrapper class. Then, when you make your TransferHandler, make a DataFlavor localFlavor = new ActivationDataFlavor(YourWrapperClass.class, DataFlavor.javaJVMLocalObjectMimeType, "description"); Next, override the createTransferable method to have new DataHandler(passedInComponent, localFlavor.getMimeType()); and return a new Transferable in which you've overridden all the methods to only have your localFlavor. Finally, in the importData method, make sure to import your data as your localFlavor type. This will prevent dragging to the deaktop as the flavor you defined is local to the JVM.
I'm using JXTreeTable to display some data and I want to use the provided mechanisms of SwingX to change the renderer for some columns.
I previously used a JXTable and custom implementations of TableCellRenderer but this doesn't work anymore (I see strings where I should have progress bars, buttons,...).
I thus tried to achieve what I want by doing:
examsTable.getColumn(6).setCellRenderer(new DefaultTableRenderer(new ButtonProvider()));
But the overriden method createRenderer of ComponentProvider is called once (even when I have more than one line in my JXTreeTable) and no button is shown (the method only contains return new JButton();).
Thanks!
Edit> Hoped you would answer kleopatra and thus happy you did.
I did my best but somehow the table is not displayed. I guess I forgot something (I'm a C++ developer new to Java) but I guess it's not a serious problem and it's probably not related to my main problem.
import java.awt.Dimension;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import org.jdesktop.swingx.JXTreeTable;
import org.jdesktop.swingx.decorator.HighlighterFactory;
import org.jdesktop.swingx.renderer.CellContext;
import org.jdesktop.swingx.renderer.ComponentProvider;
import org.jdesktop.swingx.renderer.DefaultTableRenderer;
import org.jdesktop.swingx.treetable.AbstractTreeTableModel;
public class Test extends JFrame {
JXTreeTable table = new JXTreeTable();
JPanel panel = new JPanel();
public Test() {
setMinimumSize(new Dimension(400, 400));
table.setEditable(false);
table.setDragEnabled(false);
table.setColumnSelectionAllowed(false);
table.setHighlighters(HighlighterFactory.createAlternateStriping());
table.setRowHeight(20);
table.setMinimumSize(new Dimension(200, 200));
class Series {
public String seriesInstanceUID;
public String patientName;
public String patientBirthDate;
public String securityToken;
public Series(String seriesInstanceUID, String patientName, String patientBirthDate, String securityToken) {
this.seriesInstanceUID = seriesInstanceUID;
this.patientName = patientName;
this.patientBirthDate = patientBirthDate;
this.securityToken = securityToken;
}
}
class Study {
public List<Series> series = new ArrayList<Series>();
}
class Root {
public List<Study> studies = new ArrayList<Study>();
}
AbstractTreeTableModel model = new AbstractTreeTableModel() {
Root root = new Root() {
{
studies.add(new Study() {
{
series.add(new Series("Series 1.1", "Mr. X", "1988-10-23", "sec-xx-1"));
series.add(new Series("Series 1.2", "Mr. X", "1988-10-23", "sec-xx-2"));
series.add(new Series("Series 1.3", "Mr. X", "1988-10-23", "sec-xx-3"));
}
});
studies.add(new Study() {
{
series.add(new Series("Series 2.1", "Mrs. Y", "1960-02-11", "sec-yy-1"));
}
});
studies.add(new Study() {
{
series.add(new Series("Series 3.1", "HAL 9000", "1975-04-21", "sec-zz-1"));
series.add(new Series("Series 3.2", "HAL 9000", "1975-04-21", "sec-zz-2"));
}
});
}
};
#Override
public String getColumnName(int columnIndex) {
switch (columnIndex) {
case 0:
return "Series Instance UID";
case 1:
return "Patient Name";
case 2:
return "Patient Birth Date";
case 3:
return "View online";
default:
return "";
}
}
#Override
public int getIndexOfChild(Object parent, Object child) {
if (parent == root) {
return root.studies.indexOf(child);
}
if (parent instanceof Study) {
Study study = (Study) parent;
return study.series.indexOf(child);
}
return -1;
}
#Override
public int getChildCount(Object parent) {
if (parent == root) {
return root.studies.size();
}
if (parent instanceof Study) {
Study study = (Study) parent;
return study.series.size();
}
return 0;
}
#Override
public Object getChild(Object parent, int index) {
if (parent == root) {
return root.studies.get(index);
}
if (parent instanceof Study) {
Study study = (Study) parent;
return study.series.get(index);
}
return null;
}
#Override
public Object getValueAt(Object node, int columnIndex) {
if (!(node instanceof Series) && !(node instanceof Study))
return null;
if (columnIndex < 0 || columnIndex >= getColumnCount())
return null;
if (root == null)
return null;
if (node instanceof Series) {
Series series = (Series) node;
if (columnIndex == 0)
return series.seriesInstanceUID;
else if (columnIndex == 1)
return series.patientName;
else if (columnIndex == 2)
return series.patientBirthDate;
else if (columnIndex == 3)
return series.securityToken;
} else if (node instanceof Study) {
// Empty for now
}
return null;
}
#Override
public int getColumnCount() {
return 4;
}
#Override
public Object getRoot() {
return root;
}
public void update() {
modelSupport.fireNewRoot();
}
};
table.setTreeTableModel(model);
table.getColumnModel().getColumn(3).setCellRenderer(new DefaultTableRenderer(new ComponentProvider<JButton>() {
{
rendererComponent.setHorizontalAlignment(JButton.CENTER);
}
#Override
protected void format(CellContext context) {
rendererComponent.setText(getValueAsString(context));
}
#Override
protected void configureState(CellContext context) {
rendererComponent.setHorizontalAlignment(getHorizontalAlignment());
}
#Override
protected JButton createRendererComponent() {
return new JButton("View online");
}
}));
panel.add(table);
this.setContentPane(panel);
}
public static void main(String[] args) {
(new Test()).setVisible(true);
}
}
EDIT> I actually have observed that it works. But not the way I want. I want to see a real button (now it looks just a little bit different from the rest of the line), know the path to it (parent object and column index) and see effects when clicking or hovering on it (button should look pressed,...).
How do I achieve that?
The button is used, it only appears not to be in some LAFs:-)
Technically, the reasons are
the highlighter sets the button's background
the default visuals (used by the provider) sets the border to the default as returned by the cellContext
In combination the button doesn't look like a button for Metal (while it is unchanged f.i. in Windows)
No satisfying solution, options are
not use striping
let the button have a dont-touch-my-background property, extend the ColorHighlighter to respect that and use that custom highlighter for striping (there's an example in the test package)
I have subclassed org.eclipse.swt.widgets.Composite to create a new composite control. I want to capture MouseEnter and MouseExit events in this control but the problem I have is that when the mouse is hovered over a component in the control (say, a Label) the MouseExit event is fired, even though the label is part of the whole Composite.
Is there any way to stop this event being fired? I only want to see the event if the mouse leaves the total boundary of the control. Here is some example code to show you what I mean.
public class MyControl extends Composite{
Label label;
public MyControl(Composite parent, String label) {
super(parent, SWT.NONE);
label = new Label(this,0);
label.setText(label);
this.addListener(SWT.MouseEnter, new Listener() {
#Override
public void handleEvent(Event event) {
// handle this event
}
});
this.addListener(SWT.MouseExit, new Listener() {
#Override
public void handleEvent(Event event) {
// handle this event
}
});
}
}
You can simply put an logic in your event handler to see if the control is a child of your new control and ignore it. Something like the following: (I haven't tested the code, but I think this should work for you)
this.addListener(SWT.MouseExit, new Listener() {
#Override
public void handleEvent(Event event) {
for (Control control : ParentClass.this.getChildren()) {
if (control == event.item)
return;
}
// handler logic goes here
}
});
I solved the same problem (MouseExit is sent to a Composite when the mouse enters one of its children) with an event filter. Here's the code.
public class MouseTracker implements Listener {
static MouseTracker instance;
private static class Item {
Composite composite;
boolean inside;
MouseTrackListener listener;
}
private List<Item> listeners = new ArrayList<>();
private MouseTracker() {
}
public static MouseTracker getInstance() {
if (instance == null)
instance = new MouseTracker();
return instance;
}
private void install() {
Display.getCurrent().addFilter(SWT.MouseEnter, this);
Display.getCurrent().addFilter(SWT.MouseExit, this);
Display.getCurrent().addFilter(SWT.Resize, this);
}
private void uninstall() {
Display.getCurrent().removeFilter(SWT.MouseEnter, this);
Display.getCurrent().removeFilter(SWT.MouseExit, this);
Display.getCurrent().removeFilter(SWT.Resize, this);
}
public void addMouseTrackListener(Composite c, MouseTrackListener listener) {
if (listeners.isEmpty())
install();
Item i = new Item();
i.composite = c;
i.inside = false;
i.listener = listener;
listeners.add(i);
}
public void removeMouseTrackListener(Composite c, MouseTrackListener listener) {
listeners.removeIf((i) -> i.composite == c && i.listener == listener);
if (listeners.isEmpty())
uninstall();
}
public void handleEvent(Event e) {
boolean hasDisposed = false;
for (Item i : listeners) {
Composite c = i.composite;
if (c.isDisposed())
hasDisposed = true;
else {
Point p = Display.getCurrent().getCursorLocation();
boolean containsMouse = c.getBounds().contains(c.getParent().toControl(p));
if (i.inside != containsMouse) {
i.inside = containsMouse;
if (containsMouse)
i.listener.mouseEnter(new MouseEvent(e));
else
i.listener.mouseExit(new MouseEvent(e));
}
}
}
if (hasDisposed) {
listeners.removeIf((i) -> i.composite.isDisposed());
if (listeners.isEmpty())
uninstall();
}
}
}