001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.dialogs.relation; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.Component; 007import java.awt.Dialog; 008import java.io.IOException; 009 010import javax.swing.JTree; 011import javax.swing.SwingUtilities; 012import javax.swing.event.TreeExpansionEvent; 013import javax.swing.event.TreeWillExpandListener; 014import javax.swing.tree.ExpandVetoException; 015import javax.swing.tree.TreePath; 016 017import org.openstreetmap.josm.Main; 018import org.openstreetmap.josm.data.osm.DataSet; 019import org.openstreetmap.josm.data.osm.DataSetMerger; 020import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 021import org.openstreetmap.josm.data.osm.Relation; 022import org.openstreetmap.josm.gui.PleaseWaitRunnable; 023import org.openstreetmap.josm.gui.progress.PleaseWaitProgressMonitor; 024import org.openstreetmap.josm.gui.progress.ProgressMonitor; 025import org.openstreetmap.josm.io.OsmApi; 026import org.openstreetmap.josm.io.OsmServerObjectReader; 027import org.openstreetmap.josm.io.OsmTransferException; 028import org.xml.sax.SAXException; 029 030/** 031 * This is a {@link JTree} rendering the hierarchical structure of {@link Relation}s. 032 * 033 * @see RelationTreeModel 034 */ 035public class RelationTree extends JTree { 036 /** 037 * builds the UI 038 */ 039 protected void build() { 040 setRootVisible(false); 041 setCellRenderer(new RelationTreeCellRenderer()); 042 addTreeWillExpandListener(new LazyRelationLoader()); 043 } 044 045 /** 046 * constructor 047 */ 048 public RelationTree() { 049 super(); 050 build(); 051 } 052 053 /** 054 * constructor 055 * @param model the tree model 056 */ 057 public RelationTree(RelationTreeModel model) { 058 super(model); 059 build(); 060 } 061 062 /** 063 * replies the parent dialog this tree is embedded in. 064 * 065 * @return the parent dialog; null, if there is no parent dialog 066 */ 067 protected Dialog getParentDialog() { 068 Component c = RelationTree.this; 069 while (c != null && !(c instanceof Dialog)) { 070 c = c.getParent(); 071 } 072 return (Dialog) c; 073 } 074 075 /** 076 * An adapter for TreeWillExpand-events. If a node is to be expanded which is 077 * not loaded yet this will trigger asynchronous loading of the respective 078 * relation. 079 * 080 */ 081 class LazyRelationLoader implements TreeWillExpandListener { 082 083 @Override 084 public void treeWillCollapse(TreeExpansionEvent event) throws ExpandVetoException { 085 // do nothing 086 } 087 088 @Override 089 public void treeWillExpand(TreeExpansionEvent event) throws ExpandVetoException { 090 TreePath path = event.getPath(); 091 Relation parent = (Relation) event.getPath().getLastPathComponent(); 092 if (!parent.isIncomplete() || parent.isNew()) 093 // we don't load complete or new relations 094 return; 095 // launch the download task 096 Main.worker.submit(new RelationLoader(getParentDialog(), parent, path)); 097 } 098 } 099 100 /** 101 * Asynchronous download task for a specific relation 102 * 103 */ 104 class RelationLoader extends PleaseWaitRunnable { 105 private boolean canceled; 106 private Exception lastException; 107 private Relation relation; 108 private DataSet ds; 109 private TreePath path; 110 111 RelationLoader(Dialog dialog, Relation relation, TreePath path) { 112 super( 113 tr("Load relation"), 114 new PleaseWaitProgressMonitor( 115 dialog 116 ), 117 false /* don't ignore exceptions */ 118 ); 119 this.relation = relation; 120 this.path = path; 121 } 122 123 @Override 124 protected void cancel() { 125 OsmApi.getOsmApi().cancel(); 126 this.canceled = true; 127 } 128 129 @Override 130 protected void finish() { 131 if (canceled) 132 return; 133 if (lastException != null) { 134 Main.error(lastException); 135 return; 136 } 137 DataSetMerger visitor = new DataSetMerger(Main.main.getEditLayer().data, ds); 138 visitor.merge(); 139 if (!visitor.getConflicts().isEmpty()) { 140 Main.main.getEditLayer().getConflicts().add(visitor.getConflicts()); 141 } 142 final RelationTreeModel model = (RelationTreeModel) getModel(); 143 SwingUtilities.invokeLater( 144 new Runnable() { 145 @Override 146 public void run() { 147 model.refreshNode(path); 148 } 149 } 150 ); 151 } 152 153 @Override 154 protected void realRun() throws SAXException, IOException, OsmTransferException { 155 try { 156 OsmServerObjectReader reader = new OsmServerObjectReader(relation.getId(), OsmPrimitiveType.from(relation), true); 157 ds = reader.parseOsm(progressMonitor 158 .createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false)); 159 } catch (Exception e) { 160 if (canceled) { 161 Main.warn(tr("Ignoring exception because task was canceled. Exception: {0}", e.toString())); 162 return; 163 } 164 this.lastException = e; 165 } 166 } 167 } 168}