[MOBY-guts] biomoby commit

Paul Gordon gordonp at dev.open-bio.org
Tue Jun 9 19:29:11 UTC 2009


gordonp
Tue Jun  9 15:29:11 EDT 2009
Update of /home/repository/moby/moby-live/Java/src/main/ca/ucalgary/seahawk/gui
In directory dev.open-bio.org:/tmp/cvs-serv21143/src/main/ca/ucalgary/seahawk/gui

Modified Files:
	MobyContentPane.java 
Log Message:
Whole shwack of changes to track drag 'n' drop and related functionality required for Daggoo service wrapping
moby-live/Java/src/main/ca/ucalgary/seahawk/gui MobyContentPane.java,1.17,1.18
===================================================================
RCS file: /home/repository/moby/moby-live/Java/src/main/ca/ucalgary/seahawk/gui/MobyContentPane.java,v
retrieving revision 1.17
retrieving revision 1.18
diff -u -r1.17 -r1.18
--- /home/repository/moby/moby-live/Java/src/main/ca/ucalgary/seahawk/gui/MobyContentPane.java	2008/01/14 23:00:45	1.17
+++ /home/repository/moby/moby-live/Java/src/main/ca/ucalgary/seahawk/gui/MobyContentPane.java	2009/06/09 19:29:11	1.18
@@ -2,7 +2,10 @@
 package ca.ucalgary.seahawk.gui;
 
 // For external links
+import ca.ucalgary.seahawk.services.DaggooClient;
 import ca.ucalgary.seahawk.util.*;
+import ca.ucalgary.services.SoapServlet;
+import ca.ucalgary.services.util.PBERecorder;
 
 import org.biomoby.client.MobyRequest;
 import org.biomoby.client.MobyRequestEvent;
@@ -23,6 +26,7 @@
 import javax.swing.event.*;
 
 import java.awt.*;
+import java.awt.datatransfer.*;
 import java.awt.print.*;
 import java.awt.event.*;
 import java.io.*;
@@ -35,11 +39,16 @@
  * Also provides link handling (including service options popup) and drag 'n' drop capabilities.
  */
 
-public class MobyContentPane extends JPanel implements Printable, CaretListener, HyperlinkListener, MouseListener, MobyRequestEventHandler, KeyListener, ChangeListener{
+public class MobyContentPane extends JPanel implements Printable, CaretListener, HyperlinkListener, MouseListener, MouseMotionListener, MobyRequestEventHandler, KeyListener, ChangeListener, ClipboardOwner, ActionListener{
     public static final String MOBY_SERVICE_POPUP_NAME = "seahawkServicePopup";
     public static final String WAITING_TAB_ICON_RESOURCE = "ca/ucalgary/seahawk/resources/images/hourglass.gif";
     public static final String FAILED_TAB_ICON_RESOURCE = "ca/ucalgary/seahawk/resources/images/failed.gif";
     public static final String LOADED_TAB_ICON_RESOURCE = "ca/ucalgary/seahawk/resources/images/document.gif";
+    // An undocumented "feature" of Java temp file creation is that assigned prefixes are turned to lower case,
+    // therefore you want to makr the prefix lower case if you're going to check for this prefix's existence in a file name
+    // anywhere in your code.
+    public static final String WSDL_RESULTFILE_PREFIX = "wsdl_results_parsed";
+    public static final String SERVICE_CREATION_MSG = "Create a service returning this datatype";
     private static final int TAB_ICON_SPACER = 2;
 
     private static ImageIcon hourglassIcon; 
@@ -65,11 +74,29 @@
     private boolean isContentsXML = false;
     private DataRecorder dataRecorder;
 
+    // Next two items used for service wrapping
+    private JMenuItem createServicePopupItem;
+    private MobyDataInstance itemToReturn;
+
     // Text selection members
     private int dot;
     private int mark;
+    private int oldMark;
+    private int oldDot;
+    private boolean dragging = false;
+    private boolean isWrapping = false; // are we wrapping a service using PBE at the moment?
     private String selectedTextData;
+    private boolean overHyperlink = false;
+    private URL lastHyperlinkDragged = null;
+    private URL lastHyperlinkHovered = null;
+    
     private static org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(MobyContentPane.class);
+    private static SoapServlet soapServlet;
+    private final int portNum = 8254;
+    private final static String servletPath = "/SOAPServlet";
+    private static Acme.Serve.Serve servletContainer;
+    private static Map<String,MobyContentGUI> id2GuiMap;
+
     public MobyContentPane(MobyContentGUI cGUI, MobyServicesGUI sGUI, JTabbedPane parentComponent, DataRecorder recorder, JLabel statusBar){
 	tabbedPane = parentComponent;
 	status = statusBar;
@@ -90,11 +117,15 @@
 	//editorPane.setPreferredSize(parentComponent.getPreferredSize());
 	editorPane.setEditable(false);
 	editorPane.addMouseListener(this);
+	editorPane.addMouseMotionListener(this);
 	editorPane.addHyperlinkListener(this);
 	editorPane.addCaretListener(this);
 	scrollPane = new JScrollPane(editorPane);
 	add(scrollPane);	
 
+	createServicePopupItem = new JMenuItem(SERVICE_CREATION_MSG);
+	createServicePopupItem.addActionListener(this);
+
 	history = new Vector<URL>();
 	historyTabLabels = new HashMap<URL,String>();
 
@@ -129,6 +160,8 @@
 		loadedIcon = new ImageIcon(u);
 	    }
 	}
+
+	id2GuiMap = new HashMap<String,MobyContentGUI>();	    
     }
 
     public void stateChanged(ChangeEvent ce){
@@ -160,6 +193,53 @@
 	}
     }
 
