wangting
2024-09-27 a3e87f78ee262ca9bb7d9b0c997639d5f3295890
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
package com.vci.client.omd.lifecycle.ui;
 
import java.awt.Point;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
 
import javax.swing.JComponent;
import javax.swing.TransferHandler;
 
import org.jgraph.JGraph;
import org.jgraph.graph.AttributeMap;
import org.jgraph.graph.CellView;
import org.jgraph.graph.ConnectionSet;
import org.jgraph.graph.DefaultGraphModel;
import org.jgraph.graph.GraphConstants;
import org.jgraph.graph.GraphLayoutCache;
import org.jgraph.graph.GraphModel;
import org.jgraph.graph.GraphTransferable;
import org.jgraph.graph.ParentMap;
 
public class MyTransferHandler extends TransferHandler{
    protected boolean alwaysReceiveAsCopyAction = false;
 
    /* Pointer to the last inserted array of cells. */
    protected Object out, in;
 
    /* How many times the last transferable was inserted. */
    protected int inCount = 0;
 
    public boolean canImport(JComponent comp, DataFlavor[] flavors) {
        for (int i = 0; i < flavors.length; i++)
            if (flavors[i] == GraphTransferable.dataFlavor)
                return true;
        return false;
    }
 
    /* Public entry point to create a Transferable. */
    public Transferable createTransferableForGraph(JGraph graph) {
        return createTransferable(graph);
    }
 
    protected Transferable createTransferable(JComponent c) {
        if (c instanceof JGraph) {
            JGraph graph = (JGraph) c;
            if (!graph.isSelectionEmpty()) {
                return createTransferable(graph, graph.getSelectionCells());
            }
        }
        return null;
    }
 
    protected Transferable createTransferable(JGraph graph, Object[] cells) {
        Object[] flat = graph.getDescendants(graph.order(cells));
        ParentMap pm = ParentMap.create(graph.getModel(), flat, false, true);
        ConnectionSet cs = ConnectionSet.create(graph.getModel(), flat, false);
        Map viewAttributes = GraphConstants.createAttributes(flat, graph
                .getGraphLayoutCache());
        Rectangle2D bounds = graph.getCellBounds(graph.getSelectionCells());
        bounds = new AttributeMap.SerializableRectangle2D(bounds.getX(), bounds
                .getY(), bounds.getWidth(), bounds.getHeight());
        out = flat;
        return create(graph, flat, viewAttributes, bounds, cs, pm);
    }
 
    protected GraphTransferable create(JGraph graph, Object[] cells,
            Map viewAttributes, Rectangle2D bounds, ConnectionSet cs,
            ParentMap pm) {
        return new GraphTransferable(cells, viewAttributes, bounds, cs, pm);
    }
 
    protected void exportDone(JComponent comp, Transferable data, int action) {
        if (comp instanceof JGraph && data instanceof GraphTransferable) {
            JGraph graph = (JGraph) comp;
            if (action == TransferHandler.COPY) {
                Object[] cells = ((GraphTransferable) data).getCells();
                graph.getGraphLayoutCache().remove(cells);
            }
            graph.getUI().updateHandle();
            graph.getUI().setInsertionLocation(null);
        }
    }
 
    public int getSourceActions(JComponent c) {
        return COPY_OR_MOVE;
    }
 
