Java: DropTarget: Forward Transferable to JTable inside - java

I've enabled the Drag'n'Drop facilities of a JTable by setting a TransferHandler to it. Now I can add items and reorder them inside this table. For easier adding items from another location (let's say windows desktop) I also set a DropTarget to the JPanel which holds the table and some other components. This DropTarget simply forwards the Transferable to the table's TransferHandler which adds the item at the end of the list:
panel.setDropTarget(new DropTarget(table, new DropTargetAdapter(){
#Override
public void drop(DropTargetDropEvent dtde){
TransferSupport transferSupport = new TransferHandler.TransferSupport(table, dtde.getTransferable());
table.getTransferHandler().importData(transferSupport);
}
}));
I've instantiated 4 of these panels so I can both add items from outside and move them from one to another panel.
That works for me, except the fact, that it performs always a COPY, not a MOVE. If I ask the DropTargetDropEvent for the DropAction with getDropAction() an exception with "Not a Drop" is thrown. (strange: Why exists a DropTargetDropEvent which isn't a drop?)
An exportDone() is also implemented and works correct when I drop directly into the body of the table. When dropping on the underlaying panel, exportDone() is invoked too, but with SourceAction==NONE (int 0).
What am I doing wrong? What is the right way to perform the right (COPY or MOVE) action when dropping on one of my panels?

Related

MouseListener across several panels?

My user story is the following:
In order to upgrade a tower, the player has to drag a gun from the shop and drop it on the tower.
In practice, the GameView contains both the BattlefieldCanvas and the ShopView. Besides, the ShopView contains a GunSelector for each buyable gun. All those guys are sub-classes of JPanel.
I'm currently using a MousListener to handle several actions performed on the BattlefieldCanvas; I thought I could use the same stuff to handle a mouse trip from one of the GunSelector to the BattlefieldCanvas ( = across several panels ), so I tried to add the same MousListener to the gun selectors and the battlefield.
Problem: doesn't work. The getSource() method of the event object returns a reference to the gun selector while the mouse is actually released on the battlefield.
PS: Unlike gun selectors, towers are not swing components but images drawn by the paintComponent method.
1) To answer to your original question as to why source is still the component mouse was clicked on. You just need to read the JavaDoc:
public Object getSource()
The object on which the Event initially occurred.
Returns:
The object on which the Event initially occurred.
2) Now, how can we get the actual component that mouse is released on. You can try this approach:
#Override
public void mouseReleased(MouseEvent e) {
Component theCOmponentMouseIsReleasedOn = frame.findComponentAt( e.getLocationOnScreen() );
}
You don't need to always call findComponentAt on frame, you can call on the container that contains your BattlefieldCanvas.

Java Swing - make two JLists "siblings" - i.e. only one item in either can be selected

I have a JPanel which contains two JLists - both can have an item in them selected - as expected.
What I'd love to be able to do is have it so that only one item in either to be selected
Can anyone help me with either
a) "marrying" them so this can be the case
b) giving me some tips for the best practice to write listeners which can preside over them both and unselect all the elements of one when the other is selected - I'd rather avoid this if possible as I can see it getting ugly!!
Thanks :)
I think the best solution, also for the user, is putting a radio button next with a category label to each list, so you clearly disable the other each time you select one.
I can imagine the user clicking values on the first list, then clicking on the next one and seeing all the values he clicked are gone, with logical frustration...
Then when you are taking the values from the form, just take the enabled ones
The listener is not that difficult nor ugly to write. I would
make sure the lists only support single selection
add the same selection listener to both lists' selection model
This listener can be implemented as
public void valueChanged(ListSelectionEvent e){
if ( e.isAdjusting()) return;
ListSelectionModel sourceSelectionModel = (ListSelectionModel) e.getSource();
if ( !sourceSelectionModel.isSelectionEmpty() ){
//still need to implement the findOtherSelectionModel method
ListSelectionModel other = findOtherSelectionModel( sourceSelectionModel );
other.clearSelection();
}
}
Note that clearing the selection will trigger the listener again, but due to the isSelectionEmpty check you will not end up with a loop. Another approach would be to disable the listener (e.g. with a boolean flag) right before you call clearSelection on the other list.