+    public void actionPerformed(ActionEvent e){
+	if(e.getSource() == createServicePopupItem){
+	    if(itemToReturn != null){
+		createService();
+	    }
+	}
+	else{
+	    logger.warn("Content pane: ignoring unrecognized source of event " + e);
+	}
+    }
+
+    private void createService(){
+	// item name, set by MobyContentGUI's processResults(), is namedXPath#numericXPtr
+	String[] locSpec = itemToReturn.getName().split("#");
+	String xPath = locSpec[0];
+	if(locSpec.length == 1){
+	    logger.warn("Could not find a '#' in object name " + itemToReturn.getName());
+	    return;
+	}
+	String xPtr = locSpec[1];
+
+	// remove the last part of the xPath, which is actually the value, not the selection
+	String valXPath = xPath.substring(xPath.lastIndexOf("/")+1);
+	xPath = xPath.substring(0, xPath.lastIndexOf("/"));
+
+	//todo: MOBY_OUTPUT_NS_PARAM MOBY_OUTPUT_TYPE_PARAM
+	String transformationParam = "";
+	if(locSpec.length == 3){
+	    transformationParam = "&"+PBERecorder.OUTPUT_RULE_URI_PARAM+"="+locSpec[2];	    
+	}
+
+	// we assume the wrapping form has already been launched
+	// so we'll get the cookie and cotinue the process at the xpath selection stage
+	URL formURL = null;
+	try{
+	    formURL = new URL("http://localhost:"+portNum+servletPath+"?"+
+			      PBERecorder.CONTEXT_XPTR_PARAM+"="+xPtr+"&"+
+			      PBERecorder.RETURN_XPATH_PARAM+"="+xPath+"&"+
+			      PBERecorder.VALUE_XPATH_PARAM+"="+valXPath+
+			      transformationParam);
+	} catch (java.net.MalformedURLException murle){
+	    logger.error("Could not create URL for wrapping form: " + murle);	    
+	    return;
+	}
+	launchInWebBrowser(formURL);
+    }
+
     public void setPreferredSize(Dimension dims){
 	super.setPreferredSize(dims);
 	scrollPane.setPreferredSize(dims);
@@ -180,6 +260,91 @@
 	return editorPane.getText();
     }
 
