|
|
1.1 root 1: #import "draw.h"
2:
3: /* See the Dragging.rtf for overview of Dragging in Draw. */
4:
5: #define ERROR -1
6:
7: @implementation GraphicView(Drag)
8:
9: /*
10: * Determines whether there is a Graphic at the specified point that is
11: * willing to accept a dragged color. If color is non-NULL, then the
12: * color is actually set in that Graphic (i.e. you can find out if any
13: * Graphics is "willing" to accept a color by calling this with
14: * color == NULL).
15: *
16: * We use the mechanism of sending a Graphic the message colorAcceptorAt:
17: * and letting it return a Graphic (rather than just asking each Graphic
18: * doYouAcceptAColorAt:) so that Group's of Graphics can return one of
19: * the Graphic's inside itself as the one to handle a dropped color.
20: */
21:
22: - (BOOL)acceptsColor:(NXColor *)color atPoint:(const NXPoint *)point
23: {
24: id change;
25: NXRect gbounds;
26: Graphic *graphic;
27: int i, count = [glist count];
28:
29: if (!point) return NO;
30:
31: for (i = 0; i < count; i++) {
32: graphic = [glist objectAt:i];
33: if (graphic = [graphic colorAcceptorAt:point]) {
34: if (color) {
35: [graphic getExtendedBounds:&gbounds];
36: change = [[FillGraphicsChange alloc] initGraphicView:self forChangeToGraphic:graphic];
37: [change startChange];
38: [graphic setFillColor:color];
39: [self cache:&gbounds]; // acceptColor:atPoint:
40: [window flushWindow];
41: [change endChange];
42: return YES;
43: } else {
44: return YES;
45: }
46: }
47: }
48:
49: return NO;
50: }
51:
52: /* Just counts the number of Pasteboard types in types. */
53:
54: static int countTypes(const NXAtom *types)
55: {
56: int count = 0;
57: while (types && *types) {
58: count++;
59: types++;
60: }
61: return count;
62: }
63:
64: /*
65: * Registers the view with the Workspace Manager so that when the
66: * user picks up an icon in the Workspace and drags it over our view
67: * and lets go, dragging messages will be sent to our view.
68: * We register for NXFilenamePboardType because we handle data link
69: * files and NXImage and Text files dragged into draw as well as any
70: * random file when the Control key is depressed (indicating a link
71: * operation) during the drag. We also accept anything that NXImage
72: * is able to handle (even if it's not in a file, i.e., it's directly
73: * in the dragged Pasteboard--unusual, but we can handle it, so why
74: * not?).
75: */
76:
77: - registerForDragging
78: {
79: const NXAtom *pboardImageTypes;
80: [self registerForDraggedTypes:&NXFilenamePboardType count:1];
81: [self registerForDraggedTypes:&NXColorPboardType count:1];
82: pboardImageTypes = [NXImage imagePasteboardTypes];
83: [self registerForDraggedTypes:pboardImageTypes count:countTypes(pboardImageTypes)];
84: return self;
85: }
86:
87: /*
88: * This is where we determine whether the contents of the dragging Pasteboard
89: * is acceptable to Draw. The gvFlags.drag*Ok flags say whether we can accept
90: * the dragged information as a result of a copy or link (or both) operation.
91: * If NXImage can handle the Pasteboard, then we know we can do copy. We
92: * always know we can do link as long as we have a linkManager. We cache as
93: * much of the answer around as we can so that draggingUpdated: will be fast.
94: * Of course, we can't cache the part of the answer which is dependent upon
95: * the position inside our view (important for colors).
96: */
97:
98: - (NXDragOperation)draggingEntered:(id <NXDraggingInfo>)sender
99: {
100: Pasteboard *pboard;
101: NXDragOperation sourceMask;
102:
103: gvFlags.dragCopyOk = NO;
104: gvFlags.dragLinkOk = NO;
105:
106: sourceMask = [sender draggingSourceOperationMask];
107: pboard = [sender draggingPasteboard];
108:
109: if (IncludesType([pboard types], NXColorPboardType)) {
110: NXPoint p = [sender draggingLocation];
111: [self convertPoint:&p fromView:nil];
112: if ([self acceptsColor:NULL atPoint:&p]) return NX_DragOperationGeneric;
113: } else if (sourceMask & NX_DragOperationCopy) {
114: if ([NXImage canInitFromPasteboard:pboard]) {
115: gvFlags.dragCopyOk = YES;
116: } else if (IncludesType([pboard types], NXFilenamePboardType)) {
117: gvFlags.dragCopyOk = YES;
118: }
119: }
120:
121: if (linkManager) gvFlags.dragLinkOk = YES;
122:
123: if (sourceMask & NX_DragOperationCopy) {
124: if (gvFlags.dragCopyOk) return NX_DragOperationCopy;
125: } else if (sourceMask & NX_DragOperationLink) {
126: if (gvFlags.dragLinkOk) return NX_DragOperationLink;
127: }
128:
129: return NX_DragOperationNone;
130: }
131:
132: /*
133: * This is basically the same as draggingEntered: but, instead of being
134: * called when the dragging enters our view, it is called every time the
135: * mouse moves while dragging inside our view.
136: */
137:
138: - (NXDragOperation)draggingUpdated:(id <NXDraggingInfo>)sender
139: {
140: NXDragOperation sourceMask = [sender draggingSourceOperationMask];
141: if (IncludesType([[sender draggingPasteboard] types], NXColorPboardType)) {
142: NXPoint p = [sender draggingLocation];
143: [self convertPoint:&p fromView:nil];
144: if ([self acceptsColor:NULL atPoint:&p]) return NX_DragOperationGeneric;
145: } else if (sourceMask & NX_DragOperationCopy) {
146: if (gvFlags.dragCopyOk) return NX_DragOperationCopy;
147: } else if (sourceMask & NX_DragOperationLink) {
148: if (gvFlags.dragLinkOk) return NX_DragOperationLink;
149: }
150: return NX_DragOperationNone;
151: }
152:
153: #define FILE_ICON_OR_LINK_BUTTON NXLocalString("Do you want the link to this file to appear as a Link Button or as the icon of the file?", NULL, "Question asked of a user when he drags a file icon into Draw.")
154: #define FILE_ICON NXLocalizedString("File Icon", NULL, "Button choice if user wants a file dragged into Draw to appear as an icon.")
155: #define LINK_BUTTON NXLocalizedString("Link Button", NULL, "Button choice if user wants a file dragged into Draw to appear as a link button.")
156: #define FILE_CONTENTS_OR_ICON_OR_LINK_BUTTON NXLocalizedString("Do you want the contents of this file to appear in Draw, or would you like to create only a link to this data, in which case, do you want the link to appear as a Link Button or as the icon of the file?", NULL, "Question asked of a user when he drags a file which can be imaged directly in Draw into Draw.")
157: #define FILE_CONTENTS NXLocalizedString("File Contents", NULL, "Button choice if user wants a file dragged into Draw to appear as the contents of the file.")
158:
159: #define BAD_IMAGE NXLocalString("Unable to import that image into Draw.", NULL, "Message of alert which lets the user know that an image (PostScript or TIFF or something) that he tried to import was some how defective.")
160:
161: /*
162: * Takes the name of a saved link (.objlink) file and incorporates
163: * the "linked thing" into the view. This is really just some glue between
164: * the dragging mechanism and the addLink:toGraphic:at:update: method
165: * which does all the work of actually incorporating the linked stuff
166: * into the view.
167: */
168:
169: - (int)createGraphicForDraggedLink:(const char *)file at:(const NXPoint *)p
170: {
171: NXDataLink *link;
172: Graphic *graphic = nil;
173:
174: if (linkManager) {
175: link = [[NXDataLink alloc] initFromFile:file];
176: if ([self addLink:link toGraphic:graphic at:p update:UPDATE_IMMEDIATELY]) {
177: return YES;
178: } else {
179: // addLink: frees the link if there's an error
180: NXRunAlertPanel(NULL, BAD_IMAGE, NULL, NULL, NULL);
181: return ERROR;
182: }
183: }
184:
185: return NO;
186: }
187:
188: /* A couple of convenience methods to determine what kind of file we have. */
189:
190: static BOOL isNXImageFile(const char *file)
191: {
192: const char *extension = strrchr(file, '.');
193: return (extension && [NXImage imageRepForFileType:extension+1]) ? YES : NO;
194: }
195:
196: static BOOL isRTFFile(const char *file)
197: {
198: const char *extension = strrchr(file, '.');
199: return (extension && !strcmp(extension, ".rtf")) ? YES : NO;
200: }
201:
202: /*
203: * Creates a Graphic from a file NXImage or the Text object can handle
204: * (or just allows linking it if NXImage nor Text can handle the file).
205: * It links to it if the doLink is YES.
206: *
207: * If we are linking, then we ask the user if she wants the file's icon,
208: * a link button, or (if we can do so) the actually contents of the file
209: * to appear in the view.
210: *
211: * Note the use of the workspace protocol object to get information about
212: * the file. We know that we cannot import the contents of a WriteNow or
213: * other known document format into Draw, so we don't even give the user
214: * the option of trying to do so.
215: *
216: * Again, if it ends up that we are linking, we just call the all-powerful
217: * addLink:toGraphic:at:update: method in gvLinks.m, otherwise, we just
218: * call placeGraphic:at:.
219: */
220:
221: - (int)createGraphicForDraggedFile:(const char *)file withIcon:(NXImage *)icon at:(const NXPoint *)p andLink:(BOOL)doLink
222: {
223: NXAtom fileType;
224: Graphic *graphic;
225: NXDataLink *link;
226: int choice, updateMode = UPDATE_NORMALLY;
227: BOOL isImportable;
228:
229: isImportable = isNXImageFile(file) || isRTFFile(file);
230: if (!isImportable && [[Application workspace] getInfoForFile:file application:NULL type:&fileType]) {
231: isImportable = (fileType == NXPlainFileType);
232: }
233:
234: if (!linkManager) doLink = NO;
235:
236: if (doLink) {
237: if (isImportable) {
238: choice = NXRunAlertPanel(NULL, FILE_CONTENTS_OR_ICON_OR_LINK_BUTTON, FILE_CONTENTS, FILE_ICON, LINK_BUTTON);
239: } else {
240: choice = NXRunAlertPanel(NULL, FILE_ICON_OR_LINK_BUTTON, FILE_ICON, LINK_BUTTON, NULL);
241: if (choice == NX_ALERTDEFAULT) {
242: choice = NX_ALERTALTERNATE;
243: } else if (choice == NX_ALERTALTERNATE) {
244: choice = NX_ALERTOTHER;
245: }
246: }
247: } else if (isImportable) {
248: choice = NX_ALERTDEFAULT;
249: } else {
250: return NO;
251: }
252:
253: if (choice == NX_ALERTDEFAULT) { // import the contents of the file
254: if (isNXImageFile(file)) {
255: graphic = [[Image allocFromZone:[self zone]] initFromFile:file];
256: } else {
257: graphic = [[TextGraphic allocFromZone:[self zone]] initFromFile:file];
258: }
259: [icon free];
260: updateMode = UPDATE_NORMALLY;
261: } else if (choice == NX_ALERTALTERNATE) { // show the file's icon
262: graphic = [[Image allocFromZone:[self zone]] initFromIcon:icon];
263: updateMode = UPDATE_NEVER;
264: } else { // show a link button
265: graphic = [[Image allocFromZone:[self zone]] initWithLinkButton];
266: [icon free];
267: updateMode = UPDATE_NEVER;
268: }
269:
270: if (graphic) {
271: if (doLink) {
272: link = [[NXDataLink alloc] initLinkedToFile:file];
273: if ([self addLink:link toGraphic:graphic at:p update:UPDATE_NORMALLY]) return YES;
274: } else {
275: if ([self placeGraphic:graphic at:p]) return YES;
276: }
277: }
278:
279: NXRunAlertPanel(NULL, BAD_IMAGE, NULL, NULL, NULL);
280:
281: return ERROR;
282: }
283:
284: /*
285: * If we get this far, we are pretty sure we can succeed (though we're
286: * not 100% sure because it might be, for example, a WriteNow file
287: * without the link button down).
288: *
289: * We return YES here and do the work in conclude so that we don't
290: * timeout if we are dragging in a big image or something that will
291: * take a long time to import (or if we have to ask the user a question
292: * to figure out how to import the dragged thing). The bummer here
293: * is that if creating the image should fail for some reason, we can't
294: * give the slide-back feedback because it's too late to do so in
295: * concludeDragOperation:.
296: */
297:
298: - (BOOL)performDragOperation:(id <NXDraggingInfo>)sender
299: {
300: Pasteboard *pboard = [sender draggingPasteboard];
301:
302: if (IncludesType([pboard types], NXColorPboardType)) {
303: BOOL retval = NO;
304: NXColor color = NXReadColorFromPasteboard(pboard);
305: NXPoint p = [sender draggingLocation];
306: [self convertPoint:&p fromView:nil];
307: retval = [self acceptsColor:&color atPoint:&p];
308: [NXApp updateWindows]; // reflect color change in Inspector, et. al.
309: return retval;
310: }
311:
312: return YES;
313: }
314:
315: /* Another convenience method for identifying .objlink files. */
316:
317: static BOOL isLinkFile(const char *file)
318: {
319: const char *extension = strrchr(file, '.');
320: return (extension && !strcmp(extension+1, NXDataLinkFilenameExtension)) ? YES : NO;
321: }
322:
323: /*
324: * Actually do the "drop" of the drag here.
325: *
326: * Note that if we successfully dropped, we bring the window
327: * we dropped into to the front and activate ourselves. We also
328: * update our inspectors, etc. (this is especially important for
329: * the LinkInspector window!) by calling updateWindows.
330: */
331:
332: - concludeDragOperation:(id <NXDraggingInfo>)sender
333: {
334: NXPoint p;
335: int length;
336: Pasteboard *pboard;
337: char *data, *file, *tab;
338: int foundOne = NO;
339: BOOL doLink;
340:
341: p = [sender draggingLocation];
342: [self convertPoint:&p fromView:nil];
343:
344: doLink = ([self draggingUpdated:sender] == NX_DragOperationLink);
345: pboard = [sender draggingPasteboard];
346:
347: if (IncludesType([pboard types], NXFilenamePboardType)) {
348: [pboard readType:NXFilenamePboardType data:&data length:&length];
349: file = data;
350: while (file) {
351: if (tab = strchr(file, '\t')) *tab = '\0';
352: if (isLinkFile(file)) {
353: foundOne = [self createGraphicForDraggedLink:file at:&p] || foundOne;
354: } else {
355: foundOne = [self createGraphicForDraggedFile:file withIcon:[sender draggedImageCopy] at:&p andLink:doLink] || foundOne;
356: }
357: file = tab ? ++tab : NULL;
358: }
359: [pboard deallocatePasteboardData:data length:length];
360: }
361:
362: if (!foundOne) foundOne = [self pasteForeignDataFromPasteboard:pboard andLink:doLink at:&p];
363:
364: if (foundOne > 0) {
365: [NXApp activateSelf:YES];
366: [window makeKeyAndOrderFront:self];
367: [NXApp updateWindows];
368: }
369:
370: return self;
371: }
372:
373: @end
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.