    // NOTE: 1. We abuse return value to signal removal to the sender.
    // 2. We always clone cells when transferred between two models
    // This is because they contain parts of the model's data.
    // 3. Transfer is passed to importDataImpl for unsupported
    // dataflavors (becaue method may return false, see 1.)
    public boolean importData(JComponent comp, Transferable t) {
        try {
            if (comp instanceof JGraph) {
                JGraph graph = (JGraph) comp;
                GraphModel model = graph.getModel();
                GraphLayoutCache cache = graph.getGraphLayoutCache();
                if (t.isDataFlavorSupported(GraphTransferable.dataFlavor)
                        && graph.isEnabled()) {
                    // May be null
                    Point p = graph.getUI().getInsertionLocation();
 
                    // Get Local Machine Flavor
                    Object obj = t
                            .getTransferData(GraphTransferable.dataFlavor);
                    GraphTransferable gt = (GraphTransferable) obj;
 
                    // Get Transferred Cells
                    Object[] cells = gt.getCells();
 
                    // Check if all cells are in the model
                    boolean allInModel = true;
                    for (int i = 0; i < cells.length && allInModel; i++)
                        allInModel = allInModel && model.contains(cells[i]);
 
                    // Count repetitive inserts
                    if (in == cells)
                        inCount++;
                    else
                        inCount = (allInModel) ? 1 : 0;
                    in = cells;
 
                    // Delegate to handle
                    if (p != null && in == out
                            && graph.getUI().getHandle() != null) {
                        int mod = (graph.getUI().getDropAction() == TransferHandler.COPY) ? InputEvent.CTRL_MASK
                                : 0;
                        graph.getUI().getHandle().mouseReleased(
                                new MouseEvent(comp, 0, 0, mod, p.x, p.y, 1,
                                        false));
                        return false;
                    }
 
                    // Get more Transfer Data
                    Rectangle2D bounds = gt.getBounds();
                    Map nested = gt.getAttributeMap();
                    ConnectionSet cs = gt.getConnectionSet();
                    ParentMap pm = gt.getParentMap();
 
                    // Move across models or via clipboard always clones
                    if (!allInModel
                            || p == null
                            || alwaysReceiveAsCopyAction
                            || graph.getUI().getDropAction() == TransferHandler.COPY) {
 
                        // Translate cells
                        double dx = 0, dy = 0;
 
                        // Cloned via Drag and Drop
                        if (nested != null) {
                            if (p != null && bounds != null) {
                                Point2D insert = graph.fromScreen(graph
                                        .snap((Point2D) p.clone()));
                                dx = insert.getX() - bounds.getX();
                                dy = insert.getY() - bounds.getY();
 
                                // Cloned via Clipboard
                            } else {
                                Point2D insertPoint = getInsertionOffset(graph,
                                        inCount, bounds);
                                if (insertPoint != null) {
                                    dx = insertPoint.getX();
                                    dy = insertPoint.getY();
                                }
                            }
                        }
 
                        handleExternalDrop(graph, cells, nested, cs, pm, dx, dy);
 
                        // Signal sender to remove only if moved between
                        // different models
                        return (graph.getUI().getDropAction() == TransferHandler.COPY && !allInModel);
                    }
 
                    // We are dealing with a move across multiple views
                    // of the same model
                    else {
 
                        // Moved via Drag and Drop
                        if (p != null) {
                            // Scale insertion location
                            Point2D insert = graph.fromScreen(graph
                                    .snap(new Point(p)));
 
                            // Compute translation vector and translate all
                            // attribute maps.
                            if (bounds != null && nested != null) {
                                double dx = insert.getX() - bounds.getX();
                                double dy = insert.getY() - bounds.getY();
                                AttributeMap.translate(nested.values(), dx, dy);
                            } else if (bounds == null) {
 
                                // Prevents overwriting view-local
                                // attributes
                                // for known cells. Note: This is because
                                // if bounds is null, the caller wants
                                // to signal that the bounds were
                                // not available, which is typically the
                                // case if no graph layout cache
                                // is at hand. To avoid overriding the
                                // local attributes such as the bounds
                                // with the default bounds from the model,
                                // we remove all attributes that travel
                                // along with the transferable. (Since
                                // all cells are already in the model
                                // no information is lost by doing this.)
                                double gs2 = 2 * graph.getGridSize();
                                nested = new Hashtable();
                                Map emptyMap = new Hashtable();
                                for (int i = 0; i < cells.length; i++) {
 
                                    // This also gives us the chance to
                                    // provide useful default location and
                                    // resize if there are no useful bounds
                                    // that travel along with the cells.
                                    if (!model.isEdge(cells[i])
                                            && !model.isPort(cells[i])) {
 
                                        // Check if there are useful bounds
                                        // defined in the model, otherwise
                                        // resize,
                                        // because the view does not yet exist.
                                        Rectangle2D tmp = graph
                                                .getCellBounds(cells[i]);
                                        if (tmp == null)
                                            tmp = GraphConstants
                                                    .getBounds(model
                                                            .getAttributes(cells[i]));
 
                                        // Clone the rectangle to force a
                                        // repaint
                                        if (tmp != null)
                                            tmp = (Rectangle2D) tmp.clone();
 
                                        Hashtable attrs = new Hashtable();
                                        Object parent = model
                                                .getParent(cells[i]);
                                        if (tmp == null) {
                                            tmp = new Rectangle2D.Double(p
                                                    .getX(), p.getY(), gs2 / 2,
                                                    gs2);
                                            GraphConstants.setResize(attrs,
                                                    true);
 
                                            // Shift
                                            p.setLocation(p.getX() + gs2, p
                                                    .getY()
                                                    + gs2);
                                            graph.snap(p);
                                            // If parent processed then childs
                                            // are already located
                                        } else if (parent == null
                                                || !nested
                                                        .keySet()
                                                        .contains(
                                                                model
                                                                        .getParent(cells[i]))) {
                                            CellView view = graph
                                                    .getGraphLayoutCache()
                                                    .getMapping(cells[i], false);
                                            if (view != null && !view.isLeaf()) {
                                                double dx = p.getX()
                                                        - tmp.getX();
                                                double dy = p.getY()
                                                        - tmp.getY();
                                                GraphLayoutCache
                                                        .translateViews(
                                                                new CellView[] { view },
                                                                dx, dy);
                                            } else {
                                                tmp.setFrame(p.getX(),
                                                        p.getY(), tmp
                                                                .getWidth(),
                                                        tmp.getHeight());
                                            }
 
                                            // Shift
                                            p.setLocation(p.getX() + gs2, p
                                                    .getY()
                                                    + gs2);
                                            graph.snap(p);
                                        }
                                        GraphConstants.setBounds(attrs, tmp);
                                        nested.put(cells[i], attrs);
                                    } else {
                                        nested.put(cells[i], emptyMap);
                                    }
                                }
                            }
 
                            // Edit cells (and make visible)
                            cache.edit(nested, null, null, null);
                        }
 
                        // Select topmost cells in group-structure
                        graph.setSelectionCells(DefaultGraphModel
                                .getTopmostCells(model, cells));
 
                        // Don't remove at sender
                        return false;
                    }
                } else
                    return importDataImpl(comp, t);
            }
        } catch (Exception exception) {
            // System.err.println("Cannot import: " +
            // exception.getMessage());
            exception.printStackTrace();
        }
        return false;
    }
 