+    /** 
+     * Launch a servlet create a CGI form from a WSDL document, and that'll capture user events 
+     * while filling in the form to call a web service.  This'll allow a Moby service to be created
+     * for later calling.
+     */
+    private void loadWSDL(URL u){
+
+	if(DaggooClient.acceptAnExistingWrapper(u, this)){
+	    return;
+	}
+
+	// Launch the wrapping servlet if not launched yet
+	editorPane.setText("Launching service wrapper interface...");
+
+	// Launch the servlet
+	if(contentGUI.getServletContainer() == null){
+	    soapServlet = new SoapServlet();   
+
+	    final Acme.Serve.Serve srv = new Acme.Serve.Serve();
+	    java.util.Properties properties = new java.util.Properties();
+	    properties.put("port", portNum);
+	    srv.arguments = properties;
+	    srv.addServlet(servletPath, soapServlet); // our deployment
+	    Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+		    public void run() {
+			try {
+			    srv.notifyStop();
+			}catch(Exception e) {
+			    // May throw NullPointerException if server was already killed,
+			    // or maybe an IOException otherwise
+			    logger.warn("Trouble shutting down SOAP-wrapping servlet on port " 
+					+ portNum + ": " + e);
+			}
+			srv.destroyAllServlets();
+		    }
+		}));
+	    new Thread(){public void run(){srv.serve();}}.start();
+
+	    PBERecorder recorder = new PBERecorder();
+	    recorder.setGUIMap(id2GuiMap);
+	    soapServlet.setRecorder(recorder);
+	    // So we can stop the container later, assign the final var to a class scope one.
+	    contentGUI.setServletContainer(srv); 
+	}
+
+	// uniq id and GUI link up allow unification of browser events and the Sehawk GUI for PBE
+	String uniqID = ""+Math.random();
+	id2GuiMap.put(uniqID, contentGUI);
+
+	URL formURL = null;
+	try{
+	    formURL = new URL("http://localhost:"+portNum+servletPath+"?"+
+			      SoapServlet.WSDL_HTTP_PARAM+"="+u+"&"+
+			      SoapServlet.ID_PARAM+"="+uniqID);
+	} catch (java.net.MalformedURLException murle){
+	    logger.error("Could not create URL for wrapping form: " + murle);	    
+	    return;
+	}
+	launchInWebBrowser(formURL);
+    }
+
+    private void launchInWebBrowser(URL u){
+	// Launch the browser with the form the servlet hosts
+	try{
+	    Desktop desktop = Desktop.getDesktop();
+	    if(!desktop.isSupported(Desktop.Action.BROWSE)) {
+		editorPane.setText("Your Java does not support external browser launch.  " +
+				   "Please manually launch a browser and go to the URL " +
+				   u);
+	    }
+	    desktop.browse(u.toURI());
+	} catch (java.net.URISyntaxException urise){
+	    logger.error("Could not create URI from URL ("+u+"): " + urise);
+	} catch (java.io.IOException ioe){
+	    logger.error("Could not launch browser for form (" + u + "): " + ioe);
+	}
+    }
+
+    /**
+     * Tells whether the tab is in the middle of wrapping a Web Service at the moment.
+     */
+    public boolean isWrappingService(){
+	return isWrapping;
+    }
+
     /**
      * This is the URLLoader callback method the MOBY data fetcher
      * will call when MOBY XML data is ready to be seen.
@@ -200,11 +365,31 @@
 	    URLConnection urlCon = url.openConnection();
 
 	    boolean unformatted = false;
+	    // See if it's a WSDL file.  If so, launch the service wrapper functionality
+	    if(urlString.endsWith(".wsdl")){
+		loadWSDL(url);
+		isWrapping = true;
+		return;  // don't load the file in the pane
+	    }
+	    else{
+		isWrapping = false;
+	    }
+
 	    // It's XML that needs to be transformed to HTML
 	    if(urlString.endsWith(".xml") ||
 	       "text/xml".equals(urlCon.getContentType())){
 		editorPane.setContentType("text/html");
 		isContentsXML = true;
+		// Is it the second stage of wrapping, where data results are being seen?
+		if(urlString.indexOf(WSDL_RESULTFILE_PREFIX) != -1){
+		    System.err.println("Wrapping service results");
+		    isWrapping = true;
+		}
+		else{
+		    //System.err.println("Not wrapping service results, xml file name ("+
+		    //	       urlString +
+		    //	       ") does not contain " + WSDL_RESULTFILE_PREFIX);
+		}
 		
 		// Tell the stylesheet the URL of the moby data (it will create xpointers to it)
 		contentGUI.getTransformer().setParameter(XSL_DOC_SOURCE_PARAM, url.toString());
@@ -227,13 +412,26 @@
 		catch(TransformerException te){
 		    status.setText("Sorry!  Could not transform the MOBY data into presentation form");
 		    logger.error("Sorry!  Could not transform the MOBY data into presentation form: " + te);
-		    return;
+		    return;  // don't load the file in the pane
 		}
 		catch(Exception e){
-		    status.setText("Note: There was an error transforming the MOBY data " +
-				   "into presentation form, it may not be displayed correctly");
-		    logger.error("Note: There was an error transforming the MOBY data " +
-				 "into presentation form, it may not be displayed correctly", e);
+		    // check to see if it's wsdl without a .wsdl suffix...
+		    String body = HTMLUtils.getURLContents(url);
+		    if(body.indexOf("definitions ") != -1 &&
+		       body.indexOf("\"http://schemas.xmlsoap.org/wsdl/\"") != -1){
+			loadWSDL(url);
+			isWrapping = true;
+		    }
+		    // TODO if the data in XML that can be converted to MOBY...
+		    // else if(){
+		    //}
+		    else{
+			status.setText("Note: There was an error transforming the MOBY data " +
+				       "into presentation form, it may not be displayed correctly");
+			logger.error("Note: There was an error transforming the MOBY data " +
+				     "into presentation form, it may not be displayed correctly", e);
+		    }
+		    return;
 		}
 		// Get rid of inter-tag whitespaces
 		htmlContents = htmlContents.replaceAll(">[ \\t\\r\\n]+<", "><");
@@ -287,13 +485,20 @@
 		}
 		//editorPane.setContentType("text/plain");
 		isContentsXML = false;
-		//editorPane.setFont(java.awt.Font.getFont("Monospaced"));
+		//editorPane.setFont(Font.getFont("Monospaced"));
 		editorPane.setContentType("text/html");
 		String body = HTMLUtils.getURLContents(url);
 		if(body.toLowerCase().indexOf("<html") != -1){
 		    resultBuffer = null;
 		}
 		else{
+		    // one last check to see if it's wsdl without a .wsdl suffix...
+		    if(body.indexOf("definitions ") != -1 &&
+		       body.indexOf("\"http://schemas.xmlsoap.org/wsdl/\"") != -1){
+			loadWSDL(url);
+			isWrapping = true;
+			return;  // don't load the file in the pane
+		    }
 		    resultBuffer = new StringBuffer("<html><body><pre>"+
 						    body.replaceAll("&", "&amp;").replaceAll("<","&lt;")+
 						    "</pre></body></html>");
@@ -398,6 +603,17 @@
     }
     
     /**
+     * Called after data copied from this pane is pasted.  The actual text pasted in given
+     * so that it can be unified with the object that created it (lastURLDragged) in the 
+     * PBE recorder.
+     */
+    public void exportDone(String valuePasted, String transformRuleURI){
+	if(soapServlet != null && soapServlet.getRecorder() != null){
+	    soapServlet.getRecorder().dataCopied(getDraggedData(), valuePasted, transformRuleURI);
+	}
+    }
+
+    /**
      * Called by MOBYRequest when the service request is being sent.  We
      * can create the GUI and wait message here.
      */
