home *** CD-ROM | disk | FTP | other *** search
/ NeXTSTEP 3.0 / NeXTSTEP3.0.iso / NextDeveloper / Examples / AppKit / Draw / gvDrag.m < prev    next >
Text File  |  1992-07-20  |  13KB  |  374 lines

  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 coRVQis 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 getBounds:&gbounds];
  36.         change = [[FillGraphicsChange alloc] initGraphicView:self];
  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 whetherRVR 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_DragRVSationLink;
  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 charRVTle)
  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 zRVU] 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 =RVV
  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
  374.