001: /*
002: * CONFIDENTIAL AND PROPRIETARY SOURCE CODE OF
003: * NETSCAPE COMMUNICATIONS CORPORATION
004: *
005: * Copyright (c) 1996 Netscape Communications Corporation.
006: * All Rights Reserved.
007: * Use of this Source Code is subject to the terms of the applicable
008: * license agreement from Netscape Communications Corporation.
009: */
010:
011: package graphical;
012:
013: import netscape.application.Alert;
014: import netscape.application.DragDestination;
015: import netscape.application.DragSession;
016: import netscape.application.DragSource;
017: import netscape.application.Image;
018: import netscape.application.ListItem;
019: import netscape.application.ListView;
020: import netscape.application.MouseEvent;
021: import netscape.application.Point;
022: import netscape.application.Rect;
023: import netscape.application.Target;
024: import netscape.application.View;
025: import netscape.util.Enumeration;
026: import netscape.util.Vector;
027:
028: import java.awt.Dimension;
029: import java.net.URL;
030: import java.net.MalformedURLException;
031: import java.util.Date;
032:
033: import util.BinaryTree;
034: import util.BTreeNode;
035:
036: /**
037: DragListView.
038: <p>
039: <b>Open Issues</b>
040: <ul>
041: <li>Passing in a BinaryTree is optional and is only
042: managed if null checks out. It is somewhat tempting
043: to argue that BTreeNodes should be smartened up
044: to know their BinaryTree membership and that ListItem.data()
045: turning out to be a BTreeNode would automatically trigger
046: correct data management. But not today.
047: <li>Or maybe it checks to see if the parent is a TreeView
048: and obtains the BinaryTree. May cost a ping though.
049: <li>Actually, it isn't clear that it's needed at all.
050: <li>Policy refinements (branchesFrom, sortTo, dupeTo) should be
051: crammed into a bit flag. Also, they are incompletely
052: implemented.
053: Actually, they're a total hack.
054: <li>And branches from should probably be 1) restricted to
055: BinaryTree users and 2) optionally take branch or
056: branch plus subbranch.
057: </ul>
058: *
059: */
060: public class DragListView extends ListView implements DragDestination,
061: DragSource, DragPolicy {
062: /*************/
063: /* Variables */
064: /*************/
065:
066: private DragPolicyContext dpc;
067:
068: private BinaryTree binaryTree;
069: private TreeView treeView;
070:
071: private String m_dropCommand = null;
072: private Target m_dropTarget = null;
073:
074: /**
075: * Image to be set when an item is dragged to a list.
076: * SGP: maybe not publicy.
077: */
078: public Image toImage;
079:
080: /**
081: * Image to be set when an item is dragged to a list.
082: * SGP: maybe not publicy.
083: */
084: public Image toSelectedImage;
085:
086: /**
087: * Allow branches to be dragged from origin or allow only
088: * end nodes.
089: * Only end nodes are currently supported.
090: * Default is false: only end nodes are allowed to be dragged from.
091: */
092: public boolean branchesFrom;
093:
094: /**
095: * Maintain sort on target.
096: * BinaryTrees not supported (they may support themselves).
097: * Default is false.
098: * SGP: actually, nothing is supported.
099: */
100: public boolean sortTo;
101:
102: /**
103: * Unduplicate target.
104: * Do or do not allow duplicate nodes on the list.
105: * Default is false: disallow duplicates.
106: */
107: public boolean dupeTo;
108:
109: /****************/
110: /* Constructors */
111: /****************/
112:
113: /**
114: * Constructs an empty DragListView.
115: */
116: public DragListView() {
117: // SGP: will that null be ok?
118: this (0, 0, 0, 0, DRAGFROMDISALLOWED, DRAGTODISALLOWED, null);
119: }
120:
121: /**
122: * Constructs a DragListView with bounds <B>rect</B>.
123: * @param rect rectangle
124: * @param dragSourcePolicy drag source policy
125: * @param dragDestinationPolicy drag destination policy
126: * @param dragImage drag image
127: */
128: public DragListView(Rect rect, int dragSourcePolicy,
129: int dragDestinationPolicy, Image dragImage) {
130: this (rect.x, rect.y, rect.width, rect.height, dragSourcePolicy,
131: dragDestinationPolicy, dragImage);
132: }
133:
134: /**
135: * Constructs an empty DragListView with the given bounds.
136: * @param x x
137: * @param y y
138: * @param width width
139: * @param height height
140: * @param dragSourcePolicy drag source policy
141: * @param dragDestinationPolicy drag destination policy
142: * @param dragImage drag image
143: */
144: public DragListView(int x, int y, int width, int height,
145: int dragSourcePolicy, int dragDestinationPolicy,
146: Image dragImage) {
147: super (x, y, width, height);
148:
149: dpc = new DragPolicyContext(dragImage);
150:
151: setDragSourcePolicy(dragSourcePolicy);
152: setDragDestinationPolicy(dragDestinationPolicy);
153:
154: // SGP: hack alert!
155: branchesFrom = false;
156: sortTo = false;
157: dupeTo = false;
158: }
159:
160: public void setTreeView(TreeView treeView) {
161: this .treeView = treeView;
162: this .binaryTree = treeView.getBinaryTree();
163: }
164:
165: public void setBinaryTree(BinaryTree binaryTree) {
166: this .binaryTree = binaryTree;
167: }
168:
169: public int minItemWidth() {
170: int w = super .minItemWidth();
171:
172: if (treeView != null) {
173: if (treeView.editable()) {
174: w += Header.SPACEPLUSCARETWIDTH;
175: }
176: }
177:
178: return w;
179: }
180:
181: public void sizeListView() {
182: sizeTo(minItemWidth(), height());
183: sizeToMinSize();
184: setDirty(true);
185: }
186:
187: // SGP: bad name
188: public int getIndexWithData(Object o) {
189: int n = count();
190:
191: for (int i = 0; i < n; i++) {
192: if (itemAt(i).data() == o) {
193: return i;
194: }
195: }
196:
197: return -1; // SGP
198: }
199:
200: public ListItem itemForData(Object o) {
201: int n = count();
202:
203: for (int i = 0; i < n; i++) {
204: if (itemAt(i).data() == o) {
205: return itemAt(i);
206: }
207: }
208:
209: return null;
210: }
211:
212: public void selectEmpty() {
213: // SGP: looking at ifc implementation, this
214: // may fail when we allow multiples - might have
215: // to clone or something.
216: Vector v = selectedItems();
217: Enumeration e = v.elements();
218: while (e.hasMoreElements()) {
219: ListItem li = (ListItem) e.nextElement();
220: deselectItem(li);
221: }
222: }
223:
224: /**
225: * Drag dropped notification command, sent when Category dropped.
226: */
227: public void setDragDroppedNotificationCommand(String cmd) {
228: m_dropCommand = cmd;
229: }
230:
231: /**
232: * Drag dropped notification target, sent when Category dropped.
233: */
234: public void setDragDroppedNotificationTarget(Target target) {
235: m_dropTarget = target;
236: }
237:
238: /************/
239: /* ClipList */
240: /************/
241:
242: /**
243: * A clipboard for subtrees represented by BTListItems.
244: */
245: private BTListItem[] clipList;
246:
247: /**
248: * Copies the subtree rooted at BTListItem to clipboard list.
249: */
250: private void copyToClipList(BTListItem listItem) {
251: BTListItem lastLI = getEndOfOpenChildren(listItem);
252: int lastLIix = indexOfItem(lastLI);
253: int ix = indexOfItem(listItem);
254:
255: clipList = new BTListItem[lastLIix - ix + 1];
256: for (int i = ix; i <= lastLIix; i++) {
257: clipList[i - ix] = (BTListItem) itemAt(i);
258: }
259: }
260:
261: /**
262: * Cuts the subtree rooted at listItem to clipboard list.
263: */
264: private void cutToClipList(BTListItem listItem) {
265: // System.out.println( "cutting:" );
266: BTListItem lastLI = getEndOfOpenChildren(listItem);
267: int lastLIix = indexOfItem(lastLI);
268: int ix = indexOfItem(listItem);
269:
270: clipList = new BTListItem[lastLIix - ix + 1];
271: for (int i = ix; i <= lastLIix; i++) {
272: clipList[i - ix] = (BTListItem) itemAt(ix);
273: // System.out.println( " " + clipList[ i - ix ].title() );
274: removeItemAt(ix);
275: }
276: }
277:
278: /**
279: * Pastes the clipboard list subtree after listItem at peer level.
280: * User is assumed to be reflecting the tree
281: */
282: private void pasteAfterFromClipList(BTListItem listItem) {
283: // System.out.println( "pasting:" + listItem.title() );
284: int c = clipList.length;
285: int ix = indexOfItem(listItem);
286:
287: for (int i = 0; i < c; i++) {
288: insertItemAt(clipList[i], (i + ix + 1));
289: }
290: }
291:
292: /**
293: * Checks to see if suggested subtree has moved in the BinaryTree
294: * but is not correctly reflected in the ListView.
295: * If yes, cuts and moves it.
296: * Use with caution.
297: * Returns "true" if any changes were made.
298: */
299: public boolean clipSortAndPaste(BTListItem listItem) {
300: if (binaryTree == null) {
301: return false;
302: }
303:
304: int ix = indexOfItem(listItem);
305:
306: if (ix == 0) {
307: return false;
308: }
309:
310: BTreeNode btn = (BTreeNode) listItem.data();
311: BTreeNode pbtn = btn.getParent();
312:
313: if ((pbtn.getChild() == btn) && (itemAt(ix - 1).data() != pbtn)) {
314: cutToClipList(listItem);
315: pasteAfterFromClipList((BTListItem) itemForData(pbtn));
316: return true;
317: }
318:
319: if (pbtn.getNext() == btn) {
320: BTListItem prevPeer = getPrevPeer(listItem, ix);
321:
322: if ((prevPeer == null) || (prevPeer.data() != pbtn)) {
323: cutToClipList(listItem);
324: pasteAfterFromClipList(getEndOfOpenChildren((BTListItem) itemForData(pbtn)));
325: }
326: return true;
327: }
328:
329: return false;
330: }
331:
332: private BTListItem getEndOfOpenChildren(BTListItem listItem) {
333: int ix = indexOfItem(listItem);
334: int c = count();
335: int ud = ((BTreeNode) listItem.data()).getUnaryDepth();
336:
337: for (int i = ix + 1; i < c; i++) {
338: BTListItem candidate = (BTListItem) itemAt(i);
339: int cud = ((BTreeNode) candidate.data()).getUnaryDepth();
340: if (cud <= ud) {
341: return (BTListItem) itemAt(i - 1);
342: }
343: }
344:
345: return (BTListItem) itemAt(c - 1);
346: }
347:
348: private BTListItem getPrevPeer(BTListItem li, int ix) {
349: int ud = ((BTreeNode) li.data()).getUnaryDepth();
350:
351: for (int i = ix - 1; i >= 0; i--) {
352: BTListItem candidate = (BTListItem) itemAt(i);
353: int cud = ((BTreeNode) candidate.data()).getUnaryDepth();
354:
355: if (cud < ud) {
356: return null;
357: }
358:
359: if (cud == ud) {
360: return candidate;
361: }
362: }
363:
364: return null;
365: }
366:
367: /**************/
368: /* DragPolicy */
369: /**************/
370:
371: public DragPolicyContext getDragPolicyContext() {
372: return dpc;
373: }
374:
375: public Target getDestinationTarget() {
376: return dpc.destinationTarget;
377: }
378:
379: public void setDestinationTarget(Target t) {
380: dpc.destinationTarget = t;
381: }
382:
383: public Target getSourceTarget() {
384: return dpc.sourceTarget;
385: }
386:
387: public void setSourceTarget(Target t) {
388: dpc.sourceTarget = t;
389: }
390:
391: public Image getDragImage() {
392: return dpc.dragImage;
393: }
394:
395: public void setDragImage(Image dragImage) {
396: dpc.dragImage = dragImage;
397: }
398:
399: public void setDragSourcePolicy(int dragSourcePolicy)
400: throws IllegalArgumentException {
401: if (DragPolicyContext
402: .validateDragSourcePolicy(dragSourcePolicy)) {
403: dpc.dragSourcePolicy = dragSourcePolicy;
404: } else {
405: throw new IllegalArgumentException(
406: Messages.UNKNOWNDRAGPOLICY + " " + dragSourcePolicy);
407: }
408: }
409:
410: public void setDragDestinationPolicy(int dragDestinationPolicy)
411: throws IllegalArgumentException {
412: if (DragPolicyContext
413: .validateDragDestinationPolicy(dragDestinationPolicy)) {
414: dpc.dragDestinationPolicy = dragDestinationPolicy;
415: } else {
416: throw new IllegalArgumentException(
417: Messages.UNKNOWNDRAGPOLICY + " "
418: + dragDestinationPolicy);
419: }
420: }
421:
422: /**************/
423: /* MouseEvent */
424: /**************/
425:
426: public boolean mouseDown(MouseEvent e) {
427: // System.out.println( "mouseDown()" );
428: dpc.mouseDownX = e.x;
429: dpc.mouseDownY = e.y;
430:
431: if (treeView != null) { // completeEditing messes with focus
432: treeView.completeEditing(); // and selection so do now
433: }
434:
435: return super .mouseDown(e);
436: }
437:
438: public void mouseDragged(MouseEvent e) {
439: // System.out.println( "mouseDragged()" );
440: super .mouseDragged(e);
441: ListItem li = null;
442:
443: if (dpc.dragSourcePolicy != DRAGFROMDISALLOWED) {
444: if (dpc.dragSession == null) {
445: li = selectedItem();
446:
447: Image i = li.image();
448: int iw = i.width();
449: int ix = iw - dpc.dragImage.width();
450: int iy = selectedIndex() * li.minHeight();
451:
452: if ((dpc.mouseDownX <= iw) && (dpc.mouseDownX > ix)) {
453: dpc.dragSession = new DragSession(this ,
454: dpc.dragImage, ix, iy, dpc.mouseDownX,
455: dpc.mouseDownY, "", li);
456: }
457: }
458: }
459: }
460:
461: public void mouseUp(MouseEvent e) {
462: // System.out.println( "mouseUp()" );
463: super .mouseUp(e);
464: }
465:
466: public void mouseEntered(MouseEvent e) {
467: // System.out.println( "mouseEntered()" );
468: super .mouseEntered(e);
469: }
470:
471: public void mouseMoved(MouseEvent e) {
472: // System.out.println( "mouseMoved()" );
473: super .mouseMoved(e);
474: }
475:
476: public void mouseExited(MouseEvent e) {
477: // System.out.println( "mouseExited()" );
478: super .mouseExited(e);
479: }
480:
481: /*******************/
482: /* DragDestination */
483: /*******************/
484:
485: public boolean dragEntered(DragSession ds) {
486: // System.out.println( "dragEntered()" );
487: if (dpc.dragDestinationPolicy != DRAGTODISALLOWED) {
488: if (ds.source() instanceof DragListView) { // Drag from Catg Edit?
489: if (ds.source() != this ) { // Must be same Catg Edit!
490: return false;
491: }
492: }
493:
494: /*
495: if ( ds.source().sourceView( ds ) instanceof Target )
496: {
497: dpc.sourceTarget = ( Target ) ds.source().sourceView( ds );
498: }
499: dpc.destinationTarget = this;
500: */
501: dpc.sourceDragSession = ds;
502: return true;
503: } else {
504: return false;
505: }
506: }
507:
508: public boolean dragMoved(DragSession ds) {
509: // System.out.println( "dragMoved()" );
510: if (ds.source() instanceof DragListView) { // Drag from Catg Edit?
511: if (ds.source() != this ) { // Must be same Catg Edit!
512: return false;
513: }
514: }
515:
516: return true;
517: }
518:
519: public void dragExited(DragSession ds) {
520: // System.out.println( "dragExited()" );
521: if (dpc.dragDestinationPolicy != DRAGTODISALLOWED) {
522: // dpc.sourceDragSession = null;
523: // dpc.sourceTarget = null;
524: dpc.destinationTarget = null;
525: draw();
526: }
527: }
528:
529: public boolean dragDropped(DragSession ds) {
530: // System.out.println( "dragDropped()" );
531:
532: Point destPoint = ds.destinationMousePoint();
533: ListItem destItem = this .itemForPoint(destPoint.x, destPoint.y);
534: ListItem dragItem = null;
535:
536: Object o = ds.source();
537: if (o == null) {
538: } else if (o instanceof DragPolicy) {
539: DragPolicyContext dpctmp = ((DragPolicy) o)
540: .getDragPolicyContext();
541: if (dpctmp != null) {
542: dpc.destinationTarget = dpctmp.sourceTarget;
543: }
544: }
545: if (dpc.destinationTarget != null) {
546: DragArgument da = new DragArgument(
547: DragArgument.DRAGDROPPED, this , destItem, ds
548: .source(), ds.data());
549: dpc.destinationTarget.performCommand("", da);
550: }
551:
552: if (dpc.dragDestinationPolicy == DRAGTOACCEPT) {
553: // dpc.sourceDragSession = null;
554: // dpc.sourceTarget = null;
555: dpc.destinationTarget = null;
556: return true;
557: } else if (dpc.dragDestinationPolicy == DRAGTOACCEPTINCLUDE) {
558: // SGP: not working
559: /*
560: Point destPoint = ds.destinationMousePoint();
561: ListItem
562: destItem = this.itemForPoint(
563: destPoint.x,
564: destPoint.y
565: );
566: */
567: int destIndex = indexOfItem(destItem);
568: dragItem = (ListItem) ((ListItem) ds.data()).clone();
569:
570: if (destItem.data() == dragItem.data()) { // Same TaxonomyNode?
571: return false; // Cannot drag onto itself
572: }
573:
574: Object dragObj = dragItem.data(); // SGP: clone?
575: ListItem newItem = null;
576:
577: if (destItem != null) {
578: selectOnly(destItem);
579: }
580:
581: if (treeView != null) {
582: BTreeNode destBtn = (BTreeNode) destItem.data();
583: BTreeNode dragBtn;
584: if ((dragObj instanceof BTreeNode)) {
585: dragBtn = (BTreeNode) dragObj;
586: if (destBtn.hasAncestor(dragBtn)) {
587: Alert.runAlertExternally(Alert.warningImage(),
588: Messages.TITLE_WARNING,
589: Messages.CANTDRAGONSUBTREE,
590: Messages.OKAY, null, null);
591: return false;
592: }
593: dragBtn = dragBtn.detachFromParent(BinaryTree.NEXT);
594: } else {
595: dragBtn = new BTreeNode(dragItem.title(), null);
596: dragBtn.setTerminalPolicy(BTreeNode.NEXTVISIBLE);
597: }
598: // dragBtn.recursiveDump();
599: // binaryTree.dumpTree();
600: binaryTree.currentBackup();
601: binaryTree.setEnumeration(destBtn);
602: binaryTree.insertChild(dragBtn, BinaryTree.NEXT);
603: binaryTree.currentRestore();
604: treeView.childReorganize((BTListItem) itemAt(0));
605: // System.out.println( "---< whack >---" );
606: // binaryTree.getRoot().recursiveDump();
607: // binaryTree.dumpTree();
608: // System.out.println( "---< whick >---" );
609: } else {
610: // SGP: note - not tested...
611: // SGP: should just insert after
612: // if no TreeView/BinaryTree bs
613: if (destItem == null) {
614: newItem = addItem(dragItem);
615: } else {
616: newItem = insertItemAt(dragItem, destIndex);
617: }
618: }
619:
620: // dpc.sourceTarget = null;
621: // dpc.destinationTarget = null;
622: dpc.sourceDragSession = null;
623:
624: if (newItem != null) {
625: if (toImage != null) {
626: newItem.setImage(toImage);
627: }
628:
629: if (toSelectedImage != null) {
630: newItem.setSelectedImage(toSelectedImage);
631: }
632: }
633:
634: }
635:
636: sizeListView();
637: setDirty(true);
638:
639: if (m_dropTarget != null) { // Send out drop notification?
640: m_dropTarget.performCommand(m_dropCommand, dragItem);
641: }
642:
643: return true;
644: }
645:
646: public DragDestination acceptsDrag(DragSession ds, int x, int y) {
647: // System.out.println( "acceptsDrag()" );
648:
649: /*
650: System.out.println(
651: "source [" + ds.source().toString() + "]"
652: );
653: System.out.println(
654: "data [" + ds.data().toString() + "]"
655: );
656: */
657:
658: // must be open for business
659: if (dpc.dragDestinationPolicy == DRAGTODISALLOWED) {
660: return null;
661: }
662:
663: // if we're just accepting, we do no redraw and
664: // will put up with a lot for now.
665: if (dpc.dragDestinationPolicy == DRAGTOACCEPT) {
666: return this ;
667: }
668:
669: // must be a ListItem
670: if (!(ds.data() instanceof ListItem)) {
671: return null;
672: }
673:
674: ListItem destItem = this .itemForPoint(x, y);
675: ListItem dragItem = (ListItem) ds.data();
676:
677: // don't drag onto yourself
678: if (destItem == dragItem) {
679: return null;
680: }
681:
682: if (treeView != null) {
683: // target must have a correct terminal policy
684: BTreeNode destBtn = (BTreeNode) destItem.data();
685: int destTp = destBtn.getTerminalPolicy();
686: if ((destTp != BTreeNode.OPEN)
687: && (destTp != BTreeNode.CHILDVISIBLE)) {
688: return null;
689: }
690: }
691:
692: return this ;
693: }
694:
695: /**************/
696: /* DragSource */
697: /**************/
698:
699: public View sourceView(DragSession ds) {
700: // System.out.println( "sourceView()" );
701: return this ;
702: }
703:
704: public void dragWasAccepted(DragSession ds) {
705: // System.out.println( "dragWasAccepted()" );
706:
707: if (dpc.dragSourcePolicy == DRAGFROMMOVE) {
708: treeView.childReorganize((BTListItem) itemAt(0));
709: // binaryTree.dumpTree();
710: }
711:
712: dpc.dragSession = null;
713: setDirty(true);
714: }
715:
716: public boolean dragWasRejected(DragSession ds) {
717: // System.out.println( "dragWasRejected()" );
718:
719: dpc.dragSession = null;
720: setDirty(true);
721: return true;
722: }
723: }
|