@@ -490,7 +706,9 @@
 	tabbedPane.setDisabledIconAt(tabIndex, loadedIcon);
 	tabbedPane.setTitleAt(tabIndex, msg);
 	// Store the latest title for the URL loaded, in case we revisit
-	historyTabLabels.put(history.elementAt(historyIndex), msg);
+	if(historyIndex != -1){
+	    historyTabLabels.put(history.elementAt(historyIndex), msg);
+	}
 	hasFailed = false;
     }
 
@@ -501,7 +719,7 @@
     public String toScufl() throws Exception{
         URL[] historyToExport = (URL[]) history.toArray(new URL[history.size()]);
         if(historyIndex != historyToExport.length-1){
-            // Shorten the array if we aren't cuurently on the last doc (i.e.
+            // Shorten the array if we aren't currently on the last doc (i.e.
             // we don't want to export documents forward in the history)
             URL[] truncatedHistory = new URL[historyIndex];
             System.arraycopy(historyToExport, 0, truncatedHistory, 0, historyIndex);
@@ -673,20 +891,15 @@
 	    // External, hopefully HTML link
 	    else{
 		status.setText("Launching Web browser for external link...");
-		try{
-		    BrowserLauncher.openURL(targetURL.toString());
-		}
-		catch(IOException ioe){
-		    status.setText("Couldn't launch external browser");
-		    logger.debug("Couldn't launch external browser for " + targetURL + 
-				 " because of I/O exception: " + ioe);
-		}
+		launchInWebBrowser(targetURL);
 	    }
 	    
 	}
 	if(he.getEventType() == HyperlinkEvent.EventType.ENTERED){
+	    overHyperlink = true;
+	    lastHyperlinkHovered = targetURL;
 	    if(isMobyURL(targetURL)){
-		status.setText("Click to discover more MOBY services");
+		status.setText("Click to discover more Moby services, or drag onto a Web form");
 	    }
 	    else if(targetURL == null){
 		String desc = he.getDescription();
@@ -700,29 +913,36 @@
 	    }
 	}
 	if(he.getEventType() == HyperlinkEvent.EventType.EXITED){
+	    overHyperlink = false;
 	    status.setText("");
 	}
     }
 