    /**
     * Hook method to determine offset of cells cloned via the clipboard
     * @param graph the graph the insertion is occurring on
     * @param inCount the number of time the insert has been applied
     * @param bounds the bounds of the transferred graph
     * @return the offset from the cloned cell(s)
     */
    protected Point2D getInsertionOffset(JGraph graph, int inCount, Rectangle2D bounds) {
        Point2D result = null;
        if (graph != null) {
            result = new Point2D.Double(inCount * graph.getGridSize(), inCount * graph.getGridSize());
        }
        return result;
    }
 
    protected void handleExternalDrop(JGraph graph, Object[] cells, Map nested,
            ConnectionSet cs, ParentMap pm, double dx, double dy) {
 
        // Removes all connections for which the port is neither
        // passed in the parent map nor already in the model.
        Iterator it = cs.connections();
        while (it.hasNext()) {
            ConnectionSet.Connection conn = (ConnectionSet.Connection) it
                    .next();
            if (!pm.getChangedNodes().contains(conn.getPort())
                    && !graph.getModel().contains(conn.getPort())) {
                it.remove();
            }
        }
        Map clones = graph.cloneCells(cells);
        graph.getGraphLayoutCache().insertClones(cells, clones, nested, cs, pm,
                dx, dy);
    }
 
    // For subclassers if above does not handle the insertion
    protected boolean importDataImpl(JComponent comp, Transferable t) {
        return false;
    }
 
    /**
     * @return Returns the alwaysReceiveAsCopyAction.
     */
    public boolean isAlwaysReceiveAsCopyAction() {
        return alwaysReceiveAsCopyAction;
    }
 
    /**
     * @param alwaysReceiveAsCopyAction
     *            The alwaysReceiveAsCopyAction to set.
     */
    public void setAlwaysReceiveAsCopyAction(boolean alwaysReceiveAsCopyAction) {
        this.alwaysReceiveAsCopyAction = alwaysReceiveAsCopyAction;
    }
}