|
|
1.1 root 1: #import "draw.h"
2:
3: @implementation GraphicView(Links)
4:
5: /* See the Links.rtf file for overview about Object Links in Draw. */
6:
7: #define BUFFER_SIZE 1100
8: #define INT_WIDTH 11
9:
10: /*
11: * Returns an NXSelection describe the current Graphic's selected in the view.
12: * If the user did Select All, then the gvFlags.selectAll bit is set and we
13: * return the allSelection NXSelection. If the user dragged out a rectangle,
14: * then the dragRect rectangle is set and we return a ByRect NXSelection.
15: * Otherwise, we return a ByGraphic NXSelection.
16: *
17: * We use the writeIdentifierTo: mechanism so that Group Graphic's can ask
18: * their components to write out all their identifiers so that we have a
19: * maximal chance of getting all the objects.
20: */
21:
22: - (NXSelection *)currentSelection
23: {
24: NXRect sbounds;
25: int i, graphicCount;
26: NXSelection *retval = nil;
27: char *s, *selbuf, buffer[BUFFER_SIZE];
28:
29: if (![slist count]) return [NXSelection emptySelection];
30:
31: if (gvFlags.selectAll) {
32: if ([slist count] == [glist count]) {
33: return [NXSelection allSelection];
34: } else {
35: gvFlags.selectAll = NO;
36: }
37: }
38:
39: if (dragRect) {
40: sbounds = *dragRect;
41: sprintf(buffer, "%d %d %d %d %d", ByRect,
42: (int)sbounds.origin.x, (int)sbounds.origin.y,
43: (int)(sbounds.size.width+0.5), (int)(sbounds.size.height+0.5));
44: selbuf = buffer;
45: } else {
46: graphicCount = 0;
47: i = [slist count];
48: while (i--) graphicCount += [[slist objectAt:i] graphicCount];
49: if (graphicCount > (BUFFER_SIZE / INT_WIDTH)) {
50: NX_MALLOC(selbuf, char, graphicCount * INT_WIDTH);
51: } else {
52: selbuf = buffer;
53: }
54: sprintf(buffer, "%d %d", ByList, graphicCount);
55: s = selbuf + strlen(selbuf);
56: i = [slist count];
57: while (i--) {
58: *s++ = ' ';
59: [[slist objectAt:i] writeIdentifierTo:s];
60: s += strlen(s);
61: }
62: }
63:
64: retval = [[NXSelection allocFromZone:[self zone]] initWithDescription:selbuf length:strlen(selbuf)+1];
65:
66: if (selbuf != buffer) free(selbuf);
67:
68: return retval;
69: }
70:
71: /*
72: * Used for destination selections only.
73: * Just extracts the unique identifier for the destination Image
74: * or TextGraphic and then searches through the glist to find that
75: * Graphic and returns it.
76: *
77: * Again, we use the graphicIdentifiedBy: mechanism so that we
78: * descend into Group's of Graphics to find a destination.
79: */
80:
81: - (Graphic *)findGraphicInSelection:(NXSelection *)selection
82: {
83: int i;
84: Graphic *graphic;
85: const char *selectionInfo;
86: int selectionInfoLength, identifier, selectionType;
87:
88: selectionInfo = [selection descriptionOfLength:&selectionInfoLength];
89: if (selectionInfo) {
90: sscanf(selectionInfo, "%d %d", &selectionType, &identifier);
91: if (selectionType == ByGraphic) {
92: for (i = [glist count]-1; i >= 0; i--) {
93: if (graphic = [[glist objectAt:i] graphicIdentifiedBy:identifier]) return graphic;
94: }
95: }
96: }
97:
98: return nil;
99: }
100:
101: /*
102: * Returns YES and theRect is valid only if the selection is one which
103: * the user created by dragging out a rectangle.
104: */
105:
106: - (BOOL)getRect:(NXRect *)theRect forSelection:(NXSelection *)selection
107: {
108: NXRect stackRect;
109: const char *selectionInfo;
110: int selectionInfoLength;
111: DrawSelectionType selectionType;
112:
113: if (selectionInfo = [selection descriptionOfLength:&selectionInfoLength]) {
114: if (!theRect) theRect = &stackRect;
115: sscanf(selectionInfo, "%d %f %f %f %f", (int *)&selectionType,
116: &(theRect->origin.x), &(theRect->origin.y),
117: &(theRect->size.width), &(theRect->size.height));
118: if (selectionType == ByRect) return YES;
119: }
120:
121: return NO;
122: }
123:
124: /*
125: * For source selections only.
126: * Returns the list of Graphics in the current document which were
127: * in the selection passed to this method. Note that any Group
128: * which includes a Graphic in the passed selection will be included
129: * in its entirety.
130: */
131:
132: - (List *)findGraphicsInSelection:(NXSelection *)selection
133: {
134: int i, count;
135: Graphic *graphic;
136: List *list = nil;
137: NXRect sBounds, gBounds;
138: int selectionInfoLength;
139: const char *s, *theGraphics;
140: DrawSelectionType selectionType;
141:
142: if ([selection isEqual:[NXSelection allSelection]]) {
143: count = [glist count];
144: list = [[List allocFromZone:[self zone]] initCount:count];
145: for (i = 0; i < count; i++) [list addObject:[glist objectAt:i]];
146: } else if ([self getRect:&sBounds forSelection:selection]) {
147: count = [glist count];
148: list = [[List allocFromZone:[self zone]] init];
149: for (i = 0; i < count; i++) {
150: graphic = [glist objectAt:i];
151: [graphic getBounds:&gBounds];
152: NXInsetRect(&gBounds, -0.1, -0.1);
153: if (NXIntersectsRect(&gBounds, &sBounds)) [list addObject:graphic];
154: }
155: } else if (s = [selection descriptionOfLength:&selectionInfoLength]) {
156: sscanf(s, "%d %d", (int *)&selectionType, &count);
157: if (selectionType == ByList) {
158: if (s = strchr(s, ' ')) s = strchr(s+1, ' ');
159: if (s++) {
160: theGraphics = s;
161: list = [[List allocFromZone:[self zone]] init];
162: count = [glist count];
163: for (i = 0; i < count; i++) {
164: graphic = [glist objectAt:i];
165: s = theGraphics;
166: while (s && *s) {
167: if ([graphic graphicIdentifiedBy:atoi(s)]) {
168: [list addObject:graphic];
169: break;
170: }
171: if (s = strchr(s, ' ')) s++;
172: }
173: }
174: }
175: }
176: }
177:
178: if (![list count]) {
179: [list free];
180: list = nil;
181: }
182:
183: return list;
184: }
185:
186: /*
187: * Importing/Exporting links.
188: */
189:
190: /*
191: * This method is called by copyToPasteboard:. It just puts a link to the currentSelection
192: * (presumably just written to the pasteboard by copyToPasteboard:) into the specified
193: * pboard. Note that it only does all this if we are writing all possible types to the
194: * pasteboard (typesList == NULL) or if we explicitly ask for the link to be written
195: * (typesList includes NXDataLinkPboardType).
196: */
197:
198: - writeLinkToPasteboard:(Pasteboard *)pboard types:(const NXAtom *)typesList
199: {
200: NXDataLink *link;
201:
202: if (linkManager && (!typesList || IncludesType(typesList, NXDataLinkPboardType))) {
203: if (link = [[NXDataLink alloc] initLinkedToSourceSelection:[self currentSelection] managedBy:linkManager supportingTypes:TypesDrawExports() count:NUM_TYPES_DRAW_EXPORTS]) {
204: [pboard addTypes:&NXDataLinkPboardType num:1 owner:[self class]];
205: [link writeToPasteboard:pboard];
206: [link free];
207: }
208: [linkManager writeLinksToPasteboard:pboard]; // for embedded linked things
209: }
210:
211: return self;
212: }
213:
214: /*
215: * Sets up a link from the Draw document to another document.
216: * This is called by the drag stuff (gvDrag.m) and the normal copy/paste stuff (gvPasteboard.m).
217: * We allow for the case of graphic being nil as long as the link is capable of supplying
218: * data of a type we can handle (currently Text or Image).
219: */
220:
221: - (BOOL)addLink:(NXDataLink *)link toGraphic:(Graphic *)graphic at:(const NXPoint *)p update:(int)update
222: {
223: NXSelection *selection = nil;
224:
225: if (!graphic && link && update != UPDATE_NEVER) {
226: if (TextPasteType([link types])) {
227: graphic = [[TextGraphic allocFromZone:[self zone]] initEmpty];
228: } else if (MatchTypes([link types], [NXImage imagePasteboardTypes])) {
229: graphic = [[Image allocFromZone:[self zone]] initEmpty];
230: }
231: update = UPDATE_IMMEDIATELY;
232: }
233:
234: if (graphic && link) {
235: selection = [graphic selection];
236: if ([linkManager addLink:link at:selection]) {
237: if (!update) [link setUpdateMode:NX_UpdateNever];
238: [graphic setLink:link];
239: if (graphic = [self placeGraphic:graphic at:p]) {
240: if (update == UPDATE_IMMEDIATELY) {
241: [link updateDestination];
242: graphic = [self findGraphicInSelection:selection];
243: if (![graphic isValid]) {
244: NXRunLocalizedAlertPanel(NULL, "Import Link",
245: "Unable to import linked data.", NULL, NULL, NULL,
246: "Message given to user when import of linked data fails.");
247: [self removeGraphic:graphic];
248: } else {
249: return YES;
250: }
251: } else {
252: return YES;
253: }
254: }
255: }
256: }
257:
258: [link free];
259: [selection free];
260: [graphic free];
261:
262: return NO;
263: }
264:
265: /*
266: * Keeping links up to date.
267: * These methods are called either to update a link that draw has to another
268: * document or to cause Draw to update another document that is linked to it.
269: */
270:
271: /*
272: * Sent whenever NeXTSTEP wants us to update some data in our document which
273: * we get by being linked to some other document.
274: */
275:
276: - pasteFromPasteboard:(Pasteboard *)pboard at:(NXSelection *)selection
277: {
278: id graphic;
279: NXRect gBounds;
280:
281: if (graphic = [self findGraphicInSelection:selection]) {
282: gBounds = [graphic reinitFromPasteboard:pboard];
283: [self cache:&gBounds]; // updating a destination link
284: [window flushWindow];
285: [self dirty];
286: return self;
287: }
288:
289: return nil;
290: }
291:
292: /*
293: * Lazy pasteboard method for cheapCopyAllowed case ONLY.
294: * See copyToPasteboard:at:cheapCopyAllowed: below.
295: */
296:
297: - pasteboard:(Pasteboard *)sender provideData:(const char *)type
298: {
299: List *list;
300: NXStream *stream;
301: NXSelection *selection;
302:
303: selection = [[NXSelection allocFromZone:[self zone]] initFromPasteboard:sender];
304: list = [self findGraphicsInSelection:selection];
305: if (list && (stream = NXOpenMemory(NULL, 0, NX_WRITEONLY))) {
306: if (type == NXPostScriptPboardType) {
307: [self writePSToStream:stream usingList:list];
308: } else if (type == NXTIFFPboardType) {
309: [self writeTIFFToStream:stream usingList:list];
310: }
311: [sender writeType:type fromStream:stream];
312: NXCloseMemory(stream, NX_FREEBUFFER);
313: }
314: [list free];
315: [selection free];
316:
317: return self;
318: }
319:
320: /*
321: * Called by NeXTSTEP when some other document needs to be updated because
322: * they are linked to something in our document.
323: */
324:
325: - copyToPasteboard:(Pasteboard *)pboard at:(NXSelection *)selection cheapCopyAllowed:(BOOL)cheapCopyAllowed
326: {
327: List *list;
328: NXStream *stream;
329: NXTypedStream *ts;
330: id retval = self;
331: const char *types[3];
332:
333: types[1] = NXPostScriptPboardType;
334: types[2] = NXTIFFPboardType;
335:
336: if (cheapCopyAllowed) {
337: if (list = [self findGraphicsInSelection:selection]) {
338: types[0] = NXSelectionPboardType;
339: [pboard declareTypes:types num:3 owner:self];
340: [selection writeToPasteboard:pboard];
341: } else {
342: retval = nil;
343: }
344: [list free];
345: } else {
346: types[0] = DrawPboardType;
347: [pboard declareTypes:types num:3 owner:[self class]];
348: list = [self findGraphicsInSelection:selection];
349: if (list && (stream = NXOpenMemory(NULL, 0, NX_WRITEONLY))) {
350: if (ts = NXOpenTypedStream(stream, NX_WRITEONLY)) {
351: NXWriteRootObject(ts, list);
352: NXCloseTypedStream(ts);
353: }
354: [pboard writeType:DrawPboardType fromStream:stream];
355: NXCloseMemory(stream, NX_FREEBUFFER);
356: } else {
357: retval = nil;
358: }
359: [list free];
360: }
361:
362: return retval;
363: }
364:
365:
366: /*
367: * Supports linking to an entire file (not just a selection therein).
368: * This occurs when you drag a file into Draw and link (see gvDrag).
369: * This is very analogous to the pasteFromPasteboard:at: above.
370: */
371:
372: - importFile:(const char *)filename at:(NXSelection *)selection
373: {
374: id graphic;
375: NXRect gBounds;
376:
377: if (graphic = [self findGraphicInSelection:selection]) {
378: gBounds = [graphic reinitFromFile:filename];
379: [self cache:&gBounds]; // updating a link to an imported file
380: [window flushWindow];
381: [self dirty];
382: return self;
383: }
384:
385: return nil;
386: }
387:
388: /* Other Links methods */
389:
390: /*
391: * Just makes the Link Inspector panel reflect whether any of the
392: * Graphic's currently selected are linked to some other document.
393: */
394:
395: - updateLinksPanel
396: {
397: int i, linkCount = 0;
398: Graphic *foundGraphic = nil, *graphic = nil;
399:
400: if (linkManager) {
401: for (i = [slist count]-1; i >= 0; i--) {
402: if (graphic = [[slist objectAt:i] graphicLinkedBy:NULL]) {
403: if ([graphic isKindOf:[Group class]]) {
404: linkCount += 2;
405: break;
406: } else {
407: linkCount += 1;
408: foundGraphic = graphic;
409: }
410: }
411: }
412: if (linkCount == 1) {
413: [NXDataLinkPanel setLink:[foundGraphic link] andManager:linkManager isMultiple:NO];
414: } else if (linkCount) {
415: [NXDataLinkPanel setLink:[foundGraphic link] andManager:linkManager isMultiple:YES];
416: } else {
417: [NXDataLinkPanel setLink:nil andManager:linkManager isMultiple:NO];
418: }
419: }
420:
421: return self;
422: }
423:
424: - (NXDataLinkManager *)linkManager
425: {
426: return linkManager;
427: }
428:
429: /*
430: * When we get a linkManager via this method, we must go and revive all the links.
431: * This is due to the fact that we don't archive ANY link information when we
432: * save a Draw document. However, the unique identifiers ARE archived, and thus,
433: * when we unarchive, we can recreate NXSelections with those unique identifiers
434: * and then ask the NXDataLinkManager for the link objects associated with those
435: * NXSelections.
436: *
437: * After we have revived all the links, we call breakLinkAndRedrawOutlines:
438: * with nil (meaning redraw the link outlines for all links).
439: */
440:
441: - setLinkManager:(NXDataLinkManager *)aLinkManager
442: {
443: if (!linkManager) {
444: linkManager = aLinkManager;
445: [glist makeObjectsPerform:@selector(reviveLink:) with:linkManager];
446: [self breakLinkAndRedrawOutlines:nil];
447: }
448: return self;
449: }
450:
451: /*
452: * This is called when the user chooses Open Source.
453: * It uses the trick of drawing directly into the GraphicView
454: * which, of course, is only ephemeral since the REAL contents
455: * of the GraphicView are stored in the backing store.
456: * This is convenient because Open Source is only a temporary
457: * the the user calls to see where the data for his link is
458: * coming from.
459: */
460:
461: - showSelection:(NXSelection *)selection
462: {
463: id retval = self;
464: List *graphics = nil;
465: NXRect *newInvalidRect;
466: NXRect sBounds, linkBounds;
467:
468: [self lockFocus];
469: if (invalidRect) {
470: [self drawSelf:invalidRect :1];
471: newInvalidRect = invalidRect;
472: invalidRect = NULL;
473: } else{
474: NX_MALLOC(newInvalidRect, NXRect, 1);
475: }
476: if ([self getRect:&linkBounds forSelection:selection]) {
477: PSsetgray(NX_LTGRAY);
478: NXFrameRectWithWidth(&linkBounds, 2.0);
479: *newInvalidRect = linkBounds;
480: graphics = [self findGraphicsInSelection:selection];
481: if (graphics) {
482: [self getBBox:&sBounds of:graphics];
483: NXUnionRect(&sBounds, newInvalidRect);
484: } else {
485: invalidRect = newInvalidRect;
486: [self scrollRectToVisible:invalidRect];
487: [window flushWindow];
488: retval = nil;
489: }
490: } else {
491: graphics = [self findGraphicsInSelection:selection];
492: if (graphics) {
493: [self getBBox:&sBounds of:graphics];
494: *newInvalidRect = sBounds;
495: } else {
496: retval = nil;
497: }
498: }
499:
500: if (retval) {
501: NXFrameLinkRect(&sBounds, NO);
502: invalidRect = newInvalidRect;
503: NXInsetRect(invalidRect, -NXLinkFrameThickness(), -NXLinkFrameThickness());
504: [self scrollRectToVisible:invalidRect];
505: [window flushWindow];
506: }
507:
508: [self unlockFocus];
509: [graphics free];
510:
511: return retval;
512: }
513:
514: /*
515: * Called when the Show Links button in the Link Inspector panel is clicked
516: * (the link argument will be nil in this case), or when a link is broken
517: * (the link argument will be the link that was broken).
518: */
519:
520: - breakLinkAndRedrawOutlines:(NXDataLink *)link
521: {
522: int i;
523: Graphic *graphic;
524: BOOL gotOne = NO;
525: NXRect eBounds, recacheBounds;
526:
527: for (i = [glist count]-1; i >= 0; i--) {
528: graphic = [glist objectAt:i];
529: if (graphic = [graphic graphicLinkedBy:link]) {
530: if (link && ([graphic link] == link) &&
531: ([link updateMode] == NX_UpdateNever)) {
532: [self removeGraphic:graphic];
533: }
534: if (!link || [linkManager areLinkOutlinesVisible]) {
535: [graphic getExtendedBounds:&eBounds];
536: if (gotOne) {
537: NXUnionRect(&eBounds, &recacheBounds);
538: } else {
539: recacheBounds = eBounds;
540: gotOne = YES;
541: }
542: }
543: }
544: }
545: if (gotOne) {
546: [self cache:&recacheBounds andUpdateLinks:NO];
547: [window flushWindow];
548: }
549:
550: return self;
551: }
552:
553: /*
554: * Tracking Link Changes.
555: *
556: * This is how we get "Continuous" updating links.
557: *
558: * We simply assume that a thing someone is linked to in our document
559: * changes whenever we have to redraw any rectangle in the GraphicView
560: * which intersects the linked-to rectangle. See cache:andUpdateLinks:
561: * in GraphicView.m.
562: */
563:
564: typedef struct {
565: NXRect linkRect;
566: NXDataLink *link;
567: BOOL dragged, all;
568: } LinkRect;
569:
570: - updateTrackedLinks:(const NXRect *)sRect
571: {
572: int i;
573: LinkRect *lr;
574: List *graphics;
575: NXSelection *selection;
576: NXRect *lRect, newRect;
577:
578: for (i = [linkTrackingRects count]-1; i >= 0; i--) {
579: if (NXIntersectsRect(sRect, (NXRect *)[linkTrackingRects elementAt:i])) {
580: lr = ((LinkRect *)[linkTrackingRects elementAt:i]);
581: [lr->link sourceEdited];
582: lRect = (NXRect *)[linkTrackingRects elementAt:i];
583: if (!lr->dragged && !lr->all && !NXContainsRect(lRect, sRect)) {
584: selection = [lr->link sourceSelection];
585: if (graphics = [self findGraphicsInSelection:selection]) {
586: [self getBBox:&newRect of:graphics];
587: *lRect = newRect;
588: [graphics free];
589: }
590: }
591: }
592: }
593:
594: return self;
595: }
596:
597: /* Add to linkTrackingRects. */
598:
599: - startTrackingLink:(NXDataLink *)link
600: {
601: LinkRect trackRect;
602: List *graphics = nil;
603: NXSelection *selection;
604: BOOL all = NO, dragged = NO, piecemeal = NO;
605:
606: selection = [link sourceSelection];
607: if ([selection isEqual:[NXSelection allSelection]]) {
608: all = YES;
609: trackRect.linkRect = bounds;
610: } else if ([self getRect:&trackRect.linkRect forSelection:selection]) {
611: dragged = YES;
612: } else if (graphics = [self findGraphicsInSelection:selection]) {
613: [self getBBox:&trackRect.linkRect of:graphics];
614: piecemeal = YES;
615: [graphics free];
616: } else {
617: return nil;
618: }
619:
620: if (all || dragged || piecemeal) {
621: if (!linkTrackingRects) {
622: linkTrackingRects = [[Storage allocFromZone:[self zone]] initCount:1 elementSize:sizeof(LinkRect) description:"{ffff@}"];
623: }
624: [self stopTrackingLink:link];
625: trackRect.link = link;
626: trackRect.dragged = dragged;
627: trackRect.all = all;
628: [linkTrackingRects addElement:&trackRect];
629: }
630:
631: return nil;
632: }
633:
634: /* Remove from linkTrackingRects. */
635:
636: - stopTrackingLink:(NXDataLink *)link
637: {
638: int i;
639:
640: for (i = [linkTrackingRects count]-1; i >= 0; i--) {
641: if (((LinkRect *)[linkTrackingRects elementAt:i])->link == link) {
642: [linkTrackingRects removeElementAt:i];
643: return self;
644: }
645: }
646:
647: return nil;
648: }
649:
650: @end
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.