I'm trying to achieve making drop targets visible (or adding them) as soon as the user starts a drag.
The documentation explains that this should be handled in onDrag when ACTION_DRAG_STARTED is received, which could then be used to say highlight the View as being able to accept the drag.
However, my view (which is actually a LinearLayout) should look different when no drag is going on, and show drop targets when a drag is initiated.
Normal look:
[Item A][Item B]
When drag starts it should look like:
[ ][Item A][ ][Item B][ ]
Where the empty parenthesis represent locations where the drag can be dropped.
I've tried the following things to achieve this:
1) Dynamically add views
When top-level container receives ACTION_DRAG_STARTED, dynamically add the drop target views. Problem: the newly added views never receive ACTION_DRAG_STARTED themselves (or any other events) and so they cannot accept the drop.
2) Have hidden drop targets
Always have View.GONE drop targets in between the real items available all the time, and just make them View.VISIBLE when the drag starts:
if(event.getAction() == DragEvent.ACTION_DRAG_STARTED) {
// Make all containers visible:
for(int i = 0; i < cc.getChildCount(); i++) {
cc.getChildAt(i).setVisibility(View.VISIBLE);
}
}
Problem: apparently View.GONE also means the view does not receive events. Same thing with View.INVISIBLE.
So, what are my options? Using say View.VISIBLE and doing some dynamic resizing when the drag starts/end? Seems really silly...
Any better suggestions?
You need to change the visibility just before calling startDrag() or startDragAndDrop(). I demonstrate that in this sample app (from this chapter of this book, FWIW).
In that sample, if you run it on a tablet, I will initiate a drag-and-drop operation on a long-click of an item in a RecyclerView:
#Override
public boolean onLongClick(View v) {
if (listener!=null) {
listener.onStartDrag();
}
ClipData clip=ClipData.newRawUri(title.getText(), videoUri);
View.DragShadowBuilder shadow=new View.DragShadowBuilder(thumbnail);
itemView.startDrag(clip, shadow, Boolean.TRUE, 0);
return(true);
}
But before I call startDrag(), I let a registered listener know that I am about to start the drag. That listener is the hosting activity, which makes my "hotspot" drop target visible:
#Override
public void onStartDrag() {
info.setVisibility(View.VISIBLE);
}
The net effect is akin to a home screen, where specific "actions" appear (e.g., uninstall) when you start the drag.
I tried your second approach initially, and it didn't work. My assumption is that calling startDrag() or startDragAndDrop() basically captures the roster of visible drop targets, and so changes to that roster (new widgets, newly-visible widgets) have no effect after this point.
Related
Background: I am using custom AWT D&D in my (heavily) customized JTextPane subclass. In other words, I have disabled Swing's D&D with pane.setDragEnabled(false) and I use my own DragSource, DragGestureListener, etc.
The problem: The default selection behavior in JTextPane when Swing's D&D is disabled is as follows:
Select some text using the mouse
Mouse press inside the selection with the intent of starting a drag
Desired: selection is not lost.
Actual: upon mouse press, selection is immediately lost, giving no opportunity for me to start my drag operation since there is now nothing to drag.
I have partially traced this back to BasicTextUI$DragListener, since this is the class that calls the pane's getDragEnabled() method, but BasicTestUI doesn't seems to do much with the text component's selection. So, I'm still not exactly where the selection is being cleared, but I need to find it so I can eliminate the behavior.
I have employed a hack that involves setting a persistent highlight from a carat listener, so even though the selection is lost a highlight will remain that my drag can interact with. I am not happy with this and it has other side effects.
Many thanks for any pointers.
After many more hours of reviewing JDK source, I've determined that the selection behavior is controlled by the Caret and not anything in the text component or UI hierarchy.
A mildly customized Caret seems to do the trick. Note that if you don't override mouseDragged(), the custom drag will still work but the selection will typically be altered in the pane after the drag starts, making the user think they're only dragging part of the text they selected.
textPane.setCaret(new DefaultCaret() {
#Override
public void mousePressed(MouseEvent evt) {
int pos = textPane.viewToModel(evt.getPoint());
if (pos > textPane.getSelectionStart() && pos < textPane.getSelectionEnd()) {
return;
}
super.mousePressed(evt);
}
#Override
public void mouseDragged(MouseEvent e) {
if (dragItem != null) return;
super.mouseDragged(e);
}
});
I'm working in SWT (no JFace), and I'm attempting to customize the behavior of a Tree that I'm using a sidebar.
The top-level items in the tree shouldn't be selectable; they're basically headers. Only the children of these items should be selectable. As a result, I would like the UI to behave in a way that indicates this; clicking on one of these top-level items should expand or collapse but shouldn't provide any sort of visual feedback outside of the indicator changing its state and the children's visibility changing.
The problem seems to be that on OS X, the expand/collapse indicator is a triangle (don't know if it's an image or a unicode character) that either points right or down, but is also colored based on the "selection" state. I've managed to override all of the relevant behavior for the top-level items except for the arrow changing color.
I've used an SWT.EraseItem listener to hook in to the background drawing so that the background doesn't change color. This works as expected.
I've used an SWT.PaintItem listener to make sure that the text in the top-level item doesn't change color. This works as expected, but doesn't seem to have any influence over the indicator; I've even tried not resetting the GC's foreground color, but the color of the indicator still changes:
Not Selected:
Selected
Some of the things that I've attempted to do, all of which have failed:
Just drawing a rectangle on top of indicator. The indicator seems to always be on top, no matter what I attach the PaintListener to.
Using a Listener on the Selection event type, checking to see if the event.item is one of the top-level items, and then fiddling with the event bits and redraw flags. This doesn't seem to work well at all; the behavior is completely unpredictable. The code looks something like this:
sideBar.addListener(SWT.Selection, new Listener()
{
#Override
public void handleEvent(Event event)
{
TreeItem eventItem = (TreeItem) event.item;
if(sideBarRoots.contains(event.item))
{
event.detail = 0;
event.type = 0;
event.doit = false;
sideBar.setRedraw(false);
}
else
{
sideBar.setRedraw(true);
event.type = SWT.Selection;
event.doit = true;
}
}
});
Since the redraw flag is just a hint, sometimes it gets set properly and others it doesn't. It can have either no effect, or can get locked in to a state where nothing redraws in the sidebar.
I'm aware that a lot of this behavior is highly coupled to the behavior of the underlying native widgets, but I was wondering if there was any way to get the behavior that I'm looking for without roll a custom widget from scratch?
I think in a situation as yours you should ideally not allow selection on a tree header.
You can either cancel the selection event, by not allowing it to be clickable. But that might be a little kludgy implementation, especially for key navigation.
The safer approach, if possible with your data, would be to just move the selection whenever a tree parent node is selected. So it should work like this that if a parent node is selected, either by keyboard or mouse: you expand the node and move the selection to the first child.
This way you can avoid all the paint trickery to hide the selection state.
I'm very new to Android programming, and trying to understand touch events with nested views. To start, here's a description of my app:
I have a relative layout that I've added via the GUI editor. Everything is default. I've also created a class called ClipGrid that extends ScrollView. Nested inside that, I make a HorizontalScrollView. Inside of that, I make a TableLayout and it's rows. The rows contain buttons.
The end result is a grid of buttons. It displays 4x4 at once, but can scroll either direction to display other buttons.
I call it to the screen from my main activity like this:
ClipGrid clip_grid = new ClipGrid(this);
setContentView(clip_grid);
I did that just for testing purposes, and I think I will have to change it later when I want to add other views to my relativelayout. But I think it might have implications for touch events.
in the end, I want to detect when the grid has been moved and snap the newly viewable 4x4 grid of buttons to the edge of my layout when the user lifts their finger. I'm just not sure how to go about implementing this and any help would be appreciated. Thanks.
The way touch events are handled is kind of a cascading effect that starts from the top view and goes down to the lower nested views. Basically, Android will pass the event to each view until true is returned.
The general way you could implement the onTouchEvent event of a View would be:
#Override
public boolean onTouchEvent(MotionEvent event) {
boolean actionHandled = false;
final int action = event.getAction();
switch(action & MotionEventCompat.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
// user first touches view with pointer
break;
case MotionEvent.ACTION_MOVE:
// user is still touching view and moving pointer around
break;
case MotionEvent.ACTION_UP:
// user lifts pointer
break;
}
// if the action was not handled by this touch, send it to other views
if (!actionHandled)
actionHandled |= super.onTouch(v, MotionEvent.event);
return actionHandled;
}
Here's the situation, I have a jFrame with a tabbed pane and within the tabs I have a couple of jTables and a jTree. I want to be able to chain the selections between the tables and the tree based on whether a user uses a ctrl/shift + click versus a regular click. (If you hold ctrl and click in the first table/tree, it adds to the overall selection, if you use a regular click it clears the selections in the other tables/tree). Currently I'm having an issue with Java's jTree component. I have added a TreeSelectionListener and a MouseListener with a class that implements both interfaces, call it MyBigListener;
i.e.
MyBigListener listener = new MyBigListener();
jTree1.addMouseListener( listener );
jTree1.addTreeSelectionListener( listener );
MyBigListener implements TreeSelectionListener, MouseListener {
private boolean chained = false;
public synchronized setChained(boolean ch){
chained = ch;
}
public synchronized boolean isChained(){
return chained
}
public void valueChanged(TreeSelectionEvent e){
if(isChained()){ blah... }
}
public void mousePressed(MouseEvent e){
setChained(e.isControlDown() || e.isShiftDown());
}
}
My plan was to set a boolean flag if the user uses a ctrl/shift + click that I could check during the valueChanged(TreeSelectionEvent e) implemented by the tree selection listener.
I want to be able to process the mouse events before the valueChanged TreeSelectionEvents, but the problem is that I receive the mouse events after the valueChanged treeSelection event. This seems weird to me that I receive the selection change before the mouse pressed event fires, when the selection change is actually initiated by the mouse pressed. (I've already synchronized the boolean flag setting, which ironically helped to highlight the mis-order of events.)
I've already tried alternatives like adding a keyListener, but this doesn't work when the focus is on a separate frame, which goofs me up when a user holds ctrl and then clicks into the jTree causing it to receive both the focus and fire any valueChanged selection events.
Any help would be appreciated, thanks!
--EDIT-- #akf
I have separate jTables and jTrees in a tabbed Pane that serve as a summary/control panel for data in a nodal-graph. I'm using these components in the tabbed Pane to do coordinated selection to a graph displayed in a separate jFrame. Individually each table works just fine for its selection as does the jTree. It's coordinating between the panes that's tricky. This works fine so far with jTable components because I fire the new selections as the result of a MouseEvent where I can tell if the shift/ctrl button was down, formulate my new selection, and pass it to the parent frame which coordinates selections between all panes and sends the final selection to the graph. However, the parent needs to know if it needs to chain a selection between panes, or squash the others. With the jTables it's fine again, because I fire selection changes as the result of a mouse click. The jTree is more of a problem because I'm doing some forced selections. If you click on a branch, it forces all leaves into the selection. I needed to implement a TreeSelectionListener to do that, but only get a valueChanged(TreeSelectionEvent) to realized changes. I added a mouseListener to listen for ctrl+clicks and shift+clicks, but apparently the events don't always happen in the same order.. at least so far I receive the valueChanged event before the mousePressed event, so checking to if a ctrl+click happened posts after the selection has already been modified.
Right now, I'm posting a pending selection change and then have the MouseListener grab that and send it up the chain, but if these events aren't guaranteed to happen in the same order, at some point it's going to fail. Implementing a delayer also rubs me the wrong way.
Thanks for the help so far.
--EDIT2-- #ykaganovich
I think overriding the fireValueChanged method is closer to the right way to go about things. Depending on my definition of what actions should cause a "chained" selection to the other components, I'd need to gather some context on what's going on before the valuedChanged method fires. This basically means calling it myself in all cases where I can define what it means by who triggers it. I.e. If a mouse event causes it and ctrl is down then set what I need to set (interpret) then fire. If it's going to change due to a keyboard event, again, set what I need to set, then fire. I don't think the TreeSelectionModel is the way to go, because I still won't know what the circumstances were when the event fired. I think this means I'll need to rewrite parts of the jTree to do this but I guess I'll see how it goes. Thanks.
Don't do it that way, override JTree.fireValueChanged instead.
Try something like this (untested):
class ChainedSelectionEvent extends TreeSelectionEvent {
ChainedSelectionEvent(TreeSelectionEvent e) {
super(e.newSource, e.paths, e.areNew, e.oldLeadSelectionPath, e.newLeadSelectionPath);
}
}
protected void fireValueChanged(TreeSelectionEvent e) {
if(chained) { // figure out separately
super.fireValueChanged(new ChainedSelectionEvent(e));
} else {
super.fireValueChanged(e);
}
}
Then check instanceof ChainedSelectionEvent in your listener
EDIT
Actually, I think the right way to do this is to implement your own TreeSelectionModel, and override fireValueChanged there instead. Assuming setSelectionPath(s) methods imply a new selection, and add/removeSelectionPath(s) imply chaining, you could distinguish between the two cleanly. I don't like listening to either keyboard or mouse events explicitly, because there's more than one way to change a selection (e.g. if someone is holding down SHIFT and hitting a down-arrow, you won't get a mouse event).
You may get the mouse event before or after the tree selection event. Best not to rely on such orders. The reason is that the tree selection event is caused in response to the mouse event. Is that mouse event listener called before or after your listener? Could be either.
This sort of thing is closely involved with the implementation of the PL&F.
the key here is to understand that a JTree is delivered with a BasicTreeUI.MouseHandler, see javax.swing.plaf.basic.BasicTreeUI.
to answer the central question, "what fires fireValueChanged", the answer is "this mouse handler"... which turns out to be the only mouse listener returned when you go tree.getMouseListeners().
So you have to replace this default mouse listener with your own version ... which is a little tricky. I use Jython, which everyone needs to discover. This code shouldn't be too difficult to translate into Java however:
from javax.swing.plaf.basic import BasicTreeUI
def makeMouseHandlerClass():
class MouseHandlerClass( BasicTreeUI.MouseHandler ):
def mousePressed( self, mouseEvent ):
genLog.info( "mouse handler MOUSE PRESSED!" )
nTFSelf.mousePressedStatus = True
BasicTreeUI.MouseHandler.mousePressed( self, mouseEvent )
nTFSelf.mousePressedStatus = False
return MouseHandlerClass
suppliedMouseHandler = nTFSelf.taskTree.mouseListeners[ 0 ]
nTFSelf.taskTree.removeMouseListener( suppliedMouseHandler )
nTFSelf.taskTree.addMouseListener( makeMouseHandlerClass()( nTFSelf.taskTree.getUI() ))
... basically the equivalent Java here would be to extend BasicTreeUI.MouseHandler as MyMouseHandler, and then in the last line replace with new MyMouseHandler( tree.getUI() )... "nTFSelf" here is merely a reference to the "this" object of the code where all this is being written...
If I create a set of actions to be used in a JFace application and I assign images to those actions, those images show up in both the toolbar (where I want them) and in the menus (where I don't want them).
Other than supplying two completely separate sets of actions (which eliminates part of the point of actions in the first place), how can I arrange to have those images displayed ONLY in the toolbar, and have the menus display only text?
I ran into this problem as well (except I wanted different text for the toolbar and menu.) I ended up using the same action, but two different instances of it.
// Use this one in the menu
Action a = new Action();
// Use this one in the toolbar
Action a2 = new Action();
a2.setImageDescriptor(img);
Alternately, you could store the image descriptor in the action for the toolbar version and set it to null for the menu version.
I haven't checked this myself, but give this method a looksee:
public int getStyle() { ... }
It's defined in the Action class, and it appears to return the type of interface element that the Action is graphically represented as. So then you could override the getImageDescriptor() method:
public ImageDescriptor getImageDescriptor() {
if (getStyle() == AS_DROP_DOWN_MENU)
return null; // No icon in a menu
return someImageDescriptor; // Actual icon
}
I ended up essentially duplicating the actions, making pairs, one with an image, one without, thereby eliminating the benefit of actions in the first place (sigh). On the other hand, since each action does just invoke a single method in the controller to perform the work, it's not too insidious and grouping the pairs together makes it reasonably clear what's going on.