home *** CD-ROM | disk | FTP | other *** search
/ NOVA - For the NeXT Workstation / NOVA - For the NeXT Workstation.iso / SourceCode / AdobeExamples / NX_HitDetect / DrawingView.m < prev    next >
Encoding:
Text File  |  1992-12-19  |  23.5 KB  |  951 lines

  1.  
  2. /*
  3.  * (a)  (C) 1990 by Adobe Systems Incorporated. All rights reserved.
  4.  *
  5.  * (b)  If this Sample Code is distributed as part of the Display PostScript
  6.  *    System Software Development Kit from Adobe Systems Incorporated,
  7.  *    then this copy is designated as Development Software and its use is
  8.  *    subject to the terms of the License Agreement attached to such Kit.
  9.  *
  10.  * (c)  If this Sample Code is distributed independently, then the following
  11.  *    terms apply:
  12.  *
  13.  * (d)  This file may be freely copied and redistributed as long as:
  14.  *    1) Parts (a), (d), (e) and (f) continue to be included in the file,
  15.  *    2) If the file has been modified in any way, a notice of such
  16.  *      modification is conspicuously indicated.
  17.  *
  18.  * (e)  PostScript, Display PostScript, and Adobe are registered trademarks of
  19.  *    Adobe Systems Incorporated.
  20.  * 
  21.  * (f) THE INFORMATION BELOW IS FURNISHED AS IS, IS SUBJECT TO
  22.  *    CHANGE WITHOUT NOTICE, AND SHOULD NOT BE CONSTRUED
  23.  *    AS A COMMITMENT BY ADOBE SYSTEMS INCORPORATED.
  24.  *    ADOBE SYSTEMS INCORPORATED ASSUMES NO RESPONSIBILITY
  25.  *    OR LIABILITY FOR ANY ERRORS OR INACCURACIES, MAKES NO
  26.  *    WARRANTY OF ANY KIND (EXPRESS, IMPLIED OR STATUTORY)
  27.  *    WITH RESPECT TO THIS INFORMATION, AND EXPRESSLY
  28.  *    DISCLAIMS ANY AND ALL WARRANTIES OF MERCHANTABILITY, 
  29.  *    FITNESS FOR PARTICULAR PURPOSES AND NONINFRINGEMENT
  30.  *    OF THIRD PARTY RIGHTS.
  31.  */
  32.  
  33. /*
  34.  *    DrawingView.m
  35.  *
  36.  *    This view represents the page that the bezier is drawn onto. It is
  37.  *    a subview of the doc view and can be zoomed and reduced.
  38.  *
  39.  *    The buffers are used to allow for fast redrawing and moving. 
  40.  *    BuffeAlpha is a view inside a ClipView in a plain window and
  41.  *    bufferBeta is a view in a plain window without a ClipView. The
  42.  *    drawing is performed in the alpha buffer and then this is
  43.  *    composited into the buffered window. When the bezier is
  44.  *    selected and changed, the drawing of the new bezier 
  45.  *    occurs in the buffered window. With each mouse drag the
  46.  *    old image in alpha is first composited into the window and
  47.  *    then the new bezier drawn atop the old image.
  48.  *
  49.  *    When the image is moved, it is first drawn into the beta
  50.  *    buffer and then simply composited to a new location. If
  51.  *    the image is larger than the beta buffer, then the image
  52.  *    is redrawn instead of using the buffer.
  53.  *
  54.  *    Version:    2.0
  55.  *    Author:    Ken Fromm
  56.  *    History:
  57.  *            03-07-91        Added this comment.
  58.  */
  59.  
  60. #import "Bezier.h"
  61. #import "HitPointView.h"
  62. #import "DetectApp.h"
  63. #import "DocView.h"
  64. #import "DrawingView.h"
  65. #import "DrawingViewWraps.h"
  66. #import <appkit/Application.h>
  67. #import <appkit/Cell.h>
  68. #import <appkit/Cursor.h>
  69. #import <appkit/Matrix.h>
  70. #import <appkit/View.h>
  71. #import <appkit/ClipView.h>
  72. #import <appkit/Window.h>
  73. #import <appkit/nextstd.h>
  74. #import <dpsclient/wraps.h>
  75. #import <appkit/timer.h>
  76.  
  77. @implementation DrawingView
  78.  
  79. static char    fontname[ ] = "ControlPointsFont";
  80.  
  81. /*
  82. *    Timers used to automatically scroll when the mouse is
  83. *    outside the drawing view and not moving.
  84. */
  85. static void startTimer(NXTrackingTimer ** timer, int *timermask, id window)
  86. {
  87.     if (!*timer)
  88.     {
  89.         *timer = NXBeginTimer(NULL, 0.15, 0.2);
  90.         *timermask = NX_TIMERMASK;
  91.         [window addToEventMask:NX_TIMERMASK];
  92.     }
  93. }
  94.  
  95. static void stopTimer(NXTrackingTimer **timer, int *timermask, id window)
  96. {
  97.     if (*timer)
  98.     {
  99.         NXEndTimer(*timer);
  100.         *timer = NULL;
  101.         *timermask = 0;
  102.         [window removeFromEventMask:NX_TIMERMASK];            
  103.     }
  104. }
  105.  
  106. static void compositeBuffer(int gstate, const NXRect * srce, const NXPoint * dest, int op)
  107. {
  108.       PScomposite(NX_X(srce), NX_Y(srce), NX_WIDTH(srce), NX_HEIGHT(srce),
  109.                 gstate, dest->x, dest->y, op);
  110. }
  111.  
  112. /*
  113. *    Instance variables - hitPoint holds the usre path for
  114. *    hit detection while drawUpath is a scratch buffer used for
  115. *    several misc. purposes.
  116. */
  117. - initFrame:(NXRect *) frm
  118. {    
  119.     [super initFrame:frm]; 
  120.     [[self allocateGState] setClipping:NO]; 
  121.  
  122.     [self  createGrid];
  123.     PSWDefineFont(fontname);
  124.  
  125.     /*
  126.     *  Normally these buffers might be allocated in the Application subclass and kept
  127.     *  as an application wide resource for use with multiple documents. It is allocated
  128.     *  here to simplify the code.
  129.     */
  130.     NX_MALLOC(drawUpath.pts, float, PTS_UPATH_BUFFER);
  131.     NX_MALLOC(drawUpath.ops, char, OPS_UPATH_BUFFER);
  132.  
  133.     NX_MALLOC(hitPoint.pts, float, NUM_POINTS_HIT);
  134.     NX_MALLOC(hitPoint.ops, char, NUM_OPS_HIT);
  135.     [self initializeHitPoint];
  136.  
  137.     bufferAlpha = [NXApp getBufferAlpha];
  138.     bufferBeta = [NXApp getBufferBeta];
  139.  
  140.     selected = NO;
  141.     timer = NULL;
  142.     
  143.     return self;
  144. }
  145.  
  146. /*
  147.  *  The user path uses relative movements to reduce the number
  148.  *  of points that have to be inserted each mouse down.
  149.  */
  150. - initializeHitPoint
  151. {
  152.     int        i;
  153.  
  154.     for (i = 0; i < NUM_POINTS_HIT; i++)
  155.         hitPoint.pts[i] = 0;            
  156.     hitPoint.num_pts = i;
  157.      
  158.     hitPoint.ops[0] = dps_setbbox;
  159.     hitPoint.ops[1] = dps_moveto;
  160.     hitPoint.ops[2] = dps_rlineto;
  161.     hitPoint.ops[3] = dps_rlineto;
  162.     hitPoint.ops[4] = dps_rlineto;
  163.     hitPoint.ops[5] = dps_closepath;
  164.     hitPoint.num_ops = 6;
  165.  
  166.     return self;
  167. }
  168.  
  169. /*
  170. *    Create a cached user path and store it in the server. The grid does not change
  171. *    so it can be stored in the server instead of being resent each time. Drawing the
  172. *    entire grid each time instead of just the portion that is necessary is faster in
  173. *    this case because the cache is every time. When selectively drawing only 
  174. *    the portion necessary, a different path is cached thereby reducing the number
  175. *    of cache hits.
  176. *    
  177. */
  178. - createGrid
  179. {
  180.     int        i, j, num_pts, num_ops;
  181.  
  182.     float        pt;
  183.  
  184.     char        *ops;
  185.  
  186.     float        *pts;
  187.  
  188.     num_ops = ceil((bounds.size.width/SIZEGRID + bounds.size.height/SIZEGRID) * 2) + 2;
  189.     num_pts = num_ops * 2 + 4;
  190.     NX_MALLOC(pts, float, num_pts); 
  191.     NX_MALLOC(ops, char, num_ops);
  192.  
  193.     i = j = 0;
  194.     ops[j++] = dps_ucache;
  195.  
  196.     pts[i++] = bounds.origin.x;
  197.     pts[i++] = bounds.origin.y;
  198.     pts[i++] = bounds.origin.x + bounds.size.width;
  199.     pts[i++] = bounds.origin.y + bounds.size.height;
  200.     ops[j++] = dps_setbbox;
  201.  
  202.     for (pt = bounds.origin.x; pt < bounds.origin.x + bounds.size.width; pt += SIZEGRID)
  203.     {
  204.         pts[i++] = pt;
  205.         pts[i++] = bounds.origin.y;
  206.         ops[j++] = dps_moveto;
  207.         
  208.         pts[i++] = pt;
  209.         pts[i++] = bounds.origin.y + bounds.size.height;
  210.         ops[j++] = dps_lineto;
  211.     }
  212.  
  213.     for (pt = bounds.origin.y; pt < bounds.origin.y + bounds.size.height; pt += SIZEGRID)
  214.     {
  215.         pts[i++] = bounds.origin.x;
  216.         pts[i++] = pt;
  217.         ops[j++] = dps_moveto;
  218.         
  219.         pts[i++] = bounds.origin.x + bounds.size.width;
  220.         pts[i++] = pt;
  221.         ops[j++] = dps_lineto;
  222.     }
  223.  
  224.     /* Store it as a user object first by placing it on the stack and then defining it */
  225.     PSWSetUpath(pts, i, ops, j);
  226.     gridUpath = DPSDefineUserObject(gridUpath);
  227.  
  228.     if (pts)
  229.         NX_FREE(pts);
  230.     if (ops)
  231.         NX_FREE(ops);
  232.  
  233.     return self;
  234. }
  235.  
  236. /* When the bezier is created, make sure it can be seen. */
  237. - createObject
  238. {
  239.     NXRect    visRect;
  240.  
  241.     [self  getVisibleRect:&visRect];
  242.     objectId = [[Bezier alloc]  initFrame:&visRect];
  243.     
  244.     return self;
  245. }
  246.  
  247. - free
  248. {
  249.     if (drawUpath.pts)
  250.         NX_FREE(drawUpath.pts);
  251.     if (drawUpath.ops)
  252.         NX_FREE(drawUpath.ops);
  253.  
  254.     if (hitPoint.pts)
  255.         NX_FREE(hitPoint.pts);
  256.     if (hitPoint.ops)
  257.         NX_FREE(hitPoint.ops);
  258.  
  259.     return [super free];
  260. }
  261.  
  262.  
  263. /*
  264. *    Change the menu title and toggle the trace boolean
  265. */
  266. - traceDetect:sender
  267. {
  268.     if (!tracedetect)
  269.         [[sender selectedCell] setTitle:"Hit Detection On"];
  270.     else
  271.         [[sender selectedCell] setTitle:"Hit Detection Off"];
  272.  
  273.     tracedetect = !tracedetect;
  274.     
  275.     return self;
  276. }
  277.  
  278. - traceDraw:sender
  279. {
  280.     if (!tracedraw)
  281.         [[sender selectedCell] setTitle:"Drawing On"];
  282.     else
  283.         [[sender selectedCell] setTitle:"Drawing Off"];
  284.  
  285.     tracedraw = !tracedraw;
  286.     
  287.     return self;
  288. }
  289.  
  290. - drawGrid:sender
  291. {
  292.     drawgrid = [sender state];
  293.     
  294.     [self  display];
  295.  
  296.     return self;
  297. }
  298.  
  299. - compositeBufferAlpha:(NXRect *) r
  300. {
  301.     NXRect        viewFrame;
  302.  
  303.     if (!r)
  304.     {
  305.         [self  getVisibleRect:&viewFrame];
  306.         r = &viewFrame;
  307.     }
  308.  
  309.     [bufferAlpha  lockFocus];
  310.         compositeBuffer([self  gState], r, &r->origin, NX_COPY);
  311.     [bufferAlpha unlockFocus];
  312.     
  313.     return self;
  314. }
  315.  
  316. /*
  317. *    When the drawing view moves, then move bufferAlpha so that
  318. *    the composites from bufferAlpha are taken from the correct spot.
  319. *    The beta buffer does not need to be moved because the drawing
  320. *    always starts in the lower left corner of the beta buffer.
  321. */
  322. - moveTo:(NXCoord)x :(NXCoord)y
  323. {
  324.     [super moveTo:x :y];
  325.     [bufferAlpha moveTo:x :y];
  326.  
  327.     return self;
  328. }
  329.  
  330. /*
  331. *    A scale that happens to this view should be reflected in the alpha and
  332. *    beta buffers.
  333. */
  334. - scale:(NXCoord)x :(NXCoord)y
  335. {
  336.     [super scale:x :y];
  337.     [bufferAlpha scale:x :y];
  338.     [bufferBeta scale:x :y];
  339.  
  340.     return self;
  341. }
  342.  
  343. /*
  344. *    A sizeTo should also be reflected in the alpha and beta buffers.
  345. */
  346. - sizeTo:(NXCoord)x :(NXCoord)y
  347. {
  348.     [super sizeTo:x :y];
  349.     [bufferAlpha sizeTo:x :y];
  350.     [bufferBeta sizeTo:x :y];
  351.  
  352.     return self;
  353. }
  354.  
  355. /*
  356. *    Constrain the point within the view. An offset is needed because when
  357. *    an object is moved, it is often grabbed in the center of the object. If the
  358. *    lower left offset and the upper right offset were not included then part of
  359. *    the object could be moved off of the view. (In some applications, that might
  360. *    be allowed but in this one the object is constrained to always lie in the
  361. *    page.)
  362. */
  363. - constrainPoint:(NXPoint *)aPt  withOffset:(const NXSize*)llOffset  :(const NXSize*)urOffset
  364. {
  365.     float            margin;
  366.  
  367.     NXPoint        viewMin, viewMax;
  368.  
  369.     margin = ceil(FONTSIZE/2);
  370.  
  371.     viewMin.x = bounds.origin.x + llOffset->width + margin;
  372.     viewMin.y = bounds.origin.y + llOffset->height + margin;
  373.  
  374.     viewMax.x = bounds.origin.x + bounds.size.width - urOffset->width - margin;
  375.     viewMax.y = bounds.origin.y + bounds.size.height  - urOffset->height - margin;
  376.  
  377.     aPt->x = MAX(viewMin.x, aPt->x);
  378.     aPt->y = MAX(viewMin.y, aPt->y);
  379.  
  380.     aPt->x = MIN(viewMax.x, aPt->x);    
  381.     aPt->y = MIN(viewMax.y, aPt->y);
  382.  
  383.     return self;
  384. }
  385.  
  386. /*
  387. *    Constrain a rectangle within the view.
  388. */
  389. - constrainRect:(NXRect *)aRect
  390. {
  391.     float            margin;
  392.     
  393.     NXPoint        viewMin, viewMax;
  394.  
  395.     margin = ceil(FONTSIZE/2);
  396.  
  397.     viewMin.x = bounds.origin.x + margin;
  398.     viewMin.y = bounds.origin.y + margin;
  399.  
  400.     viewMax.x = bounds.origin.x + bounds.size.width  - aRect->size.width - margin;
  401.     viewMax.y = bounds.origin.y + bounds.size.height - aRect->size.height - margin;
  402.  
  403.     aRect->origin.x = MAX(viewMin.x, aRect->origin.x);
  404.     aRect->origin.y = MAX(viewMin.y, aRect->origin.y);
  405.  
  406.     aRect->origin.x = MIN(viewMax.x, aRect->origin.x );    
  407.     aRect->origin.y = MIN(viewMax.y, aRect->origin.y);
  408.  
  409.     return self;
  410. }
  411.  
  412. - (BOOL) isScrolling
  413. {
  414.     return scrolling;
  415. }
  416.  
  417. /*
  418.  * Scrolls to rectangle passed in if it is not in visible portion of the view.
  419.  * If the rectangle is larger in width or height than the view, the scrollRectToVisible
  420.  * method is not altogether consistent. As a result, the rectangle contains only
  421.  * the image that was previously visible.
  422.  */
  423.  - scrollToRect:(const NXRect *)toRect
  424. {
  425.     NXRect        visRect, tooRect;
  426.  
  427.     [self getVisibleRect:&visRect];
  428.     if (!NXContainsRect(&visRect, toRect))
  429.     {
  430.         scrolling = YES;
  431.         [window disableFlushWindow];
  432.         [self scrollRectToVisible:toRect];
  433.         [window reenableFlushWindow];
  434.         scrolling = NO;
  435.  
  436.         startTimer(&timer, &timermask, window);
  437.     }
  438.     else
  439.         stopTimer(&timer, &timermask, window);
  440.  
  441.     return self;
  442. }
  443.  
  444. /*
  445.  *    Redraws the graphic. The image from the alpha buffer is composited
  446.  *    into the window and then the changed object is drawn atop the
  447.  *    old image. A copy of the image is necessary because when the
  448.  *    window is scrolled the alpha buffer is also scrolled. When the
  449.  *    alpha buffer is scrolled, the old image might have to be redrawn.
  450.  *    As a result, a copy is created and the changes performed on the
  451.  *    copy.  Care is taken to limit the amount of area that must be
  452.  *    composited and redrawn. A timer is started is the scrolling rect
  453.  *    moves outside the visible portion of the view.
  454.  */
  455.  
  456. - redrawObject:(int) pt_num :(NXRect *)redrawRect
  457. {
  458.     id            copyId;
  459.  
  460.     BOOL        tracking = YES;
  461.  
  462.     int            old_mask;
  463.  
  464.     NXPoint        pt, pt_last, pt_old, delta;
  465.     
  466.     NXRect        rect_now, rect_last, rect_scroll, rect_vis;
  467.  
  468.     NXEvent        *event;
  469.  
  470.     /*
  471.     *  Composite the current image in the window into the first buffer.
  472.     */
  473.     [self  compositeBufferAlpha:NULL];
  474.  
  475.     /*
  476.     *  Create a copy of the selected object. If we scroll we will need
  477.     *  to redraw the old curve. If we do not create a copy we will
  478.     *  not have an old curve.
  479.     */
  480.     copyId = [objectId  copy];
  481.     [copyId  copyPts:objectId];
  482.  
  483.     timermask = 0;
  484.  
  485.     if (tracedraw)
  486.         DPSTraceContext(DPSGetCurrentContext(), YES);
  487.     
  488.     /*
  489.     *  The rect_scroll will cause scrolling whenever it goes outside the
  490.     *  visible portion of the view.
  491.     */
  492.     [self  getVisibleRect:&rect_vis];
  493.     [copyId  getScrollRect:pt_num :&rect_scroll];
  494.     NXIntersectionRect(&rect_vis, &rect_scroll);
  495.  
  496.     [copyId  getBounds:&rect_now  withKnobs:YES];
  497.     *redrawRect = rect_last = rect_now;
  498.  
  499.     [copyId  getPoint:pt_num :&pt_last];
  500.     pt_old = pt_last;
  501.  
  502.     old_mask = [window addToEventMask:
  503.         NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK];
  504.     event = [NXApp getNextEvent:
  505.         NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK];
  506.     if (event->type != NX_MOUSEUP)
  507.     {
  508.         while (tracking)
  509.         {
  510.             /*
  511.             *  If its a timer event than use the last point. It will be converted to
  512.             *  into the view's coordinate so it will appear as a new point.
  513.             */
  514.             if (event->type == NX_TIMER)
  515.                 pt = pt_old;
  516.             else
  517.                 pt = pt_old = event->location;
  518.  
  519.             [self convertPoint:&pt fromView:nil];
  520.             [copyId constrainPoint:&pt andNumber:pt_num toView:self];
  521.  
  522.             delta.x = pt.x - pt_last.x;
  523.             delta.y = pt.y - pt_last.y;
  524.  
  525.             if (delta.x || delta.y)
  526.             {
  527.                 /* Change the point location and get the new bounds. */
  528.                 [copyId setPoint:pt_num :&delta];
  529.                 [copyId getBounds:&rect_now  withKnobs:YES];
  530.  
  531.                 /* Change the scrolling rectangle. */
  532.                 NXOffsetRect(&rect_scroll, delta.x, delta.y);
  533.                 [self  scrollToRect:&rect_scroll];
  534.  
  535.                 /* Composite the old image and then redraw the new curve. */
  536.                 compositeBuffer([bufferAlpha gState], &rect_last, &rect_last.origin, NX_COPY);
  537.                 [self  drawObject:NULL  for:copyId  withUcache:NO];
  538.                 [self  drawControl:NULL  for:copyId];
  539.  
  540.                 /* Flush the drawing so that it's consistent. */
  541.                 [window flushWindow];
  542.                 NXPing();
  543.                 
  544.                 rect_last = rect_now;
  545.                 pt_last = pt;
  546.             }
  547.             else
  548.                 stopTimer(&timer, &timermask, window);
  549.  
  550.             event = [NXApp getNextEvent:NX_MOUSEUPMASK|
  551.                             NX_MOUSEDRAGGEDMASK|timermask];
  552.             tracking = (event->type != NX_MOUSEUP);
  553.         }
  554.         stopTimer(&timer, &timermask, window);
  555.     }
  556.     [window setEventMask:old_mask];
  557.  
  558.     [objectId  free];
  559.     objectId = copyId;
  560.  
  561.     /*
  562.     *  Figure out the area that has to be redrawn
  563.     *  (the union of the old and the new rectangles).
  564.     */
  565.     NXUnionRect(&rect_now, redrawRect);
  566.  
  567.     return self;
  568. }
  569.  
  570. /*
  571.  *    Moves the graphic object. If the selected graphic can fit in the beta
  572.  *    buffer than the image is drawn into this buffer and then composited
  573.  *    to each new location. The image is redrawn at the new location
  574.  *    when the user releases the mouse button. If the selected graphic
  575.  *    cannot fit in the beta buffer than it is redrawn each time. This can
  576.  *    happen when the drawing view is scaled upwards.
  577.  *
  578.  *    The offsets constrain the selected object to stay within the dimensions
  579.  *    of the view. 
  580.  */
  581. - moveObject:(NXEvent *)event :(NXRect *)redrawRect
  582. {
  583.     BOOL        tracking = YES, beta;
  584.  
  585.     int            old_mask;
  586.  
  587.     float            scale;
  588.  
  589.     NXSize        llOffset, urOffset;
  590.  
  591.     NXPoint        pt, pt_last, pt_old, delta, delta_scroll;
  592.  
  593.     NXRect        rect_now, rect_start, rect_last, rect_scroll, rect_vis;
  594.  
  595.     /*
  596.     *  Composite the current image in the window
  597.     *  into the first buffer.
  598.     */
  599.     [self  compositeBufferAlpha:NULL];
  600.  
  601.     if (tracedraw)
  602.         DPSTraceContext(DPSGetCurrentContext(), YES);
  603.  
  604.     /* Check whether the object can fit in the second buffer. */
  605.     [[bufferBeta superview] getFrame:&rect_now];
  606.     [objectId  getBounds:&rect_start  withKnobs:YES];
  607.     scale = [superview  scale];  
  608.     if (rect_now.size.width > rect_start.size.width * scale &&
  609.         rect_now.size.height > rect_start.size.height * scale)
  610.     {
  611.         [bufferBeta  setDrawOrigin:rect_start.origin.x  :rect_start.origin.y];
  612.         [bufferBeta  lockFocus];
  613.             PSsetgray(NX_WHITE);
  614.             PSsetalpha(0.0);
  615.             NXRectFill(&rect_start);
  616.             PSsetalpha(1.0);
  617.             [self  drawObject:NULL  for:objectId  withUcache:NO];
  618.             [self  drawControl:NULL  for:objectId];
  619.         [bufferBeta  unlockFocus];
  620.         beta = YES;
  621.     }
  622.     else
  623.         beta = NO;
  624.  
  625.     /*
  626.     *  Get the scrolling rectangle. If it turns out to be the visible portion of the window
  627.     *  then reduce it a bit so that the user is not playing pong when trying to
  628.     *  move the image.
  629.     */
  630.     [objectId  getScrollRect:-1  :&rect_scroll];
  631.     [self  getVisibleRect:&rect_vis];
  632.     if (NXContainsRect(&rect_scroll, &rect_vis))
  633.     {
  634.         rect_scroll = rect_vis;
  635.         NXInsetRect(&rect_scroll, rect_scroll.size.width * .20, rect_scroll.size.height * .20);
  636.     }
  637.     else
  638.         NXIntersectionRect(&rect_vis, &rect_scroll);
  639.  
  640.     *redrawRect = rect_now = rect_last = rect_start;
  641.     delta_scroll.x = rect_scroll.origin.x - rect_now.origin.x;
  642.     delta_scroll.y = rect_scroll.origin.y - rect_now.origin.y;
  643.  
  644.     timermask = 0;
  645.     pt_last = pt_old = event->location;
  646.     [self convertPoint:&pt_last  fromView:nil];
  647.     
  648.     /* Calculate where the mouse point falls relative to the object. */
  649.     llOffset.width = pt_last.x - rect_start.origin.x;
  650.     llOffset.height = pt_last.y - rect_start.origin.y;    
  651.     urOffset.width = rect_start.origin.x + rect_start.size.width - pt_last.x;
  652.     urOffset.height = rect_start.origin.y + rect_start.size.height - pt_last.y;
  653.  
  654.     old_mask = [window addToEventMask:
  655.             NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK];
  656.     event = [NXApp getNextEvent:
  657.             NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK];
  658.     if (event->type != NX_MOUSEUP)
  659.     {
  660.         while (tracking)
  661.         {            
  662.             if (event->type == NX_TIMER)
  663.                 pt = pt_old;
  664.             else
  665.                 pt = pt_old = event->location;
  666.                 
  667.             [self convertPoint:&pt fromView:nil];
  668.             [self constrainPoint:&pt  withOffset:&llOffset :&urOffset];
  669.             [self constrainPoint:&pt_last  withOffset:&llOffset :&urOffset];
  670.             delta.x = pt.x - pt_last.x;
  671.             delta.y = pt.y - pt_last.y;
  672.  
  673.             if (delta.x || delta.y)
  674.             {
  675.                 NXOffsetRect(&rect_now, delta.x, delta.y);
  676.                 [self  constrainRect:&rect_now];
  677.  
  678.                 rect_scroll.origin.x = rect_now.origin.x + delta_scroll.x;
  679.                 rect_scroll.origin.y = rect_now.origin.y + delta_scroll.y;
  680.                 [self  scrollToRect:&rect_scroll];
  681.  
  682.                 /*
  683.                 *  Copy the old image into the window. If using the second buffer, copy
  684.                 *  it atop the old buffer. Otherwise, translate and redraw.
  685.                 */
  686.                 compositeBuffer([bufferAlpha gState], &rect_last, &rect_last.origin, NX_COPY);
  687.                 if (beta)
  688.                 {
  689.                     compositeBuffer([bufferBeta gState], &rect_start, &rect_now.origin, 
  690.                                 NX_SOVER);
  691.                 }
  692.                 else
  693.                 {
  694.                     PSgsave();
  695.                     PStranslate(rect_now.origin.x - rect_start.origin.x,
  696.                         rect_now.origin.y - rect_start.origin.y);
  697.                     [self  drawObject:NULL  for:objectId  withUcache:YES];
  698.                     [self  drawControl:NULL  for:objectId];
  699.                     PSgrestore();
  700.                 }
  701.  
  702.                 /* Flush the drawing so that it's consistent. */
  703.                 [window flushWindow];
  704.                 NXPing();
  705.                 
  706.                 rect_last = rect_now;
  707.                 pt_last = pt;
  708.             }
  709.             else
  710.                 stopTimer(&timer, &timermask, window);
  711.  
  712.             event = [NXApp getNextEvent:NX_MOUSEUPMASK|
  713.                         NX_MOUSEDRAGGEDMASK|timermask];
  714.  
  715.             tracking = (event->type != NX_MOUSEUP);
  716.         }
  717.         stopTimer(&timer, &timermask, window);
  718.  
  719.         delta.x = rect_now.origin.x - rect_start.origin.x;
  720.         delta.y = rect_now.origin.y - rect_start.origin.y;        
  721.         [objectId moveAll:&delta];
  722.     }
  723.  
  724.     NXUnionRect(&rect_now, redrawRect);
  725.     [window setEventMask:old_mask];
  726.  
  727.     return self;
  728. }
  729.  
  730. /* Check to see whether a control point has been hit. */
  731.  - (BOOL) checkControl:(const NXPoint *) p :(int *) pt_num
  732.  {
  733.      BOOL    hit;
  734.     
  735.     float        controlsize, hitsetting;
  736.  
  737.     NXRect    hitRect;
  738.  
  739.     controlsize = [superview  controlPointSize];
  740.     hitsetting = [superview  hitSetting];    
  741.     NXSetRect(&hitRect, p->x - hitsetting/2, p->y - hitsetting/2, hitsetting, hitsetting);
  742.     hit = [objectId  hitControl:&hitRect :pt_num :controlsize];
  743.     
  744.     return hit;
  745.  
  746.  }
  747.  
  748. /*
  749.  *  A method in the DrawingView class. Invoked by the mouse down
  750.  *  event. The mouse point and the current hit setting dimensions
  751.  *  are placed in the hit point user path before invoking the
  752.  *  hitObject method of the Bezier object.
  753.  */
  754. - (BOOL) checkObject:(const NXPoint *) p
  755.  {
  756.      BOOL    hit;
  757.  
  758.     float        hitsetting;
  759.     
  760.     hitsetting = [superview  hitSetting];    
  761.  
  762.     /*  Bounding Box */
  763.     hitPoint.pts[0] = floor(p->x - hitsetting/2);
  764.     hitPoint.pts[1] = floor(p->y - hitsetting/2);
  765.     hitPoint.pts[2] = ceil(p->x + hitsetting/2);
  766.     hitPoint.pts[3] = ceil(p->y + hitsetting/2);
  767.     
  768.     /*  Moveto */
  769.     hitPoint.pts[4] = p->x - hitsetting/2;
  770.     hitPoint.pts[5] = p->y - hitsetting/2;
  771.  
  772.     /* Rlineto's */
  773.     hitPoint.pts[7] = hitsetting;
  774.     hitPoint.pts[8] = hitsetting;
  775.     hitPoint.pts[11] = -hitsetting;
  776.  
  777.     if (tracedetect)
  778.         DPSTraceContext(DPSGetCurrentContext(), YES);
  779.  
  780.     hit = [objectId hitObject:&hitPoint];
  781.     
  782.     if (tracedetect)
  783.         DPSTraceContext(DPSGetCurrentContext(), NO);
  784.     
  785.     return hit;
  786.  
  787.  }
  788.  
  789.  /*
  790.  *    If the docview is zooming, then scale the drawing view. Else
  791.  *    check for hit detection on the bezier or the control points.
  792.  */
  793. - mouseDown:(NXEvent *)event
  794. {
  795.     BOOL        redraw = YES;
  796.     
  797.     int            pt_num;
  798.  
  799.     NXPoint        p;
  800.     
  801.     NXRect        drawRect;
  802.  
  803.      p = event->location; 
  804.     if ([superview  isZooming])
  805.         return [nextResponder  scaleDrawView:self  toPoint:&p];
  806.  
  807.     [self convertPoint:&p fromView:nil];
  808.     [self  lockFocus];
  809.         if (!selected)
  810.         {
  811.             if ([self checkObject:&p])
  812.             {
  813.                 [objectId    getBounds:&drawRect  withKnobs:YES];
  814.                 selected = YES;
  815.             }
  816.             else
  817.                 redraw = NO;    
  818.         }
  819.         else
  820.         {
  821.             if ([self checkControl:&p :&pt_num])
  822.                 [self  redrawObject:pt_num :&drawRect];            
  823.             else if ([self checkObject:&p])
  824.                 [self  moveObject:event :&drawRect];    
  825.             else
  826.             {
  827.                 [objectId    getBounds:&drawRect  withKnobs:YES];
  828.                 selected = NO;
  829.             }
  830.         }
  831.  
  832.         /*
  833.         *  The view has already been focused and we know what
  834.         *  has to be redrawn so call drawSelf:: instead of display
  835.         */
  836.         if (redraw)
  837.         {
  838.             [self drawSelf:&drawRect :1];
  839.             [window flushWindow];
  840.             NXPing();
  841.         }
  842.  
  843.     [self  unlockFocus];    
  844.  
  845.     return self;
  846. }
  847.  
  848. /*
  849. *    Draw the object. This is a relatively simple drawing operation. More
  850. *    sophiticated drawing apps would probably want to pass the current
  851. *    drawing parameters to avoid unnecessarily resetting them with
  852. *    each drawing.
  853. */
  854. - drawObject:(NXRect *)r  for:object  withUcache:(BOOL)flag
  855. {
  856.     [object drawObject:r  withUcache:flag];
  857.  
  858.     return self;
  859. }
  860.  
  861. /*
  862. *    Draw the control points using the user path buffer to hold
  863. *    the data for the xyshow. Having the object fill in the data 
  864. *    structure allows for combining the control points for multiple
  865. *    objects. This increases drawing performance by reducing
  866. *    the number of operations.
  867. */
  868. - drawControl:(NXRect *)r  for:object
  869. {
  870.     int            i;
  871.  
  872.     NXPoint        lastpoint;
  873.  
  874.     PSselectfont(fontname, [superview controlPointSize]); 
  875.     PSsetgray(NX_BLACK);
  876.     PSsetlinewidth(0.15);
  877.  
  878.     lastpoint.x = 0;
  879.     lastpoint.y = 0;
  880.  
  881.     drawUpath.num_ops = 0;
  882.     drawUpath.num_pts = 0;
  883.     [object  putControlUPath:&drawUpath forRect:r  :&lastpoint];
  884.     if (drawUpath.num_ops > 0)
  885.     {
  886.         drawUpath.ops[drawUpath.num_ops] = 0;
  887.         drawUpath.pts[drawUpath.num_pts] = 0;
  888.         drawUpath.pts[drawUpath.num_pts + 1] = 0;
  889.  
  890.         PSWDrawControlPoints(drawUpath.pts[0], drawUpath.pts[1],
  891.             &drawUpath.pts[2], drawUpath.num_pts, drawUpath.ops);
  892.     }
  893.  
  894.     drawUpath.num_ops = 0;
  895.     drawUpath.pts[0] = 99999;
  896.     drawUpath.pts[1] = 99999;
  897.     drawUpath.pts[2] = -99999;
  898.     drawUpath.pts[3] = -99999;
  899.     drawUpath.num_pts = 4;
  900.     [object  putControlLinesUPath:&drawUpath  forRect:r];
  901.     if (drawUpath.num_ops > 0)
  902.     {
  903.         DPSDoUserPath(&drawUpath.pts[4], drawUpath.num_pts - 4, dps_float,
  904.             drawUpath.ops, drawUpath.num_ops, drawUpath.pts, dps_ustroke);
  905.     }
  906.     
  907.     return self;
  908. }
  909.  
  910. /*
  911. *    Compare the bounds of the object with the rectangle to draw in order to
  912. *    eliminate unnecessary drawing.
  913. */
  914. - drawSelf:(NXRect *)r :(int) count
  915. {
  916.     NXRect        objRect;
  917.  
  918.     if (tracedraw)
  919.         DPSTraceContext(DPSGetCurrentContext(), YES);
  920.  
  921.     PSsetgray(NX_WHITE);
  922.     NXRectFill(r);
  923.  
  924.     if (drawgrid)
  925.     {
  926.         PSgsave();
  927.         PSsetgray(COLORGRID);
  928.         PSsetlinewidth(WIDTHGRID);
  929.         NXRectClip(r);
  930.         PSWUpathStroke(gridUpath);
  931.         PSgrestore();
  932.     }
  933.  
  934.     [self drawObject:r  for:objectId  withUcache:YES];
  935.     if (selected && NXDrawingStatus == NX_DRAWING)
  936.         [self  drawControl:r  for:objectId];
  937.  
  938.     if (scrolling)
  939.         [self  compositeBufferAlpha:r];
  940.  
  941.     if (tracedraw)
  942.     {
  943.         if (![superview isTraceZooming]  || ![superview isZooming])
  944.             DPSTraceContext(DPSGetCurrentContext(), NO);
  945.     }
  946.  
  947.     return self;            
  948. }
  949.  
  950. @end
  951.