JTree, always display all nodes in "edit mode"

I'm displaying a tree of custom objects, and I've got custom custom CellTreeEditor and CellTreeRenderer set.
Now what I really want is to always display all objects as in "edit mode". Right now I have the CellTreeRenderer.getTreeCellRendererComponent() and CellTreeEditor.getTreeCellEditorComponent() implemented almost identically. This kind of works, but I still have to click a node to focus it before I can do any editing.
Is there any more sensible way of doing this, perhaps like saying no renderer should never be used, defaulting to my CellTreeEditor?
******UPDATE****
To clearify: What I have is a tree looking like this (and yes, it also looks like crap, but that's beside the point):
Right now, I accomplish this by having a renderer and an editor that returns identical components from getTreeCell[Renderer|Editor]Component().
If I click on the down-arrow on the ComboBox supplied by the renderer, it will flicker slighty as it opens the dropdown, but then be interrupted and replaced by my editor component. This means I have to click it again to open the dropdown. This is the behaviour I want to avoid.
Expanding my comment: no, you don't want to have your editor shared across cells (nasty thingies start to happen) Instead, add a TreeCellListener which listens for changes in the lead (aka: focused) selection path and then explicitly start editing on that path
final JXTree tree = new JXTree();
tree.setEditable(true);
tree.expandAll();
TreeSelectionListener l = new TreeSelectionListener() {
#Override
public void valueChanged(TreeSelectionEvent e) {
final TreePath path = e.getNewLeadSelectionPath();
if (path != null) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
tree.startEditingAtPath(path);
}
});
}
}
};
tree.addTreeSelectionListener(l);
The trick to really make it work is the usual: wrap the custom reaction into an invokeLater to be sure the tree's internal update is complete

Revalidating JList - custom elements

