/* * Copyright 2003-2010 Tufts University Licensed under the * Educational Community License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may * obtain a copy of the License at * * http://www.osedu.org/licenses/ECL-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an "AS IS" * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing * permissions and limitations under the License. */ package tufts.vue; import tufts.Util; import tufts.vue.gui.*; import java.awt.*; import java.awt.dnd.*; import java.awt.datatransfer.*; import osid.dr.*; import java.util.*; import java.io.*; import java.io.IOException; import java.net.URL; import java.util.regex.*; import javax.swing.*; import java.awt.event.*; import javax.swing.tree.*; import javax.swing.tree.DefaultMutableTreeNode; import osid.filing.*; import tufts.oki.remoteFiling.*; import tufts.oki.localFiling.*; /** * A List that is droppable for the datasources. Only My favorites will * take a drop. * * This class assocates the DataSourceListCellRenderer with this list, * includes "addOrdered" for adding to the model yet maintaining an * internal sort order based on type, and provides a DropTarget with * drag/drop code for indicating droppable items in the list on * drag-over and accepting drops. Most of the code in this class is * some hairy drop code, especially for parsing out dropped * Resources/Files, that should be elsewhere: e.g., right in the * data-source that accept drops: FavoritesDataSource (and potentially * someday: RSSDataSource). * * Note that this currently creates it's own DefaultListModel to keep * the data sources ordered as it would like, and is a list made of up * of both tufts.vue.DataSource's and edu.tufts.vue.dsm.DataSource's, * which are obtained via separate API calls. his would be better * handled if it was directly backed by re-orderable list(s) of * data sources that comes from the VueDataSourceManager (which * already handles persistance of the lists). This would also allow * user re-ordering of the data sources, which would automatically be * persistent. * * @version $Revision: 1.67 $ / $Date: 2010-02-03 19:17:41 $ / $Author: mike $ * @author Ranjani Saigal */ // TODO: remove constructor reference to DataSourceViewer: handle via globally posted events // (this handle is only used in one place here). public class DataSourceList extends JList implements DropTargetListener { private static final org.apache.log4j.Logger Log = org.apache.log4j.Logger.getLogger(DataSourceList.class); static Object IndicatedDragOverValue; private static final boolean debug = true; private static final int ACCEPTABLE_DROP_TYPES = 0 | DnDConstants.ACTION_COPY | DnDConstants.ACTION_LINK | DnDConstants.ACTION_MOVE ; private final ContentViewer dsViewer; public DataSourceList(ContentViewer dsViewer) { super(new DefaultListModel()); this.dsViewer = dsViewer; this.setSelectionMode(DefaultListSelectionModel.SINGLE_SELECTION); this.setFixedCellHeight(-1); // TODO: create a generic resource drop handler from MapDropTarget to use // in places such as this (this code originiated as a copy of MapDropTarget, // but is now completely old/out of sync with with it). new DropTarget(this, ACCEPTABLE_DROP_TYPES, this); this.setCellRenderer(new DataSourceListCellRenderer()); edu.tufts.vue.dsm.impl.VueDataSourceManager.getInstance().addDataSourceListener (new edu.tufts.vue.dsm.DataSourceListener() { public void changed(edu.tufts.vue.dsm.DataSource[] dataSource, Object state, edu.tufts.vue.dsm.DataSource changed) { if (DEBUG.DATA || DEBUG.EVENTS) Log.debug("data sources changed: repaint"); repaint(); } }); } private Object locationToValue(Point p) { int index = locationToIndex(p); return getModel().getElementAt(index); } /* We are going to add some cleverness to the way data sources are ordered. There are four types of data sources: 1. edu.tufts.vue.dsm.DataSource 2. LocalFileDataSource 3. RemoteFileDataSource 4. FavoritesDataSource New additions to the list are inserted at the bottom of their "group" */ public void addOrdered(Object o) { DefaultListModel model = (DefaultListModel)getModel(); int size = model.size(); if (o instanceof edu.tufts.vue.dsm.DataSource) { if ( ((edu.tufts.vue.dsm.DataSource)o).getRepositoryDisplayName().trim().length() == 0 ) { Log.debug("ignoring repository with empty display name: " + Util.tags(o)); return; } for (int i=0; i < size; i++) { Object obj = model.elementAt(i); if (!(obj instanceof edu.tufts.vue.dsm.DataSource)) { model.insertElementAt(o,i); return; } } model.insertElementAt(o,size); } else if (o instanceof LocalFileDataSource) { for (int i=0; i < size; i++) { Object obj = model.elementAt(i); if ( (!(obj instanceof edu.tufts.vue.dsm.DataSource)) && (!(obj instanceof LocalFileDataSource)) ) { model.insertElementAt(o,i); return; } } model.insertElementAt(o,size); } else if (o instanceof RemoteFileDataSource) { for (int i=0; i < size; i++) { Object obj = model.elementAt(i); if (obj instanceof FavoritesDataSource) { model.insertElementAt(o,i); return; } } model.insertElementAt(o,size); } else { model.addElement(o); } } public DefaultListModel getModelContents() { return (DefaultListModel) getModel(); } private void setIndicated(Object value) { if (IndicatedDragOverValue != value) { IndicatedDragOverValue = value; repaint(); } } private static final boolean ALLOW_FEED_DROPS = true; public void dragOver(DropTargetDragEvent e) { Object over = locationToValue(e.getLocation()); if (DEBUG.DND) out("dragOver: " + over); if (over instanceof FavoritesDataSource) { e.acceptDrag(e.getDropAction()); setIndicated(over); } else { setIndicated(null); if (ALLOW_FEED_DROPS) e.acceptDrag(DnDConstants.ACTION_LINK); else e.rejectDrag(); } } public void dragEnter(DropTargetDragEvent e) { if (DEBUG.DND) out("dragEnter"); setIndicated(null); e.acceptDrag(ACCEPTABLE_DROP_TYPES); } public void dragExit(DropTargetEvent e) { if (DEBUG.DND) out("dragExit"); setIndicated(null); } public void dropActionChanged( DropTargetDragEvent e ) { e.acceptDrag(ACCEPTABLE_DROP_TYPES); } public void drop(DropTargetDropEvent e) { setIndicated(null); final Object over = locationToValue(e.getLocation()); if (DEBUG.DND) out("DROP over " + over); if (over instanceof FavoritesDataSource) { if (DEBUG.DND) out("drop ACCEPTED"); e.acceptDrop(e.getDropAction()); } else { if (ALLOW_FEED_DROPS) { // This will allow auto-creating RSS Feed data-sources on the drop of a // "feed:" string // TODO: for this to fully work, we need the refractoring of MapDropTarget // so that processTransferable will not try to add the resource automatically // to the map! // TODO: we should also really put add the feed to the VueDataSourceManager, // (where it could auto-persist it), then either handle an event from // the VDSM to trigger the addOrdered, or to it manually here. e.acceptDrop(e.getDropAction()); final Transferable transfer = e.getTransferable(); if (DEBUG.DND) try { new MapDropTarget(null).processTransferable(transfer, null); } catch (Throwable t) {} if (transfer.isDataFlavorSupported(DataFlavor.stringFlavor)) { String txt = MapDropTarget.extractData(transfer, DataFlavor.stringFlavor, String.class); if (txt.startsWith("feed:")) { BrowseDataSource feed = new edu.tufts.vue.rss.RSSDataSource(txt, txt); // crap: this is a local data source -- we have no central model // for the local data sources -- only for OSID *installed* data sources // edu.tufts.vue.dsm.impl.VueDataSourceManager.getInstance().add(feed); addOrdered(feed); DataSourceViewer.saveDataSourceViewer(); //DataSourceViewer.cacheDataSourceViewers(); // start it loading return; } } } if (DEBUG.DND) out("drop rejected"); e.rejectDrop(); return; } // TODO: after merging drop handling with global VUE transfer // handler, check to see if dropped resource is a "feed" // resource, and if so, auto-add an RSSDataSource if dropped // anywhere other than on a FavoritesDataSource. // final int current = this.getSelectedIndex(); // final int index = locationToIndex(e.getLocation()); // try { // setSelectedIndex(index); // } catch (Throwable t) { // Log.error("drop; setSelectedIndex " + index + ":", t); // } // DataSource ds = (DataSource)getSelectedValue(); final tufts.vue.DataSource ds = (DataSource) over; if (DEBUG.DND) out("DROP ON DATA SOURCE: " + ds.getDisplayName()); try { FavoritesWindow fw = (FavoritesWindow)ds.getResourceViewer(); VueDandDTree favoritesTree = fw.getFavoritesTree(); favoritesTree.setRootVisible(true); DefaultTreeModel model = (DefaultTreeModel)favoritesTree.getModel(); FavoritesNode rootNode = (FavoritesNode)model.getRoot(); boolean success = true; Transferable transfer = e.getTransferable(); DataFlavor[] dataFlavors = transfer.getTransferDataFlavors(); //String resourceName = null; // never set elsewhere! java.util.List fileList = null; java.util.List resourceList = null; if (DEBUG.DND) out("RESOURCE TRANSFER FOUND: " + transfer); try { if (transfer.isDataFlavorSupported(Resource.DataFlavor)) { final Object data = transfer.getTransferData(Resource.DataFlavor); Collection<Resource> droppedResources = null; if (data instanceof Resource) { droppedResources = (Collection) Collections.singletonList(data); } else if (data instanceof Collection) { droppedResources = (Collection) data; } else { Util.printStackTrace("Unhandled drop type: " + Util.tag(data) + "; " + data); return; } for (Resource resource : droppedResources) { if (DEBUG.DND) Log.debug("Found: " + Util.tags(resource)); ResourceNode newNode; // TODO: ALL THIS CODE IS IDENNTICAL TO THAT IN VueDandDTree if (resource.isLocalFile()) { final CabinetResource cr = (CabinetResource) resource; newNode = new CabinetNode(cr, CabinetNode.LOCAL); if (DEBUG.DND) Log.debug("CABINET RESOURCE: " + resource + "Entry: "+cr.getEntry()+ "entry type:"+cr.getEntry().getClass()+" type:"+cr.getEntry()); } else { newNode =new ResourceNode(resource); } model.insertNodeInto(newNode, rootNode, newNode.getChildCount()); favoritesTree.expandPath(new TreePath(rootNode.getPath())); favoritesTree.setRootVisible(false); } } else if (transfer.isDataFlavorSupported(DataFlavor.javaFileListFlavor)){ fileList = (java.util.List)transfer.getTransferData(DataFlavor.javaFileListFlavor); java.util.Iterator iter = fileList.iterator(); while(iter.hasNext()){ File file = (File)iter.next(); if (file.isDirectory()){ try{ final LocalFilingManager manager = LocalFileDataSource.getLocalFilingManager(); osid.shared.Agent agent = null; LocalCabinet cab = LocalCabinet.instance(file.getAbsolutePath(),agent,null); // todo: need to extend Resource class and/or refactor this code so // we don't need the LOCAL / REMOTE distinction, or can discover it // generically. //Resource res = Resource.instance(cab); CabinetResource res = CabinetResource.create(cab); CabinetEntry entry = res.getEntry(); if (file.getPath().toLowerCase().endsWith(".url")) { String url = MapDropTarget.convertWindowsURLShortCutToURL(file); if (url != null) { res.setSpec(url); String resName; if (file.getName().length() > 4) resName = file.getName().substring(0, file.getName().length() - 4); else resName = file.getName(); res.setTitle(resName); } } CabinetNode cabNode = null; if (entry instanceof RemoteCabinetEntry) cabNode = new CabinetNode(res, CabinetNode.REMOTE); else cabNode = new CabinetNode(res, CabinetNode.LOCAL); model.insertNodeInto(cabNode, rootNode, 0); favoritesTree.expandPath(new TreePath(rootNode.getPath())); favoritesTree.setRootVisible(false); cabNode.explore(); }catch (Exception ex) {System.out.println("DataSourceList.drop: "+ex);} } else { // // TODO: this uses an empty CabinetResource as simply a file path // // holder, so we can create a CabinetNode (and maybe persist the // // CabinetResource. Can refactor most of this all out. Handle the // // shortcut processing FIRST, then can create a CabinetResource from a // // java.io.File, tho even that is probably overkill... // FileNode fileNode = new FileNode(file); // //tufts.Util.printStackTrace("Unsupported DROP onto " + ds + "; of " + transfer); // model.insertNodeInto(cabNode, rootNode, 0); // favoritesTree.expandPath(new TreePath(rootNode.getPath())); // favoritesTree.setRootVisible(false); try{ LocalFilingManager manager = new LocalFilingManager(); // get a filing manager osid.shared.Agent agent = null; // SMF 2008-04-17: THIS IS BROKEN ANYWAY -- did it every work? // We always create a LocalCabinet, even if it's a LocalByteStore! // So dropping directories in works fine, but drop anything else and you're hosed. // Yeah: it's not working in the current build. Can't tell if it ever has. LocalCabinet cab = LocalCabinet.instance(file.getAbsolutePath(),agent,null); CabinetResource res = CabinetResource.create(cab); //res.setTitle(file.getAbsolutePath()); CabinetEntry oldentry = res.getEntry(); res.setEntry(null); if (file.getPath().toLowerCase().endsWith(".url")) { // Search a windows .url file (an internet shortcut) // for the actual web reference. String url = MapDropTarget.convertWindowsURLShortCutToURL(file); if (url != null) { //resourceSpec = url; res.setSpec(url); String resName; if (file.getName().length() > 4) resName = file.getName().substring(0, file.getName().length() - 4); else resName = file.getName(); //res.setTitle(resourceName); // was always set to null!!! res.setTitle(resName); } } CabinetNode cabNode = null; if (oldentry instanceof RemoteCabinetEntry) cabNode = new CabinetNode(res, CabinetNode.REMOTE); else cabNode = new CabinetNode(res, CabinetNode.LOCAL); model.insertNodeInto(cabNode, rootNode, 0); favoritesTree.expandPath(new TreePath(rootNode.getPath())); favoritesTree.setRootVisible(false); }catch (Exception ex) {System.out.println("DataSourceList.drop: "+ex);} } } } else if (transfer.isDataFlavorSupported(DataFlavor.stringFlavor)){ String dataString = (String)transfer.getTransferData(DataFlavor.stringFlavor); Resource resource = URLResource.create(dataString); ResourceNode newNode =new ResourceNode(resource); model.insertNodeInto(newNode, rootNode, 0); favoritesTree.expandPath(new TreePath(rootNode.getPath())); favoritesTree.setRootVisible(false); } } catch (Exception ex) { ex.printStackTrace(); } e.dropComplete(success); favoritesTree.setRootVisible(true); favoritesTree.expandRow(0); favoritesTree.setRootVisible(false); if (dsViewer.getBrowsedDataSource() == null) { dsViewer.setActiveDataSource(ds); //VUE.setActiveDataSource(ds); } // Very annoying if you want to drag items from search results into My Favorites: // else if (dsViewer.getBrowsedDataSource() == ds) // dsViewer.expandBrowse(); //this.setSelectedIndex(current); } catch (Exception ex) { if (DEBUG.DND) tufts.Util.printStackTrace(ex); //this.setSelectedIndex(current); VueUtil.alert(null, VueResources.getString("dialog.addfav.message"),VueResources.getString("dialog.addfav.message")); } } private void out(String s) { Log.debug(s); } }