-    protected void showMobyOptions(URL targetURL){
+    public MobyDataInstance getDraggedData(){
+	return urlToMobyData(lastHyperlinkDragged);
+    }
+
+    protected MobyDataInstance urlToMobyData(URL targetURL){
 	// Build a MOBY object using the URL info
 	// Links have the form moby_xml_url#child_xpath refering to complex objects
 	// or http://moby/namespace?id=moby_id&string=cdata_value for simple objects and strings
-	String docFragID = targetURL
-.getRef();  // We store the xpath in the anchor part of the URL
-
-	MobyDataInstance mobyData = null;
-	// Complex case, load the doc fragment from the MOBY XML source file
 
+	String docFragID = targetURL.getRef();  // We store the xpath in the anchor part of the URL
 
-	if(docFragID != null && docFragID.length() > 0 && contentGUI.getDocumentBuilder() != null){
-	    mobyData = loadMobyDataFromXPointer(targetURL);
+	// Complex case, load the doc fragment from the MOBY XML source file
+	if(docFragID != null && docFragID.length() > 0 && contentGUI.getDocumentBuilder() != null &&
+	   !targetURL.getHost().equals("moby")){
+	    return loadMobyDataFromXPointer(targetURL);
 	}
 
 	// Simple case, build the object using the values encoded in the URL itself
 	else{
-	    mobyData = loadMobyDataFromURLQuery(targetURL);
+	    return loadMobyDataFromURLQuery(targetURL);
 	}
+    }
+
+    protected void showMobyOptions(URL targetURL){
+	MobyDataInstance mobyData = urlToMobyData(targetURL);
 
 	if(mobyData == null){
 	    logger.warn("Cannot create MOBY service list, could " +
@@ -749,9 +969,17 @@
     }
 
     /**
-     * Subclasses can use this method to expand the popup menu.
+     * Subclasses can use this method to expand the popup menu.  Be sure to call super.addExtraMobyOptions()
      */
     protected void addExtraMobyOptions(JPopupMenu popup, MobyDataInstance mdi){
+	// If we are wrapping a service, and you're getting the context menu, you must be 
+	// looking at service results, in which case let's give the option to create a service.
+	if(isWrapping){
+	    itemToReturn = mdi;
+	    popup.add(createServicePopupItem);
+	    popup.setVisible(true);	    
+	}
+
     }
 
     /**
@@ -823,6 +1051,11 @@
 		    else if(mobyName.length() == 0 && token.startsWith("name=") && token.length() >= 5){
 			if(token.length() > 5){
 			    mobyName = token.substring(5);
+			    // The name may also contain #xxx, which will be in the ref part of 
+			    // the URL and needs to be appended.
+			    if(targetURL.getRef() != null){
+				mobyName += "#"+targetURL.getRef();
+			    }
 			}
 			else{
 			    mobyName = "";
@@ -975,7 +1208,7 @@
     }
 
     // MouseListener interface implementation
-    public void mouseClicked(MouseEvent e){
+    public void mousePressed(MouseEvent e){
 	// Allow middle button paste
 	if(e.getButton() == MouseEvent.BUTTON2 ||
 	   e.getButton() == MouseEvent.BUTTON3 && e.isAltDown()){
@@ -1005,12 +1238,28 @@
     
     public void mouseExited(MouseEvent e){}
     
-    public void mousePressed(MouseEvent e){
+    public void mouseClicked(MouseEvent e){
 	lastClickX = e.getX();
 	lastClickY = e.getY();
 	if(checkSelectionPressed(lastClickX, lastClickY)){
 	    if(selectedTextData == null){
-		return;
+		// If there is no selected text, we need to reconstruct it from the info we have
+		// about what was highlighted just before.
+		if(oldMark != oldDot){
+		    try{
+			if(oldMark > oldDot){
+			    selectedTextData = editorPane.getText(oldDot, oldMark-oldDot);
+			}
+			else{
+			    selectedTextData = editorPane.getText(oldMark, oldDot-oldMark);
+			}
+		    } catch(javax.swing.text.BadLocationException ble){
+			logger.warn("Bad dot/mark record for getText(): " + ble);
+		    }
+		    finally{
+			oldMark = oldDot; // clear the old selection so only the first click activates the above code
+		    }
+		}
 	    }
 	    MobyDataInstance[] mobyData = loadMobyDataFromString(selectedTextData);
 	    if(mobyData == null || mobyData.length == 0){
@@ -1043,10 +1292,18 @@
     }
     
     protected boolean checkSelectionPressed(int x, int y){
+	int pressedLoc = editorPane.viewToModel(new Point(x, y));
 	if(mark == dot){
-	    return false;  // nothing selected
+	    if(oldMark < oldDot && (pressedLoc >= oldMark && pressedLoc <= oldDot) ||
+	       oldMark > oldDot && (pressedLoc <= oldMark && pressedLoc >= oldDot)){
+		// clicked on a spot that was just highlighted previously (but caret events happen before mouse events)
+		return true;
+	    }
+	    else{
+		oldMark = oldDot;
+		return false;  // nothing selected
+	    }
 	}
-	int pressedLoc = editorPane.viewToModel(new Point(x, y));
 	if(mark < dot && (pressedLoc >= mark && pressedLoc <= dot) ||
 	   mark > dot && (pressedLoc <= mark && pressedLoc >= dot)){
 	    return true;
@@ -1075,8 +1332,12 @@
 	    //status.setText("selection canceled");
 	    return;
 	}
+	else{  // keep track of the previous selection so we can use it once it's gone
+	    oldMark = mark;
+	    oldDot = dot;
+	}
 	selectedTextData = editorPane.getSelectedText();
-	status.setText("Click your selection to use as MOBY data!");
+	status.setText("Click your selection to use it as MOBY data!");
     }
 
     /**
@@ -1102,6 +1363,43 @@
     public void keyTyped(KeyEvent e){}
 
     public void paste(){
-	getTransferHandler().importData(this, java.awt.Toolkit.getDefaultToolkit().getSystemClipboard().getContents(this));
+	getTransferHandler().importData(this, Toolkit.getDefaultToolkit().getSystemClipboard().getContents(this));
+    }
+
+    public void mouseDragged(MouseEvent e){
+	// if a hyperlink is being dragged, and we're wrapping, this should be a copy event
+	if(overHyperlink && !dragging){
+	    dragging = true;  // so we don't export many times in a row while dragging
+	    lastHyperlinkDragged = lastHyperlinkHovered;
+	    status.setText("Drop the hyperlink onto a Web form field to populate it");
+	    System.err.println("Dragging "+lastHyperlinkDragged);
+	    getTransferHandler().exportToClipboard(this, 
+						   Toolkit.getDefaultToolkit().getSystemClipboard(), 
+						   TransferHandler.COPY);
+	    getTransferHandler().exportAsDrag(this, e, TransferHandler.COPY);
+	}
+	else if(!dragging){
+	    System.err.println("Dragging with no hyperlink activated");
+	}
+    }
+
+    public void mouseMoved(MouseEvent e){
+	// if a hyperlink is being dragged, and we're wrapping, this should be a copy event
+	// normally it'd go to mouseDragged, but sometimes that's flakey so check manually for the 
+	// mouse button being down here.
+	if(overHyperlink && (e.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) == MouseEvent.BUTTON1_DOWN_MASK){
+	    System.err.println("Got wonky drag");
+	    lastHyperlinkDragged = lastHyperlinkHovered;
+	    status.setText("Drop the hyperlink onto a Web form field to populate it");
+	    System.err.println("Dragging "+lastHyperlinkDragged);
+	    getTransferHandler().exportAsDrag(this, e, TransferHandler.COPY);
+	}
+	else if(dragging){
+	    dragging = false;
+	}
+    }
+
+    public void lostOwnership(Clipboard clipboard, Transferable contents){
+	System.err.println("Lost clipboard ownership");
     }
 }




More information about the MOBY-guts mailing list