I'm using a JList to hold chat data for my chat program.
It uses a custom list renderer to render a custom JPanel object as the element type.
This JPanel contains two JLabels (anchored to the top, for name and time), and a JTextArea (anchored to the bottom, for chat message).
It looks like this:
Everything works great, but I want to add a hide/show feature.
Using a previously programmed PopupMenu handler, I have a popup appear when you right click on an element.
When you click hide (or show, it's a toggle) then it should minimize the element like so...
The only problem is... it doesn't update the JList cell size as you can see the large empty region where the text used to be.
However, when I type another message...
The JList fixes the cell size completing the 'hide' operation.
My question is how do you get the JList to revalidate/repaint/etc programmatically.
And don't think I haven't tried all the obvious solutions...
public void setHidden(boolean hidden) {
// this is in the custom JPanel class
System.out.println("Initial: " + this.getPreferredSize());
// TextArea is the JTextArea which we set invisible when we want to hide it.
TextArea.setVisible(!hidden); // TextArea is a variable btw
this.invalidate();
this.validate();
this.repaint();
System.out.println("After: " + this.getPreferredSize());
container.revalidate();
}
/*
* This is what the above printlns show when you hide, then show the element.
*
* Initial: java.awt.Dimension[width=176,height=38]
* After: java.awt.Dimension[width=176,height=20]
* Initial: java.awt.Dimension[width=176,height=20]
* After: java.awt.Dimension[width=176,height=38]
*/
public void revalidate() {
// container.revalidate() ^^^
// list is the list containing the chat elements
list.invalidate();
list.validate();
list.repaint();
}
The custom JPanel class uses a GroupLayout to render the components.
Do you guys have any knowledge on how to programmically cause a JList to revalidate its cell sizes?
... besides the methods that I've posted? :)
Solution:
After searching method after method and testing if they would solve my problem, I found that executing this code after a hide/show operation would cause the cell height (and width) to be recalculated and without any unwanted visual 'flicker' of the JList.
list.setFixedCellHeight(0);
list.setFixedCellWidth(0);
list.setFixedCellHeight(-1);
list.setFixedCellWidth(-1);
Without seeing any code, I can only guess: the most probable reason is that you're doing the hide under the feet of the list, that is without its model notifying its listeners. The list's ui delegate caches the cell size deep inside, which is cleared on receiving ListEvents
This is job for JTable with two Columns (Chat and Boolean) in the TableModel and with visible Chat Column only, the trick is by using by implement RowFilter where you set as parameter to the second column only String "false" (Object in the JTable with Boolean is possible filtering with returns value in the String "true" / "false")
This is a very peculiar shortcoming of the JList class. I ran into the issue myself in the course of cleaning up some of my code in unrelated areas.
For what it's worth, removing the element from the ListModel and then adding it again will produce the appropriate dimensions for the associated rendered component in the JList. It's an odd way to go about it, and seems to cause the list to behave the same way as the accepted (and preferred) solution:
list.setFixedCellHeight(0);
list.setFixedCellWidth(0);
list.setFixedCellHeight(-1);
list.setFixedCellWidth(-1);
I stumbled upon this problem because the code for my project was originally written to invoke the removeAllElements() method of the ListModel and then add all of the elements again one by one using addElement(). Everything was working great until I decided that I should rewrite the program so that it would simply leave the model alone whenever changes to the dimensions of the displayed components in the JList were requested by the user. In other words, it was unnecessary to involve the model because elements were not being added or removed from the list. Unfortunately, after changing the preferred size of the renderer, no amount of repaint() or revalidate() method calls on the JList would cause it to layout its elements correctly. In my case, only resizing the parent component (a JFrame) produced the intended behavior.

Making a JTable cell editable - but *not* by double clicking

I am trying to add a column to a JTable with the following behaviour (similar to Windows Explorer and similar shells):
The cell can be clicked once to select it, as usual.
The cell can be double-clicked to perform a separate action (launching an external program.)
The cell value (a string) can still be edited, by single-clicking a second time (after a pause) or by pressing F2 when the cell is highlighted.
Double-clicking must not trigger editing of the cell, but I would like to leave any other default editing triggers operational if possible.
I have tried adding a MouseListener to the table, and consuming all MouseEvents, but this does not work - if I return true from isCellEditable() then my listener never receives any click events but if I return false then F2 no longer works.
Can this be achieved using only event listeners? I would prefer not to mess with the PLAF functions if I can avoid it.
The DefaultCellEditor has a setClickCountToStart() method to control mouse clicks for editing. The default is 2. Changing this will have no effect on F2 functionality.
Therefore you can set editing to be a triple click.
Not sure exactly how to handle two single clicks to start editing but I guess you would use a Timer to keep track of the first click and then do the editing if the second single click is within you time interval.
You will have to make your own cellEditor and ovveride
public boolean isCellEditable( EventObject e )
You can distinguish between single and double click with the clickCount on the eventObject
If its a single Click and its on a selected cell you can return true otherwise return false;
retrieve row and column with
int row = ( (JTable) e.getSource() ).rowAtPoint(e.getPoint());
int column = ( (JTable) e.getSource() ).columnAtPoint(e.getPoint());
to enable F2 you can add custom inputMap en actionMap entries
similar too
table.getInputMap().put(KeyStroke.getKeyStroke("DOWN"), "doMyArrowDown");
table.getTable().getActionMap().put("doMyArrowDown", new ArrowDownAction());
and from your action you can then fire the cellediting yourself
table.editCellAt(row, column );
I have solved this by wrapping the existing CellEditor with a Proxy and intercepting calls to isCellEditable, returning false for all mouse events and delegating all other calls to the original CellEditor.
This is slightly more complex than camickr's solution but works for all editors (I have 4 in all.)

Categories