|
|
1.1 root 1: #import "draw.h"
2:
3: extern NXAtom NXDataLinkPboardType;
4:
5: /* This file is best read in a window as wide as this comment (so that this comment fits on one line). */
6:
7: @interface GraphicView(PrivateMethods)
8:
9: /* Private methods */
10:
11: - selectionCache;
12: - getBBox:(NXRect *)bbox of:(List *)list extended:(BOOL)extended;
13: - recacheSelection:(BOOL)updateLinks;
14: - compositeSelection:(const NXRect *)sbounds from:(int)gstate;
15: - (int)cacheSelection;
16: - dirty:sender;
17:
18: - resetGUP;
19: - (BOOL)move:(NXEvent *)event;
20: - moveGraphicsBy:(NXPoint *)vector andDraw:(BOOL)drawFlag;
21: - dragSelect:(NXEvent *)event;
22: - alignGraphicsBy:(AlignmentType)alignType edge:(NXCoord *)edge;
23: - alignBy:(AlignmentType)alignType;
24:
25: @end
26:
27: const char *DrawPboardType = "Draw Graphic List Type version 3.0";
28:
29: BOOL InMsgPrint = NO; /* whether we are in msgPrint: */
30:
31: #define LEFTARROW 172
32: #define RIGHTARROW 174
33: #define UPARROW 173
34: #define DOWNARROW 175
35:
36: @implementation GraphicView : View
37: /*
38: * The GraphicView class is the core of a DrawDocument.
39: *
40: * It overrides the View methods related to drawing and event handling
41: * and allows manipulation of Graphic objects.
42: *
43: * The user is allowed to select objects, move them around, group and
44: * ungroup them, change their font, and cut and paste them to the pasteboard.
45: * It understands multiple formats including PostScript and TIFF as well as
46: * its own internal format. The GraphicView can also import PostScript and
47: * TIFF documents and manipulate them as Graphic objects.
48: *
49: * This is a very skeleton implementation and is intended purely for
50: * example purposes. It should be very easy to add new Graphic objects
51: * merely by subclassing the Graphic class. No code need be added to the
52: * GraphicView class when a new Graphic subclass is added.
53: *
54: * Moving is accomplished using a selection cache which is shared among
55: * all instances of GraphicView in the application. The objects in the
56: * selection are drawn using opaque ink on a transparent background so
57: * that when they are moved around, the user can see through them to the
58: * objects that are not being moved.
59: *
60: * All of the drawing is done in an off-screen window which is merely
61: * composited back to the screen. This makes for very fast redraw of
62: * areas obscured either by the selection moving or the user's scrolling.
63: *
64: * The glist instance variable is just an ordered list of all the Graphics
65: * in the GraphicView. The slist is an ordered list of the Graphic objects
66: * in the selection. In the original Draw code it was almost always kept
67: * in the same order as the glist, but could be jumbled by doing a shift-drag
68: * select to add to an existing selection. We are now extremely careful about
69: * keeping the slist in the same order as the glist.
70: *
71: * cacheWindow is the off-screen window into which the objects are
72: * drawn. Flags: grid is the distance between pixels in the grid
73: * imposed on drawing; cacheing is used so that drawSelf:: knows when
74: * to composite from the off-screen cache and when to draw into the
75: * off-screen cache; groupInSlist is used to keep track of whether a
76: * Group Graphic is in the slist so that it knows when to highlight
77: * the Ungroup entry in the menu.
78: *
79: * This class should be able to be used outside the context of this
80: * application since it takes great pains not to depend on any other objects
81: * in the application.
82: */
83:
84: /*
85: * Of course, one should NEVER use global variables in an application, but
86: * the following is necessary. DrawStatus is
87: * analogous to the Application Kit's NXDrawingStatus and reflects whether
88: * we are in some modal loop. By definition, that modal loop is "atomic"
89: * since we own the mouse during its duration (of course, all bets are off
90: * if we have multiple mice!).
91: */
92:
93: DrawStatusType DrawStatus = Normal; /* global state reflecting what we
94: are currently doing (resizing, etc.) */
95:
96: const float DEFAULT_GRID_GRAY = 0.8333;
97:
98: static Graphic *currentGraphic = nil; /* won't be used if NXApp knows how
99: to keep track of the currentGraphic */
100:
101: static float KeyMotionDeltaDefault = 0.0;
102:
103: /* Private C functions needed to implement methods in this class. */
104:
105: static Window *createCache(NXSize *size, NXZone *zone)
106: /*
107: * Creates an appropriately size off-screen window to cache bits in.
108: */
109: {
110: NXRect cacheRect;
111:
112: cacheRect.origin.x = 0.0;
113: cacheRect.origin.y = 0.0;
114: cacheRect.size = *size;
115:
116: return [[[Window allocFromZone:zone] initContent:&cacheRect
117: style:NX_PLAINSTYLE
118: backing:NX_RETAINED
119: buttonMask:0
120: defer:NO] reenableDisplay];
121: }
122:
123: /* Code-cleaning macros */
124:
125: #define stopTimer(timer) if (timer) { \
126: NXEndTimer(timer); \
127: timer = NULL; \
128: }
129:
130: #define startTimer(timer) if (!timer) timer = NXBeginTimer(NULL, 0.1, 0.1);
131:
132: #define GRID (gvFlags.gridDisabled ? 1.0 : (gvFlags.grid ? (NXCoord)gvFlags.grid : 1.0))
133:
134: #define grid(point) \
135: (point).x = floor(((point).x / GRID) + 0.5) * GRID; \
136: (point).y = floor(((point).y / GRID) + 0.5) * GRID;
137:
138: static void getRegion(NXRect *region, const NXPoint *p1, const NXPoint *p2)
139: /*
140: * Returns the rectangle which has p1 and p2 as its corners.
141: */
142: {
143: region->size.width = p1->x - p2->x;
144: region->size.height = p1->y - p2->y;
145: if (region->size.width < 0.0) {
146: region->origin.x = p2->x + region->size.width;
147: region->size.width = ABS(region->size.width);
148: } else {
149: region->origin.x = p2->x;
150: }
151: if (region->size.height < 0.0) {
152: region->origin.y = p2->y + region->size.height;
153: region->size.height = ABS(region->size.height);
154: } else {
155: region->origin.y = p2->y;
156: }
157: }
158:
159: static BOOL checkForGroup(List *list)
160: /*
161: * Looks through the given list searching for objects of the Group class.
162: * We use this to keep the gvFlags.groupInSlist flag up to date when things
163: * are removed from the slist (the list of selected objects). That way
164: * we can efficiently keep the Ungroup menu item up to date.
165: */
166: {
167: int i = [list count];
168: while (i--) if ([[list objectAt:i] isKindOf:[Group class]]) return YES;
169: return NO;
170: }
171:
172: /* Hack to support growable Text objects. */
173:
174: static View *createEditView(GraphicView *self)
175: /*
176: * editView is essentially a dumb, FLIPPED (with extra emphasis on the
177: * flipped) subview of our GraphicView which completely covers it and
178: * which automatically sizes itself to always completely cover the
179: * GraphicView. It is necessary since growable Text objects only work
180: * when they are subviews of a flipped view.
181: *
182: * See TextGraphic for more details about why we need editView
183: * (it is purely a workaround for a limitation of the Text object).
184: */
185: {
186: View *view;
187:
188: [self setAutoresizeSubviews:YES];
189: view = [[View allocFromZone:[self zone]] initFrame:&(self->frame)];
190: [view setFlipped:YES];
191: [view setAutosizing:NX_WIDTHSIZABLE|NX_HEIGHTSIZABLE];
192: [self addSubview:view];
193:
194: return view;
195: }
196:
197: /* Factory methods. */
198:
199: + initialize
200: /*
201: * We up the version of the class so that we can read old .draw files.
202: * See the read: method for how we use the version.
203: */
204: {
205: [GraphicView setVersion:1];
206: DrawPboardType = NXUniqueStringNoCopy(DrawPboardType);
207: return self;
208: }
209:
210: /* Alignment methods */
211:
212: + (SEL)actionFromAlignType:(AlignmentType)alignType
213: {
214: switch (alignType) {
215: case LEFT: return @selector(moveLeftEdgeTo:);
216: case RIGHT: return @selector(moveRightEdgeTo:);
217: case BOTTOM: return @selector(moveBottomEdgeTo:);
218: case TOP: return @selector(moveTopEdgeTo:);
219: case HORIZONTAL_CENTERS: return @selector(moveVerticalCenterTo:);
220: case VERTICAL_CENTERS: return @selector(moveHorizontalCenterTo:);
221: case BASELINES: return @selector(moveBaselineTo:);
222: }
223: return (SEL)NULL;
224: }
225:
226: /* Creation methods. */
227:
228: static void initClassVars()
229: /*
230: * Sets up any default values.
231: */
232: {
233: static BOOL registered = NO;
234: const char *validSendTypes[4];
235: const char *validReturnTypes[4];
236:
237: if (!KeyMotionDeltaDefault) {
238: const char *value = NXGetDefaultValue([NXApp appName], "KeyMotionDelta");
239: if (value) KeyMotionDeltaDefault = atof(value);
240: KeyMotionDeltaDefault = MAX(KeyMotionDeltaDefault, 1.0);
241: }
242: if (!registered) {
243: registered = YES;
244: validSendTypes[0] = NXPostScriptPboardType;
245: validSendTypes[1] = NXTIFFPboardType;
246: validSendTypes[2] = DrawPboardType;
247: validSendTypes[3] = NULL;
248: [NXApp registerServicesMenuSendTypes:validSendTypes andReturnTypes:[NXImage imagePasteboardTypes]];
249: validReturnTypes[0] = DrawPboardType;
250: validReturnTypes[1] = NULL;
251: [NXApp registerServicesMenuSendTypes:validSendTypes andReturnTypes:validReturnTypes];
252: }
253: }
254:
255: - initFrame:(const NXRect *)frameRect
256: /*
257: * Initializes the view's instance variables.
258: * This view is considered important enough to allocate it a gstate.
259: */
260: {
261: [super initFrame:frameRect];
262: glist = [[List allocFromZone:[self zone]] init];
263: slist = [[List allocFromZone:[self zone]] init];
264: cacheWindow = createCache(&bounds.size, [self zone]);
265: gvFlags.grid = 10;
266: gvFlags.gridDisabled = 1;
267: [self allocateGState];
268: gridGray = DEFAULT_GRID_GRAY;
269: PSInit();
270: currentGraphic = [Rectangle class]; /* default graphic */
271: currentGraphic = [self currentGraphic]; /* trick to allow NXApp to control currentGraphic */
272: editView = createEditView(self);
273: initClassVars();
274: [self registerForDragging];
275: return self;
276: }
277:
278: /* Free method */
279:
280: - free
281: {
282: if (gupCoords) {
283: NX_FREE(gupCoords);
284: NX_FREE(gupOps);
285: NX_FREE(gupBBox);
286: }
287: [glist freeObjects];
288: [glist free];
289: [slist free];
290: [cacheWindow free];
291: if (![editView superview]) [editView free];
292: return [super free];
293: }
294:
295: /* Used by Change's */
296:
297: - setGroupInSlist:(BOOL)setting
298: {
299: gvFlags.groupInSlist = setting;
300: return self;
301: }
302:
303: - resetGroupInSlist
304: {
305: gvFlags.groupInSlist = checkForGroup(slist);
306: return self;
307: }
308:
309: - resetLockedFlag
310: {
311: int i, count;
312:
313: gvFlags.locked = NO;
314: count = [glist count];
315: for (i = 0; (i < count) && (!gvFlags.locked); i++)
316: if ([[glist objectAt:i] isLocked])
317: gvFlags.locked = YES;
318:
319: return self;
320: }
321:
322: - redrawGraphics:graphicsList afterChangeAgent:changeAgent performs:(SEL)aSelector
323: {
324: NXRect afterBounds, beforeBounds;
325:
326: if ([graphicsList count]) {
327: [self getBBox:&beforeBounds of:graphicsList];
328: [changeAgent perform:aSelector];
329: [self getBBox:&afterBounds of:graphicsList];
330: NXUnionRect(&beforeBounds, &afterBounds);
331: [self cache:&afterBounds]; // recache after change object did something
332: } else {
333: [changeAgent perform:aSelector];
334: }
335:
336: return self;
337: }
338:
339: /* Public interface methods. */
340:
341: - (BOOL)isEmpty
342: {
343: return [glist count] == 0;
344: }
345:
346: - (BOOL)hasEmptySelection
347: {
348: return [slist count] == 0;
349: }
350:
351: - dirty
352: {
353: if ([[window delegate] respondsTo:@selector(dirty:)]) [[window delegate] dirty:self];
354: return self;
355: }
356:
357: - getSelection
358: /*
359: * Resets slist by going through the glist and locating all the Graphics
360: * which respond YES to the isSelected method.
361: */
362: {
363: int i;
364: Graphic *graphic;
365:
366: [slist free];
367: slist = [[List allocFromZone:[self zone]] init];
368: gvFlags.groupInSlist = NO;
369: i = [glist count];
370: while (i--) {
371: graphic = [glist objectAt:i];
372: if ([graphic isSelected]) {
373: [slist insertObject:graphic at:0];
374: gvFlags.groupInSlist = gvFlags.groupInSlist || [graphic isKindOf:[Group class]];
375: }
376: }
377:
378: return self;
379: }
380:
381: - getBBox:(NXRect *)bbox of:(List *)list
382: {
383: return [self getBBox:bbox of:list extended:YES];
384: }
385:
386: - graphicsPerform:(SEL)aSelector
387: /*
388: * Performs the given aSelector on each member of the slist, then
389: * recaches and redraws the larger of the area covered by the objects before
390: * the selector was applied and the area covered by the objects after the
391: * selector was applied. If you want to perform a method on each item
392: * in the slist and NOT redraw, then use the List method makeObjectsPerform:.
393: */
394: {
395: int i, count;
396: Graphic *graphic;
397: NXRect eb, affectedBounds;
398:
399: count = [slist count];
400: if (count) {
401: [[slist objectAt:0] getExtendedBounds:&affectedBounds];
402: for (i = 1; i < count; i++) {
403: graphic = [slist objectAt:i];
404: NXUnionRect([graphic getExtendedBounds:&eb], &affectedBounds);
405: }
406: for (i = 0; i < count; i++) {
407: graphic = [slist objectAt:i];
408: [graphic perform:aSelector];
409: NXUnionRect([graphic getExtendedBounds:&eb], &affectedBounds);
410: }
411: [self cache:&affectedBounds]; // graphicsPerform:
412: }
413:
414: return self;
415: }
416:
417: - graphicsPerform:(SEL)aSelector with:(void *)argument
418: {
419: int i, count;
420: Graphic *graphic;
421: NXRect eb, affectedBounds;
422:
423: count = [slist count];
424: if (count) {
425: [[slist objectAt:0] getExtendedBounds:&affectedBounds];
426: for (i = 1; i < count; i++) {
427: graphic = [slist objectAt:i];
428: NXUnionRect([graphic getExtendedBounds:&eb], &affectedBounds);
429: }
430: for (i = 0; i < count; i++) {
431: graphic = [slist objectAt:i];
432: [graphic perform:aSelector with:argument];
433: NXUnionRect([graphic getExtendedBounds:&eb], &affectedBounds);
434: }
435: [self cache:&affectedBounds]; // graphicsPerform:with:
436: }
437:
438: return self;
439: }
440:
441: - cache:(const NXRect *)rect andUpdateLinks:(BOOL)updateLinks
442: /*
443: * Draws all the Graphics intersected by rect into the off-screen cache,
444: * then composites the rect back to the screen (but does NOT flushWindow).
445: * If updateLinks is on, then we check to see if the redrawn area intersects
446: * an area that someone has created a link to (see gvLinks.m).
447: */
448: {
449: gvFlags.cacheing = YES;
450: [self drawSelf:rect :1];
451: gvFlags.cacheing = NO;
452:
453: if ([self canDraw]) {
454: [self lockFocus];
455: [self drawSelf:rect :1];
456: [self unlockFocus];
457: }
458:
459: if (updateLinks && !gvFlags.suspendLinkUpdate) [self updateTrackedLinks:rect];
460:
461: return self;
462: }
463:
464: - cache:(const NXRect *)rect
465: {
466: return [self cache:rect andUpdateLinks:YES];
467: }
468:
469: - cacheAndFlush:(const NXRect *)rect
470: {
471: [self cache:rect]; // cacheAndFlush:
472: [window flushWindow];
473: return self;
474: }
475:
476: - insertGraphic:(Graphic *)graphic
477: /*
478: * Inserts the specified graphic into the glist and draws it.
479: * The new graphic will join the selection, not replace it.
480: */
481: {
482: NXRect eb;
483:
484: if (graphic) {
485: if ([graphic isSelected]) [slist insertObject:graphic at:0];
486: [glist insertObject:graphic at:0];
487: [graphic wasAddedTo:self];
488: [self cache:[graphic getExtendedBounds:&eb]]; // insertGraphic:
489: if ([graphic isKindOf:[Group class]]) gvFlags.groupInSlist = YES;
490: [window flushWindow];
491: }
492:
493: return self;
494: }
495:
496: - removeGraphic:(Graphic *)graphic
497: /*
498: * Removes the graphic from the GraphicView and redraws.
499: */
500: {
501: int i;
502: NXRect eb;
503: Graphic *g = nil;
504:
505: if (!graphic) return self;
506:
507: i = [glist count];
508: while (g != graphic && i--) g = [glist objectAt:i];
509: if (g == graphic) {
510: [g getExtendedBounds:&eb];
511: [glist removeObjectAt:i];
512: [graphic wasRemovedFrom:self];
513: [slist removeObject:g];
514: if ([g isKindOf:[Group class]]) gvFlags.groupInSlist = checkForGroup(slist);
515: [self cache:&eb]; // removeGraphic:
516: [window flushWindow];
517: }
518:
519: return self;
520: }
521:
522: - (Graphic *)selectedGraphic
523: /*
524: * If there is one and only one Graphic selected, this method returns it.
525: */
526: {
527: if ([slist count] == 1) {
528: Graphic *graphic = [slist objectAt:0];
529: return [graphic isKindOf:[Group class]] ? nil : graphic;
530: } else {
531: return nil;
532: }
533: }
534:
535: - (List *)selectedGraphics
536: /*
537: * Result valid only immediately after call. GraphicView
538: * reserves the right to free the list without warning.
539: */
540: {
541: return slist;
542: }
543:
544: - (List *)graphics
545: /*
546: * Result valid only immediately after call. GraphicView
547: * reserves the right to free the list without warning.
548: */
549: {
550: return glist;
551: }
552:
553: /* Methods to modify the grid of the GraphicView. */
554:
555: - (int)gridSpacing
556: {
557: return gvFlags.grid;
558: }
559:
560: - (BOOL)gridIsVisible
561: {
562: return gvFlags.showGrid;
563: }
564:
565: - (BOOL)gridIsEnabled
566: {
567: return !gvFlags.gridDisabled;
568: }
569:
570: - (float)gridGray
571: {
572: return gridGray;
573: }
574:
575: - setGridSpacing:(int)gridSpacing
576: {
577: id change;
578:
579: if (gridSpacing != gvFlags.grid && gridSpacing > 0 && gridSpacing < 256) {
580: change = [[GridChange alloc] initGraphicView:self];
581: [change startChange];
582: gvFlags.grid = gridSpacing;
583: if (gvFlags.showGrid) {
584: [self resetGUP];
585: [self cache:&bounds andUpdateLinks:NO];
586: [window flushWindow];
587: }
588: [change endChange];
589: }
590:
591: return self;
592: }
593:
594: - setGridEnabled:(BOOL)flag
595: {
596: id change;
597:
598: change = [[GridChange alloc] initGraphicView:self];
599: [change startChange];
600: gvFlags.gridDisabled = flag ? NO : YES;
601: [change endChange];
602:
603: return self;
604: }
605:
606: - setGridVisible:(BOOL)flag
607: {
608: id change;
609:
610: if (gvFlags.showGrid != flag) {
611: change = [[GridChange alloc] initGraphicView:self];
612: [change startChange];
613: gvFlags.showGrid = flag;
614: if (flag) [self resetGUP];
615: [self cache:&bounds andUpdateLinks:NO];
616: [window flushWindow];
617: [change endChange];
618: }
619:
620: return self;
621: }
622:
623: - setGridGray:(float)gray
624: {
625: id change;
626:
627: if (gray != gridGray) {
628: change = [[GridChange alloc] initGraphicView:self];
629: [change startChange];
630: gridGray = gray;
631: if (gvFlags.showGrid) {
632: [self cache:&bounds andUpdateLinks:NO];
633: [window flushWindow];
634: }
635: [change endChange];
636: }
637:
638: return self;
639: }
640:
641: - setGridSpacing:(int)gridSpacing andGray:(float)gray
642: {
643: id change;
644:
645: if (gray != gridGray || (gridSpacing != gvFlags.grid && gridSpacing > 0 && gridSpacing < 256)) {
646: change = [[GridChange alloc] initGraphicView:self];
647: [change startChange];
648: gridGray = gray;
649: if (gvFlags.grid != gridSpacing && gridSpacing > 0 && gridSpacing < 256) {
650: gvFlags.grid = gridSpacing;
651: if (gvFlags.showGrid) [self resetGUP];
652: }
653: if (gvFlags.showGrid) {
654: [self cache:&bounds andUpdateLinks:NO];
655: [window flushWindow];
656: }
657: [change endChange];
658: }
659:
660: return self;
661: }
662:
663: - grid:(NXPoint *)p
664: {
665: grid(*p);
666: return self;
667: }
668:
669: /* Public methods for importing foreign data types into the GraphicView */
670:
671: #define LOAD_IMAGE NXLocalString("Import Data", NULL, "Title of the alert which lets the user know that the image he has incorporated into his document does not fit.")
672: #define IMAGE_TOO_LARGE NXLocalString("The imported data is too large to fit on the page. Resize it to fit?", NULL, "Alert message which asks user if he wants to scale an image or some text that he is incorporating into his document to fit within the size of the document.")
673: #define SCALE NXLocalString("Resize", NULL, "Button choice which allows the user to scale an image he is incorporating into his document so that it will fit within the size of the document.")
674: #define DONT_SCALE NXLocalString("Don't Resize", NULL, "Button choice which allows the user to leave an image he is incorporating into his document the same size even though it will not fit within the size of his document.")
675:
676: - placeGraphic:(Graphic *)graphic at:(const NXPoint *)location
677: /*
678: * Places the graphic centered at the given location on the page.
679: * If the graphic is too big, the user is asked whether the graphic
680: * should be scaled.
681: */
682: {
683: int scale;
684: NXPoint offset;
685: float sx, sy, factor;
686: NXRect gbounds, myBounds, visibleRect;
687: id change;
688:
689: if (graphic) {
690: [graphic getExtendedBounds:&gbounds];
691: if (gbounds.size.width > bounds.size.width || gbounds.size.height > bounds.size.height) {
692: scale = NXRunAlertPanel(LOAD_IMAGE, IMAGE_TOO_LARGE, SCALE, DONT_SCALE, CANCEL);
693: if (scale < 0) {
694: [graphic free];
695: return nil;
696: } else if (scale > 0) {
697: sx = (bounds.size.width / gbounds.size.width) * 0.95;
698: sy = (bounds.size.height / gbounds.size.height) * 0.95;
699: factor = MIN(sx, sy);
700: gbounds.size.width *= factor;
701: gbounds.size.height *= factor;
702: [graphic sizeTo:&gbounds.size];
703: }
704: }
705: if (location) {
706: [graphic centerAt:location];
707: } else {
708: [self getVisibleRect:&visibleRect];
709: visibleRect.origin.x += floor(visibleRect.size.width / 2.0 + 0.5);
710: visibleRect.origin.y += floor(visibleRect.size.height / 2.0 + 0.5);
711: [graphic centerAt:&visibleRect.origin];
712: }
713: [graphic getExtendedBounds:&gbounds];
714: myBounds = bounds;
715: NXContainRect(&myBounds, &gbounds);
716: offset.x = bounds.origin.x - myBounds.origin.x;
717: offset.y = bounds.origin.y - myBounds.origin.y;
718: if (offset.x || offset.y) [graphic moveBy:&offset];
719:
720: change = [[CreateGraphicsChange alloc] initGraphicView:self graphic:graphic];
721: [change startChangeIn:self];
722: [self deselectAll:self];
723: [graphic select];
724: [self insertGraphic:graphic];
725: [self scrollGraphicToVisible:graphic];
726: [change endChange];
727: }
728:
729: return graphic;
730: }
731:
732: /* Methods overridden from superclass. */
733:
734: - sizeTo:(NXCoord)width :(NXCoord)height
735: /*
736: * Overrides View's sizeTo:: so that the cacheWindow is resized when
737: * the View is resized.
738: */
739: {
740: if (width != bounds.size.width || height != bounds.size.height) {
741: [super sizeTo:width :height];
742: [cacheWindow free];
743: cacheWindow = createCache(&bounds.size, [self zone]);
744: [self resetGUP];
745: [self cache:&bounds andUpdateLinks:NO];
746: }
747: return self;
748: }
749:
750: - mouseDown:(NXEvent *)event
751: /*
752: * This method handles a mouse down.
753: *
754: * If a current tool is in effect, then the mouse down causes a new
755: * Graphic to begin being created. Otherwise, the selection is modified
756: * either by adding elements to it or removing elements from it, or moving
757: * it. Here are the rules:
758: *
759: * Tool in effect
760: * Shift OFF
761: * create a new Graphic which becomes the new selection
762: * Shift ON
763: * create a new Graphic and ADD it to the current selection
764: * Control ON
765: * leave creation mode, and start selection
766: * Otherwise
767: * Shift OFF
768: * a. Click on a selected Graphic -> select graphic further back
769: * b. Click on an unselected Graphic -> that Graphic becomes selection
770: * Shift ON
771: * a. Click on a selected Graphic -> remove it from selection
772: * b. Click on unselected Graphic -> add it to selection
773: * Alternate ON
774: * if no affected graphic, causes drag select to select only objects
775: * completely contained within the dragged box.
776: *
777: * Essentially, everything works as one might expect except the ability to
778: * select a Graphic which is deeper in the list (i.e. further toward the
779: * back) by clicking on the currently selected Graphic.
780: *
781: * This is a very hairy mouseDown:. Most need not be this scary.
782: */
783: {
784: NXPoint p;
785: NXRect eb;
786: int i, corner, oldMask;
787: id change, factory;
788: Graphic *g = nil, *startg = nil;
789: BOOL shift, control, gotHit = NO, deepHit = NO, didDrag = NO;
790:
791: /*
792: * You only need to do the following line in a mouseDown: method if
793: * you receive this message because one of your subviews gets the
794: * mouseDown: and does not respond to it (thus, it gets passed up the
795: * responder chain to you). In this case, our editView receives the
796: * mouseDown:, but doesn't do anything about it, and when it comes
797: * to us, we want to become the first responder.
798: *
799: * Normally you won't have a subview which doesn't do anything with
800: * mouseDown:, in which case, you need only return YES from the
801: * method acceptsFirstResponder (see that method below) and will NOT
802: * need to do the following makeFirstResponder:. In other words,
803: * don't put the following line in your mouseDown: implementation!
804: *
805: * Sorry about confusing this issue ...
806: */
807:
808: if ([window firstResponder] != self) {
809: if (event->data.mouse.click < 2) {
810: [window makeFirstResponder:self];
811: } else {
812: return [[window firstResponder] mouseDown:event];
813: }
814: }
815:
816: shift = (event->flags & NX_SHIFTMASK) ? YES : NO;
817: control = (event->flags & NX_CONTROLMASK) ? YES : NO;
818:
819: p = event->location;
820: [self convertPoint:&p fromView:nil];
821:
822: oldMask = [window addToEventMask:NX_MOUSEDRAGGEDMASK|NX_MOUSEUPMASK];
823:
824: i = 0; // See if a Graphic wants to handle this event itself
825: [self lockFocus];
826: do {
827: g = [glist objectAt:i++];
828: if ([g handleEvent:event at:&p inView:self]) return self;
829: } while (g != nil);
830: [self unlockFocus];
831:
832: factory = [self currentGraphic];
833: if (!control && (factory || (event->data.mouse.click == 2))) {
834: id editFactory = factory;
835: if ((event->data.mouse.click == 2) && ![editFactory isEditable]) editFactory = [TextGraphic class];
836: if ([editFactory isEditable]) { /* if editable, try to edit one */
837: i = 0;
838: g = [glist objectAt:i++];
839: while (g != nil) {
840: if ([g isKindOf:editFactory] && [g hit:&p]) {
841: if ([g isSelected]) {
842: [g deselect];
843: [self cache:[g getExtendedBounds:&eb] andUpdateLinks:NO];
844: [slist removeObject:g];
845: }
846: [g edit:event in:editView];
847: goto done;
848: }
849: g = [glist objectAt:i++];
850: }
851: }
852: }
853: if (!control && factory) {
854: if (factory && !g) { /* not editing or no editable graphic found */
855: g = [[factory allocFromZone:[self zone]] init];
856: if ([NXApp respondsTo:@selector(inspectorPanel)]) {
857: [[[NXApp inspectorPanel] delegate] initializeGraphic:g];
858: }
859: if ([g create:event in:self]) {
860: change = [[CreateGraphicsChange alloc] initGraphicView:self graphic:g];
861: [change startChange];
862: if (!shift) [self deselectAll:self];
863: [self insertGraphic:g];
864: [g edit:NULL in:editView];
865: [change endChange];
866: } else {
867: [g free];
868: }
869: }
870: } else { /* selecting/resizing/moving */
871: i = 0;
872: g = [glist objectAt:i++];
873: while (g != nil && !gotHit) {
874: corner = [g knobHit:&p];
875: if (corner > 0) { /* corner hit */
876: gotHit = YES;
877: change = [[ResizeGraphicsChange alloc] initGraphicView:self graphic:g];
878: [change startChange];
879: [g resize:event by:corner in:self];
880: [change endChange];
881: } else if (corner) { /* complete miss */
882: g = [glist objectAt:i++];
883: } else g = nil; /* non-corner opaque hit */
884: }
885: i = 0;
886: if (!gotHit) g = [glist objectAt:i++];
887: while (g && !gotHit && !deepHit) {
888: if ([g isSelected] && [g hit:&p]) {
889: if (shift) {
890: gotHit = YES;
891: [g deselect];
892: [self cache:[g getExtendedBounds:&eb] andUpdateLinks:NO];
893: [slist removeObject:g];
894: if ([g isKindOf:[Group class]]) gvFlags.groupInSlist = checkForGroup(slist);
895: } else {
896: gotHit = [self move:event];
897: if (!gotHit) {
898: deepHit = ![g isOpaque];
899: if (!deepHit) gotHit = YES;
900: }
901: }
902: }
903: g = [glist objectAt:i++];
904: }
905: startg = g;
906: if (!gotHit) do {
907: if (!g) {
908: i = 0;
909: g = [glist objectAt:i++];
910: }
911: if (![g isSelected] && [g hit:&p]) {
912: gotHit = YES;
913: if (!shift) {
914: [self deselectAll:self];
915: [slist addObject:g];
916: gvFlags.groupInSlist = [g isKindOf:[Group class]];
917: }
918: [g select];
919: if (shift) [self getSelection];
920: if (deepHit || ![self move:event]) {
921: [self cache:[g getExtendedBounds:&eb] andUpdateLinks:NO];
922: }
923: } else {
924: g = [glist objectAt:i++];
925: }
926: } while (!gotHit && g != startg);
927:
928: if (!gotHit && !deepHit) {
929: if (!shift) {
930: [self lockFocus];
931: [self deselectAll:self];
932: [self unlockFocus];
933: didDrag = YES;
934: }
935: [self dragSelect:event];
936: }
937: }
938: done:
939: if (!didDrag && dragRect) {
940: NX_FREE(dragRect);
941: dragRect = NULL;
942: }
943: gvFlags.selectAll = NO;
944:
945: [window flushWindow];
946: [window setEventMask:oldMask];
947:
948: return self;
949: }
950:
951: - drawSelf:(const NXRect *)rects :(int)rectCount
952: /*
953: * Draws the GraphicView.
954: *
955: * If cacheing is on or if NXDrawingStatus != NX_DRAWING, then all the
956: * graphics which intersect the specified rectangles will be drawn (and
957: * clipped to those rectangles). Otherwise, the specified rectangles
958: * are composited to the screen from the off-screen cache. The invalidRect
959: * stuff is to clean up any temporary drawing we have in the view
960: * (currently used only to show a source selection in the links mechanism--
961: * see showSelection: in gvLinks.m).
962: */
963: {
964: NXRect *rp;
965: NXRect r, visibleRect;
966: int i, j, gstate;
967:
968: if (rects == NULL) return self;
969:
970: if (!gvFlags.cacheing && invalidRect && (rects != invalidRect)) {
971: NXUnionRect(rects, invalidRect);
972: [self drawSelf:invalidRect :1];
973: [window flushWindow];
974: NX_FREE(invalidRect);
975: invalidRect = NULL;
976: return self;
977: }
978:
979: if (gvFlags.cacheing || NXDrawingStatus != NX_DRAWING) {
980: if (NXDrawingStatus == NX_DRAWING) {
981: [[cacheWindow contentView] lockFocus];
982: NXRectClip(rects);
983: PSsetgray(NX_WHITE);
984: for (j = (rectCount > 1) ? 1 : 0; j < rectCount; j++) {
985: NXRectFill(&rects[j]);
986: if (gvFlags.showGrid && gvFlags.grid >= 4) {
987: PSsetlinewidth(0.0);
988: PSsetgray(gridGray);
989: DPSDoUserPath(gupCoords, gupLength, dps_short,
990: gupOps, gupLength >> 1, gupBBox, dps_ustroke);
991: PSsetgray(NX_WHITE);
992: }
993: }
994: }
995: for (j = (rectCount > 1) ? 1 : 0; j < rectCount; j++) {
996: i = [glist count];
997: while (i--) [[glist objectAt:i] draw:&rects[j]];
998: }
999: [Graphic showFastKnobFills];
1000: if (NXDrawingStatus == NX_DRAWING) {
1001: [[cacheWindow contentView] unlockFocus];
1002: }
1003: }
1004:
1005: if (!gvFlags.cacheing && NXDrawingStatus == NX_DRAWING) {
1006: gstate = [cacheWindow gState];
1007: [self getVisibleRect:&visibleRect];
1008: for (j = 0; j < rectCount; j++) {
1009: rp = &r;
1010: r = rects[j];
1011: if (!NXEqualRect(rp, &visibleRect)) rp = NXIntersectionRect(&visibleRect, rp);
1012: if (rp) {
1013: PScomposite(NX_X(rp), NX_Y(rp), NX_WIDTH(rp), NX_HEIGHT(rp), gstate,
1014: NX_X(rp), NX_Y(rp), NX_COPY);
1015: }
1016: }
1017: }
1018:
1019: return self;
1020: }
1021:
1022: - keyDown:(NXEvent *)event
1023: /*
1024: * Handles one of the arrow keys being pressed.
1025: * Note that since it might take a while to actually move the selection
1026: * (if it is large), we check to see if a bunch of arrow key events have
1027: * stacked up and move them all at once.
1028: */
1029: {
1030: NXPoint p;
1031: NXEvent e;
1032: NXCoord delta;
1033: BOOL gotOne, first;
1034: NXEvent* eptr = event;
1035:
1036: if ((event->data.key.charSet != NX_ASCIISET ||
1037: event->data.key.charCode != 127) &&
1038: (event->data.key.charSet != NX_SYMBOLSET ||
1039: (event->data.key.charCode != LEFTARROW &&
1040: event->data.key.charCode != RIGHTARROW &&
1041: event->data.key.charCode != DOWNARROW &&
1042: event->data.key.charCode != UPARROW))) {
1043: return [super keyDown:event];
1044: }
1045:
1046: if (event->data.key.charSet == NX_ASCIISET) return [self delete:self];
1047:
1048: p.x = p.y = 0.0;
1049: delta = KeyMotionDeltaDefault;
1050: delta = floor(delta / GRID) * GRID;
1051: delta = MAX(delta, GRID);
1052:
1053: first = YES;
1054: do {
1055: gotOne = NO;
1056: if (eptr->data.key.charSet == NX_SYMBOLSET) {
1057: switch (eptr->data.key.charCode) {
1058: case LEFTARROW:
1059: p.x -= delta;
1060: gotOne = YES;
1061: break;
1062: case RIGHTARROW:
1063: p.x += delta;
1064: gotOne = YES;
1065: break;
1066: case UPARROW:
1067: p.y += delta;
1068: gotOne = YES;
1069: break;
1070: case DOWNARROW:
1071: p.y -= delta;
1072: gotOne = YES;
1073: break;
1074: default:
1075: break;
1076: }
1077: }
1078: if (eptr && gotOne && !first) [NXApp getNextEvent:NX_KEYDOWNMASK];
1079: first = NO;
1080: } while (gotOne && (eptr = [NXApp peekNextEvent:NX_KEYDOWNMASK into:&e]));
1081:
1082: if (p.x || p.y) {
1083: [self moveGraphicsBy:&p andDraw:YES];
1084: [[self window] flushWindow];
1085: NXPing();
1086: }
1087:
1088: return self;
1089: }
1090:
1091: /* Accepting becoming the First Responder */
1092:
1093: - (BOOL)acceptsFirstResponder
1094: /*
1095: * GraphicView always wants to become the first responder when it is
1096: * clicked on in a window, so it returns YES from this method.
1097: */
1098: {
1099: return YES;
1100: }
1101:
1102: - (BOOL)acceptsFirstMouse
1103: /*
1104: * GraphicView really only wants to accept first mouse if that
1105: * first mouse starts a drag, but there is no way to detect that
1106: * case, so we're stuck accepting all first mouse events!
1107: */
1108: {
1109: return YES;
1110: }
1111:
1112: /* Printing */
1113:
1114: - beginSetup
1115: /*
1116: * Spit out the custom PostScript defs.
1117: */
1118: {
1119: [super beginSetup];
1120: PSInit();
1121: return self;
1122: }
1123:
1124: /*
1125: * These two method set and get the factory object used to create new
1126: * Graphic objects (i.e. the subclass of Graphic to use).
1127: * They are kind of weird since they check to see if the
1128: * Application object knows what the current graphic is. If it does, then
1129: * it lets it handle these methods. Otherwise, it determines the
1130: * current graphic by querying the sender to find out what its title is
1131: * and converts that title to the name to a factory object. This allows
1132: * the GraphicView to stand on its own, but also use an application wide
1133: * tool palette if available.
1134: * If the GraphicView handles the current graphic by itself, it does so
1135: * by querying the sender of setCurrentGraphic: to find out its title.
1136: * It assumes, then, that that title is the name of the factory object to
1137: * use and calls objc_getClass() to get a pointer to it.
1138: * If the application is not control what our current graphic is, then
1139: * we restrict creations to be made only when the control key is down.
1140: * Otherwise, it is the other way around (control key leaves creation
1141: * mode). This is due to the fact that the application can be smart
1142: * enough to set appropriate cursors when a tool is on. The GraphicView
1143: * can't be.
1144: */
1145:
1146: - (Graphic *)currentGraphic
1147: {
1148: if ([NXApp respondsTo:@selector(currentGraphic)]) {
1149: return [NXApp currentGraphic];
1150: } else {
1151: return currentGraphic;
1152: }
1153: }
1154:
1155: - setCurrentGraphic:sender
1156: {
1157: currentGraphic = objc_getClass([[sender selectedCell] title]);
1158: return self;
1159: }
1160:
1161: /* These methods write out the form information. */
1162:
1163: - (BOOL)hasFormEntries
1164: {
1165: int i;
1166: for (i = [glist count]-1; i >= 0; i--) {
1167: if ([[glist objectAt:i] isFormEntry]) return YES;
1168: }
1169: return NO;
1170: }
1171:
1172: - writeFormEntriesToFile:(const char *)file
1173: {
1174: int i;
1175: NXStream *stream;
1176:
1177: stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
1178: NXPrintf(stream, "Page Size: w = %d, h = %d\n", (int)bounds.size.width, (int)bounds.size.height);
1179: for (i = [glist count]-1; i >= 0; i--) {
1180: [[glist objectAt:i] writeFormEntryToStream:stream];
1181: }
1182: NXSaveToFile(stream, file);
1183: NXCloseMemory(stream, NX_FREEBUFFER);
1184:
1185: return self;
1186: }
1187:
1188: /*
1189: * Target/Action methods.
1190: */
1191:
1192: - delete:sender
1193: {
1194: int i;
1195: Graphic *graphic;
1196: id change;
1197:
1198: i = [slist count];
1199: if (i > 0) {
1200: change = [[DeleteGraphicsChange alloc] initGraphicView:self];
1201: [change startChange];
1202: [self graphicsPerform:@selector(deactivate)];
1203: [slist makeObjectsPerform:@selector(activate)];
1204: while (i--) {
1205: graphic = [slist objectAt:i];
1206: [glist removeObject:graphic];
1207: [graphic wasRemovedFrom:self];
1208: }
1209: if (originalPaste == [slist objectAt:0]) [slist removeObjectAt:0];
1210: [slist free];
1211: slist = [[List allocFromZone:[self zone]] init];
1212: gvFlags.groupInSlist = NO;
1213: [window flushWindow];
1214: [change endChange];
1215: return self;
1216: } else {
1217: return nil;
1218: }
1219: }
1220:
1221: - selectAll:sender
1222: /*
1223: * Selects all the items in the glist.
1224: */
1225: {
1226: int i;
1227: Graphic *g;
1228: NXRect visibleRect;
1229:
1230: i = [glist count];
1231: if (!i) return self;
1232:
1233: [slist free];
1234: slist = [[List allocFromZone:[self zone]] init];
1235: [[cacheWindow contentView] lockFocus];
1236: while (i--) {
1237: g = [glist objectAt:i];
1238: if (![g isLocked]) {
1239: [g select];
1240: [g draw:NULL];
1241: [slist insertObject:g at:0];
1242: gvFlags.groupInSlist = gvFlags.groupInSlist || [g isKindOf:[Group class]];
1243: }
1244: }
1245: [Graphic showFastKnobFills];
1246: [[cacheWindow contentView] unlockFocus];
1247: [self getVisibleRect:&visibleRect];
1248: if ([self canDraw]) {
1249: [self lockFocus];
1250: [self drawSelf:&visibleRect :1];
1251: [self unlockFocus];
1252: }
1253: if (sender != self) [window flushWindow];
1254: gvFlags.selectAll = YES;
1255:
1256: return self;
1257: }
1258:
1259: - deselectAll:sender
1260: /*
1261: * Deselects all the items in the slist.
1262: */
1263: {
1264: NXRect sbounds;
1265:
1266: if ([slist count] > 0) {
1267: [self getBBox:&sbounds of:slist];
1268: [slist makeObjectsPerform:@selector(deselect)];
1269: [self cache:&sbounds andUpdateLinks:NO];
1270: [slist free];
1271: slist = [[List allocFromZone:[self zone]] init];
1272: gvFlags.groupInSlist = NO;
1273: if (sender != self) [window flushWindow];
1274: }
1275:
1276: return self;
1277: }
1278:
1279: - lock:sender
1280: /*
1281: * Locks all the items in the selection so that they can't be selected
1282: * or resized or moved. Useful if there are some Graphics which are getting
1283: * in your way. Undo this with unlock:.
1284: */
1285: {
1286: id change;
1287:
1288: if ([slist count] > 0) {
1289: change = [[LockGraphicsChange alloc] initGraphicView:self];
1290: [change startChange];
1291: gvFlags.locked = YES;
1292: [slist makeObjectsPerform:@selector(lock)];
1293: [self deselectAll:sender];
1294: [change endChange];
1295: }
1296:
1297: return self;
1298: }
1299:
1300: - unlock:sender
1301: {
1302: id change;
1303:
1304: change = [[UnlockGraphicsChange alloc] initGraphicView:self];
1305: [change startChange];
1306: [glist makeObjectsPerform:@selector(unlock)];
1307: gvFlags.locked = NO;
1308: [change endChange];
1309:
1310: return self;
1311: }
1312:
1313: - bringToFront:sender
1314: /*
1315: * Brings each of the items in the slist to the front of the glist.
1316: * The item in the front of the slist will be the new front element
1317: * in the glist.
1318: */
1319: {
1320: id change;
1321: int i;
1322:
1323: i = [slist count];
1324: if (i) {
1325: change = [[BringToFrontGraphicsChange alloc] initGraphicView:self];
1326: [change startChange];
1327: while (i--) [glist insertObject:[glist removeObject:[slist objectAt:i]] at:0];
1328: [self recacheSelection];
1329: [change endChange];
1330: }
1331:
1332: return self;
1333: }
1334:
1335: - sendToBack:sender
1336: {
1337: int i, count;
1338: id change;
1339:
1340: count = [slist count];
1341: if (count > 0) {
1342: change = [[SendToBackGraphicsChange alloc] initGraphicView:self];
1343: [change startChange];
1344: for (i = 0; i < count; i++) [glist addObject:[glist removeObject:[slist objectAt:i]]];
1345: [self recacheSelection];
1346: [change endChange];
1347: }
1348:
1349: return self;
1350: }
1351:
1352: - group:sender
1353: /*
1354: * Creates a new Group object with the current slist as its member list.
1355: * See the Group class for more info.
1356: */
1357: {
1358: int i;
1359: NXRect eb;
1360: Graphic *graphic;
1361: id change;
1362:
1363: i = [slist count];
1364: if (i > 1) {
1365: change = [[GroupGraphicsChange alloc] initGraphicView:self];
1366: [change startChange];
1367: while (i--) [glist removeObject:[slist objectAt:i]];
1368: graphic = [[Group allocFromZone:[self zone]] initList:slist];
1369: [change noteGroup:graphic];
1370: [glist insertObject:graphic at:0];
1371: slist = [[List allocFromZone:[self zone]] init];
1372: [slist addObject:graphic];
1373: gvFlags.groupInSlist = YES;
1374: [self cache:[graphic getExtendedBounds:&eb]];
1375: if (sender != self) [window flushWindow];
1376: [change endChange];
1377: }
1378:
1379: return self;
1380: }
1381:
1382:
1383: - ungroup:sender
1384: /*
1385: * Goes through the slist and ungroups any Group objects in it.
1386: * Does not descend any further than that (i.e. all the Group objects
1387: * in the slist are ungrouped, but any Group objects in those ungrouped
1388: * objects are NOT ungrouped).
1389: */
1390: {
1391: int i, k;
1392: NXRect sbounds;
1393: id graphic;
1394: id change;
1395:
1396: if (!gvFlags.groupInSlist || [slist count] == 0)
1397: return nil;
1398:
1399: change = [[UngroupGraphicsChange alloc] initGraphicView:self];
1400: [change startChange];
1401: [self getBBox:&sbounds of:slist];
1402: i = [slist count];
1403: while (i--) {
1404: graphic = [slist objectAt:i];
1405: if ([graphic isKindOf:[Group class]]) {
1406: k = [glist indexOf:graphic];
1407: [glist removeObjectAt:k];
1408: [graphic transferSubGraphicsTo:glist at:k];
1409: }
1410: }
1411: [self cache:&sbounds];
1412: if (sender != self) [window flushWindow];
1413: [self getSelection];
1414: [change endChange];
1415:
1416: return self;
1417: }
1418:
1419: - align:sender
1420: {
1421: [self alignBy:(AlignmentType)[[sender selectedCell] tag]];
1422: return self;
1423: }
1424:
1425: - changeAspectRatio:sender
1426: {
1427: id change;
1428:
1429: change = [[AspectRatioGraphicsChange alloc] initGraphicView:self];
1430: [change startChange];
1431: [self graphicsPerform:@selector(sizeToNaturalAspectRatio)];
1432: [change endChange];
1433:
1434: [window flushWindow];
1435: return self;
1436: }
1437:
1438: - alignToGrid:sender
1439: {
1440: id change;
1441:
1442: change = [[AlignGraphicsChange alloc] initGraphicView:self];
1443: [change startChange];
1444: [self graphicsPerform:@selector(alignToGrid:) with:self];
1445: [window flushWindow];
1446: [change endChange];
1447:
1448: return self;
1449: }
1450:
1451: - sizeToGrid:sender
1452: {
1453: id change;
1454:
1455: change = [[DimensionsGraphicsChange alloc] initGraphicView:self];
1456: [change startChange];
1457: [self graphicsPerform:@selector(sizeToGrid:) with:self];
1458: [window flushWindow];
1459: [change endChange];
1460:
1461: return self;
1462: }
1463:
1464: - enableGrid:sender
1465: /*
1466: * If the tag of the sender is non-zero, then gridding is enabled.
1467: * If the tag is zero, then gridding is disabled.
1468: */
1469: {
1470: [self setGridEnabled:[sender selectedTag] ? YES : NO];
1471: return self;
1472: }
1473:
1474: - hideGrid:sender
1475: /*
1476: * If the tag of the sender is non-zero, then the grid is made visible
1477: * otherwise, it is hidden (but still conceivable in effect).
1478: */
1479: {
1480: [self setGridVisible:[sender selectedTag] ? YES : NO];
1481: return self;
1482: }
1483:
1484: - showLinks:sender
1485: /*
1486: * If the tag of the sender is non-zero, then linked items are
1487: * shown with a border around them (see redrawLinkOutlines: in gvLinks.m).
1488: */
1489: {
1490: [linkManager setLinkOutlinesVisible:[sender selectedTag] ? YES : NO];
1491: return self;
1492: }
1493:
1494: - spellCheck:sender
1495: {
1496: int i;
1497: NXCoord curY, newY, maxY = 0.0;
1498: NXCoord curX, newX, maxX = 0.0;
1499: NXRect egbounds, gbounds;
1500: id fr = [window firstResponder];
1501: Graphic *graphic, *editingGraphic, *newEditingGraphic = nil;
1502:
1503: if (![fr isKindOf:[Text class]] || ![[NXSpellChecker sharedInstance] checkSpelling:NX_CheckSpellingToEnd of:fr]) {
1504: if ([fr isKindOf:[Text class]]) {
1505: editingGraphic = [fr delegate];
1506: [editingGraphic getBounds:&egbounds];
1507: curY = egbounds.origin.y + egbounds.size.height;
1508: curX = egbounds.origin.x;
1509: } else {
1510: curX = 0.0;
1511: curY = 10000.0;
1512: }
1513: maxY = 0.0; maxX = 10000.0;
1514: for (i = [glist count]-1; i >= 0; i--) {
1515: graphic = [glist objectAt:i];
1516: if ([graphic isKindOf:[TextGraphic class]]) {
1517: [graphic getBounds:&gbounds];
1518: newY = gbounds.origin.y + gbounds.size.height;
1519: newX = gbounds.origin.x;
1520: if ((newY > maxY || (newY == maxY && newX < maxX)) && (newY < curY || (newY == curY && newX > curX))) {
1521: maxY = newY;
1522: maxX = newX;
1523: newEditingGraphic = graphic;
1524: }
1525: }
1526: }
1527: [window makeFirstResponder:self];
1528: if (newEditingGraphic) {
1529: [newEditingGraphic edit:NULL in:editView];
1530: return [self spellCheck:sender];
1531: }
1532: }
1533:
1534: return self;
1535: }
1536:
1537: - showGuessPanel:sender
1538: {
1539: return [[[NXSpellChecker sharedInstance] spellingPanel] makeKeyAndOrderFront:sender];
1540: }
1541:
1542: /* Cover-Sheet items (see TextGraphic.m). */
1543:
1544: - doAddCoverSheetEntry:sender localizable:(BOOL)flag
1545: {
1546: const char *entry = [[sender selectedCell] altTitle];
1547: if (!entry || !*entry) entry = [[sender selectedCell] title];
1548: [self placeGraphic:[[TextGraphic allocFromZone:[self zone]] initFormEntry:entry localizable:flag] at:NULL];
1549: return self;
1550: }
1551:
1552: - addLocalizableCoverSheetEntry:sender
1553: {
1554: return [self doAddCoverSheetEntry:sender localizable:YES];
1555: }
1556:
1557: - addCoverSheetEntry:sender
1558: {
1559: return [self doAddCoverSheetEntry:sender localizable:NO];
1560: }
1561:
1562: /* The venerable print: method. */
1563:
1564: - print:sender
1565: {
1566: return [self printPSCode:sender];
1567: }
1568:
1569: /*
1570: * Target/Action methods to change Graphic parameters from a Control.
1571: * If the sender is a Matrix, then the selectedRow is used to determine
1572: * the value to use (for linecap, linearrow, etc.) otherwise, the
1573: * sender's floatValue or intValue is used (whichever is appropriate).
1574: * This allows interface builders the flexibility to design different
1575: * ways of setting those values.
1576: */
1577:
1578: - takeGridValueFrom:sender
1579: {
1580: [self setGridSpacing:[sender intValue]];
1581: return self;
1582: }
1583:
1584: - takeGridGrayFrom:sender
1585: {
1586: [self setGridGray:[sender floatValue]];
1587: return self;
1588: }
1589:
1590: - takeGrayValueFrom:sender
1591: {
1592: float value;
1593:
1594: value = [sender floatValue];
1595: [self graphicsPerform:@selector(setGray:) with:&value];
1596: [window flushWindow];
1597:
1598: return self;
1599: }
1600:
1601: - takeLineWidthFrom:sender
1602: {
1603: id change;
1604: float width = [sender floatValue];
1605:
1606: change = [[LineWidthGraphicsChange alloc] initGraphicView:self lineWidth:width];
1607: [change startChange];
1608: [self graphicsPerform:@selector(setLineWidth:) with:&width];
1609: [window flushWindow];
1610: [change endChange];
1611:
1612: return self;
1613: }
1614:
1615: - takeLineJoinFrom:sender
1616: {
1617: int joinValue;
1618: id change;
1619:
1620: if ([sender respondsTo:@selector(selectedRow)])
1621: joinValue = [sender selectedRow];
1622: else
1623: joinValue = [sender intValue];
1624:
1625: change = [[LineJoinGraphicsChange alloc] initGraphicView:self lineJoin:joinValue];
1626: [change startChange];
1627: [self graphicsPerform:@selector(setLineJoin:) with:(void *)joinValue];
1628: [window flushWindow];
1629: [change endChange];
1630:
1631: return self;
1632: }
1633:
1634: - takeLineCapFrom:sender
1635: {
1636: int capValue;
1637: id change;
1638:
1639: if ([sender respondsTo:@selector(selectedRow)])
1640: capValue = [sender selectedRow];
1641: else
1642: capValue = [sender intValue];
1643:
1644: change = [[LineCapGraphicsChange alloc] initGraphicView:self lineCap:capValue];
1645: [change startChange];
1646: [self graphicsPerform:@selector(setLineCap:) with:(void *)capValue];
1647: [window flushWindow];
1648: [change endChange];
1649:
1650: return self;
1651: }
1652:
1653: - takeLineArrowFrom:sender
1654: {
1655: int arrowValue;
1656: id change;
1657:
1658: if ([sender respondsTo:@selector(selectedRow)])
1659: arrowValue = [sender selectedRow];
1660: else
1661: arrowValue = [sender intValue];
1662:
1663: change = [[ArrowGraphicsChange alloc] initGraphicView:self lineArrow:arrowValue];
1664: [change startChange];
1665: [self graphicsPerform:@selector(setLineArrow:) with:(void *)arrowValue];
1666: [window flushWindow];
1667: [change endChange];
1668:
1669: return self;
1670: }
1671:
1672: - takeFillValueFrom:sender
1673: {
1674: int fillValue;
1675: id change;
1676:
1677: if ([sender respondsTo:@selector(selectedRow)])
1678: fillValue = [sender selectedRow];
1679: else
1680: fillValue = [sender intValue];
1681:
1682: change = [[FillGraphicsChange alloc] initGraphicView:self fill:fillValue];
1683: [change startChange];
1684: [self graphicsPerform:@selector(setFill:) with:(void *)fillValue];
1685: [window flushWindow];
1686: [change endChange];
1687:
1688: return self;
1689: }
1690:
1691: - takeFrameValueFrom:sender
1692: {
1693: if ([sender respondsTo:@selector(selectedRow)]) {
1694: [self graphicsPerform:@selector(setFramed:) with:(void *)[sender selectedRow]];
1695: } else {
1696: [self graphicsPerform:@selector(setFramed:) with:(void *)[sender intValue]];
1697: }
1698: [window flushWindow];
1699: return self;
1700: }
1701:
1702: - takeLineColorFrom:sender
1703: {
1704: id change;
1705: NXColor color = [sender color];
1706:
1707: change = [[LineColorGraphicsChange alloc] initGraphicView:self color:&color];
1708: [change startChange];
1709: [self graphicsPerform:@selector(setLineColor:) with:&color];
1710: [window flushWindow];
1711: [change endChange];
1712:
1713: return self;
1714: }
1715:
1716: - takeFillColorFrom:sender
1717: {
1718: id change;
1719: NXColor color = [sender color];
1720:
1721: change = [[FillGraphicsChange alloc] initGraphicView:self];
1722: [change startChange];
1723: [self graphicsPerform:@selector(setFillColor:) with:&color];
1724: [window flushWindow];
1725: [change endChange];
1726:
1727: return self;
1728: }
1729:
1730: - takeFormEntryStatusFrom:sender
1731: {
1732: [self graphicsPerform:@selector(setFormEntry:) with:(void *)[sender intValue]];
1733: [window flushWindow];
1734: return self;
1735: }
1736:
1737: - changeFont:sender
1738: {
1739: id change;
1740:
1741: if ([window firstResponder] == self) {
1742: change = [[MultipleChange alloc] initChangeName:FONT_OPERATION];
1743: [change startChange];
1744: [self graphicsPerform:@selector(changeFont:) with:sender];
1745: [window flushWindow];
1746: [change endChange];
1747: }
1748: return self;
1749: }
1750:
1751: /* Archiver-related methods. */
1752:
1753: - awake
1754: /*
1755: * After the GraphicView is unarchived, its cache must be created.
1756: * If we are loading in this GraphicView just to print it, then we need
1757: * not load up our cache.
1758: */
1759: {
1760: PSInit();
1761: if (!InMsgPrint) {
1762: cacheWindow = createCache(&bounds.size, [self zone]);
1763: [self cache:&bounds andUpdateLinks:NO];
1764: }
1765: initClassVars();
1766: [self registerForDragging];
1767: return [super awake];
1768: }
1769:
1770: - write:(NXTypedStream *)stream
1771: /*
1772: * Writes out the glist and the flags.
1773: * No need to write out the slist since it can be regenerated from the glist.
1774: * We also ensure that no Text object that might be a subview of the
1775: * editView gets written out by removing all subviews of the editView.
1776: */
1777: {
1778: [super write:stream];
1779: NXWriteTypes(stream, "@sf", &glist, &gvFlags, &gridGray);
1780: NXWriteObject(stream, editView);
1781: return self;
1782: }
1783:
1784: - read:(NXTypedStream *)stream
1785: /*
1786: * Reads in the glist and the flags, and regenerates the slist from the glist.
1787: */
1788: {
1789: int i;
1790: List *evsvs;
1791: Graphic *graphic, *newGraphic;
1792:
1793: [super read:stream];
1794: NXReadTypes(stream, "@sf", &glist, &gvFlags, &gridGray);
1795: for (i = [glist count]-1; i >= 0; i--) {
1796: graphic = [glist objectAt:i];
1797: newGraphic = [graphic replaceWithImage];
1798: if (graphic != newGraphic) {
1799: if (graphic) {
1800: [glist replaceObjectAt:i with:newGraphic];
1801: } else {
1802: [glist removeObjectAt:i];
1803: }
1804: }
1805: }
1806: [self getSelection];
1807: [self resetGUP];
1808: if (NXTypedStreamClassVersion(stream, [self name]) < 1) {
1809: editView = createEditView(self);
1810: } else {
1811: editView = NXReadObject(stream);
1812: }
1813:
1814: evsvs = [editView subviews];
1815: for (i = [evsvs count]-1; i >= 0; i--) [[evsvs objectAt:i] free];
1816:
1817: return self;
1818: }
1819:
1820: /* Methods to deal with being/becoming the First Responder */
1821:
1822: /* Strings that appear in menus. */
1823:
1824: static const char *HIDE_GRID;
1825: static const char *SHOW_GRID;
1826: static const char *TURN_GRID_OFF;
1827: static const char *TURN_GRID_ON;
1828: static const char *SHOW_LINKS;
1829: static const char *HIDE_LINKS;
1830: static BOOL menuStringsInitted = NO;
1831:
1832: static void initMenuItemStrings(void)
1833: {
1834: HIDE_GRID = NXLocalString("Hide Grid", NULL, "Menu item which hides the background grid which the user can overlay on his document.");
1835: SHOW_GRID = NXLocalString("Show Grid", NULL, "Menu item which shows a background grid which the user can overlay on his document.");
1836: TURN_GRID_OFF = NXLocalString("Turn Grid Off", NULL, "Menu item which turns off the background grid so that items do not move and resize on even grid boundaries. It does not hide the grid if it is currently showing.");
1837: TURN_GRID_ON = NXLocalString("Turn Grid On", NULL, "Menu item which turns a background grid on so that the user's actions are rounded to even grid boundaries. It does not show the grid if the grid is currently hidden.");
1838: SHOW_LINKS = NXLocalString("Show Links", NULL, "Menu item which turns on the borders around linked items (using Object Links).");
1839: HIDE_LINKS = NXLocalString("Hide Links", NULL, "Menu item which turns off the borders around linked items (using Object Links).");
1840: menuStringsInitted = YES;
1841: }
1842:
1843: /* Validates whether a menu command makes sense now */
1844:
1845: static BOOL updateMenuItem(MenuCell *menuCell, const char *zeroItem, const char *oneItem, BOOL state)
1846: {
1847: if (state) {
1848: if (strcmp([menuCell title], zeroItem) || [menuCell tag] != 0) {
1849: [menuCell setTitle:zeroItem];
1850: [menuCell setTag:0];
1851: [menuCell setEnabled:NO]; // causes it to get redrawn
1852: }
1853: } else {
1854: if (strcmp([menuCell title], oneItem) || [menuCell tag] != 1) {
1855: [menuCell setTitle:oneItem];
1856: [menuCell setTag:1];
1857: [menuCell setEnabled:NO]; // causes it to get redrawn
1858: }
1859: }
1860:
1861: return YES;
1862: }
1863:
1864: - (BOOL)validateCommand:(MenuCell *)menuCell
1865: /*
1866: * Can be called to see if the specified action is valid on this view now.
1867: * It returns NO if the GraphicView knows that action is not valid now,
1868: * otherwise it returns YES. Note the use of the Pasteboard change
1869: * count so that the GraphicView does not have to look into the Pasteboard
1870: * every time paste: is validated.
1871: */
1872: {
1873: Pasteboard *pb;
1874: int i, count, gcount;
1875: SEL action = [menuCell action];
1876: static BOOL pboardHasPasteableType = NO;
1877: static BOOL pboardHasPasteableLink = NO;
1878: static int cachedPasteboardChangeCount = -1;
1879:
1880: if (!menuStringsInitted) initMenuItemStrings();
1881: if (action == @selector(bringToFront:)) {
1882: if ((count = [slist count]) && [glist count] > count) {
1883: for (i = 0; i < count; i++) {
1884: if ([slist objectAt:i] != [glist objectAt:i]) {
1885: return YES;
1886: }
1887: }
1888: }
1889: return NO;
1890: } else if (action == @selector(sendToBack:)) {
1891: if ((count = [slist count]) && (gcount = [glist count]) > count) {
1892: for (i = 1; i <= count; i++) {
1893: if ([slist objectAt:count-i] != [glist objectAt:gcount-i]) {
1894: return YES;
1895: }
1896: }
1897: }
1898: return NO;
1899: } else if (action == @selector(group:) ||
1900: action == @selector(align:)) {
1901: return([slist count] > 1);
1902: } else if (action == @selector(ungroup:)) {
1903: return(gvFlags.groupInSlist && [slist count] > 0);
1904: } else if (action == @selector(spellCheck:)) {
1905: for (i = [glist count]-1; i >= 0; i--) if ([[glist objectAt:i] isKindOf:[TextGraphic class]]) return YES;
1906: return NO;
1907: } else if (action == @selector(deselectAll:) ||
1908: action == @selector(lock:) ||
1909: action == @selector(changeAspectRatio:) ||
1910: action == @selector(cut:) ||
1911: action == @selector(copy:)) {
1912: return([slist count] > 0);
1913: } else if (action == @selector(alignToGrid:) ||
1914: action == @selector(sizeToGrid:)) {
1915: return(GRID > 1 && [slist count] > 0);
1916: } else if (action == @selector(unlock:)) {
1917: return gvFlags.locked;
1918: } else if (action == @selector(selectAll:)) {
1919: return([glist count] > [slist count]);
1920: } else if (action == @selector(paste:) || action == @selector(pasteAndLink:) || action == @selector(link:)) {
1921: pb = [Pasteboard new];
1922: count = [pb changeCount];
1923: if (count != cachedPasteboardChangeCount) {
1924: cachedPasteboardChangeCount = count;
1925: pboardHasPasteableType = (DrawPasteType([pb types]) != NULL);
1926: pboardHasPasteableLink = pboardHasPasteableType ? IncludesType([pb types], NXDataLinkPboardType) : NO;
1927: }
1928: return (action == @selector(paste:)) ? pboardHasPasteableType : pboardHasPasteableLink;
1929: } else if (action == @selector(hideGrid:)) {
1930: return (gvFlags.grid >= 4) ? updateMenuItem(menuCell, HIDE_GRID, SHOW_GRID, [self gridIsVisible]) : NO;
1931: } else if (action == @selector(enableGrid:)) {
1932: return (gvFlags.grid > 1) ? updateMenuItem(menuCell, TURN_GRID_OFF, TURN_GRID_ON, [self gridIsEnabled]) : NO;
1933: } else if (action == @selector(showLinks:)) {
1934: return linkManager ? updateMenuItem(menuCell, HIDE_LINKS, SHOW_LINKS, [linkManager areLinkOutlinesVisible]) : NO;
1935: } else if (action == @selector(print:)) {
1936: return([glist count] > 0);
1937: }
1938:
1939: return YES;
1940: }
1941:
1942: /* Useful scrolling routines. */
1943:
1944: - scrollGraphicToVisible:(Graphic *)graphic
1945: {
1946: NXPoint p;
1947: NXRect eb;
1948:
1949: p = bounds.origin;
1950: NXContainRect([graphic getExtendedBounds:&eb], &bounds);
1951: p.x -= bounds.origin.x;
1952: p.y -= bounds.origin.y;
1953: if (p.x || p.y) {
1954: [graphic moveBy:&p];
1955: bounds.origin.x += p.x;
1956: bounds.origin.y += p.y;
1957: [self scrollRectToVisible:[graphic getExtendedBounds:&eb]];
1958: }
1959:
1960: return self;
1961: }
1962:
1963: - scrollPointToVisible:(const NXPoint *)point
1964: {
1965: NXRect r;
1966:
1967: r.origin.x = point->x - 5.0;
1968: r.origin.y = point->y - 5.0;
1969: r.size.width = r.size.height = 10.0;
1970:
1971: return [self scrollRectToVisible:&r];
1972: }
1973:
1974: - scrollSelectionToVisible
1975: {
1976: NXRect sbounds;
1977:
1978: if ([slist count]) {
1979: [self getBBox:&sbounds of:slist];
1980: return [self scrollRectToVisible:&sbounds];
1981: }
1982:
1983: return self;
1984: }
1985:
1986: /* Private selection management methods. */
1987:
1988: - createCacheWindow:(Window *)selectioncache
1989: /*
1990: * Shares an off-screen window used to draw the selection in so that it
1991: * can be dragged around. If the current off-screen window is equal in
1992: * size or larger than the passed size, then it is simply returned.
1993: * Otherwise, it is resized to be the specified size.
1994: */
1995: {
1996: NXRect rect;
1997:
1998: if (!selectioncache) {
1999: rect = bounds;
2000: selectioncache = [Window newContent:&rect
2001: style:NX_PLAINSTYLE
2002: backing:NX_RETAINED
2003: buttonMask:0
2004: defer:NO];
2005: [selectioncache reenableDisplay];
2006: } else {
2007: [selectioncache getFrame:&rect];
2008: if (rect.size.width < bounds.size.width || rect.size.height < bounds.size.height) {
2009: [selectioncache sizeWindow:MAX(rect.size.width, bounds.size.width)
2010: :MAX(rect.size.height, bounds.size.height)];
2011: }
2012: }
2013:
2014: return selectioncache;
2015: }
2016:
2017: - selectionCache
2018: {
2019: static Window *selectioncache = nil;
2020: return selectioncache = [self createCacheWindow:selectioncache];
2021: }
2022:
2023: - getBBox:(NXRect *)bbox of:(List *)list extended:(BOOL)extended
2024: /*
2025: * Returns a rectangle which encloses all the objects in the list.
2026: */
2027: {
2028: int i;
2029: NXRect eb;
2030:
2031: i = [list count];
2032: if (i) {
2033: if (extended) {
2034: [[list objectAt:--i] getExtendedBounds:bbox];
2035: while (i--) NXUnionRect([[list objectAt:i] getExtendedBounds:&eb], bbox);
2036: } else {
2037: [[list objectAt:--i] getBounds:bbox];
2038: while (i--) {
2039: [[list objectAt:i] getBounds:&eb];
2040: NXUnionRect(&eb, bbox);
2041: }
2042: }
2043: } else {
2044: bbox->size.width = bbox->size.height = 0.0;
2045: }
2046:
2047: return self;
2048: }
2049:
2050: - recacheSelection:(BOOL)updateLinks
2051: /*
2052: * Redraws the selection in the off-screen cache (not the selection cache),
2053: * then composites it back on to the screen.
2054: */
2055: {
2056: NXRect sbounds;
2057:
2058: if ([slist count]) {
2059: [self getBBox:&sbounds of:slist];
2060: gvFlags.cacheing = YES;
2061: [self drawSelf:&sbounds :1];
2062: gvFlags.cacheing = NO;
2063: [self display:&sbounds :1];
2064: if (updateLinks && !gvFlags.suspendLinkUpdate) [self updateTrackedLinks:&sbounds];
2065: }
2066:
2067: return self;
2068: }
2069:
2070: - recacheSelection
2071: {
2072: return [self recacheSelection:YES];
2073: }
2074:
2075: - compositeSelection:(const NXRect *)sbounds from:(int)gstate
2076: /*
2077: * Composites from the specified gstate whatever part of sbounds is
2078: * currently visible in the View.
2079: */
2080: {
2081: PScomposite(0.0, 0.0, NX_WIDTH(sbounds), NX_HEIGHT(sbounds),
2082: gstate, NX_X(sbounds), NX_Y(sbounds), NX_SOVER);
2083: [window flushWindow];
2084: NXPing();
2085: return self;
2086: }
2087:
2088: - (int)cacheList:(List *)aList into:(Window *)selectionCache withTransparentBackground:(BOOL)transparentBackground
2089: /*
2090: * Caches the selection into the application-wide selection cache
2091: * window (a window which has alpha in it). See also: selectionCache:.
2092: * It draws the objects without their knobbies in the selection cache,
2093: * but it leaves them selected. Returns the gstate of the selection
2094: * cache.
2095: */
2096: {
2097: int i;
2098: NXRect sbounds, scframe;
2099:
2100: [self getBBox:&sbounds of:aList];
2101: [selectionCache getFrame:&scframe];
2102: if (scframe.size.width < sbounds.size.width || scframe.size.height < sbounds.size.height) {
2103: [selectionCache sizeWindow:MAX(scframe.size.width, sbounds.size.width)
2104: :MAX(scframe.size.height, sbounds.size.height)];
2105: }
2106: [[selectionCache contentView] lockFocus];
2107: PSsetgray(NX_WHITE);
2108: PSsetalpha(transparentBackground ? 0.0 : 1.0); /* 0.0 means fully transparent */
2109: PStranslate(- sbounds.origin.x, - sbounds.origin.y);
2110: sbounds.size.width += 1.0;
2111: sbounds.size.height += 1.0;
2112: NXRectFill(&sbounds);
2113: sbounds.size.width -= 1.0;
2114: sbounds.size.height -= 1.0;
2115: PSsetalpha(1.0); /* set back to fully opaque */
2116: i = [aList count];
2117: while (i--) [[[[aList objectAt:i] deselect] draw:NULL] select];
2118: [Graphic showFastKnobFills];
2119: PStranslate(sbounds.origin.x, sbounds.origin.y);
2120: [[selectionCache contentView] unlockFocus];
2121:
2122: return [selectionCache gState];
2123: }
2124:
2125: - (int)cacheList:(List *)aList into:(Window *)selectionCache
2126: {
2127: return [self cacheList:aList into:selectionCache withTransparentBackground:YES];
2128: }
2129:
2130: - (int)cacheSelection
2131: {
2132: return [self cacheList:slist into:[self selectionCache] withTransparentBackground:YES];
2133: }
2134:
2135: /* Other private methods. */
2136:
2137: - cacheGraphic:(Graphic *)graphic
2138: /*
2139: * Draws the graphic into the off-screen cache, then composites
2140: * it back to the screen.
2141: * NOTE: This ONLY works if the graphic is on top of the list!
2142: * That is why it is a private method ...
2143: */
2144: {
2145: NXRect eb;
2146:
2147: [[cacheWindow contentView] lockFocus];
2148: [graphic draw:NULL];
2149: [Graphic showFastKnobFills];
2150: [[cacheWindow contentView] unlockFocus];
2151: [self display:[graphic getExtendedBounds:&eb] :1];
2152:
2153: return self;
2154: }
2155:
2156: - resetGUP
2157: /*
2158: * The "GUP" is the Grid User Path. It is a user path which draws a grid
2159: * the size of the bounds of the GraphicView. This gets called whenever
2160: * the View is resized or the grid spacing is changed. It sets up all
2161: * the arguments to DPSDoUserPath() called in drawSelf::.
2162: */
2163: {
2164: int x, y, i, j;
2165: short w, h;
2166:
2167: if (gvFlags.grid < 4) return self;
2168:
2169: x = (int)bounds.size.width / (gvFlags.grid ? gvFlags.grid : 1);
2170: y = (int)bounds.size.height / (gvFlags.grid ? gvFlags.grid : 1);
2171: gupLength = (x << 2) + (y << 2);
2172: if (gupCoords) {
2173: NX_FREE(gupCoords);
2174: NX_FREE(gupOps);
2175: NX_FREE(gupBBox);
2176: }
2177: NX_ZONEMALLOC([self zone], gupCoords, short, gupLength);
2178: NX_ZONEMALLOC([self zone], gupOps, char, gupLength >> 1);
2179: NX_ZONEMALLOC([self zone], gupBBox, short, 4);
2180: w = bounds.size.width;
2181: h = bounds.size.height;
2182: j = 0;
2183: for (i = 1; i <= y; i++) {
2184: gupCoords[j++] = 0.0;
2185: gupCoords[j++] = i * (gvFlags.grid ? gvFlags.grid : 1);
2186: gupCoords[j++] = w;
2187: gupCoords[j] = gupCoords[j-2];
2188: j++;
2189: }
2190: for (i = 1; i <= x; i++) {
2191: gupCoords[j++] = i * (gvFlags.grid ? gvFlags.grid : 1);
2192: gupCoords[j++] = 0.0;
2193: gupCoords[j] = gupCoords[j-2];
2194: j++;
2195: gupCoords[j++] = h;
2196: }
2197: i = gupLength >> 1;
2198: while (i) {
2199: gupOps[--i] = dps_lineto;
2200: gupOps[--i] = dps_moveto;
2201: }
2202: gupBBox[0] = gupBBox[1] = 0;
2203: gupBBox[2] = bounds.size.width + 1;
2204: gupBBox[3] = bounds.size.height + 1;
2205:
2206: return self;
2207: }
2208:
2209: #define MOVE_MASK NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK
2210:
2211: - (BOOL)move:(NXEvent *)event
2212: /*
2213: * Moves the selection by cacheing the selected graphics into the
2214: * selection cache, then compositing them repeatedly as the user
2215: * moves the mouse. The tracking loop uses TIMER events to autoscroll
2216: * at regular intervals. TIMER events do not have valid mouse coordinates,
2217: * so the last coordinates are saved and restored when there is a TIMER event.
2218: */
2219: {
2220: int gstate;
2221: NXEvent peek;
2222: NXCoord dx, dy;
2223: NXTrackingTimer *timer = NULL;
2224: NXPoint p, start, last, sboundspad;
2225: NXRect minbounds, sbounds, startbounds, visibleRect;
2226: BOOL canScroll, tracking = YES, alternate, horizConstrain = NO, vertConstrain = NO, hideCursor;
2227:
2228: last = event->location;
2229: alternate = (event->flags & NX_ALTERNATEMASK) ? YES : NO;
2230:
2231: event = [NXApp getNextEvent:MOVE_MASK];
2232: if (event->type == NX_MOUSEUP) return NO;
2233:
2234: hideCursor = NXGetDefaultValue([NXApp appName], "HideCursorOnMove") ? YES : NO;
2235: if (hideCursor) PShidecursor();
2236:
2237: [self convertPoint:&last fromView:nil];
2238: [self grid:&last];
2239:
2240: [self lockFocus];
2241:
2242: gstate = [self cacheSelection];
2243: gvFlags.suspendLinkUpdate = YES; /* we'll update links when the move is complete */
2244: [self graphicsPerform:@selector(deactivate)];
2245: gvFlags.suspendLinkUpdate = NO;
2246: [self getBBox:&sbounds of:slist];
2247: startbounds = sbounds;
2248: [self getBBox:&minbounds of:slist extended:NO];
2249: sboundspad.x = minbounds.origin.x - sbounds.origin.x;
2250: sboundspad.y = minbounds.origin.y - sbounds.origin.y;
2251: [self compositeSelection:&sbounds from:gstate];
2252:
2253: [self getVisibleRect:&visibleRect];
2254: canScroll = !NXEqualRect(&visibleRect, &bounds);
2255:
2256: start = sbounds.origin;
2257:
2258: while (tracking) {
2259: p = event->location;
2260: [self convertPoint:&p fromView:nil];
2261: [self grid:&p];
2262: dx = p.x - last.x;
2263: dy = p.y - last.y;
2264: if (dx || dy) {
2265: [self drawSelf:&sbounds :1];
2266: if (alternate && (dx || dy)) {
2267: if (ABS(dx) > ABS(dy)) {
2268: horizConstrain = YES;
2269: dy = 0.0;
2270: } else {
2271: vertConstrain = YES;
2272: dx = 0.0;
2273: }
2274: alternate = NO;
2275: } else if (horizConstrain) {
2276: dy = 0.0;
2277: } else if (vertConstrain) {
2278: dx = 0.0;
2279: }
2280: NXOffsetRect(&sbounds, dx, dy);
2281: minbounds.origin.x = sbounds.origin.x + sboundspad.x;
2282: minbounds.origin.y = sbounds.origin.y + sboundspad.y;
2283: [self tryToPerform:@selector(updateRulers:) with:(void *)&minbounds];
2284: if (!canScroll || NXContainsRect(&visibleRect, &sbounds)) {
2285: [self compositeSelection:&sbounds from:gstate];
2286: stopTimer(timer);
2287: }
2288: last = p;
2289: }
2290: tracking = (event->type != NX_MOUSEUP);
2291: if (tracking) {
2292: if (canScroll && !NXContainsRect(&visibleRect, &sbounds)) {
2293: [window disableFlushWindow];
2294: [self scrollPointToVisible:&p]; // actually we want to keep the "edges" of the
2295: // Graphic being resized that were visible when
2296: // the move started visible throughout the
2297: // moving session
2298: [self getVisibleRect:&visibleRect];
2299: [self compositeSelection:&sbounds from:gstate];
2300: [[window reenableFlushWindow] flushWindow];
2301: startTimer(timer);
2302: }
2303: p = event->location;
2304: if (![NXApp peekNextEvent:MOVE_MASK into:&peek]) {
2305: event = [NXApp getNextEvent:MOVE_MASK|NX_TIMERMASK];
2306: } else {
2307: event = [NXApp getNextEvent:MOVE_MASK];
2308: }
2309: if (event->type == NX_TIMER) event->location = p;
2310: }
2311: }
2312:
2313: if (canScroll) stopTimer(timer);
2314:
2315: if (hideCursor) PSshowcursor();
2316:
2317: p.x = sbounds.origin.x - start.x;
2318: p.y = sbounds.origin.y - start.y;
2319: if (p.x || p.y)
2320: [self moveGraphicsBy:&p andDraw:NO];
2321:
2322: gvFlags.suspendLinkUpdate = YES; /* we'll update links when the move is complete */
2323: [self graphicsPerform:@selector(activate)];
2324: gvFlags.suspendLinkUpdate = NO;
2325: NXUnionRect(&sbounds, &startbounds);
2326: [self updateTrackedLinks:&startbounds];
2327:
2328: [self tryToPerform:@selector(updateRulers:) with:nil];
2329:
2330: [window flushWindow];
2331: [self unlockFocus];
2332:
2333: return YES;
2334: }
2335:
2336: - moveGraphicsBy:(NXPoint *)vector andDraw:(BOOL)drawFlag
2337: {
2338: id change;
2339:
2340: change = [[MoveGraphicsChange alloc] initGraphicView:self vector:vector];
2341: [change startChange];
2342: if (drawFlag) {
2343: [self graphicsPerform:@selector(moveBy:) with:(id)vector];
2344: } else {
2345: [slist makeObjectsPerform:@selector(moveBy:) with:(id)vector];
2346: }
2347: [change endChange];
2348:
2349: return self;
2350: }
2351:
2352: #define DRAG_MASK (NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK|NX_TIMERMASK)
2353:
2354: - dragSelect:(NXEvent *)event
2355: /*
2356: * Allows the user the drag out a box to select all objects either
2357: * intersecting the box, or fully contained within the box (depending
2358: * on the state of the ALTERNATE key). After the selection is made,
2359: * the slist is updated.
2360: */
2361: {
2362: int i;
2363: Graphic *graphic;
2364: NXPoint p, last, start;
2365: NXTrackingTimer *timer = NULL;
2366: NXRect visibleRect, eb, region, oldRegion;
2367: BOOL mustContain, shift, canScroll, oldRegionSet = NO;
2368:
2369: p = start = event->location;
2370: [self convertPoint:&start fromView:nil];
2371: last = start;
2372:
2373: shift = (event->flags & NX_SHIFTMASK) ? YES : NO;
2374: mustContain = (event->flags & NX_ALTERNATEMASK) ? YES : NO;
2375:
2376: [self lockFocus];
2377:
2378: [self getVisibleRect:&visibleRect];
2379: canScroll = !NXEqualRect(&visibleRect, &bounds);
2380: if (canScroll) startTimer(timer);
2381:
2382: PSsetgray(NX_LTGRAY);
2383: PSsetlinewidth(0.0);
2384:
2385: region.size.width = region.size.height = 0.0;
2386: event = [NXApp getNextEvent:DRAG_MASK];
2387: while (event->type != NX_MOUSEUP) {
2388: if (event->type == NX_TIMER) event->location = p;
2389: p = event->location;
2390: [self convertPoint:&p fromView:nil];
2391: if (p.x != last.x || p.y != last.y) {
2392: getRegion(®ion, &p, &start);
2393: [window disableFlushWindow];
2394: if (oldRegionSet) {
2395: NXInsetRect(&oldRegion, -1.0, -1.0);
2396: [self drawSelf:&oldRegion :1];
2397: }
2398: if (canScroll) {
2399: [self scrollRectToVisible:®ion];
2400: [self scrollPointToVisible:&p];
2401: }
2402: PSrectstroke(region.origin.x, region.origin.y, region.size.width, region.size.height);
2403: [self tryToPerform:@selector(updateRulers:) with:(void *)®ion];
2404: [[window reenableFlushWindow] flushWindow];
2405: oldRegion = region; oldRegionSet = YES;
2406: last = p;
2407: NXPing();
2408: }
2409: p = event->location;
2410: event = [NXApp getNextEvent:DRAG_MASK];
2411: }
2412:
2413: if (canScroll) stopTimer(timer);
2414:
2415: for (i = [glist count] - 1; i >= 0; i--) {
2416: graphic = [glist objectAt:i];
2417: [graphic getExtendedBounds:&eb];
2418: if (![graphic isLocked] && ![graphic isSelected] &&
2419: ((mustContain && NXContainsRect(®ion, &eb)) ||
2420: (!mustContain && NXIntersectsRect(®ion, &eb)))) {
2421: [graphic select];
2422: }
2423: }
2424: [self getSelection];
2425:
2426: if (!dragRect) NX_MALLOC(dragRect, NXRect, 1);
2427: *dragRect = region;
2428:
2429: NXInsetRect(®ion, -1.0, -1.0);
2430: [self drawSelf:®ion :1];
2431: [self recacheSelection:NO];
2432:
2433: [self tryToPerform:@selector(updateRulers:) with:nil];
2434:
2435: [self unlockFocus];
2436:
2437: return self;
2438: }
2439:
2440: - alignGraphicsBy:(AlignmentType)alignType edge:(NXCoord *)edge
2441: {
2442: SEL action;
2443: id change;
2444:
2445: change = [[AlignGraphicsChange alloc] initGraphicView:self];
2446: [change startChange];
2447: action = [GraphicView actionFromAlignType:alignType];
2448: [self graphicsPerform:action with:edge];
2449: [change endChange];
2450:
2451: return self;
2452: }
2453:
2454: - alignBy:(AlignmentType)alignType
2455: {
2456: int i;
2457: NXRect rect;
2458: Graphic *graphic;
2459: NXCoord minEdge = 10000.0;
2460: NXCoord maxEdge = 0.0;
2461: NXCoord baseline = 0.0;
2462:
2463: for (i = [slist count]-1; i >= 0 && !baseline; i--) {
2464: graphic = [slist objectAt:i];
2465: [graphic getBounds:&rect];
2466: switch (alignType) {
2467: case LEFT:
2468: if (rect.origin.x < minEdge)
2469: minEdge = rect.origin.x;
2470: break;
2471: case RIGHT:
2472: if (rect.origin.x + rect.size.width > maxEdge)
2473: maxEdge = rect.origin.x + rect.size.width;
2474: break;
2475: case BOTTOM:
2476: if (rect.origin.y < minEdge)
2477: minEdge = rect.origin.y;
2478: break;
2479: case TOP:
2480: if (rect.origin.y + rect.size.height > maxEdge)
2481: maxEdge = rect.origin.y + rect.size.height;
2482: break;
2483: case HORIZONTAL_CENTERS:
2484: if (rect.origin.y + floor(rect.size.height / 2.0) < minEdge)
2485: minEdge = rect.origin.y + floor(rect.size.height / 2.0);
2486: break;
2487: case VERTICAL_CENTERS:
2488: if (rect.origin.x + floor(rect.size.width / 2.0) < minEdge)
2489: minEdge = rect.origin.x + floor(rect.size.width / 2.0);
2490: break;
2491: case BASELINES:
2492: baseline = [graphic baseline];
2493: break;
2494: }
2495: }
2496:
2497: switch (alignType) {
2498: case LEFT:
2499: case BOTTOM:
2500: case HORIZONTAL_CENTERS:
2501: case VERTICAL_CENTERS:
2502: [self alignGraphicsBy:alignType edge:&minEdge];
2503: break;
2504: case RIGHT:
2505: case TOP:
2506: [self alignGraphicsBy:alignType edge:&maxEdge];
2507: break;
2508: case BASELINES:
2509: if (baseline) [self alignGraphicsBy:alignType edge:&baseline];
2510: }
2511: [window flushWindow];
2512:
2513: return self;
2514: }
2515:
2516: @end
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.