1/*
2 Project: Graphos
3 GRDocView.m
4
5 Copyright (C) 2000-2018 GNUstep Application Project
6
7 Author: Enrico Sersale (original GDraw implementation)
8 Author: Ing. Riccardo Mottola
9
10 This application is free software; you can redistribute it and/or
11 modify it under the terms of the GNU General Public
12 License as published by the Free Software Foundation; either
13 version 2 of the License, or (at your option) any later version.
14
15 This application is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18 Library General Public License for more details.
19
20 You should have received a copy of the GNU General Public
21 License along with this library; if not, write to the Free
22 Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
23 */
24
25#import "GRDocView.h"
26#import "Graphos.h"
27#import "GRDrawableObject.h"
28#import "GRFunctions.h"
29#import "GRBezierPath.h"
30#import "GRBox.h"
31#import "GRBoxEditor.h"
32#import "GRText.h"
33#import "GRTextEditor.h"
34#import "GRCircle.h"
35#import "GRCircleEditor.h"
36#import "GRPropsEditor.h"
37#import "GRImage.h"
38
39#define UNDO_ACTION_OBJPROPS @"Change Object Properties"
40#define UNDO_ACTION_CP_SYMMETRIC @"Change Point to Symmetric"
41#define UNDO_ACTION_CP_CUSP @"Change Point to Cusp"
42#define UNDO_ACTION_CP_EXTRACT @"Change Point by Extracting"
43#define UNDO_ACTION_CP_OVERLAP @"Change Point by Overlapping"
44#define UNDO_ACTION_CP_DELETE @"Delete Point"
45
46#define ZOOM_FACTORS 13
47#define STD_ZOOM_INDEX 5
48float zFactors[ZOOM_FACTORS] = {0.25, 0.33, 0.5, 0.66, 0.75, 1, 1.25, 1.5, 2, 2.5, 3, 4, 6};
49
50
51@implementation GRDocView
52
53
54- (id)initWithFrame:(NSRect)aRect
55{
56  pageRect = NSMakeRect(0, 0, 695, 942);
57  a4Rect = NSMakeRect(50, 50, 595, 842);
58  zmdRect = NSMakeRect(50, 50, 595, 842);
59
60  self = [super initWithFrame: pageRect];
61  if(self)
62    {
63      NSImage *img;
64
65      img = [NSImage imageNamed: @"blackarrow.tiff"];
66      cur = [[NSCursor alloc] initWithImage: img hotSpot: NSMakePoint(0, 0)];
67      [cur setOnMouseEntered: YES];
68      [cur setOnMouseExited: YES];
69      [self addTrackingRect: [self frame]
70	    owner: cur userData: NULL
71	    assumeInside: YES];
72
73      objects = [[NSMutableArray alloc] initWithCapacity: 1];
74      delObjects = [[NSMutableArray alloc] initWithCapacity: 1];
75      lastObjects = nil;
76      shiftclick = NO;
77      altclick = NO;
78      ctrlclick = NO;
79      zIndex = STD_ZOOM_INDEX;
80      zFactor = zFactors[zIndex];
81    }
82  return self;
83}
84
85- (void)dealloc
86{
87    [cur release];
88    [objects release];
89    [delObjects release];
90    [lastObjects release];
91    [super dealloc];
92}
93
94- (NSDictionary *) objectDictionary
95{
96  NSMutableDictionary *objsdict;
97  NSMutableArray *objectOrder;
98  NSString *str = nil;
99  id obj;
100  NSUInteger counter;
101  NSUInteger p = 0;
102  NSUInteger c = 0;
103  NSUInteger t = 0;
104  NSUInteger b = 0;
105  NSUInteger i = 0;
106
107  objsdict = [NSMutableDictionary dictionaryWithCapacity: 1];
108  objectOrder = [NSMutableArray arrayWithCapacity: [objects count]];
109  for(counter = 0; counter < [objects count]; counter++)
110    {
111      obj = [objects objectAtIndex: counter];
112      NSLog(@"class: %@", [obj className]);
113      if([obj isKindOfClass: [GRBezierPath class]])
114        {
115	  str = [NSString stringWithFormat: @"path%lu", (unsigned long)p];
116	  p++;
117        }
118      else if([obj isKindOfClass: [GRImage class]])
119        {
120	  str = [NSString stringWithFormat: @"image%lu", (unsigned long)i];
121	  i++;
122        }
123      else if([obj isKindOfClass: [GRBox class]])
124        {
125	  str = [NSString stringWithFormat: @"box%lu", (unsigned long)b];
126	  b++;
127        }
128      else if([obj isKindOfClass: [GRCircle class]])
129        {
130	  str = [NSString stringWithFormat: @"circle%lu", (unsigned long)c];
131	  c++;
132        }
133      else if([obj isKindOfClass: [GRText class]])
134        {
135	  str = [NSString stringWithFormat: @"text%lu", (unsigned long)t];
136	  t++;
137        }
138      else
139	{
140	  [NSException raise:@"Unhandled object type" format:@"%@", [obj class]];
141	}
142      if (str)
143        {
144          [objectOrder addObject: str];
145          [objsdict setObject: [obj objectDescription] forKey: str];
146        }
147    }
148  [objsdict setObject:[NSNumber numberWithFloat:FILE_FORMAT_VERSION] forKey:@"Version"];
149  [objsdict setObject:objectOrder forKey:@"Order"];
150  return [NSDictionary dictionaryWithDictionary: objsdict];
151}
152
153
154- (BOOL)createObjectsFromDictionary:(NSDictionary *)dict
155{
156    NSArray *keys;
157    NSString *key;
158    NSDictionary *objdict;
159    GRBezierPath *bzPath;
160    GRText *gGRText;
161    GRBox *box;
162    GRCircle *circle;
163    GRImage *image;
164    NSUInteger i;
165    float version;
166    NSNumber *versionNumber;
167
168    if(!dict)
169      return NO;
170
171    [objects removeAllObjects];
172    version = 0.0;
173    versionNumber = [dict objectForKey:@"Version"];
174    if (versionNumber)
175      version = [versionNumber floatValue];
176    NSLog(@"loading file of version: %f", version);
177
178    if (version < 0.3)
179      {
180	/* loading for files without ordering */
181
182	NSLog(@"Loading old file version, < 0.3");
183	keys = [dict allKeys];
184	for(i = 0; i < [keys count]; i++)
185	  {
186	    key = [keys objectAtIndex: i];
187	    objdict = [dict objectForKey: key];
188	    if(!objdict)
189	      return NO;
190
191	    if([key rangeOfString: @"path"].length)
192	      {
193		bzPath = [[GRBezierPath alloc] initFromData: objdict
194						     inView: self zoomFactor: zFactor];
195		[objects addObject: bzPath];
196		[bzPath release];
197		edind = [objects count] -1;
198	      }
199	    else if([key rangeOfString: @"text"].length)
200	      {
201		gGRText = [[GRText alloc] initFromData: objdict
202						inView: self zoomFactor: zFactor];
203		[objects addObject: gGRText];
204		[gGRText release];
205	      }
206	    else if([key rangeOfString: @"box"].length)
207	      {
208		box = [[GRBox alloc] initFromData: objdict
209					   inView: self zoomFactor: zFactor];
210		[objects addObject: box];
211		[box release];
212	      }
213	    else if([key rangeOfString: @"circle"].length)
214	      {
215		circle = [[GRCircle alloc] initFromData: objdict
216						 inView: self zoomFactor: zFactor];
217                [circle setCircle:YES];
218		[objects addObject: circle];
219		[circle release];
220	      }
221	    else if ([key isEqualToString:@"Version"])
222	      {
223		/* skip, already parsed */
224	      }
225	    else
226	      {
227		[NSException raise:@"Unsupported object in file." format:@"Key: %@", key];
228	      }
229	  }
230      }
231    else
232      {
233	/* loading of files with encoded ordering */
234	NSArray *order;
235
236	NSLog(@"Loading version 0.3 or later, ordered objects");
237	order = [dict objectForKey:@"Order"];
238	for(i = 0; i < [order count]; i++)
239	  {
240	    key = [order objectAtIndex: i];
241	    objdict = [dict objectForKey: key];
242	    if(!objdict)
243	      return NO;
244
245	    if([key rangeOfString: @"path"].length)
246	      {
247		bzPath = [[GRBezierPath alloc] initFromData: objdict
248						     inView: self zoomFactor: zFactor];
249		[objects addObject: bzPath];
250		[bzPath release];
251		edind = [objects count] -1;
252	      }
253	    else if([key rangeOfString: @"text"].length)
254	      {
255		gGRText = [[GRText alloc] initFromData: objdict
256						inView: self zoomFactor: zFactor];
257		[objects addObject: gGRText];
258		[gGRText release];
259	      }
260	    else if([key rangeOfString: @"box"].length)
261	      {
262		box = [[GRBox alloc] initFromData: objdict
263					   inView: self zoomFactor: zFactor];
264		[objects addObject: box];
265		[box release];
266	      }
267	    else if([key rangeOfString: @"image"].length)
268	      {
269		image = [[GRImage alloc] initFromData: objdict
270					   inView: self zoomFactor: zFactor];
271		[objects addObject: image];
272		[image release];
273	      }
274	    else if([key rangeOfString: @"circle"].length)
275	      {
276		circle = [[GRCircle alloc] initFromData: objdict
277						 inView: self zoomFactor: zFactor];
278                /* 0.5 and prior only had circles, not ovals */
279                if (version < 0.6)
280                  [circle setCircle:YES];
281		[objects addObject: circle];
282		[circle release];
283	      }
284	    else
285	      {
286		[NSException raise:@"Unsupported object in file." format:@"Key: %@", key];
287	      }
288	  }
289      }
290    [self setNeedsDisplay:YES];
291    return YES;
292}
293
294- (void)addPath
295{
296  GRBezierPath *path;
297  GRPropsEditor *objInspector;
298
299  objInspector = [(Graphos *)[[NSApplication sharedApplication] delegate] objectInspector];
300
301  path = [[GRBezierPath alloc] initInView: self
302			       zoomFactor: zFactor
303			   withProperties: [objInspector properties]];
304
305  [objects addObject: path];
306  [path release];
307  edind = [objects count] -1;
308  [[NSNotificationCenter defaultCenter] postNotificationName:@"ObjectSelectionChanged" object:self];
309}
310
311- (void)addTextAtPoint:(NSPoint)p
312{
313  GRText *gdtxt;
314  NSInteger i;
315  NSUndoManager *uMgr;
316  GRPropsEditor *objInspector;
317
318  objInspector = [(Graphos *)[[NSApplication sharedApplication] delegate] objectInspector];
319
320  uMgr = [self undoManager];
321  /* save the method on the undo stack */
322  [[uMgr prepareWithInvocationTarget: self] restoreLastObjects];
323  [uMgr setActionName:@"Add Text"];
324
325  [self saveCurrentObjects];
326
327  for(i = 0; i < [objects count]; i++)
328    [[[objects objectAtIndex: i] editor] unselect];
329
330  gdtxt = [[GRText alloc] initInView: self
331			     atPoint: p
332			  zoomFactor: zFactor
333		      withProperties: [objInspector properties]
334			  openEditor: YES];
335  if (gdtxt)
336    [objects addObject: gdtxt];
337  [[gdtxt editor] select];
338  [gdtxt release];
339  [self setNeedsDisplay: YES];
340  [[NSNotificationCenter defaultCenter] postNotificationName:@"ObjectSelectionChanged" object:self];
341}
342
343- (void)addBox
344{
345  GRBox *box;
346  GRPropsEditor *objInspector;
347
348  objInspector = [(Graphos *)[[NSApplication sharedApplication] delegate] objectInspector];
349
350  box = [[GRBox alloc] initInView: self
351		       zoomFactor: zFactor
352		   withProperties: [objInspector properties]];
353
354  [objects addObject: box];
355  [[box editor] select];
356  [box release];
357  [self setNeedsDisplay: YES];
358  edind = [objects count] -1;
359  [[NSNotificationCenter defaultCenter] postNotificationName:@"ObjectSelectionChanged" object:self];
360}
361
362- (void)addCircle
363{
364  GRCircle *circle;
365  GRPropsEditor *objInspector;
366
367  objInspector = [(Graphos *)[[NSApplication sharedApplication] delegate] objectInspector];
368
369  circle = [[GRCircle alloc] initInView: self
370			     zoomFactor: zFactor
371			 withProperties: [objInspector properties]];
372
373  [objects addObject: circle];
374  [[circle editor] select];
375  [circle release];
376  [self setNeedsDisplay: YES];
377  [[NSNotificationCenter defaultCenter] postNotificationName:@"ObjectSelectionChanged" object:self];
378  edind = [objects count] -1;
379}
380
381- (NSArray *)duplicateObjects:(NSArray *)objs andMoveTo:(NSPoint)p
382{
383  id obj, duplObj;
384  NSMutableArray *duplObjs;
385  NSUInteger i;
386
387  duplObjs = [NSMutableArray arrayWithCapacity: 1];
388
389  for(i = 0; i < [objs count]; i++)
390    {
391      obj = [objs objectAtIndex: i];
392      duplObj = [obj copy];
393      [[obj editor] unselect];
394      [[duplObj editor] selectAsGroup];
395      [duplObj moveAddingCoordsOfPoint: p];
396      [objects addObject: duplObj];
397      [duplObjs addObject: duplObj];
398      [duplObj release];
399    }
400  edind = [objects count] -1;
401  [self setNeedsDisplay: YES];
402
403  return duplObjs;
404}
405
406- (void)updatePrintInfo: (NSPrintInfo *)pi;
407{
408  float lm, rm;
409
410  if (pi == nil)
411    {
412      NSLog(@"invalid printer information");
413      return;
414    }
415  lm = [pi leftMargin];
416  rm = [pi rightMargin];
417  if (lm <= 0 || rm <= 0 || [pi paperSize].width <= 0 || [pi paperSize].height <= 0)
418    {
419      NSLog(@"invalid margin / paper size information. %f %f %f %f", lm, rm,[pi paperSize].width, [pi paperSize].height);
420      return;
421    }
422  pageRect = NSMakeRect(0,0,[pi paperSize].width, [pi paperSize].height);
423
424  a4Rect = NSMakeRect([pi leftMargin], [pi bottomMargin],
425		      pageRect.size.width-([pi leftMargin]+[pi rightMargin]),
426		      pageRect.size.height-([pi topMargin]+[pi bottomMargin]));
427
428  zmdRect = a4Rect;
429  zIndex = STD_ZOOM_INDEX;
430  zFactor = zFactors[zIndex];
431
432  [self setFrame: pageRect];
433  [self setNeedsDisplay:YES];
434}
435
436- (void)deleteSelectedObjects
437{
438    id obj;
439    NSMutableArray *deleted;
440    NSUInteger i, count;
441
442    deleted = [NSMutableArray arrayWithCapacity: 1];
443
444    count = [objects count];
445    for(i = 0; i < count; i++)
446    {
447        obj = [objects objectAtIndex: i];
448        if([[obj editor] isGroupSelected])
449        {
450            [deleted addObject: obj];
451            [objects removeObject: obj];
452            count--;
453            i--;
454        }
455    }
456    if([deleted count])
457    {
458        [delObjects addObject: deleted];
459    }
460    [self setNeedsDisplay: YES];
461}
462
463- (void)startDrawingAtPoint:(NSPoint)p
464{
465  NSEvent *nextEvent;
466  GRBezierPath *bzpath;
467  id obj;
468  BOOL isNewEditor = YES;
469  NSUInteger i;
470  NSUndoManager *uMgr;
471
472  uMgr = [self undoManager];
473  /* save the method on the undo stack */
474  [[uMgr prepareWithInvocationTarget: self] restoreLastObjects];
475  [uMgr setActionName:@"Create Path"];
476
477  [self saveCurrentObjects];
478
479  for(i = 0; i < [objects count]; i++)
480    {
481      obj = [objects objectAtIndex: i];
482      if([obj isKindOfClass: [GRBezierPath class]])
483        if(![[obj editor] isDone])
484          {
485            isNewEditor = NO;
486            edind = i;
487          }
488    }
489
490  if(isNewEditor)
491    for(i = 0; i < [objects count]; i++)
492      {
493        GRObjectEditor *objEdi;
494
495        objEdi = [[objects objectAtIndex: i] editor];
496        if (![objEdi isSelected])
497          [objEdi unselect];
498      }
499
500    nextEvent = [[self window] nextEventMatchingMask:
501        NSLeftMouseUpMask | NSLeftMouseDraggedMask];
502    [self verifyModifiersOfEvent: nextEvent];
503
504    if([nextEvent type] != NSLeftMouseDragged)
505      {
506        if(isNewEditor)
507          {
508            [self addPath];
509            bzpath = [objects objectAtIndex: edind];
510            [[bzpath editor] selectForEditing];
511            [bzpath addControlAtPoint: p];
512            [self setNeedsDisplay: YES];
513            return;
514          }
515        else
516          {
517            bzpath = [objects objectAtIndex: edind];
518            [[bzpath editor] selectForEditing];
519            if(shiftclick)
520                p = pointApplyingCostrainerToPoint(p, [[bzpath lastPoint] center]);
521            [bzpath addLineToPoint: p];
522            [self setNeedsDisplay: YES];
523            return;
524          }
525      }
526    else
527      {
528        if(isNewEditor)
529          {
530            [self addPath];
531            bzpath = [objects objectAtIndex: edind];
532            [[bzpath editor] selectForEditing];
533            [bzpath addControlAtPoint: p];
534          }
535        else
536          {
537            bzpath = [objects objectAtIndex: edind];
538            [[bzpath editor] selectForEditing];
539            if(shiftclick)
540                p = pointApplyingCostrainerToPoint(p, [[bzpath lastPoint] center]);
541            [bzpath addControlAtPoint: p];
542          }
543        [self setNeedsDisplay: YES];
544
545        do
546          {
547            p = [nextEvent locationInWindow];
548            p = [self convertPoint: p fromView: nil];
549	    p = GRpointDeZoom(p, zFactor);
550
551            if(shiftclick)
552              p = pointApplyingCostrainerToPoint(p, [[bzpath lastPoint] center]);
553
554            [bzpath addCurveWithBezierHandlePosition: p];
555
556            [self setNeedsDisplay: YES];
557
558            nextEvent = [[self window] nextEventMatchingMask:
559                NSLeftMouseUpMask | NSLeftMouseDraggedMask];
560            [self verifyModifiersOfEvent: nextEvent];
561          }
562        while([nextEvent type] != NSLeftMouseUp);
563
564        [bzpath confirmNewCurve];
565        [self setNeedsDisplay: YES];
566      }
567}
568
569- (void)startBoxAtPoint:(NSPoint)p
570{
571  NSEvent *nextEvent;
572  GRBox *box;
573  NSUInteger i;
574  NSUndoManager *uMgr;
575
576  uMgr = [self undoManager];
577  /* save the method on the undo stack */
578  [[uMgr prepareWithInvocationTarget: self] restoreLastObjects];
579  [uMgr setActionName:@"Create Box"];
580
581  [self saveCurrentObjects];
582
583  for(i = 0; i < [objects count]; i++)
584    {
585      GRObjectEditor *objEdi;
586
587      objEdi = [[objects objectAtIndex: i] editor];
588      if (![objEdi isSelected])
589        [objEdi unselect];
590    }
591
592  nextEvent = [[self window] nextEventMatchingMask:
593                               NSLeftMouseUpMask | NSLeftMouseDraggedMask];
594  [self verifyModifiersOfEvent: nextEvent];
595
596  if([nextEvent type] != NSLeftMouseDragged)
597    {
598      NSLog(@"is not left mouse dragged");
599
600      [self addBox];
601      box = [objects objectAtIndex: edind];
602      [[box editor] selectForEditing];
603      [box setStartAtPoint: p];
604      [self setNeedsDisplay: YES];
605      return;
606    }
607  else
608    {
609      NSLog(@"is left mouse dragged");
610
611      [self addBox];
612      box = [objects objectAtIndex: edind];
613      [[box editor] selectForEditing];
614      [box setStartAtPoint: p];
615      [box setEndAtPoint: p];
616      [self setNeedsDisplay: YES];
617
618      [self moveControlPointOfEditor: (GRBezierPathEditor *)[box editor] toPoint: p];
619
620      [[box editor] unselect];
621      [[box editor] selectAsGroup];
622    }
623}
624
625/* this has a lot in common with startBoxAtPoint */
626- (void)startCircleAtPoint:(NSPoint)p
627{
628  NSEvent *nextEvent;
629  GRCircle *circle;
630  NSUInteger i;
631  NSUndoManager *uMgr;
632
633  uMgr = [self undoManager];
634  /* save the method on the undo stack */
635  [[uMgr prepareWithInvocationTarget: self] restoreLastObjects];
636  [uMgr setActionName:@"Create Circle"];
637
638  [self saveCurrentObjects];
639
640
641  for(i = 0; i < [objects count]; i++)
642    {
643      GRObjectEditor *objEdi;
644
645      objEdi = [[objects objectAtIndex: i] editor];
646      if (![objEdi isSelected])
647        [objEdi unselect];
648    }
649
650  nextEvent = [[self window] nextEventMatchingMask:
651                               NSLeftMouseUpMask | NSLeftMouseDraggedMask];
652  [self verifyModifiersOfEvent: nextEvent];
653
654  if([nextEvent type] != NSLeftMouseDragged)
655    {
656      NSLog(@"is not left mouse dragged");
657
658      [self addBox];
659      circle = [objects objectAtIndex: edind];
660      [[circle editor] selectForEditing];
661      [circle setStartAtPoint: p];
662      [self setNeedsDisplay: YES];
663      return;
664    }
665  else
666    {
667      NSLog(@"is left mouse dragged");
668
669      [self addCircle];
670      circle = [objects objectAtIndex: edind];
671      [[circle editor] selectForEditing];
672      [circle setStartAtPoint: p];
673      [circle setEndAtPoint: p];
674      [self setNeedsDisplay: YES];
675
676
677      [self moveControlPointOfEditor: (GRBezierPathEditor *)[circle editor] toPoint: p];
678
679      [[circle editor] unselect];
680      [[circle editor] selectAsGroup];
681
682    }
683}
684
685
686- (void)selectObjectAtPoint:(NSPoint)p
687{
688  id obj;
689  NSMutableArray *objs;
690  NSUInteger i;
691  GRDrawableObject *hitObj;
692
693  objs = [NSMutableArray arrayWithCapacity: 1];
694
695  hitObj = nil;
696  for(i = 0; i < [objects count]; i++)
697    {
698      obj = [objects objectAtIndex: i];
699
700      if ([obj objectHitForSelection: p])
701        hitObj = obj;
702      else if ([[obj editor] isSelected])
703        [objs addObject: obj];
704    }
705
706  /* with shift we add or remove the hit object from the list*/
707  if (shiftclick && hitObj)
708    {
709      if ([[hitObj editor] isSelected])
710        {
711          [[hitObj editor] unselect];
712        }
713      else
714        {
715          [[hitObj editor] select];
716          [objs addObject:hitObj];
717        }
718    }
719  else
720    {
721      /* if not shift, if the object is already selected
722         we essentially don't change the selection.
723         else, we select it and deselect the rest */
724      if ([[hitObj editor] isSelected])
725        {
726          [objs addObject:hitObj];
727        }
728      else
729        {
730          NSEnumerator *e;
731          GRDrawableObject *o;
732
733          e = [objs objectEnumerator];
734          while ((o = [e nextObject]))
735            {
736              [[o editor] unselect];
737            }
738          [objs removeAllObjects];
739          if (hitObj)
740            {
741              [[hitObj editor] select];
742              [objs addObject: hitObj];
743            }
744        }
745    }
746  [self setNeedsDisplay: YES];
747
748  if([objs count])
749    [self moveSelectedObjects: objs startingPoint: p];
750  [[NSNotificationCenter defaultCenter] postNotificationName:@"ObjectSelectionChanged" object:self];
751}
752
753- (void)editPathAtPoint:(NSPoint)p
754{
755  id obj;
756  NSUInteger i;
757  NSUndoManager *uMgr;
758
759  uMgr = [self undoManager];
760  /* save the method on the undo stack */
761  [[uMgr prepareWithInvocationTarget: self] restoreLastObjects];
762  [uMgr setActionName:@"Edit Path"];
763
764  [self saveCurrentObjectsDeep];
765
766  /* we look for a path that is selected and process editing
767     we no longer auto-select the object for editing though, nor automatically unselect the editing of the object */
768  for(i = 0; i < [objects count]; i++)
769    {
770      obj = [objects objectAtIndex: i];
771      if([obj isKindOfClass: [GRBezierPath class]] && [[obj editor] isSelected])
772        {
773          if([obj onControlPoint: p])
774            {
775              [[obj editor] selectForEditing];
776              [self moveControlPointOfEditor: (GRBezierPathEditor *)[obj editor] toPoint: p];
777              return;
778            }
779          else
780            {
781              if([self moveBezierHandleOfEditor: (GRBezierPathEditor *)[obj editor] toPoint: p])
782                return;
783            }
784        }
785      else if ([[obj editor] isSelected] && ([obj isKindOfClass: [GRBox class]] || [obj isKindOfClass: [GRCircle class]]))
786        {
787          if([obj onControlPoint: p])
788            {
789              [[obj editor] selectForEditing];
790              [self moveControlPointOfEditor: (GRBezierPathEditor *)[obj editor] toPoint: p];
791              return;
792            }
793        }
794      else if([obj isKindOfClass: [GRText class]] && [[obj editor] isSelected])
795        {
796          /* we have no actions for GRText */
797        }
798    }
799  [self setNeedsDisplay: YES];
800}
801
802- (void)editTextAtPoint:(NSPoint)p
803{
804  id obj;
805  NSUInteger i;
806  NSUndoManager *uMgr;
807
808  uMgr = [self undoManager];
809  /* save the method on the undo stack */
810  [[uMgr prepareWithInvocationTarget: self] restoreLastObjects];
811  [uMgr setActionName:@"Edit Text"];
812
813  [self saveCurrentObjectsDeep];
814
815  for(i = 0; i < [objects count]; i++)
816    [[[objects objectAtIndex: i] editor] unselect];
817
818    for(i = 0; i < [objects count]; i++)
819    {
820        obj = [objects objectAtIndex: i];
821        if([obj isKindOfClass: [GRText class]])
822        {
823            if([obj objectHitForSelection: p])
824            {
825                [[obj editor] select];
826                [self setNeedsDisplay: YES];
827                [obj edit];
828            }
829        }
830    }
831    [self setNeedsDisplay: YES];
832}
833
834/** for keyboard equivalent */
835- (void)editSelectedText
836{
837    id obj;
838    NSUInteger i;
839
840    for(i = 0; i < [objects count]; i++)
841    {
842        obj = [objects objectAtIndex: i];
843        if([obj isKindOfClass: [GRText class]])
844        {
845            if([obj isSelect])
846                [obj edit];
847        }
848    }
849}
850
851- (void)moveSelectedObjects:(NSArray *)objs startingPoint:(NSPoint)startp
852{
853  NSEvent *nextEvent;
854  NSArray *moveobjs = nil;
855  id obj;
856  NSPoint p, op, diffp;
857  BOOL dupl = NO;
858  NSUInteger i;
859  NSUndoManager *uMgr;
860
861    uMgr = [self undoManager];
862    /* save the method on the undo stack */
863    [[uMgr prepareWithInvocationTarget: self] restoreLastObjects];
864    [uMgr setActionName:@"Move Object"];
865
866    [self saveCurrentObjectsDeep];
867
868    nextEvent = [[self window] nextEventMatchingMask:
869				 NSLeftMouseUpMask | NSLeftMouseDraggedMask];
870    if([nextEvent type] == NSLeftMouseDragged)
871      {
872        [self verifyModifiersOfEvent: nextEvent];
873        op.x = startp.x;
874        op.y = startp.y;
875
876        do
877	  {
878	    p = [nextEvent locationInWindow];
879	    p = [self convertPoint: p fromView: nil];
880	    p = GRpointDeZoom(p, zFactor);
881
882	    if(shiftclick)
883	      p = pointApplyingCostrainerToPoint(p, startp);
884
885	    if(altclick && !dupl)
886	      {
887		moveobjs = [self duplicateObjects: objs andMoveTo: NSMakePoint(0, 0)];
888		dupl = YES;
889	      }
890	    else if(!moveobjs)
891	      {
892		moveobjs = [NSArray arrayWithArray: objs];
893	      }
894
895	    diffp.x = p.x - op.x;
896	    diffp.y = p.y - op.y;
897	    for(i = 0; i < [moveobjs count]; i++)
898	      {
899		obj = [moveobjs objectAtIndex: i];
900		[obj moveAddingCoordsOfPoint: diffp];
901	      }
902	    op.x = p.x;
903	    op.y = p.y;
904	    [self setNeedsDisplay: YES];
905	    nextEvent = [[self window] nextEventMatchingMask:
906					 NSLeftMouseUpMask | NSLeftMouseDraggedMask];
907	    [self verifyModifiersOfEvent: nextEvent];
908	  }
909	while([nextEvent type] != NSLeftMouseUp);
910
911        if(dupl)
912	  {
913            diffp.x = p.x - startp.x;
914            diffp.y = p.y - startp.y;
915	  }
916	else
917	  {
918            diffp.x = startp.x - p.x;
919            diffp.y = startp.y - p.y;
920	  }
921      }
922}
923
924
925/* propagates control point editing down to each editor */
926- (BOOL)moveControlPointOfEditor:(GRPathEditor *)editor toPoint:(NSPoint)pos
927{
928  NSPoint p;
929
930  p = [editor moveControlAtPoint: pos];
931  if(p.x == pos.x && p.y == pos.y)
932    return NO;
933
934  [self setNeedsDisplay: YES];
935  return YES;
936}
937
938- (BOOL)moveBezierHandleOfEditor:(GRBezierPathEditor *)editor toPoint:(NSPoint)pos
939{
940  NSPoint p;
941
942  p = [editor moveBezierHandleAtPoint: pos];
943  if(p.x == pos.x && p.y == pos.y)
944    return NO;
945
946  [self setNeedsDisplay: YES];
947  return YES;
948}
949
950- (void)changePointsOfCurrentPathToSymmetric:(id)sender
951{
952  NSUndoManager *uMgr;
953  NSUInteger i;
954  NSArray *points;
955  GRBezierPath *path;
956
957  path = nil;
958  for(i = 0; i < [objects count]; i++)
959    {
960      GRDrawableObject *obj;
961      obj = [objects objectAtIndex: i];
962
963      if([[obj editor] isSelected] && [obj isKindOfClass:[GRBezierPath class]])
964        {
965          path = (GRBezierPath *)obj;
966        }
967    }
968
969  if (!path)
970    return;
971
972  points = [(GRBezierPathEditor *)[path editor] selectedControlPoints];
973  if (!points || [points count] == 0)
974    return;
975
976  uMgr = [self undoManager];
977  /* save the method on the undo stack, but stack actions */
978  if ([[uMgr undoActionName] isEqualToString: UNDO_ACTION_CP_SYMMETRIC] == NO)
979    {
980      [self saveCurrentObjectsDeep];
981      [[uMgr prepareWithInvocationTarget: self] restoreLastObjects];
982      [uMgr setActionName: UNDO_ACTION_CP_SYMMETRIC];
983    }
984
985  for (i = 0; i < [points count]; i++)
986    {
987      [(GRBezierControlPoint *)[points objectAtIndex: i] setSymmetricalHandles:YES];
988    }
989  [path remakePath];
990  [self setNeedsDisplay: YES];
991}
992
993- (void)changePointsOfCurrentPathToCusp:(id)sender
994{
995  NSUndoManager *uMgr;
996  NSUInteger i;
997  NSArray *points;
998  GRBezierPath *path;
999
1000  path = nil;
1001  for(i = 0; i < [objects count]; i++)
1002    {
1003      GRDrawableObject *obj;
1004      obj = [objects objectAtIndex: i];
1005
1006      if([[obj editor] isSelected] && [obj isKindOfClass:[GRBezierPath class]])
1007        {
1008          path = (GRBezierPath *)obj;
1009        }
1010    }
1011
1012  if (!path)
1013    return;
1014
1015  points = [(GRBezierPathEditor *)[path editor] selectedControlPoints];
1016  if (!points || [points count] == 0)
1017    return;
1018
1019  uMgr = [self undoManager];
1020  /* save the method on the undo stack, but stack actions */
1021  if ([[uMgr undoActionName] isEqualToString: UNDO_ACTION_CP_CUSP] == NO)
1022    {
1023      [self saveCurrentObjectsDeep];
1024      [[uMgr prepareWithInvocationTarget: self] restoreLastObjects];
1025      [uMgr setActionName: UNDO_ACTION_CP_CUSP];
1026    }
1027
1028  for (i = 0; i < [points count]; i++)
1029    {
1030      [(GRBezierControlPoint *)[points objectAtIndex: i] setSymmetricalHandles:NO];
1031    }
1032  [path remakePath];
1033  [self setNeedsDisplay:YES];
1034}
1035
1036- (void)changePointsOfCurrentPathByOverlap:(id)sender
1037{
1038  NSUndoManager *uMgr;
1039  NSUInteger i;
1040  NSArray *points;
1041  GRBezierPath *path;
1042
1043  path = nil;
1044  for(i = 0; i < [objects count]; i++)
1045    {
1046      GRDrawableObject *obj;
1047      obj = [objects objectAtIndex: i];
1048
1049      if([[obj editor] isSelected] && [obj isKindOfClass:[GRBezierPath class]])
1050        {
1051          path = (GRBezierPath *)obj;
1052        }
1053    }
1054
1055  if (!path)
1056    return;
1057
1058  points = [(GRBezierPathEditor *)[path editor] selectedControlPoints];
1059  if (!points || [points count] == 0)
1060    return;
1061
1062  uMgr = [self undoManager];
1063  /* save the method on the undo stack, but stack actions */
1064  if ([[uMgr undoActionName] isEqualToString: UNDO_ACTION_CP_OVERLAP] == NO)
1065    {
1066      [self saveCurrentObjectsDeep];
1067      [[uMgr prepareWithInvocationTarget: self] restoreLastObjects];
1068      [uMgr setActionName: UNDO_ACTION_CP_OVERLAP];
1069    }
1070
1071  for (i = 0; i < [points count]; i++)
1072    {
1073      [(GRBezierControlPoint *)[points objectAtIndex: i] overlapHandles];
1074    }
1075  [path remakePath];
1076  [self setNeedsDisplay: YES];
1077}
1078
1079- (void)changePointsOfCurrentPathByExtract:(id)sender
1080{
1081  NSUndoManager *uMgr;
1082  NSUInteger i;
1083  NSArray *points;
1084  GRBezierPath *path;
1085
1086  path = nil;
1087  for(i = 0; i < [objects count]; i++)
1088    {
1089      GRDrawableObject *obj;
1090      obj = [objects objectAtIndex: i];
1091
1092      if([[obj editor] isSelected] && [obj isKindOfClass:[GRBezierPath class]])
1093        {
1094          path = (GRBezierPath *)obj;
1095        }
1096    }
1097
1098  if (!path)
1099    return;
1100
1101  points = [(GRBezierPathEditor *)[path editor] selectedControlPoints];
1102  if (!points || [points count] == 0)
1103    return;
1104
1105  uMgr = [self undoManager];
1106  /* save the method on the undo stack, but stack actions */
1107  if ([[uMgr undoActionName] isEqualToString: UNDO_ACTION_CP_EXTRACT] == NO)
1108    {
1109      [self saveCurrentObjectsDeep];
1110      [[uMgr prepareWithInvocationTarget: self] restoreLastObjects];
1111      [uMgr setActionName: UNDO_ACTION_CP_EXTRACT];
1112    }
1113
1114  for (i = 0; i < [points count]; i++)
1115    {
1116      [(GRBezierControlPoint *)[points objectAtIndex: i] extractHandles];
1117    }
1118  [path remakePath];
1119  [self setNeedsDisplay: YES];
1120}
1121
1122- (IBAction)deletePointsOfCurrentPath:(id)sender
1123{
1124  NSUndoManager *uMgr;
1125  NSUInteger i;
1126  NSArray *points;
1127  GRBezierPath *path;
1128
1129  path = nil;
1130  for(i = 0; i < [objects count]; i++)
1131    {
1132      GRDrawableObject *obj;
1133      obj = [objects objectAtIndex: i];
1134
1135      if([[obj editor] isSelected] && [obj isKindOfClass:[GRBezierPath class]])
1136	{
1137	  path = (GRBezierPath *)obj;
1138	}
1139    }
1140
1141  if (!path)
1142    return;
1143
1144  points = [(GRBezierPathEditor *)[path editor] selectedControlPoints];
1145  if (!points || [points count] == 0)
1146    return;
1147
1148  uMgr = [self undoManager];
1149  /* save the method on the undo stack, but stack actions */
1150  if ([[uMgr undoActionName] isEqualToString: UNDO_ACTION_CP_DELETE] == NO)
1151    {
1152      [self saveCurrentObjectsDeep];
1153      [[uMgr prepareWithInvocationTarget: self] restoreLastObjects];
1154      [uMgr setActionName: UNDO_ACTION_CP_DELETE];
1155    }
1156
1157  for (i = 0; i < [points count]; i++)
1158    {
1159      [path deletePoint: [points objectAtIndex: i]];
1160    }
1161  [path remakePath];
1162  [self setNeedsDisplay: YES];
1163}
1164
1165
1166- (void)subdividePathAtPoint:(NSPoint)p splitIt:(BOOL)split
1167{
1168  id obj;
1169  NSUInteger i;
1170
1171
1172  for(i = 0; i < [objects count]; i++)
1173    {
1174      obj = [objects objectAtIndex: i];
1175      if([obj isKindOfClass: [GRBezierPath class]] && [[obj editor] isSelected])
1176        {
1177	  if([obj onPathBorder: p])
1178            {
1179	      [obj subdividePathAtPoint: p splitIt: split];
1180	      break;
1181            }
1182        }
1183    }
1184  [self setNeedsDisplay:YES];
1185}
1186
1187- (NSDictionary *)selectionProperties
1188{
1189  NSMutableDictionary *propDict;
1190  NSUInteger i;
1191  NSUInteger selectedObjects;
1192  NSUInteger circleObjNum;
1193  NSUInteger boxObjNum;
1194  NSUInteger bezObjNum;
1195  NSUInteger pathObjNum;
1196  NSUInteger textObjNum;
1197  NSNumber *num;
1198
1199  if(![objects count])
1200    return nil;
1201
1202  selectedObjects = 0;
1203  circleObjNum = 0;
1204  boxObjNum = 0;
1205  bezObjNum = 0;
1206  pathObjNum = 0;
1207  textObjNum = 0;
1208  propDict = [NSMutableDictionary dictionaryWithCapacity: 1];
1209  for(i = 0; i < [objects count]; i++)
1210    {
1211      id obj;
1212      obj = [objects objectAtIndex: i];
1213
1214      if([[obj editor] isSelected])
1215        {
1216          selectedObjects++;
1217
1218          num = [NSNumber numberWithBool: [obj isStroked]];
1219          [propDict setObject: num forKey: @"stroked"];
1220          [propDict setObject: [obj strokeColor] forKey: @"strokecolor"];
1221          num = [NSNumber numberWithBool: [obj isFilled]];
1222          [propDict setObject: num forKey: @"filled"];
1223          [propDict setObject: [obj fillColor] forKey: @"fillcolor"];
1224
1225          if([obj isKindOfClass: [GRPathObject class]])
1226            {
1227              pathObjNum++;
1228              num = [NSNumber numberWithFloat: [obj flatness]];
1229              [propDict setObject: num forKey: @"flatness"];
1230              num = [NSNumber numberWithInt: [obj lineJoin]];
1231              [propDict setObject: num forKey: @"linejoin"];
1232              num = [NSNumber numberWithInt: [obj lineCap]];
1233              [propDict setObject: num forKey: @"linecap"];
1234              num = [NSNumber numberWithFloat: [obj miterLimit]];
1235              [propDict setObject: num forKey: @"miterlimit"];
1236              num = [NSNumber numberWithFloat: [obj lineWidth]];
1237              [propDict setObject: num forKey: @"linewidth"];
1238              if([obj isKindOfClass: [GRCircle class]])
1239                {
1240                  circleObjNum++;
1241                  num = [NSNumber numberWithBool: [obj circle]];
1242                  [propDict setObject: num forKey: @"circle"];
1243                }
1244              else if([obj isKindOfClass: [GRBox class]])
1245                {
1246                  boxObjNum++;
1247                }
1248              else if([obj isKindOfClass: [GRBezierPath class]])
1249                {
1250                  bezObjNum++;
1251                }
1252            }
1253          else if([obj isKindOfClass: [GRText class]])
1254            {
1255              textObjNum++;
1256              [propDict setObject: @"text" forKey: @"type"];
1257            }
1258        }
1259    }
1260
1261  if(selectedObjects == 0)
1262    return nil;
1263
1264  if (textObjNum + pathObjNum != selectedObjects)
1265    {
1266      NSLog(@"Internal error: Help we lost some objects.");
1267    }
1268
1269  /* we check if the selection is homogeneous or not
1270     and in case remove the keys that are not common among all objects */
1271  if (textObjNum > 0 && pathObjNum > 0)
1272    {
1273      [propDict removeObjectForKey: @"flatness"];
1274      [propDict removeObjectForKey: @"linejoin"];
1275      [propDict removeObjectForKey: @"linecap"];
1276      [propDict removeObjectForKey: @"miterlimit"];
1277      [propDict removeObjectForKey: @"linewidth"];
1278    }
1279
1280  return propDict;
1281}
1282
1283- (void)setSelectionProperties: (NSDictionary *)properties;
1284{
1285  NSUndoManager *uMgr;
1286  id obj;
1287  NSUInteger i;
1288
1289  uMgr = [self undoManager];
1290  /* save the method on the undo stack, but stack actions */
1291  if ([[uMgr undoActionName] isEqualToString: UNDO_ACTION_OBJPROPS] == NO)
1292    {
1293      [self saveCurrentObjectsDeep];
1294      [[uMgr prepareWithInvocationTarget: self] restoreLastObjects];
1295      [uMgr setActionName: UNDO_ACTION_OBJPROPS];
1296    }
1297
1298  for(i = 0; i < [objects count]; i++)
1299    {
1300      NSNumber *num;
1301
1302      obj = [objects objectAtIndex: i];
1303      if([[obj editor] isSelected])
1304        {
1305          NSColor *newColor;
1306
1307          if([obj isKindOfClass: [GRBezierPath class]] || [obj isKindOfClass: [GRBox class]] || [obj isKindOfClass: [GRCircle class]])
1308            {
1309              num = [properties objectForKey: @"flatness"];
1310              if (num)
1311                [obj setFlat: [num floatValue]];
1312
1313              num = [properties objectForKey: @"linejoin"];
1314              if (num)
1315                [obj setLineJoin: [num intValue]];
1316
1317              num = [properties objectForKey: @"linecap"];
1318              if (num)
1319                [obj setLineCap: [num intValue]];
1320
1321              num = [properties objectForKey: @"miterlimit"];
1322              if (num)
1323                [obj setMiterLimit: [num floatValue]];
1324
1325              num = [properties objectForKey: @"linewidth"];
1326              if (num)
1327                [obj setLineWidth: [num floatValue]];
1328            }
1329          num = [properties objectForKey: @"stroked"];
1330          if (num)
1331            [obj setStroked: [num boolValue]];
1332
1333          newColor = (NSColor *)[properties objectForKey: @"strokecolor"];
1334          if (newColor)
1335            [obj setStrokeColor: newColor];
1336
1337          num = [properties objectForKey: @"filled"];
1338          if (num)
1339            [obj setFilled: [num boolValue]];
1340
1341          newColor = (NSColor *)[properties objectForKey: @"fillcolor"];
1342          if (newColor)
1343            [obj setFillColor: newColor];
1344        }
1345    }
1346  [self setNeedsDisplay: YES];
1347}
1348
1349- (void)moveSelectedObjectsToFront:(id)sender
1350{
1351  id obj = nil;
1352  NSUInteger i;
1353  NSUndoManager *uMgr;
1354
1355  uMgr = [self undoManager];
1356  /* save the method on the undo stack */
1357  [[uMgr prepareWithInvocationTarget: self] restoreLastObjects];
1358  [uMgr setActionName:@"Move to front"];
1359
1360  [self saveCurrentObjectsDeep];
1361
1362  for(i = 0; i < [objects count]; i++)
1363    {
1364      if([[[objects objectAtIndex: i] editor] isGroupSelected])
1365        {
1366          obj = [[objects objectAtIndex: i] retain];
1367          break;
1368        }
1369    }
1370  if(!obj)
1371    return;
1372
1373  for(i = 0; i < [objects count]; i++)
1374    if([objects objectAtIndex: i] != obj)
1375      [[[objects objectAtIndex: i] editor] unselect];
1376
1377  for(i = 0; i < [objects count]; i++)
1378    {
1379      if((obj == [objects objectAtIndex: i]) && (i + 1 < [objects count]))
1380        {
1381          [objects removeObjectAtIndex: i];
1382          [objects insertObject: obj atIndex: i + 1];
1383          break;
1384        }
1385    }
1386  [obj release];
1387  [self setNeedsDisplay: YES];
1388}
1389
1390- (void)moveSelectedObjectsToBack:(id)sender
1391{
1392  id obj = nil;
1393  NSUInteger i;
1394  NSUndoManager *uMgr;
1395
1396  uMgr = [self undoManager];
1397  /* save the method on the undo stack */
1398  [[uMgr prepareWithInvocationTarget: self] restoreLastObjects];
1399  [uMgr setActionName:@"Move to back"];
1400
1401  [self saveCurrentObjects];
1402
1403  for(i = 0; i < [objects count]; i++)
1404    {
1405      if([[[objects objectAtIndex: i] editor] isGroupSelected])
1406        {
1407          obj = [[objects objectAtIndex: i] retain];
1408          break;
1409        }
1410    }
1411  if(!obj)
1412    return;
1413
1414  for(i = 0; i < [objects count]; i++)
1415    if([objects objectAtIndex: i] != obj)
1416      [[[objects objectAtIndex: i] editor] unselect];
1417
1418  for(i = 0; i < [objects count]; i++)
1419    {
1420      if((obj == [objects objectAtIndex: i]) && (i > 0))
1421        {
1422          [objects removeObjectAtIndex: i];
1423          [objects insertObject: obj atIndex: i - 1];
1424          break;
1425        }
1426    }
1427  [obj release];
1428  [self setNeedsDisplay: YES];
1429}
1430
1431- (void)unselectOtherObjects:(GRDrawableObject *)anObject
1432{
1433  id obj;
1434  NSUInteger i;
1435
1436  if(shiftclick)
1437    return;
1438
1439  for(i = 0; i < [objects count]; i++)
1440    {
1441      obj = [objects objectAtIndex: i];
1442      if(obj != anObject)
1443        [[obj editor] unselect];
1444    }
1445
1446  [self setNeedsDisplay: YES];
1447}
1448
1449- (void)zoomOnPoint:(NSPoint)p zoomOut:(BOOL)isout
1450{
1451  NSUInteger i;
1452
1453  i = zIndex;
1454  if(isout)
1455    {
1456      if (i == 0)
1457	return;
1458      i--;
1459    }
1460  else
1461    {
1462      if (i == ZOOM_FACTORS-1)
1463	return;
1464      i++;
1465    }
1466
1467  [self zoomOnPoint:p withFactor:i];
1468}
1469
1470
1471- (void)zoomOnPoint:(NSPoint)p withFactor:(int)index
1472{
1473  CGFloat orx, ory, szx, szy;
1474  NSRect vr;
1475  NSPoint pp;
1476  NSUInteger i;
1477
1478  zIndex = index;
1479  zFactor = zFactors[zIndex];
1480
1481  orx = a4Rect.origin.x * zFactor;
1482  ory = a4Rect.origin.y * zFactor;
1483  szx = a4Rect.size.width * zFactor;
1484  szy = a4Rect.size.height * zFactor;
1485  zmdRect = NSMakeRect(orx, ory, szx, szy);
1486  szx = pageRect.size.width * zFactor;
1487  szy = pageRect.size.height * zFactor;
1488
1489  pp.x = p.x * ([self frame].origin.x + pageRect.size.width) / [self frame].size.width;
1490  pp.y = p.y * ([self frame].origin.y + pageRect.size.height) / [self frame].size.height;
1491  vr = NSMakeRect(pp.x * zFactor - 200, pp.y * zFactor - 200, 400, 400);
1492  [self setFrame: NSMakeRect(0, 0, szx, szy)];
1493  [self scrollRectToVisible: vr];
1494
1495  for(i = 0; i < [objects count]; i++)
1496    [[objects objectAtIndex: i] setZoomFactor: zFactor];
1497
1498  [[self window] display];
1499}
1500
1501- (IBAction)zoom50:(id)sender
1502{
1503  NSUInteger i;
1504  NSPoint p;
1505  NSRect visibleRect;
1506
1507  zFactor = 0.5;
1508
1509  i = ZOOM_FACTORS - 1;
1510  while (i > 0 && zFactor < zFactors[i])
1511    i--;
1512
1513  visibleRect = [self visibleRect];
1514  p.x = NSMinX(visibleRect) + NSWidth(visibleRect) / 2;
1515  p.y = NSMinY(visibleRect) + NSHeight(visibleRect) / 2;
1516
1517  [self zoomOnPoint:p withFactor:i];
1518}
1519
1520- (IBAction)zoom100:(id)sender
1521{
1522  NSUInteger  i;
1523  NSPoint p;
1524  NSRect visibleRect;
1525
1526  zFactor = 1;
1527
1528  i = ZOOM_FACTORS - 1;
1529  while (i > 0 && zFactor < zFactors[i])
1530    i--;
1531
1532  visibleRect = [self visibleRect];
1533  p.x = NSMinX(visibleRect) + NSWidth(visibleRect) / 2;
1534  p.y = NSMinY(visibleRect) + NSHeight(visibleRect) / 2;
1535
1536  [self zoomOnPoint:p withFactor:i];
1537}
1538
1539- (IBAction)zoom200:(id)sender
1540{
1541  NSUInteger i;
1542  NSPoint p;
1543  NSRect visibleRect;
1544
1545  zFactor = 2;
1546
1547  i = ZOOM_FACTORS - 1;
1548  while (i > 0 && zFactor < zFactors[i])
1549    i--;
1550
1551  visibleRect = [self visibleRect];
1552  p.x = NSMinX(visibleRect) + NSWidth(visibleRect) / 2;
1553  p.y = NSMinY(visibleRect) + NSHeight(visibleRect) / 2;
1554
1555  [self zoomOnPoint:p withFactor:i];
1556}
1557
1558- (IBAction)zoomFitPage:(id)sender
1559{
1560  NSUInteger i;
1561  NSPoint p;
1562  NSRect visibleRect;
1563  NSRect docVRect;
1564  CGFloat fWidth;
1565  CGFloat fHeight;
1566
1567  fWidth = [self frame].size.width / zFactor;
1568  fHeight = [self frame].size.height / zFactor;
1569  docVRect = [(NSClipView*)[self superview] documentVisibleRect];
1570  i = ZOOM_FACTORS - 1;
1571  while (i > 0 && ((docVRect.size.width < fWidth * zFactors[i]) || (docVRect.size.height < fHeight * zFactors[i])))
1572    i--;
1573
1574  visibleRect = [self visibleRect];
1575  p.x = NSMinX(visibleRect) + NSWidth(visibleRect) / 2;
1576  p.y = NSMinY(visibleRect) + NSHeight(visibleRect) / 2;
1577
1578  [self zoomOnPoint:p withFactor:i];
1579}
1580
1581- (IBAction)zoomFitWidth:(id)sender
1582{
1583  NSUInteger i;
1584  NSPoint p;
1585  NSRect visibleRect;
1586  NSRect docVRect;
1587  CGFloat fWidth;
1588
1589  fWidth = [self frame].size.width / zFactor;
1590  docVRect = [(NSClipView*)[self superview] documentVisibleRect];
1591  i = ZOOM_FACTORS - 1;
1592  while (i > 0 && docVRect.size.width < fWidth * zFactors[i])
1593    i--;
1594
1595  visibleRect = [self visibleRect];
1596  p.x = NSMinX(visibleRect) + NSWidth(visibleRect) / 2;
1597  p.y = NSMinY(visibleRect) + NSHeight(visibleRect) / 2;
1598
1599  [self zoomOnPoint:p withFactor:i];
1600}
1601
1602- (void)movePageFromHandPoint:(NSPoint)handpos
1603{
1604  NSEvent *nextEvent;
1605  NSPoint p, diffp;
1606  NSRect r;
1607
1608  nextEvent = [[self window] nextEventMatchingMask:
1609                               NSLeftMouseUpMask | NSLeftMouseDraggedMask];
1610  if([nextEvent type] == NSLeftMouseDragged)
1611    {
1612      do
1613        {
1614          p = [nextEvent locationInWindow];
1615          p = [self convertPoint: p fromView: nil];
1616          p = GRpointDeZoom(p, zFactor);
1617
1618          diffp.x = p.x - handpos.x;
1619          diffp.y = p.y - handpos.y;
1620          r = [self visibleRect];
1621          r = NSMakeRect(r.origin.x - diffp.x, r.origin.y - diffp.y,
1622                         r.size.width, r.size.height);
1623          [self scrollRectToVisible: r];
1624          nextEvent = [[self window] nextEventMatchingMask:
1625                                       NSLeftMouseUpMask | NSLeftMouseDraggedMask];
1626        }
1627      while([nextEvent type] != NSLeftMouseUp);
1628      [[self window] display];
1629    }
1630}
1631
1632- (void)delete:(id)sender
1633{
1634  NSUndoManager *uMgr;
1635
1636  uMgr = [self undoManager];
1637  /* save the method on the undo stack */
1638  [[uMgr prepareWithInvocationTarget: self] restoreLastObjects];
1639  [uMgr setActionName:@"Delete"];
1640
1641  [self saveCurrentObjects];
1642
1643  [self deleteSelectedObjects];
1644}
1645
1646- (void)cut:(id)sender
1647{
1648  NSUndoManager *uMgr;
1649
1650  uMgr = [self undoManager];
1651  /* save the method on the undo stack */
1652  [[uMgr prepareWithInvocationTarget: self] restoreLastObjects];
1653  [uMgr setActionName:@"Cut"];
1654
1655  [self saveCurrentObjects];
1656  [self copy: sender];
1657  [self deleteSelectedObjects];
1658}
1659
1660- (void)copy:(id)sender
1661{
1662  NSMutableArray *types;
1663  NSPasteboard *pboard;
1664  GRDrawableObject *dObj;
1665  NSMutableArray *objsdesc;
1666  NSUInteger i;
1667
1668  dObj = nil;
1669  objsdesc = [NSMutableArray arrayWithCapacity: 1];
1670  for(i = 0; i < [objects count]; i++)
1671    {
1672      dObj = [objects objectAtIndex: i];
1673      if([[dObj editor] isGroupSelected])
1674	[objsdesc addObject: [dObj objectDescription]];
1675    }
1676
1677  if([objsdesc count])
1678    {
1679      types = [NSMutableArray arrayWithObjects: @"GRObjectPboardType", nil];
1680      pboard = [NSPasteboard generalPasteboard];
1681      [pboard declareTypes: types owner: self];
1682      [pboard setString:[objsdesc description] forType: @"GRObjectPboardType"];
1683
1684      /* if we have just a single image selected, also add a standard TIFF image */
1685      if ([objsdesc count] == 1 && [dObj isKindOfClass:[GRImage class]])
1686        {
1687          GRImage *grImg;
1688
1689          [types addObject:NSTIFFPboardType];
1690          grImg = (GRImage *)dObj;
1691          [pboard setData:[[grImg image] TIFFRepresentation] forType:NSTIFFPboardType];
1692        }
1693    }
1694}
1695
1696- (void)paste:(id)sender
1697{
1698  NSPasteboard *pboard;
1699  NSArray *grTypes;
1700  NSString *imgType;
1701  NSArray *descriptions;
1702  NSDictionary *objdesc;
1703  id obj;
1704  NSString *str;
1705  NSUInteger i;
1706  NSUndoManager *uMgr;
1707
1708  // FIXME we should push the undo stack only if copy is certain to succeed
1709  uMgr = [self undoManager];
1710  /* save the method on the undo stack */
1711  [[uMgr prepareWithInvocationTarget: self] restoreLastObjects];
1712  [uMgr setActionName:@"Paste"];
1713
1714  [self saveCurrentObjects];
1715
1716  pboard = [NSPasteboard generalPasteboard];
1717  grTypes = [NSArray arrayWithObject: @"GRObjectPboardType"];
1718  imgType = [pboard availableTypeFromArray:[NSArray arrayWithObjects:NSTIFFPboardType, nil]];
1719  NSLog(@"%@", [pboard types]);
1720  if([[pboard availableTypeFromArray: grTypes] isEqualToString: @"GRObjectPboardType"])
1721    {
1722      descriptions = [[pboard stringForType: @"GRObjectPboardType"] propertyList];
1723
1724      for(i = 0; i < [descriptions count]; i++)
1725        {
1726          objdesc = [descriptions objectAtIndex: i];
1727          str = [objdesc objectForKey: @"type"];
1728
1729          obj = nil;
1730          if([str isEqualToString: @"path"])
1731            obj = [GRBezierPath alloc];
1732          else if([str isEqualToString: @"text"])
1733            obj = [GRText alloc];
1734          else if([str isEqualToString: @"box"])
1735            obj = [GRBox alloc];
1736          else if([str isEqualToString: @"circle"])
1737            obj = [GRCircle alloc];
1738          else if([str isEqualToString: @"image"])
1739            obj = [GRImage alloc];
1740          else
1741            NSLog(@"Unknown object to paste");
1742          if (obj != nil)
1743            {
1744              obj = [obj initFromData: objdesc
1745                               inView: self zoomFactor: zFactor];
1746              [objects addObject: obj];
1747              [[obj editor] selectAsGroup];
1748              [obj release];
1749              [self setNeedsDisplay: YES];
1750            }
1751        }
1752    }
1753  else if ([imgType isEqualToString:NSTIFFPboardType])
1754    {
1755      NSImage *tmpNSImage;
1756      GRImage *tmpGRImage;
1757      NSMutableDictionary *properties;
1758      NSString *str;
1759      NSPoint pos;
1760      NSSize size;
1761
1762      tmpNSImage = [[NSImage alloc] initWithData:[pboard dataForType:NSTIFFPboardType]];
1763
1764      size = [tmpNSImage size];
1765      pos = NSMakePoint(50, pageRect.size.height-50);
1766      properties = [[NSMutableDictionary alloc] init];
1767      [properties autorelease];
1768      str = [NSString stringWithFormat: @"%.3f", pos.x];
1769      [properties setObject: str forKey: @"posx"];
1770      str = [NSString stringWithFormat: @"%.3f", pos.y];
1771      [properties setObject: str forKey: @"posy"];
1772
1773      str = [NSString stringWithFormat: @"%.3f", size.width];
1774      [properties setObject: str forKey: @"width"];
1775      str = [NSString stringWithFormat: @"%.3f", -size.height];
1776      [properties setObject: str forKey: @"height"];
1777
1778      tmpGRImage = [[GRImage alloc] initInView: self zoomFactor:zFactor withProperties:properties];
1779      [tmpGRImage setImage:tmpNSImage];
1780      [objects addObject:tmpGRImage];
1781      [tmpGRImage release];
1782      [tmpNSImage release];
1783      [self setNeedsDisplay: YES];
1784    }
1785}
1786
1787/* ----- Undo Methods ----- */
1788
1789- (NSMutableArray *)deepCopyObjects: (NSMutableArray *)objArray
1790{
1791  NSMutableArray *copyArray;
1792  NSEnumerator *e;
1793  NSObject *o;
1794
1795
1796  copyArray = [NSMutableArray arrayWithCapacity:[objArray count]];
1797
1798  e = [objArray objectEnumerator];
1799  while ((o = [e nextObject]))
1800    {
1801      [copyArray addObject:[[o copy] autorelease]];
1802    }
1803
1804  return copyArray;
1805}
1806
1807- (void)saveCurrentObjects
1808{
1809  if (objects != nil)
1810    {
1811      if (lastObjects != nil)
1812        [lastObjects release];
1813      lastObjects = [[NSMutableArray arrayWithArray:objects] retain];
1814    }
1815}
1816
1817- (void)saveCurrentObjectsDeep
1818{
1819  if (objects != nil)
1820    {
1821      if (lastObjects != nil)
1822        [lastObjects release];
1823      lastObjects = [[self deepCopyObjects: objects] retain];
1824    }
1825}
1826
1827
1828- (void)restoreLastObjects
1829{
1830    NSMutableArray *tempObjects;
1831
1832    /* backup the current status */
1833    tempObjects = [NSMutableArray arrayWithArray:objects];
1834    [objects release];
1835
1836    /* re-register for redo */
1837    [[[self undoManager] prepareWithInvocationTarget: self] restoreLastObjects];
1838
1839    /* get the last status */
1840    objects = [lastObjects retain];
1841    [lastObjects release];
1842
1843    /* set the last status to the backup */
1844    lastObjects = [tempObjects retain];
1845    [self setNeedsDisplay: YES];
1846}
1847
1848/* ----- Mouse Methods ----- */
1849
1850- (void)verifyModifiersOfEvent:(NSEvent *)theEvent
1851{
1852    if([theEvent type] == NSLeftMouseDown
1853       || [theEvent type] == NSLeftMouseDragged)
1854    {
1855        if([theEvent modifierFlags] & NSShiftKeyMask)
1856            shiftclick = YES;
1857        else
1858            shiftclick = NO;
1859        if([theEvent modifierFlags] & NSAlternateKeyMask)
1860            altclick = YES;
1861        else
1862            altclick = NO;
1863        if([theEvent modifierFlags] & NSControlKeyMask)
1864            ctrlclick = YES;
1865        else
1866            ctrlclick = NO;
1867    }
1868
1869    else if([theEvent type] == NSLeftMouseUp)
1870    {
1871        if(!([theEvent modifierFlags] & NSShiftKeyMask))
1872            shiftclick = NO;
1873        if(!([theEvent modifierFlags] & NSAlternateKeyMask))
1874            altclick = NO;
1875        if(!([theEvent modifierFlags] & NSControlKeyMask))
1876            ctrlclick = NO;
1877    }
1878}
1879
1880- (BOOL)shiftclick
1881{
1882    return shiftclick;
1883}
1884
1885- (BOOL)altclick
1886{
1887    return altclick;
1888}
1889
1890- (BOOL)ctrlclick
1891{
1892    return ctrlclick;
1893}
1894
1895- (void)mouseDown:(NSEvent *)theEvent
1896{
1897  NSPoint p;
1898  NSUInteger count = [theEvent clickCount];
1899
1900  [self verifyModifiersOfEvent: theEvent];
1901
1902  p = [theEvent locationInWindow];
1903  p = [self convertPoint: p fromView: nil];
1904  p = GRpointDeZoom(p, zFactor);
1905
1906  if(count == 1)
1907    {
1908      switch([[NSApp delegate] currentToolType])
1909        {
1910            case blackarrowtool:
1911                [self selectObjectAtPoint: p];
1912                break;
1913            case whitearrowtool:
1914                [self editPathAtPoint: p];
1915                break;
1916            case beziertool:
1917                [self startDrawingAtPoint: p];
1918                break;
1919            case texttool:
1920                [self addTextAtPoint: p];
1921                break;
1922            case circletool:
1923                [self startCircleAtPoint: p];
1924                break;
1925            case rectangletool:
1926                [self startBoxAtPoint: p];
1927                break;
1928            case painttool:
1929
1930                break;
1931            case penciltool:
1932
1933                break;
1934            case rotatetool:
1935
1936                break;
1937            case reducetool:
1938
1939                break;
1940            case reflecttool:
1941
1942                break;
1943            case scissorstool:
1944                if(altclick)
1945                    [self subdividePathAtPoint: p splitIt: NO];
1946                else
1947                    [self subdividePathAtPoint: p splitIt: YES];
1948                break;
1949            case handtool:
1950                [self movePageFromHandPoint: p];
1951                break;
1952            case magnifytool:
1953                if(altclick)
1954                    [self zoomOnPoint: p zoomOut: YES];
1955                else
1956                    [self zoomOnPoint: p zoomOut: NO];
1957                break;
1958            default:
1959                break;
1960        }
1961    }
1962  else
1963    {
1964      if([[NSApp delegate] currentToolType] == blackarrowtool)
1965	[self editTextAtPoint: p];
1966    }
1967}
1968
1969- (void)mouseUp:(NSEvent *)theEvent
1970{
1971    [self verifyModifiersOfEvent: theEvent];
1972}
1973
1974
1975/** respond to key equivalents which are not bound to menu items */
1976-(BOOL)performKeyEquivalent: (NSEvent*)theEvent
1977{
1978  NSString *keyStr;
1979  unichar keyCh;
1980  NSRect vRect, hiddRect;
1981  NSPoint vPoint;
1982  CGFloat hiddRx, hiddRy, hiddRw, hiddRh;
1983
1984#ifdef __APPLE__
1985  /* Apple is definitively broken here and on all versions tested it returns for the arrow key
1986     also a KeyUp event, which it should not, as the Event is specified to be keyDown */
1987  if ([theEvent type] == NSKeyUp)
1988    return [super performKeyEquivalent:theEvent];
1989#endif
1990
1991  keyCh = 0x0;
1992  keyStr = [theEvent characters];
1993  if ([keyStr length] > 0)
1994    keyCh = [keyStr characterAtIndex:0];
1995
1996  if (keyCh == NSDeleteFunctionKey || keyCh == NSDeleteCharacter)
1997    {
1998      [self delete:self];
1999      return YES;
2000    }
2001  else if(keyCh == NSPageUpFunctionKey)
2002    {
2003      vRect = [self visibleRect];
2004      vPoint = vRect.origin;
2005      hiddRx = vPoint.x;
2006      hiddRy = vPoint.y + vRect.size.height;
2007      hiddRw = vRect.size.width;
2008      hiddRh = vRect.size.height;
2009      hiddRect = NSMakeRect(hiddRx, hiddRy, hiddRw, hiddRh);
2010      [self scrollRectToVisible: hiddRect];
2011      return YES;
2012    }
2013  else if(keyCh == NSPageDownFunctionKey)
2014    {
2015      vRect = [self visibleRect];
2016      vPoint = vRect.origin;
2017      hiddRx = vPoint.x;
2018      hiddRy = vPoint.y - vRect.size.height;
2019      hiddRw = vRect.size.width;
2020      hiddRh = vRect.size.height;
2021      hiddRect = NSMakeRect(hiddRx, hiddRy, hiddRw, hiddRh);
2022      [self scrollRectToVisible: hiddRect];
2023      return YES;
2024    }
2025  else if (keyCh == NSLeftArrowFunctionKey)
2026    {
2027      NSLog(@"arrow left");
2028      return YES;
2029    }
2030  else if (keyCh == NSUpArrowFunctionKey)
2031    {
2032      NSLog(@"arrow up");
2033      return YES;
2034    }
2035  else if (keyCh == NSRightArrowFunctionKey)
2036    {
2037      NSLog(@"right left");
2038      return YES;
2039    }
2040  else if (keyCh == NSDownArrowFunctionKey)
2041    {
2042      NSLog(@"down left");
2043      return YES;
2044    }
2045  else
2046    NSLog(@"keyCh %x", keyCh);
2047  return [super performKeyEquivalent:theEvent];
2048}
2049
2050- (BOOL)validateMenuItem:(NSMenuItem *)mi
2051{
2052  NSUInteger i;
2053  NSUInteger selectedPaths;
2054  NSUInteger selectedObjs;
2055  SEL action;
2056
2057  selectedPaths = 0;
2058  selectedObjs = 0;
2059
2060  if ([[[mi menu] title] isEqualToString:@"Zoom"])
2061    return YES;
2062
2063  for(i = 0; i < [objects count]; i++)
2064    {
2065      GRDrawableObject *obj;
2066      obj = [objects objectAtIndex: i];
2067
2068      if([[obj editor] isSelected])
2069        {
2070          selectedObjs++;
2071          if ([obj isKindOfClass:[GRBezierPath class]])
2072            selectedPaths++;
2073        }
2074    }
2075
2076  action = [mi action];
2077  if (sel_isEqual(action, @selector(paste:)))
2078    return YES;
2079
2080  if (sel_isEqual(action, @selector(copy:)) || sel_isEqual(action, @selector(cut:)) || sel_isEqual(action, @selector(delete:)))
2081    {
2082      if (selectedObjs)
2083        return YES;
2084      else
2085        return NO;
2086    }
2087
2088  if (sel_isEqual(action, @selector(changePointsOfCurrentPathToSymmetric:)))
2089    {
2090      if ([[NSApp delegate] currentToolType] == whitearrowtool && (selectedPaths == 1))
2091        {
2092          return YES;
2093        }
2094      else
2095        return NO;
2096    }
2097
2098  if (sel_isEqual(action, @selector(changePointsOfCurrentPathToCusp:)))
2099    {
2100      if ([[NSApp delegate] currentToolType] == whitearrowtool && (selectedPaths == 1))
2101        {
2102          return YES;
2103        }
2104      else
2105        return NO;
2106    }
2107
2108  if (sel_isEqual(action, @selector(changePointsOfCurrentPathByOverlap:)))
2109    {
2110      if ([[NSApp delegate] currentToolType] == whitearrowtool && (selectedPaths == 1))
2111        {
2112          return YES;
2113        }
2114      else
2115        return NO;
2116    }
2117
2118  if (sel_isEqual(action, @selector(changePointsOfCurrentPathByExtract:)))
2119    {
2120      if ([[NSApp delegate] currentToolType] == whitearrowtool && (selectedPaths == 1))
2121        {
2122          return YES;
2123        }
2124      else
2125        return NO;
2126    }
2127
2128  if (sel_isEqual(action, @selector(deletePointsOfCurrentPath:)))
2129    {
2130      if ([[NSApp delegate] currentToolType] == whitearrowtool && (selectedPaths == 1))
2131        {
2132          return YES;
2133        }
2134      else
2135        return NO;
2136    }
2137  if (sel_isEqual(action, @selector(moveSelectedObjectsToFront:)) || sel_isEqual(action, @selector(moveSelectedObjectsToBack:)))
2138    {
2139      if (selectedObjs)
2140        return YES;
2141      else
2142        return NO;
2143    }
2144  return NO;
2145}
2146
2147/* override the menu for special context menus
2148
2149   While editing a path, display the handle options
2150 */
2151- (NSMenu *) menuForEvent: (NSEvent*)theEvent
2152{
2153  if ([[NSApp delegate] currentToolType] == whitearrowtool)
2154    {
2155      NSUInteger i;
2156      NSUInteger selectedPaths;
2157
2158      selectedPaths = 0;
2159      for(i = 0; i < [objects count]; i++)
2160        {
2161          GRDrawableObject *obj;
2162          obj = [objects objectAtIndex: i];
2163
2164          if([[obj editor] isSelected] && [obj isKindOfClass:[GRBezierPath class]])
2165            {
2166              selectedPaths++;
2167            }
2168        }
2169
2170      if (selectedPaths == 1)
2171        {
2172          NSMenu *menu;
2173          NSMenuItem *menuItem;
2174
2175
2176          menu = [[NSMenu alloc] initWithTitle: NSLocalizedString(@"Handles", @"")];
2177
2178	  menuItem = [[NSMenuItem alloc] init];
2179          [menuItem setTitle: NSLocalizedString(@"Delete", @"")];
2180          [menuItem setTarget: self];
2181          [menuItem setAction: @selector(deletePointsOfCurrentPath:)];
2182          [menu addItem: menuItem];
2183          [menuItem release];
2184
2185          menuItem = [[NSMenuItem alloc] init];
2186          [menuItem setTitle: NSLocalizedString(@"Symmetric", @"")];
2187          [menuItem setTarget: self];
2188          [menuItem setAction: @selector(changePointsOfCurrentPathToSymmetric:)];
2189          [menu addItem: menuItem];
2190          [menuItem release];
2191
2192          menuItem = [[NSMenuItem alloc] init];
2193          [menuItem setTitle: NSLocalizedString(@"Cusp", @"")];
2194          [menu addItem: menuItem];
2195          [menuItem setTarget: self];
2196          [menuItem setAction: @selector(changePointsOfCurrentPathToCusp:)];
2197          [menuItem release];
2198
2199          menuItem = [[NSMenuItem alloc] init];
2200          [menuItem setTitle: NSLocalizedString(@"Overlap to Segment", @"")];
2201          [menu addItem: menuItem];
2202          [menuItem setTarget: self];
2203          [menuItem setAction: @selector(changePointsOfCurrentPathByOverlap:)];
2204          [menuItem release];
2205
2206          menuItem = [[NSMenuItem alloc] init];
2207          [menuItem setTitle: NSLocalizedString(@"Extract to Round", @"")];
2208          [menu addItem: menuItem];
2209          [menuItem setTarget: self];
2210          [menuItem setAction: @selector(changePointsOfCurrentPathByExtract:)];
2211          [menuItem release];
2212
2213          [menu autorelease];
2214          return menu;
2215        }
2216    }
2217
2218  return [super menuForEvent: theEvent];
2219}
2220
2221
2222- (BOOL)acceptsFirstResponder
2223{
2224    return YES;
2225}
2226
2227- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent
2228{
2229    return YES;
2230}
2231
2232- (void)drawRect:(NSRect)rect
2233{
2234  NSUInteger   i;
2235  NSRect       frameRect;
2236  NSBezierPath *bzp;
2237
2238  frameRect = [self frame];
2239
2240  [[NSColor whiteColor] set];
2241  NSRectFill(rect);
2242
2243  if ([[NSGraphicsContext currentContext] isDrawingToScreen])
2244    {
2245      [[NSColor lightGrayColor] set];
2246
2247      bzp = [NSBezierPath bezierPath];
2248      [bzp moveToPoint:NSMakePoint(0, zmdRect.origin.y)];
2249      [bzp lineToPoint:NSMakePoint(frameRect.size.width, zmdRect.origin.y)];
2250      [bzp lineToPoint:NSMakePoint(rect.size.width, zmdRect.origin.y)];
2251      [bzp moveToPoint:NSMakePoint(0, zmdRect.origin.y + zmdRect.size.height)];
2252      [bzp lineToPoint:NSMakePoint(frameRect.size.width, zmdRect.origin.y + zmdRect.size.height)];
2253      [bzp moveToPoint:NSMakePoint(zmdRect.origin.x, 0)];
2254      [bzp lineToPoint:NSMakePoint(zmdRect.origin.x, frameRect.size.height)];
2255      [bzp moveToPoint:NSMakePoint(zmdRect.origin.x + zmdRect.size.width, 0)];
2256      [bzp lineToPoint:NSMakePoint(zmdRect.origin.x + zmdRect.size.width, frameRect.size.height)];
2257      [bzp stroke];
2258    }
2259
2260    for(i = 0; i < [objects count]; i++)
2261        [(GRDrawableObject *)[objects objectAtIndex: i] draw];
2262
2263}
2264
2265/* --- overridden for printing --- */
2266/**
2267 * override for a custom pagination scheme
2268 */
2269- (BOOL) knowsPageRange: (NSRangePointer) range
2270{
2271  /* we simply set one page */
2272  range->location = 1;
2273  range->length = 1;
2274
2275  return YES;
2276}
2277
2278/**
2279 * override for a custom pagination scheme
2280 */
2281- (NSRect ) rectForPage: (NSInteger) pageNumber
2282{
2283    NSRect pageRec;
2284    NSSize pageSize;
2285    NSPrintInfo *pi;
2286
2287    pi = [[[NSDocumentController sharedDocumentController] currentDocument] printInfo];
2288
2289    pageSize = [pi paperSize];
2290    pageRec = NSMakeRect(0, 0, pageSize.width, pageSize.height);
2291    NSLog(@"page rect: %f, %f, %f, %f", pageRec.origin.x, pageRec.origin.y, pageRec.size.width, pageRec.size.height);
2292    return pageRec;
2293}
2294
2295@end
2296
2297
2298