class library: Spawner - don't access PriorityQueue-array
[supercollider.git] / editors / scapp / MyDocument.M
blob09ec90e977a2f151d0e55f8c13ba73225caae8b1
1 /*
2 SuperCollider real time audio synthesis system
3 Copyright (c) 2002 James McCartney. All rights reserved.
4 http://www.audiosynth.com
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 #import <Cocoa/Cocoa.h>
22 #import "HTMLRenderer.h"
23 #import "MyDocument.h"
24 #import "SCTextView.h"
25 #import "SCVirtualMachine.h"
26 #import "GoToPanel.h"
27 #import "GetStringFromUser.h"
28 #include "SCBase.h"
29 #include "SC_DirUtils.h"
30 #import "SMLAdvancedFindController.h"
32 #include <unistd.h>
33 #include <stdlib.h>
34 #include <stdio.h>
35 #include <pthread.h>
36 #ifdef SC_WIN32
37 //# include <wx/wx.h>
38 # include <direct.h>
39 #else
40 # include <sys/param.h>
41 #endif
43 #include "PyrObject.h"
44 #include "PyrKernel.h"
45 #include "GC.h"
46 #include "VMGlobals.h"
48 bool firstWindow = true;
49 bool needBlankWindow = true;
50 extern NSTextView *gPostView;
51 extern pthread_mutex_t gLangMutex;
52 extern bool compiledOK;
54 bool docCreatedFromLang = false; //if this is true addDocument is not called
55 bool defaultDocumentUseAutoInOutDent = true;
57 NSFont *defaultFont = nil;
59 @implementation MyDocument
61 - (id)init
63 if(!defaultFont) {
64 defaultFont = [NSFont fontWithName: @"Monaco" size: 9];
65 [defaultFont retain];
67 mWindowObj = nil;
68 initTextView = nil;
69 textView = nil;
70 scrollView = nil;
71 isRichText = YES;
72 promptToSave = YES;
73 pathToSaveAttachments = nil;
74 split = NO;
75 return [super init];
78 // the first one, not the split
79 - (SCTextView*)makeTextView
81 SCTextView* aTextView = [[SCTextView alloc] initWithFrame: NSMakeRect(0,0,612,512)];
82 isRichText = YES;
83 [self initialiseTextViewParams: aTextView];
84 [aTextView setFont: defaultFont];
85 activeTextView = aTextView;
86 return aTextView;
89 - (void)initialiseTextViewParams: (SCTextView*)aTextView
91 [aTextView setAutoresizingMask: 63];
92 [[aTextView textContainer] setWidthTracksTextView: YES];
93 [aTextView setDelegate: self];
94 [aTextView setAllowsUndo: YES];
95 [aTextView setRichText: isRichText];
96 [aTextView setSmartInsertDeleteEnabled: NO];
97 [aTextView setImportsGraphics: YES];
98 [aTextView setLangClassToCall:@"CocoaDocument"
99 withKeyDownActionIndex:4 withKeyUpActionIndex:5];
100 [aTextView setObjectKeyDownActionIndex:2 setObjectKeyUpActionIndex:1];
101 [aTextView setAcceptsFirstResponder:YES];
102 [aTextView setUsesAutoInOutdent:defaultDocumentUseAutoInOutDent];
105 - (void)addDocument
107 [self sendSelection: "addDocument"];
111 - (void)windowControllerDidLoadNib:(NSWindowController*) aController
113 [super windowControllerDidLoadNib:aController];
115 NSSize contentSize;
116 contentSize = [scrollView contentSize];
117 if (initTextView) {
118 textView = initTextView;
119 } else {
120 textView = [self makeTextView];
122 NSWindow *window = [scrollView window];
125 mySplitView = [[NSSplitView alloc] initWithFrame:[scrollView frame]];
126 [window setContentView: mySplitView];
128 [mySplitView addSubview:scrollView];
130 [scrollView setDocumentView: textView];
131 [textView release];
133 [textView setSelectedRange: NSMakeRange(0,0)];
135 if (firstWindow) {
136 if (initTextView) {
137 if (needBlankWindow) {
138 [[NSDocumentController sharedDocumentController] newDocument: nil];
139 needBlankWindow = false;
141 } else {
142 firstWindow = false;
143 needBlankWindow = false;
144 gPostView = textView;
146 [[SCVirtualMachine sharedInstance] start];
150 [window makeKeyWindow];
151 [window makeFirstResponder: textView];
152 //jt: call lang..
153 if(!docCreatedFromLang)[self addDocument];
156 - (void)openCode:(id)sender
158 [self sendSelection: "openCodeFile"];
161 - (void)methodTemplates: (id)sender
163 [self sendSelection: "methodTemplates"];
166 - (void)methodReferences: (id)sender
168 [self sendSelection: "methodReferences"];
171 - (void)balanceParens: (id)sender
173 [activeTextView balanceParens: nil];
176 - (BOOL)textView:(NSTextView *)theTextView shouldChangeTextInRange:(NSRange)affectedCharRange replacementString:(NSString *)replacementString
178 if (replacementString && [replacementString length] > 0) {
179 unichar c = [replacementString characterAtIndex: 0];
180 if (strchr(")]}", c)) {
181 NSString *string = [theTextView string];
183 int length = [string length];
184 unichar* buffer = (unichar*)malloc((length+1) * sizeof(unichar));
185 [string getCharacters: buffer];
187 unsigned int start, end;
188 start = affectedCharRange.location;
189 end = start;
191 bool doMatchBraks = !blankUnparsedChars(buffer, end, true);
193 if(doMatchBraks)
195 bool res = matchBraks(&start, &end, buffer, length, c, false);
196 if (res) {
197 [self flashHighlight: YES atIndex: start wait: 0.33 timesLeft: 1];
198 } else {
199 //NSBeep();
200 [self flashHighlight: NO atIndex: affectedCharRange.location wait: 0.07 timesLeft: 6];
203 free(buffer);
206 return YES;
209 - (void)flashHighlight: (BOOL)onoff atIndex: (int)index wait: (NSTimeInterval)interval timesLeft: (int)timesLeft
211 NSLayoutManager *layoutManager = [activeTextView layoutManager];
212 NSRange highlightRange = NSMakeRange(index, 1);
214 if (onoff) {
215 [layoutManager addTemporaryAttributes:
216 [NSDictionary dictionaryWithObjectsAndKeys: [NSColor lightGrayColor], NSBackgroundColorAttributeName, nil]
217 forCharacterRange: highlightRange];
218 } else {
219 //highlightRange.length++;
220 [layoutManager removeTemporaryAttribute: NSBackgroundColorAttributeName
221 forCharacterRange: highlightRange];
223 if (timesLeft) {
224 timesLeft--;
225 onoff = !onoff;
226 SEL sel = @selector(flashHighlight:atIndex:wait:timesLeft:);
227 NSMethodSignature *sig = [MyDocument instanceMethodSignatureForSelector: sel];
228 NSInvocation *anInvocation = [NSInvocation invocationWithMethodSignature: sig];
229 [anInvocation setTarget: self];
230 [anInvocation setSelector: sel];
231 [anInvocation setArgument: &onoff atIndex: 2];
232 [anInvocation setArgument: &index atIndex: 3];
233 [anInvocation setArgument: &interval atIndex: 4];
234 [anInvocation setArgument: &timesLeft atIndex: 5];
235 [NSTimer scheduledTimerWithTimeInterval: interval invocation: anInvocation repeats: NO];
240 void SyntaxColorize(NSTextView* textView);
242 - (void)syntaxColorize: (id)sender
244 SyntaxColorize(activeTextView);
245 [activeTextView didChangeText];
248 - (void) insertText: (char*) text length: (int)length
250 NSRange selectedRange = [activeTextView selectedRange];
251 NSString *string = [[NSString alloc] initWithCString: text length: length];
252 if ([activeTextView shouldChangeTextInRange: selectedRange replacementString: string]) {
253 [activeTextView replaceCharactersInRange: selectedRange withString: string];
254 [activeTextView didChangeText];
256 [string release];
260 - (void)wrapParensBegin:(NSString*)begStr end:(NSString*)endStr
262 NSTextStorage *textStorage = [activeTextView textStorage];
263 NSRange selectedRange = [activeTextView selectedRange];
264 NSAttributedString *originalSelection = [textStorage attributedSubstringFromRange: selectedRange];
265 NSMutableAttributedString *newString = [[NSMutableAttributedString alloc] initWithAttributedString: originalSelection];
266 [newString autorelease];
268 [newString replaceCharactersInRange: NSMakeRange([newString length],0) withString: endStr];
269 [newString replaceCharactersInRange: NSMakeRange(0,0) withString: begStr];
271 if ([activeTextView shouldChangeTextInRange: selectedRange replacementString: [newString string]]) {
272 [textStorage replaceCharactersInRange: selectedRange withAttributedString: newString];
273 NSRange newSelectedRange = selectedRange;
274 if(selectedRange.length > 0) newSelectedRange.length += [begStr length] + [endStr length];
275 else
277 newSelectedRange.length = 0;
278 newSelectedRange.location = newSelectedRange.location + [begStr length];
280 [activeTextView setSelectedRange: newSelectedRange];
281 SyntaxColorize(activeTextView);
282 [activeTextView didChangeText];
286 - (void)wrapParens: (int)sender
288 [self wrapParensBegin: @"(" end: @")"];
291 - (void)wrapSquareBrackets: (int)sender
293 [self wrapParensBegin: @"[" end: @"]"];
296 - (void)wrapCurlyBrackets: (int)sender
298 [self wrapParensBegin: @"{" end: @"}"];
301 - (void)wrapComments: (int)sender
303 [self wrapParensBegin: @"/*" end: @"*/"];
307 - (void)shiftLeft: (id)sender
309 NSTextStorage *textStorage = [activeTextView textStorage];
310 NSRange selectedRange = [activeTextView selectedRange];
311 if (selectedRange.length <= 0) return;
312 NSAttributedString *originalSelection = [textStorage attributedSubstringFromRange: selectedRange];
314 NSMutableAttributedString *newString = [[NSMutableAttributedString alloc] initWithAttributedString: originalSelection];
315 [newString autorelease];
317 NSString *string = [originalSelection string];
319 int length = [string length];
320 unichar* buffer = (unichar*)malloc((length+1) * sizeof(unichar));
321 [string getCharacters: buffer];
324 int j = 1;
325 if (buffer[0] == NSTabCharacter) {
326 [newString deleteCharactersInRange: NSMakeRange(0,1)];
327 j -= 1;
329 for (int i=0; i<length-1; ++i, ++j) {
330 unichar c = buffer[i];
331 unichar d = buffer[i+1];
332 if (d == NSTabCharacter && (c == NSNewlineCharacter || c == NSCarriageReturnCharacter)) {
333 [newString deleteCharactersInRange: NSMakeRange(j,1)];
334 j -= 1;
337 free(buffer);
338 NSRange newSelectedRange = NSMakeRange(selectedRange.location, j);
340 if ([activeTextView shouldChangeTextInRange: selectedRange replacementString: [newString string]]) {
341 [textStorage replaceCharactersInRange: selectedRange withAttributedString: newString];
342 [activeTextView setSelectedRange: newSelectedRange];
343 [activeTextView didChangeText];
347 - (void)shiftRight: (id)sender
349 NSTextStorage *textStorage = [activeTextView textStorage];
350 NSRange selectedRange = [activeTextView selectedRange];
351 if (selectedRange.length <= 0) return;
352 NSAttributedString *originalSelection = [textStorage attributedSubstringFromRange: selectedRange];
354 NSString *tabStr = [[NSString alloc] initWithString: @"\t"];
355 [tabStr autorelease];
357 NSMutableAttributedString *newString = [[NSMutableAttributedString alloc] initWithAttributedString: originalSelection];
358 [newString autorelease];
360 NSString *string = [originalSelection string];
362 int length = [string length];
363 unichar* buffer = (unichar*)malloc((length+1) * sizeof(unichar));
364 [string getCharacters: buffer];
366 [newString replaceCharactersInRange: NSMakeRange(0,0) withString: tabStr];
367 int j = 1;
368 for (int i=0; i<length-1; ++i, ++j) {
369 unichar c = buffer[i];
370 if (c == NSNewlineCharacter || c == NSCarriageReturnCharacter) {
371 [newString replaceCharactersInRange: NSMakeRange(j+1,0) withString: tabStr];
372 j += 1;
375 free(buffer);
376 NSRange newSelectedRange = NSMakeRange(selectedRange.location, j+1);
378 if ([activeTextView shouldChangeTextInRange: selectedRange replacementString: [newString string]]) {
379 [textStorage replaceCharactersInRange: selectedRange withAttributedString: newString];
380 [activeTextView setSelectedRange: newSelectedRange];
381 [activeTextView didChangeText];
385 - (void)commentCode: (id)sender
387 NSTextStorage *textStorage = [activeTextView textStorage];
388 NSRange selectedRange = [activeTextView selectedRange];
389 if (selectedRange.length <= 0) return;
390 NSAttributedString *originalSelection = [textStorage attributedSubstringFromRange: selectedRange];
392 NSString *commentChars = [[NSString alloc] initWithString: @"//"];
393 [commentChars autorelease];
395 NSMutableAttributedString *newString = [[NSMutableAttributedString alloc] initWithAttributedString: originalSelection];
396 [newString autorelease];
398 NSString *string = [originalSelection string];
400 int length = [string length];
401 unichar* buffer = (unichar*)malloc((length+1) * sizeof(unichar));
402 [string getCharacters: buffer];
404 [newString replaceCharactersInRange: NSMakeRange(0,0) withString: commentChars];
405 int j = 2;
406 for (int i=0; i<length-1; ++i, ++j) {
407 unichar c = buffer[i];
408 if (c == NSNewlineCharacter || c == NSCarriageReturnCharacter) {
409 [newString replaceCharactersInRange: NSMakeRange(j+1,0) withString: commentChars];
410 j += 2;
413 free(buffer);
414 NSRange newSelectedRange = NSMakeRange(selectedRange.location, j+1);
416 if ([activeTextView shouldChangeTextInRange: selectedRange replacementString: [newString string]]) {
417 [textStorage replaceCharactersInRange: selectedRange withAttributedString: newString];
418 [activeTextView didChangeText];
419 [activeTextView setSelectedRange: newSelectedRange];
420 SyntaxColorize(activeTextView);
421 [activeTextView didChangeText];
425 - (void)uncommentCode:(id)sender
427 NSTextStorage *textStorage = [activeTextView textStorage];
428 NSRange selectedRange = [activeTextView selectedRange];
429 if (selectedRange.length <= 0) return;
430 NSAttributedString *originalSelection = [textStorage attributedSubstringFromRange: selectedRange];
432 NSMutableAttributedString *newString = [[NSMutableAttributedString alloc] initWithAttributedString: originalSelection];
433 [newString autorelease];
435 NSString *string = [originalSelection string];
437 int length = [string length];
438 if (length < 2) return;
440 unichar* buffer = (unichar*)malloc((length+1) * sizeof(unichar));
441 [string getCharacters: buffer];
444 int i = 0;
445 int j = 0;
446 if (buffer[0] == '/' && buffer[1] == '/') {
447 [newString deleteCharactersInRange: NSMakeRange(0,2)];
448 i += 2;
450 for (; i<length-2; ++i, ++j) {
451 unichar c = buffer[i];
452 unichar d = buffer[i+1];
453 unichar e = buffer[i+2];
454 if (d == '/' && e == '/' && (c == NSNewlineCharacter || c == NSCarriageReturnCharacter)) {
455 [newString deleteCharactersInRange: NSMakeRange(j+1,2)];
456 j -= 2;
459 free(buffer);
460 NSRange newSelectedRange = NSMakeRange(selectedRange.location, j+2);
462 if ([activeTextView shouldChangeTextInRange: selectedRange replacementString: [newString string]]) {
463 [textStorage replaceCharactersInRange: selectedRange withAttributedString: newString];
464 [activeTextView didChangeText];
465 [activeTextView setSelectedRange: newSelectedRange];
466 SyntaxColorize(activeTextView);
467 [activeTextView didChangeText];
472 - (IBAction) executeSelection: (id) sender
474 [self sendSelection: "interpretPrintCmdLine" ];
477 NSString* helpFileWithName(NSFileManager* fileManager, NSString* desiredHelpFile, NSString* helpFolderPath)
479 // catch the common case of wanting main help
480 if([desiredHelpFile isEqual: @""]) {
481 NSString* mainHelpPath = @"Help/Help.html";
482 if([fileManager fileExistsAtPath: mainHelpPath]) {
483 return mainHelpPath;
484 } else {
485 // maybe it's moved or changed format
486 return helpFileWithName(fileManager, @"Help", helpFolderPath);
490 // If called with no folder check in the bundle (if OS X), SC distribution Help
491 // directory, and the System and User Extensions directories.
492 if(!helpFolderPath) {
493 NSString* helpFilePath;
494 NSString* searchPath;
496 char resourceDirPath[MAXPATHLEN - 32];
497 sc_GetResourceDirectory(resourceDirPath, MAXPATHLEN - 32);
498 sc_AppendToPath(resourceDirPath, PATH_MAX - 32, "Help/");
499 searchPath = [NSString stringWithCString:(const char *) resourceDirPath];
500 helpFilePath = helpFileWithName(fileManager, desiredHelpFile, searchPath);
501 if (helpFilePath) return helpFilePath;
503 if(!sc_IsStandAlone()) {
504 searchPath = [NSString stringWithFormat: @"%@/Help/",
505 [fileManager currentDirectoryPath]];
506 helpFilePath = helpFileWithName(fileManager, desiredHelpFile, searchPath);
507 if (helpFilePath) return helpFilePath;
509 searchPath = @"/Library/Application Support/SuperCollider/Extensions/";
510 helpFilePath = helpFileWithName(fileManager, desiredHelpFile, searchPath);
511 if (helpFilePath) return helpFilePath;
513 searchPath = [NSString stringWithFormat: @"%@/Library/Application Support/SuperCollider/Extensions/",
514 NSHomeDirectory()];
515 helpFilePath = helpFileWithName(fileManager, desiredHelpFile, searchPath);
516 if (helpFilePath) return helpFilePath;
518 return NULL;
521 // the name of the help file we are looking for
522 NSString* helpFileName;
524 // substitute for these symbols, as they are not valid filenames on one or more platforms
525 if([desiredHelpFile isEqual: @"-"]) {
526 helpFileName = @"subtraction";
527 } else if([desiredHelpFile isEqual: @"/"]) {
528 helpFileName = @"division";
529 } else if([desiredHelpFile isEqual: @"**"]) {
530 helpFileName = @"exponentiation";
531 } else if([desiredHelpFile isEqual: @"%"]) {
532 helpFileName = @"modulo";
533 } else if([desiredHelpFile isEqual: @"*"]) {
534 helpFileName = @"multiplication";
535 } else if([desiredHelpFile isEqual: @"+"]) {
536 helpFileName = @"addition";
537 } else if([desiredHelpFile isEqual: @"<"]) {
538 helpFileName = @"lessthan";
539 } else if([desiredHelpFile isEqual: @"<="]) {
540 helpFileName = @"lessorequalthan";
541 } else if([desiredHelpFile isEqual: @">"]) {
542 helpFileName = @"greaterthan";
543 } else if([desiredHelpFile isEqual: @">="]) {
544 helpFileName = @"greaterorequalthan";
545 } else {
546 helpFileName = desiredHelpFile;
549 // recurse through this directory until we find it
550 NSString* helpFilePath = nil;
551 NSDirectoryEnumerator* dirEnumerator = [fileManager enumeratorAtPath:helpFolderPath];
552 NSString* candidate;
553 NSString* fullName;
554 while (candidate = [dirEnumerator nextObject]) {
555 if([[candidate lastPathComponent] isEqualToString: @".svn"]
556 || [[candidate lastPathComponent] isEqualToString: @"ignore"]
557 || [[candidate lastPathComponent] isEqualToString: @".git"]
558 || [[candidate lastPathComponent] isEqualToString: @"_darcs"]
560 [dirEnumerator skipDescendents];
561 } else {
562 NSString* trialName = [helpFolderPath stringByAppendingString: candidate];
564 char name[MAXPATHLEN];
565 char resolvedName[MAXPATHLEN];
566 [trialName getCString:name];
567 bool isAlias = false;
568 sc_ResolveIfAlias(name, resolvedName, isAlias, MAXPATHLEN);
569 fullName = [NSString stringWithCString:resolvedName];
570 // compare with valid extensions
571 if([[fullName lastPathComponent] isEqualToString: [helpFileName stringByAppendingString: @".html"]]
572 || [[fullName lastPathComponent] isEqualToString: [helpFileName stringByAppendingString: @".rtf"]]
573 || [[fullName lastPathComponent] isEqualToString: [helpFileName stringByAppendingString: @".rtfd"]]
574 || [[fullName lastPathComponent] isEqualToString: [helpFileName stringByAppendingString: @".scd"]]
575 || [[fullName lastPathComponent] isEqualToString: [helpFileName stringByAppendingString: @".help.scd"]]
576 || [[fullName lastPathComponent] isEqualToString: [helpFileName stringByAppendingString: @".schelp"]]
577 || [[fullName lastPathComponent] isEqualToString: [helpFileName stringByAppendingString: @".help.rtf"]]
578 || [[fullName lastPathComponent] isEqualToString: [helpFileName stringByAppendingString: @".htm"]]
580 helpFilePath = fullName;
581 break;
582 } else if // if that didn't work, check if this is a directory symlink (allow directories to have extensions)
583 ((([[fileManager fileAttributesAtPath: fullName traverseLink: NO] objectForKey:@"NSFileType"] == NSFileTypeSymbolicLink) || isAlias)){
584 NSString* linkName = [[fullName stringByResolvingSymlinksInPath] stringByAppendingString: @"/"];
585 if(([[fileManager fileAttributesAtPath: linkName traverseLink: NO] objectForKey:@"NSFileType"] == NSFileTypeDirectory)) {
586 // if so traverse the link and call helpFileWithName() recursively
587 helpFilePath = helpFileWithName(fileManager, desiredHelpFile, linkName);
588 if(helpFilePath) break;
594 return helpFilePath;
599 -(void)selectRangeStart:(int)rangeStart size:(int)rangeSize
602 NSRange range = NSMakeRange(rangeStart, rangeSize);
603 NSString *nsstring = [activeTextView string];
604 if (!nsstring) {
605 post("text view has no string\n");
606 return;
608 int maxlength = [nsstring length];
609 if(maxlength < 1) range = NSMakeRange(0, 0);
610 else{
611 if (rangeSize < 0) {
612 NSUInteger lineStart, lineEnd;
613 range.length = 0;
614 [nsstring getLineStart: &lineStart end: &lineEnd contentsEnd: nil forRange: range];
615 range = NSMakeRange(lineStart, lineEnd - lineStart);
617 if(rangeStart < 0) range.location = 0;
618 if(rangeStart >= maxlength) range.location = maxlength;
619 if(rangeSize + rangeStart >= maxlength) {
620 range.length = maxlength - range.location;
623 [activeTextView setSelectedRange: range];
624 [activeTextView scrollRangeToVisible: range];
627 -(void)selectLine:(int)linenum
629 NSString *nsstring = [activeTextView string];
630 int length = [nsstring length];
631 if(length < 1) return;
632 NSLayoutManager *layoutManager = [activeTextView layoutManager];
633 unsigned numberOfLines, index, numberOfGlyphs = [layoutManager numberOfGlyphs];
634 NSRange lineRange;
635 if(linenum <= 0) linenum = 1;
636 for (numberOfLines = 0, index = 0; index < numberOfGlyphs && (int)numberOfLines < linenum; numberOfLines++) {
638 (void) [layoutManager lineFragmentRectForGlyphAtIndex:index effectiveRange:&lineRange];
639 index = NSMaxRange(lineRange);
642 [activeTextView setSelectedRange: lineRange];
643 [activeTextView scrollRangeToVisible: lineRange];
647 //call from menu
648 - (IBAction)selectLineWindow: (id) sender
650 [[GoToPanel sharedInstance] orderFrontGotoLinePanel:nil];
654 - (IBAction) advancedFindReplaceAction:(id)sender
656 [[SMLAdvancedFindController sharedInstance] showAdvancedFindWindow];
659 - (IBAction) showHelpFor: (id) sender
661 [self sendSelection: "showHelp"];
664 - (IBAction)showClassBrowser:(id)sender {
665 [self sendSelection: "showClassBrowser"];
668 // LINK SUPPORT
669 // Handle a click in a link.
670 - (BOOL) textView: (NSTextView *) textView
671 clickedOnLink: (id) link
672 atIndex: (unsigned) charIndex
674 NSDocumentController* docctl = [NSDocumentController sharedDocumentController];
675 if (!docctl) return YES;
677 NSURL *desiredURL;
679 // is it a NSURL link or a NSString link?
680 if ([link isKindOfClass: [NSString class]])
682 if([link hasPrefix:@"SC://"] || [link hasPrefix:@"sc://"]) { // this means search immediately
683 NSString *helpPath = pathOfHelpFileFor([[[link substringFromIndex:5] stringByDeletingPathExtension] stringByDeletingPathExtension]);
684 if(!helpPath) {
685 post("WARNING:\nInvalid hyperlink: '%s' Please repair this.\n", [link cString]);
686 return YES;
688 desiredURL = [NSURL URLWithString: helpPath];
689 } else desiredURL = [NSURL URLWithString: [link stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding] relativeToURL: [self fileURL]];
691 } else if ([link isKindOfClass: [NSURL class]])
694 // check for schemes which we'll handle
695 if([[link scheme] isEqualToString: @"SC"] || [[link scheme] isEqualToString:@"sc"]) { // this means search immediately
697 NSString *helpPath = pathOfHelpFileFor([[[[link relativeString] substringFromIndex:5] stringByDeletingPathExtension] stringByDeletingPathExtension]);
698 if(!helpPath) {
699 post("WARNING:\nInvalid hyperlink: '%s' Please repair this.\n", [[link relativeString] cString]);
700 return YES;
702 desiredURL = [NSURL fileURLWithPath: helpPath];
703 } else if (![link scheme]) { // NULL could be a regular file link that's been edited
704 if([[link relativePath] hasPrefix: @"/"]) {
705 desiredURL = [NSURL fileURLWithPath: [link relativeString]];
706 } else {
707 desiredURL = [NSURL URLWithString: [[link relativeString] stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding] relativeToURL: [self fileURL]];
709 //desiredURL = [NSURL fileURLWithPath: [link relativeString]];
710 } else if(![[link scheme] isEqualToString: @"file"]) {
711 return NO; // it's http, etc. so pass it on to Safari or whatever
712 } else desiredURL = link; // it's a regular file:// URL
713 } else return NO;
715 MyDocument *doc = nil;
716 if([[NSFileManager defaultManager] fileExistsAtPath: [desiredURL path]]) {
718 doc = (MyDocument*)[docctl documentForURL: [desiredURL absoluteURL]];
719 } else NSLog(@"file doesn't exist at path");
720 if (!doc) {
721 doc = [docctl openDocumentWithContentsOfURL: desiredURL display: true];
722 if (!doc) {
723 // it's a bad file:// URL, post a warning and search
724 post("WARNING:\nInvalid hyperlink: '%s' Please repair this.\nSearching help directories for alternative.\n", [[desiredURL path] cString]);
725 // delete extension twice in case something.help.rtf
726 NSString *desiredHelpName = [[[[desiredURL path] lastPathComponent] stringByDeletingPathExtension] stringByDeletingPathExtension];
727 NSString *helpPath = pathOfHelpFileFor(desiredHelpName);
728 if(!helpPath) {
729 post("Can't find Help File Document for: '%s'\n", [desiredHelpName cString]);
730 return YES;
732 desiredURL = [NSURL fileURLWithPath: helpPath];
733 if([[NSFileManager defaultManager] fileExistsAtPath: [desiredURL path]]) {
734 doc = (MyDocument*)[docctl documentForURL: [desiredURL absoluteURL]];
735 } else post("file doesn't exist at path: '%s'\n", [[desiredURL path] cString]);
736 if (!doc) {
737 doc = [docctl openDocumentWithContentsOfURL: desiredURL display: true];
738 if(!doc) {
739 post("Can't open Help File Document: '%s'\n", [[desiredURL path] cString]);
740 return YES;
745 NSWindow *window = [[[doc windowControllers] objectAtIndex: 0] window];
746 if (!window) {
747 post("!! window controller returns nil ? failed to open help file window\n");
748 return YES;
750 [window makeKeyAndOrderFront: nil];
751 return YES;
754 // Create a link to a helpfile
756 - (IBAction) createLink: (id) sender
758 NSRange selection;
759 NSString *link;
760 NSMutableDictionary *linkAttributes;
762 selection = [activeTextView selectedRange];
763 // check to see if we can make a link
764 if (selection.length == 0)
766 NSRunAlertPanel (@"Link to Help file", @"Please select some text before creating a link.", nil, nil, nil);
767 return;
768 } else if(![self fileURL]) {
769 NSRunAlertPanel (@"Link to Help file", @"You must save this Document before creating a link.", nil, nil, nil);
770 return;
773 // Get the text which will go into the link.
775 link = pathOfHelpFileFor([activeTextView currentlySelectedTextOrLine: NULL]);
776 if(!link) {
777 NSRunAlertPanel (@"Link to Help file", [NSString stringWithFormat: @"No help named %@ exists.", [activeTextView currentlySelectedTextOrLine: NULL]], nil, nil, nil);
778 return;
780 BOOL targetInMainHelp = [[[self fileURL] path] rangeOfString: [NSString stringWithFormat: @"%@/Help/", [[NSFileManager defaultManager] currentDirectoryPath]]].location != NSNotFound;
781 BOOL linkInMainHelp = [link rangeOfString: [NSString stringWithFormat: @"%@/Help/", [[NSFileManager defaultManager] currentDirectoryPath]]].location != NSNotFound;
782 // if both are in main help, or both are not, produce a relative link, otherwise an SC:// URL, which will call the search system.
783 if((targetInMainHelp && linkInMainHelp) || (!targetInMainHelp && !linkInMainHelp)) {
784 link = pathOfFileRelativeToBaseDir(link, [[[self fileURL] path] stringByDeletingLastPathComponent]);
785 } else link = [NSURL URLWithString: [@"SC://" stringByAppendingString: [activeTextView currentlySelectedTextOrLine: NULL]]];
787 // Start to build attributes
788 linkAttributes = [NSMutableDictionary dictionaryWithObject: link forKey: NSLinkAttributeName];
790 // Currently always blue
791 [linkAttributes setObject: [NSColor blueColor] forKey: NSForegroundColorAttributeName];
793 // Add the attributes. This adds the link attributes to the selected range.
794 [[activeTextView textStorage] addAttributes: linkAttributes range: selection];
796 // Make sure it saves as HTML as default
797 [self setFileType: @"HTML"]; // need to change name to html
798 [self updateChangeCount: NSChangeDone];
802 - (void)sendSelection: (char*) methodName
804 if (!compiledOK) {
805 return;
808 NSRange selectedRange;
809 NSString* selection = [activeTextView currentlySelectedTextOrLine: &selectedRange];
810 const char *text = [selection UTF8String];
811 int textlength = strlen(text);
813 [[SCVirtualMachine sharedInstance] setCmdLine: text length: textlength];
815 NSRange newSelectedRange = NSMakeRange(selectedRange.location + selectedRange.length, 0);
816 [activeTextView setSelectedRange: newSelectedRange];
818 pthread_mutex_lock(&gLangMutex);
819 runLibrary(getsym(methodName));
820 pthread_mutex_unlock(&gLangMutex);
824 - (NSString *)windowNibName
826 // Override returning the nib file name of the document
827 // If you need to use a subclass of NSWindowController or if your document supports multiple NSWindowControllers, you should remove this method and override -makeWindowControllers instead.
828 return @"MyDocument";
831 - (BOOL)prepareSavePanel:(NSSavePanel *)savePanel {
832 [savePanel setDelegate: self];
833 return YES;
836 - (void)setpathToSaveAttachments:(NSString *)newPathToSaveAttachments {
837 [newPathToSaveAttachments retain];
838 [pathToSaveAttachments release];
839 pathToSaveAttachments = newPathToSaveAttachments;
842 - (NSString *)panel:(id)sender userEnteredFilename:(NSString *)filename confirmed:(BOOL)okFlag {
843 if(okFlag) {
844 if([[self fileTypeFromLastRunSavePanel] isEqualToString: @"SC Help (HTML)"] && [[textView textStorage] containsAttachments]) {
845 //.../attachments/filename/
846 [self setpathToSaveAttachments:
847 [[[[sender filename] stringByDeletingLastPathComponent] stringByAppendingPathComponent: @"attachments"] stringByAppendingPathComponent: [[[sender filename] lastPathComponent] stringByDeletingPathExtension]]];
848 return filename;
849 } else {
850 return filename;
853 return nil;
856 - (BOOL)writeToFile:(NSString*) path ofType:(NSString *)aType
858 BOOL success;
859 NSString* extension = [path pathExtension];
860 if ([extension isEqualToString: @"sc"] || [extension isEqualToString: @"txt"] || [extension isEqualToString: @"scd"] || [extension isEqualToString: @"quark"]
861 || [aType isEqualToString: @"NSStringPboardType"])
863 NSString *text = [textView string];
864 success = [text writeToFile: path atomically: YES];
865 } else if ([extension isEqualToString: @"html"] || [extension isEqualToString: @"htm"]) {
867 [textView setDefaultTabsTo: 28.0f]; // set default tabs to preserve layout
869 //[self replaceOldStyleHelpLinksWithHyperLinks];
870 NSTextStorage *textStorage = [textView textStorage];
871 [self convertFileLinksToRelative: textStorage]; // clean up any manually edited file:// links
872 NSRange range = NSMakeRange(0, [textStorage length]);
873 NSDictionary *attributes = [NSDictionary dictionaryWithObject: NSHTMLTextDocumentType forKey:NSDocumentTypeDocumentAttribute];
875 // Check for images etc.
876 if ([textStorage containsAttachments]) {
877 // this has wrappers for html and all attachments
878 NSFileWrapper *wrapper = [textStorage fileWrapperFromRange: range documentAttributes: attributes error: NULL];
879 success = wrapper ? YES : NO;
880 // if no wrapper bail out...
881 if(!success) return success;
883 // now get the html component so we can clean it up and save it under the requested name
884 NSFileWrapper *indexWrapper = [[wrapper fileWrappers] objectForKey: @"index.html"];
885 [indexWrapper retain];
886 [wrapper removeFileWrapper: indexWrapper];
887 NSMutableString *html = [[NSMutableString alloc] initWithData: [indexWrapper regularFileContents] encoding: NSUTF8StringEncoding]; // maybe should check here?
888 // strip out "file:" so as to have nice portable relative urls
889 [html replaceOccurrencesOfString: @"file:///" withString: @"" options: NSCaseInsensitiveSearch range: NSMakeRange(0, [html length])];
891 // replace "%20" with " " as psycollider doesn't like the former
892 [html replaceOccurrencesOfString: @"%20" withString: @" " options: NSCaseInsensitiveSearch range: NSMakeRange(0, [html length])];
894 // fix faded autosyntax Colours
895 [html replaceOccurrencesOfString: @"color: #0016bd" withString: @"color: #0000bf" options: NSCaseInsensitiveSearch range: NSMakeRange(0, [html length])]; // blue
896 [html replaceOccurrencesOfString: @"color: #ae1a19" withString: @"color: #bf0000" options: NSCaseInsensitiveSearch range: NSMakeRange(0, [html length])]; // red
897 [html replaceOccurrencesOfString: @"color: #2b7000" withString: @"color: #007300" options: NSCaseInsensitiveSearch range: NSMakeRange(0, [html length])]; // green
898 // grey doesn't fade
900 // NSTextView doesn't like <b> tags within spans, and unfortunately the export outputs bold underlines that way.
901 // The following figures out what the span name is for underlining, and fixes this
902 fixHTMLBoldUnderline(html);
904 // now save attachments to the final directory
905 NSFileManager *fm = [NSFileManager defaultManager];
906 // check attachments/filename/ exists
907 if(![fm fileExistsAtPath: [pathToSaveAttachments stringByDeletingLastPathComponent]]) {
908 success = [fm createDirectoryAtPath: [pathToSaveAttachments stringByDeletingLastPathComponent] attributes: nil];
909 if(!success) return success;
911 if(![fm fileExistsAtPath: pathToSaveAttachments]) {
912 success = [fm createDirectoryAtPath: pathToSaveAttachments attributes: nil];
913 if(!success) return success;
916 NSEnumerator *files = [[wrapper fileWrappers] objectEnumerator];
917 NSFileWrapper *thisFile;
918 while(thisFile = [files nextObject]) {
919 // save relative to pathToSaveAttachments (which we stashed earlier) as the path arg passed in
920 // may not be the requested path, but rather a temp save location
921 NSString *thisPath = [pathToSaveAttachments stringByAppendingPathComponent: [thisFile preferredFilename]];
922 NSString *thisFileName = [thisPath lastPathComponent];
924 // now check if this is an image and convert to png.
925 NSBitmapImageRep *imageRep = [NSBitmapImageRep imageRepWithData: [thisFile regularFileContents]];
927 if(imageRep) {
928 NSData *pngData = [imageRep representationUsingType: NSPNGFileType properties: nil];
929 NSString *pngURL = [[NSString stringWithFormat:@"attachments/%@/", [[path lastPathComponent] stringByDeletingPathExtension]]
930 stringByAppendingString: [[thisFileName stringByDeletingPathExtension] stringByAppendingPathExtension: @"png"]];
931 [html replaceOccurrencesOfString: [NSString stringWithFormat:@"<img src=\"%@\" alt=\"%@\">", thisFileName, thisFileName]
932 withString: [NSString stringWithFormat:@"<img src=\"%@\" alt=\"%@\">", pngURL, pngURL]
933 options: NSCaseInsensitiveSearch
934 range: NSMakeRange(0, [html length])
936 success = success && [pngData writeToFile: [pathToSaveAttachments stringByAppendingPathComponent: [pngURL lastPathComponent]] atomically:YES];
937 } else if([[thisFileName pathExtension] isEqualToString:@"pdf"]) {
938 // it's a pdf
939 NSString *correctURL = [[NSString stringWithFormat:@"attachments/%@/", [[path lastPathComponent] stringByDeletingPathExtension]] stringByAppendingString: thisFileName];
940 [html replaceOccurrencesOfString: [NSString stringWithFormat:@"<img src=\"%@\"", thisFileName]
941 withString: [NSString stringWithFormat:@"<img src=\"%@\"", correctURL]
942 options: NSCaseInsensitiveSearch
943 range: NSMakeRange(0, [html length])
945 success = success && [thisFile writeToFile: thisPath atomically:YES updateFilenames: YES];
946 } else {
947 // not an image
948 NSString *correctURL = [[NSString stringWithFormat:@"attachments/%@/", [[path lastPathComponent] stringByDeletingPathExtension]] stringByAppendingString: thisFileName];
949 [html replaceOccurrencesOfString: [NSString stringWithFormat:@"<object data=\"%@\">%@</object>", thisFileName, thisFileName]
950 withString: [NSString stringWithFormat:@"<object data=\"%@\">%@</object>", correctURL, correctURL]
951 options: NSCaseInsensitiveSearch
952 range: NSMakeRange(0, [html length])
954 success = success && [thisFile writeToFile: thisPath atomically:YES updateFilenames: YES];
958 // write the html
959 NSFileWrapper *htmlWrapper = [[NSFileWrapper alloc] initRegularFileWithContents: [html dataUsingEncoding: NSUTF8StringEncoding]];
960 if(!htmlWrapper) { NSLog(@"HTML Wrapper init failed."); }
961 success = success && [htmlWrapper writeToFile: path atomically:YES updateFilenames: YES];
962 if(!success) return success;
963 [indexWrapper release];
964 [htmlWrapper release];
965 [html release];
966 } else {
967 // html with no attachments, way more simpler
968 NSData *data = [textStorage dataFromRange:range documentAttributes:attributes error:NULL];
969 NSMutableString *html = [[NSMutableString alloc] initWithData: data encoding: NSUTF8StringEncoding];
970 if(html) {
971 fixHTMLBoldUnderline(html);
972 // fix faded autosyntax Colours
973 [html replaceOccurrencesOfString: @"color: #0016bd" withString: @"color: #0000bf" options: NSCaseInsensitiveSearch range: NSMakeRange(0, [html length])]; // blue
974 [html replaceOccurrencesOfString: @"color: #ae1a19" withString: @"color: #bf0000" options: NSCaseInsensitiveSearch range: NSMakeRange(0, [html length])]; // red
975 [html replaceOccurrencesOfString: @"color: #2b7000" withString: @"color: #007300" options: NSCaseInsensitiveSearch range: NSMakeRange(0, [html length])]; // green
976 // grey doesn't fade
978 data = [html dataUsingEncoding: NSUTF8StringEncoding];
979 success = data ? [data writeToFile: path atomically: YES] : NO;
980 [html release];
983 } else {
984 NSTextStorage *textStorage = [textView textStorage];
986 NSRange range = NSMakeRange(0, [textStorage length]);
987 NSMutableDictionary *dict = [NSMutableDictionary dictionary];
988 if ([textStorage containsAttachments]) {
989 if (![extension isEqualToString: @"rtfd"]) {
990 path = [[path stringByDeletingPathExtension] stringByAppendingPathExtension: @"rtfd"];
992 NSFileWrapper *wrapper = [textStorage RTFDFileWrapperFromRange: range documentAttributes: dict];
993 success = wrapper ? [wrapper writeToFile: path atomically:YES updateFilenames: YES] : NO;
994 } else {
995 if ([extension isEqualToString: @""]) {
996 path = [path stringByAppendingPathExtension: @"rtf"];
998 NSData *data = [textStorage RTFFromRange: range documentAttributes: dict];
999 success = data ? [data writeToFile: path atomically: YES] : NO;
1002 [textView breakUndoCoalescing];
1003 return success;
1006 - (NSDictionary *)fileAttributesToWriteToFile:(NSString *)fullDocumentPath ofType:(NSString *)type saveOperation: (NSSaveOperationType) saveOperationType
1008 NSMutableDictionary *attr = [NSMutableDictionary dictionary];
1009 NSNumber *creator = [NSNumber numberWithInt: 'SCjm'];
1010 [attr setObject: creator forKey: NSFileHFSCreatorCode];
1011 return attr;
1014 - (void) loadHTML:(NSURL *)url
1016 if (!url) return;
1018 if (!initTextView) initTextView = [self makeTextView];
1019 NSTextStorage* text = [initTextView textStorage];
1021 [text beginEditing]; // Bracket with begin/end editing for efficiency
1022 [[text mutableString] setString:@""]; // Empty the document
1024 NSAttributedString *htmlAttributedString = [HTMLRenderer attributedStringWithURL:url];
1026 if(htmlAttributedString)
1028 [text setAttributedString:htmlAttributedString];
1029 [initTextView setDefaultTabsTo: 28.0f];
1032 [text endEditing];
1034 [url release];
1037 - (BOOL)readFromFile:(NSString *)path ofType:(NSString *)aType
1039 NSURL *url = [NSURL fileURLWithPath: path];
1040 BOOL success = NO;
1042 NSString* extension = [path pathExtension];
1044 if (!initTextView) initTextView = [self makeTextView];
1045 NSTextStorage* text = [initTextView textStorage];
1047 if ([extension isEqualToString: @"html"] || [extension isEqualToString: @"htm"])
1049 [url retain];
1050 if (docCreatedFromLang)
1052 SEL sel = @selector(loadHTML:);
1053 NSMethodSignature *sig = [MyDocument instanceMethodSignatureForSelector: sel];
1054 SCVirtualMachine* scvm = [SCVirtualMachine sharedInstance];
1056 NSInvocation *anInvocation = [NSInvocation invocationWithMethodSignature: sig];
1057 [anInvocation setTarget: self];
1058 [anInvocation setSelector: sel];
1059 [anInvocation setArgument:&url atIndex:2];
1060 [scvm defer: anInvocation];
1062 else
1064 [self loadHTML:url];
1066 [self setpathToSaveAttachments:[[[path stringByDeletingLastPathComponent] stringByAppendingPathComponent: @"attachments"] stringByAppendingPathComponent: [[path lastPathComponent] stringByDeletingPathExtension]]];
1068 return YES;
1071 [text beginEditing]; // Bracket with begin/end editing for efficiency
1072 [[text mutableString] setString:@""]; // Empty the document
1075 NSError *error;
1076 success = [text readFromURL:url options:nil documentAttributes:nil error:&error];
1077 if(!success) NSLog(@"Error opening file: %@", error);
1079 if ([extension isEqualToString: @"sc"] || [extension isEqualToString: @"scd"]) {
1080 [initTextView setFont: defaultFont];
1081 SyntaxColorize(initTextView);
1083 [text endEditing];
1085 // in case of html with images, etc.
1086 [self setpathToSaveAttachments:[[[path stringByDeletingLastPathComponent] stringByAppendingPathComponent: @"attachments"] stringByAppendingPathComponent: [[path lastPathComponent] stringByDeletingPathExtension]]];
1087 return success;
1090 - (BOOL) shouldRunSavePanelWithAccessoryView
1092 return YES;
1095 - (SCTextView*) textView;
1097 return textView;
1100 - (SCTextView*) textView2;
1102 return textView2;
1105 - (BOOL)windowShouldClose:(id)sender
1107 return (textView != gPostView);
1109 extern PyrSymbol * s_didBecomeKey;
1110 - (void) windowDidBecomeKey:(NSNotification *)aNotification
1112 if(!docCreatedFromLang){
1113 [self callSCLangWithMethod: s_didBecomeKey];
1118 extern PyrSymbol * s_didResignKey;
1119 - (void) windowDidResignKey:(NSNotification *)aNotification
1122 if(!docCreatedFromLang){
1123 [self callSCLangWithMethod: s_didResignKey];
1127 - (void) callSCLangWithMethod: (PyrSymbol*) method {
1128 pthread_mutex_lock (&gLangMutex);
1129 if(compiledOK) {
1130 struct PyrObject *scobj = [self getSCObject];
1131 if (scobj) {
1132 SetPtr(scobj->slots + 0, self);
1133 VMGlobals *g = gMainVMGlobals;
1134 g->canCallOS = true;
1135 ++g->sp; SetObject(g->sp, scobj); // push window obj
1136 runInterpreter(g, method, 1);
1137 g->canCallOS = false;
1140 pthread_mutex_unlock (&gLangMutex);
1142 - (void)windowWillClose:(NSNotification *)aNotification
1144 //for some reason some Documents created with open do not call windowWillClose
1145 if (textView == gPostView) gPostView = nil;
1146 [self callSCLangWithMethod: s_closed];
1147 [self setSCObject: nil];
1149 - (void)dealloc{
1150 //for some reason some Documents created with open do not call windowWillClose
1151 //so that its action is called here: jan.t
1152 [pathToSaveAttachments release];
1153 [super dealloc];
1155 - (IBAction) becomePostWindow: (id) sender
1157 gPostView = textView;
1161 - (BOOL) isDocumentEdited
1163 if ([self textView] == gPostView || !promptToSave) return false;
1164 return [super isDocumentEdited];
1167 //////////////////////////////////////
1169 - (void)doToggleRich {
1170 [self setRichText:!isRichText];
1171 //[self setEncoding:NoStringEncoding];
1172 //[self setConverted:NO];
1173 if ([[textView textStorage] length] > 0) [[textView window] setDocumentEdited: YES];
1174 //[self setDocumentName:nil];
1177 /* toggleRich: puts up an alert before ultimately calling doToggleRich
1179 - (void)toggleRich:(id)sender {
1180 //int length = [[textView textStorage] length];
1181 //NSRange range;
1182 //NSDictionary *attrs;
1183 [self doToggleRich];
1185 // If we are rich and any ofthe text attrs have been changed from the default, then put up an alert first...
1186 if (isRichText && (length > 0) && (attrs = [[textView textStorage] attributesAtIndex:0 effectiveRange:&range]) && ((attrs == nil) || (range.length < length) || ![[self defaultTextAttributes:YES] isEqual:attrs])) {
1187 NSBeginAlertSheet(NSLocalizedString(@"Convert document to plain text?", @"Title of alert confirming Make Plain Text"),
1188 NSLocalizedString(@"OK", @"OK"), NSLocalizedString(@"Cancel", @"Button choice allowing user to cancel."), nil, [textView window],
1189 self, NULL, @selector(didEndToggleRichSheet:returnCode:contextInfo:), NULL,
1190 NSLocalizedString(@"Converting will lose fonts, colors, and other text styles in the document.", @"Subtitle of alert confirming Make Plain Text"));
1191 } else {
1192 [self doToggleRich];
1198 - (void)didEndToggleRichSheet:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo {
1199 if (returnCode == NSAlertDefaultReturn) [self doToggleRich];
1204 /* Doesn't check to see if the prev value is the same --- Otherwise the first time doesn't work...
1206 - (void)setRichText:(BOOL)flag {
1207 NSTextView *view = textView;
1209 isRichText = flag;
1211 //if (!isRichText) [self removeAttachments];
1213 [view setRichText:isRichText];
1214 [view setUsesRuler:isRichText]; /* If NO, this correctly gets rid of the ruler if it was up */
1215 //if (isRichText && [[Preferences objectForKey:ShowRuler] boolValue]) [view setRulerVisible:YES]; /* Show ruler if rich, and desired */
1216 [view setImportsGraphics:isRichText];
1218 if (!isRichText) {
1219 NSMutableDictionary *textAttributes = [NSMutableDictionary dictionary];
1220 [textAttributes setObject: defaultFont forKey: NSFontAttributeName ];
1222 if ([[textView textStorage] length]) {
1223 [[textView textStorage] setAttributes:textAttributes range: NSMakeRange(0, [[textView textStorage] length])];
1226 [view setTypingAttributes:textAttributes];
1231 /* Menu validation: Arbitrary numbers to determine the state of the menu items whose titles change. Speeds up the validation... Not zero. */
1232 #define TagForFirst 42
1233 #define TagForSecond 43
1235 static void validateToggleItem(NSMenuItem *aCell, BOOL useFirst, NSString *first, NSString *second) {
1236 if (useFirst) {
1237 if ([aCell tag] != TagForFirst) {
1238 [aCell setTitleWithMnemonic:first];
1239 [aCell setTag:TagForFirst];
1241 } else {
1242 if ([aCell tag] != TagForSecond) {
1243 [aCell setTitleWithMnemonic:second];
1244 [aCell setTag:TagForSecond];
1249 - (BOOL)validateMenuItem:(NSMenuItem *)aCell {
1250 SEL action = [aCell action];
1251 if (action == @selector(toggleRich:)) {
1252 validateToggleItem(aCell, isRichText, NSLocalizedString(@"&Make Plain Text", @"Menu item to make the current document plain text"), NSLocalizedString(@"&Make Rich Text", @"Menu item to make the current document rich text"));
1253 //if (![textView isEditable] || [self hasSheet]) return NO;
1254 } else if (action == @selector(toggleSplitWindow:))
1256 [aCell setState:(split ? NSOnState : NSOffState)];
1257 return YES;
1258 } else {
1259 return [super validateMenuItem: aCell];
1263 - (void)setSCObject: (struct PyrObject*)inObject
1265 mWindowObj = inObject;
1268 - (struct PyrObject*)getSCObject
1270 return mWindowObj;
1273 - (void) closeWindow
1275 [self close];
1278 - (NSScrollView*) scrollView;
1280 return scrollView;
1282 - (SCTextView*) initTextView;
1284 return initTextView;
1287 - (SCTextView*) activeTextView
1289 return activeTextView;
1292 - (void)setBackgroundColor:(NSColor *)color
1294 if ([color brightnessComponent] < 0.5) {
1295 [textView setInsertionPointColor: [NSColor whiteColor]];
1296 [textView2 setInsertionPointColor: [NSColor whiteColor]];
1297 } else {
1298 [textView setInsertionPointColor: [NSColor blackColor]];
1299 [textView2 setInsertionPointColor: [NSColor blackColor]];
1302 [textView setBackgroundColor: color];
1303 [[self initTextView] setBackgroundColor: color];
1304 [textView2 setBackgroundColor: color];
1305 [[self scrollView] setBackgroundColor: color];
1306 [scrollView2 setBackgroundColor: color];
1307 [[textView window] setAlphaValue: [color alphaComponent]];
1310 - (void)setSelectedBackgroundColor:(NSColor *)color
1312 [textView setSelectedTextAttributes: [NSDictionary dictionaryWithObject: color forKey: NSBackgroundColorAttributeName]];
1313 [textView2 setSelectedTextAttributes: [NSDictionary dictionaryWithObject: color forKey: NSBackgroundColorAttributeName]];
1316 - (BOOL)promptToSave
1318 return promptToSave;
1321 -(void)setPromptToSave:(BOOL)flag
1323 promptToSave = flag;
1325 - (void) keyDown: (NSEvent*) event
1329 - (BOOL) handleKeyDown: (NSEvent*) event;
1331 return NO;
1334 - (void) keyUp: (NSEvent*) event
1337 - (void) mouseDown: (NSEvent*) event
1342 - (IBAction)convertHelpLinksToHyperlinks:(id)sender
1344 [self replaceOldStyleHelpLinksWithHyperLinks];
1346 - (void)replaceOldStyleHelpLinksWithHyperLinks
1348 NSTextStorage *textStorage = [textView textStorage];
1349 NSRange limitRange = NSMakeRange(0, [textStorage length]);
1350 NSRange underlineRange;
1351 NSMutableArray *bracketIndices = [NSMutableArray arrayWithCapacity: 10];
1352 while (limitRange.length > 0) {
1353 id isUnderlined = [textStorage attribute: NSUnderlineStyleAttributeName
1354 atIndex: limitRange.location
1355 longestEffectiveRange: &underlineRange
1356 inRange: limitRange];
1358 if(underlineRange.length<=0) break;
1359 if (isUnderlined) {
1360 int openBracketIndex = -1, closeBracketIndex = -1;
1361 if([[textStorage string] characterAtIndex: underlineRange.location] == '[') {
1362 openBracketIndex = underlineRange.location;
1363 } else if(underlineRange.location > 0) {
1364 if([[textStorage string] characterAtIndex: underlineRange.location - 1] == '[') openBracketIndex = underlineRange.location - 1;
1367 if([[textStorage string] characterAtIndex: NSMaxRange(underlineRange) - 1] == ']') {
1368 closeBracketIndex = NSMaxRange(underlineRange) - 1;
1369 } else if(NSMaxRange(underlineRange) < [textStorage length]) {
1370 if([[textStorage string] characterAtIndex: NSMaxRange(underlineRange)] == ']') closeBracketIndex = NSMaxRange(underlineRange);
1372 if(openBracketIndex >= 0 && closeBracketIndex > 0) {
1373 [bracketIndices addObject: [NSNumber numberWithInt: openBracketIndex]];
1374 [bracketIndices addObject: [NSNumber numberWithInt: closeBracketIndex]];
1375 //[textStorage beginEditing];
1376 // remove the underline, and bold if there
1377 [textStorage removeAttribute: NSUnderlineStyleAttributeName range: underlineRange];
1378 NSFont *font = [textStorage attribute:NSFontAttributeName atIndex: underlineRange.location effectiveRange:NULL];
1379 font = [[NSFontManager sharedFontManager] convertFont: font toNotHaveTrait: NSBoldFontMask];
1380 if(font) [textStorage addAttribute: NSFontAttributeName value: font range: underlineRange];
1381 // now make the link
1382 [textView setSelectedRange: NSMakeRange(openBracketIndex + 1, closeBracketIndex - openBracketIndex - 1)];
1383 [self createLink:NULL];
1384 //[textStorage endEditing];
1389 limitRange = NSMakeRange(NSMaxRange(underlineRange), [textStorage length] - NSMaxRange(underlineRange));
1394 NSEnumerator *bracketEnum = [bracketIndices reverseObjectEnumerator];
1395 NSNumber *bracketIndex;
1396 while ((bracketIndex = [bracketEnum nextObject])) {
1397 [textStorage deleteCharactersInRange: NSMakeRange([bracketIndex intValue], 1)];
1401 // the text storage converts relative URLs to file:// URLs on open so this converts them back, and cleans up any manually edited links before saving.
1402 - (void)convertFileLinksToRelative:(NSTextStorage *)textStorage
1404 //NSTextStorage *textStorage = [textView textStorage];
1405 NSRange limitRange = NSMakeRange(0, [textStorage length]);
1406 NSRange linkRange;
1407 while (limitRange.length > 0) {
1408 id link = [textStorage attribute: NSLinkAttributeName
1409 atIndex: limitRange.location
1410 longestEffectiveRange: &linkRange
1411 inRange: limitRange];
1412 if(linkRange.length<=0) break;
1413 if (link && [link isKindOfClass: [NSURL class]] && ([[link scheme] isEqualToString: @"file"])) {
1414 NSString *newLink = pathOfFileRelativeToBaseDir([link path], [[[self fileURL] path] stringByDeletingLastPathComponent]);
1415 [textStorage addAttribute: NSLinkAttributeName value: newLink range: linkRange];
1417 limitRange = NSMakeRange(NSMaxRange(linkRange), NSMaxRange(limitRange) - NSMaxRange(linkRange));
1422 - (IBAction)toggleSplitWindow: (id)sender
1424 if(!split){
1425 textView2 = [[SCTextView alloc] initWithFrame: [scrollView frame]];
1426 [self initialiseTextViewParams: textView2];
1427 NSLog(@"delegate: %@", [textView2 delegate]);
1428 [textView2 setBackgroundColor: [textView backgroundColor]];
1429 [textView2 setSelectedTextAttributes: [textView selectedTextAttributes]];
1430 [textView2 setUsesAutoInOutdent:[textView usesAutoInOutdent]];
1431 scrollView2 = [[NSScrollView alloc] initWithFrame:[scrollView frame]];
1432 [scrollView2 setHasVerticalScroller:YES];
1434 // a bit easier to see
1435 [scrollView setBorderType:NSBezelBorder];
1436 [scrollView2 setBorderType:NSBezelBorder];
1438 [mySplitView addSubview:scrollView2];
1439 [scrollView2 setDocumentView: textView2];
1441 [[textView2 layoutManager] replaceTextStorage:[textView textStorage]];
1443 [textView2 release];
1444 [scrollView2 release];
1446 [mySplitView adjustSubviews];
1447 split = YES;
1448 [(NSMenuItem*)sender setState:NSOnState];
1449 } else {
1450 [scrollView2 removeFromSuperview];
1451 [scrollView setBorderType:NSNoBorder];
1452 scrollView2 = nil;
1453 textView2 = nil;
1454 split = NO;
1455 [(NSMenuItem*)sender setState:NSOffState];
1460 - (void)setActiveTextView:(SCTextView*)aTextView
1462 activeTextView = aTextView;
1465 - (void) setUsesAutoInOutdent: (bool) flag;
1467 [textView setUsesAutoInOutdent:flag];
1468 [textView2 setUsesAutoInOutdent:flag];
1470 @end
1473 NSString* pathOfHelpFileFor(NSString* selection)
1476 NSString* helpFilePath = nil;
1478 NSFileManager* fileManager = [NSFileManager defaultManager];
1479 if (!fileManager) return helpFilePath; // == NULL
1481 helpFilePath = helpFileWithName(fileManager, selection, nil);
1482 if (helpFilePath) return helpFilePath;
1484 return helpFilePath; // possibly == NULL
1488 void showHelpFor(NSString* selection)
1490 NSDocumentController* docctl = [NSDocumentController sharedDocumentController];
1491 if (!docctl)
1492 return;
1494 NSString *helpFilePath = pathOfHelpFileFor(selection);
1495 if(!helpFilePath) { // none found
1496 helpFilePath = helpFileWithName([NSFileManager defaultManager], @"Help", nil);
1497 if(!helpFilePath) { // not even Help.help ?
1498 post("\nCan't find help for '%s'\n", [selection cString]);
1499 return;
1503 MyDocument *doc = (MyDocument*)[docctl documentForFileName: helpFilePath];
1504 if (!doc) {
1505 doc = [docctl openDocumentWithContentsOfURL:[NSURL fileURLWithPath:helpFilePath] display:true error:nil];
1507 if (!doc) {
1508 post("Can't open Help File Document '%s'\n", [helpFilePath cString]);
1509 return;
1512 NSWindow *window = [[[doc windowControllers] objectAtIndex: 0] window];
1513 if (!window) {
1514 post("!! window controller returns nil ? failed to open help file window\n");
1515 return;
1517 [window makeKeyAndOrderFront: nil];
1521 void fixHTMLBoldUnderline(NSMutableString* html) {
1522 // NSTextView doesn't like <b> tags within spans, and unfortunately the export outputs bold underlines that way.
1523 // The following figures out what the span name is for underlining, and fixes this
1524 NSRange underlineSpan = [html rangeOfString: @"{text-decoration: underline}"];
1526 if(underlineSpan.location != NSNotFound) {
1528 unsigned lineStart, nameStart;
1529 // goto beginning of line
1530 [html getLineStart: (NSUInteger*)&lineStart end: NULL contentsEnd: NULL forRange: underlineSpan];
1531 nameStart = lineStart + 5; // offset for "span."
1532 NSString *underlineSpanClass = [html substringWithRange: NSMakeRange(nameStart, underlineSpan.location - 1 - nameStart)];
1533 //NSLog(@"span class: %@", underlineSpanClass);
1534 NSString *underlineBoldSpanTag = [NSString stringWithFormat: @"<span class=\"%@\"><b>", underlineSpanClass];
1535 NSString *replaceTag = [NSString stringWithFormat: @"<b><span class=\"%@\">", underlineSpanClass];
1536 //NSLog(@"span tag: %@", underlineBoldSpanTag);
1538 NSRange openTagRange = [html rangeOfString: underlineBoldSpanTag options: NSCaseInsensitiveSearch]; // finds the first one
1539 while (openTagRange.location != NSNotFound) {
1540 [html replaceCharactersInRange: openTagRange withString: replaceTag];
1541 // now replace the close tag
1542 unsigned int end = NSMaxRange(openTagRange);
1543 NSRange closeTagRange = [html rangeOfString: @"</b></span>" options: NSCaseInsensitiveSearch range: NSMakeRange(end, [html length] - end)];
1544 [html replaceCharactersInRange: closeTagRange withString: @"</span></b>"];
1545 // see if there's another one
1546 end = NSMaxRange(closeTagRange);
1547 openTagRange = [html rangeOfString: underlineBoldSpanTag options: NSCaseInsensitiveSearch range: NSMakeRange(end, [html length] - end)];
1552 // base is a directory, filepath is a file
1553 NSString* pathOfFileRelativeToBaseDir(NSString *filepath, NSString *baseDir) {
1555 if (![filepath isAbsolutePath]) {
1556 return filepath;
1557 } else if(![baseDir isAbsolutePath]) {
1558 return nil;
1560 NSArray *pathComponents = [filepath pathComponents];
1561 NSArray *baseComponents = [baseDir pathComponents];
1562 NSEnumerator *pathEnumerator = [pathComponents objectEnumerator];
1563 NSEnumerator *baseEnumerator = [baseComponents objectEnumerator];
1565 int i = 0, pathCount, baseCount, shortest;
1566 pathCount = [pathComponents count];
1567 baseCount = [baseComponents count];
1568 shortest = pathCount <= baseCount ? pathCount : baseCount;
1570 while((i < shortest) && [[pathEnumerator nextObject] isEqualToString: [baseEnumerator nextObject]]) {
1571 i++;
1574 NSString *result = @"";
1575 for(int j =0; j < (baseCount - i); j++) {
1576 result = [result stringByAppendingString: @"../"];
1578 result = [result stringByAppendingString: [NSString pathWithComponents: [pathComponents subarrayWithRange: NSMakeRange(i, pathCount - i) ]]];
1579 return result;