1 // Since SC v3.2 dev, Document is an ABSTRACT class. Can't be instantiated directly.
2 // Subclasses provide the editor-specific implementation, e.g. CocoaDocument for the standard Mac interface.
3 // Subclasses also (in their SC code files) add a "implementationClass" method to Document to tell it to use them.
7 classvar <dir="", <wikiDir="", <allDocuments, >current;
8 classvar <>globalKeyDownAction, <> globalKeyUpAction, <>initAction;
10 classvar <>autoRun = true;
11 classvar <>wikiBrowse = true;
13 classvar <>implementationClass;
15 classvar <theme, <themes;
17 //don't change the order of these vars:
18 var <dataptr, <>keyDownAction, <>keyUpAction, <>mouseUpAction;
19 var <>toFrontAction, <>endFrontAction, <>onClose, <>mouseDownAction;
23 var <envir, savedEnvir;
32 num = this.numberOfOpen;
34 doc = this.newFromIndex(i);
36 postColor = Color.black;
39 classColor: Color(0, 0, 0.75, 1),
40 textColor: Color(0, 0, 0, 1),
41 stringColor: Color(0.375, 0.375, 0.375, 1),
42 commentColor: Color(0.75, 0, 0, 1),
43 symbolColor: Color(0, 0.45, 0, 1),
44 numberColor: Color(0, 0, 0, 1)
47 theme = themes.default;
50 *open { | path, selectionStart=0, selectionLength=0, envir |
52 env = currentEnvironment;
53 if(this.current.notNil) { this.current.restoreCurrentEnvironment };
54 doc = Document.implementationClass.prBasicNew.initFromPath(path, selectionStart, selectionLength);
58 currentEnvironment = env
63 *new { | title="Untitled", string="", makeListener=false, envir |
65 env = currentEnvironment;
66 if(this.current.notNil) { this.current.restoreCurrentEnvironment };
67 doc = Document.implementationClass.new(title, string, makeListener);
71 currentEnvironment = env
77 path = path.standardizePath;
78 if(path == "") { dir = path } {
79 if(pathMatch(path).isEmpty) { ("there is no such path:" + path).postln } {
86 path = path.standardizePath;
87 if(path == "") {wikiDir = path } {
88 if(pathMatch(path).isEmpty) { ("there is no such path:" + path).postln } {
94 *standardizePath { | p |
96 pathName = PathName(p.standardizePath);
97 ^if(pathName.isRelativePath) {
98 dir ++ pathName.fullPath
103 *abrevPath { | path |
104 if(path.size < dir.size) { ^path };
105 if(path.copyRange(0,dir.size - 1) == dir) {
106 ^path.copyRange(dir.size, path.size - 1)
115 *hasEditedDocuments {
116 allDocuments.do { | doc, i |
124 *closeAll { | leavePostWindowOpen = true |
125 allDocuments.do { | doc, i |
126 if(leavePostWindowOpen.not) {
129 if(doc.isListener.not) {
136 *closeAllUnedited { | leavePostWindowOpen = true |
138 allDocuments.do({ | doc, i |
140 listenerWindow = doc;
142 if(doc.isEdited.not, {
147 if(leavePostWindowOpen.not, {
148 listenerWindow.close;
153 if ( thisProcess.platform.hasFeature( \emacs ), {
154 ^this.implementationClass.current;
160 ^allDocuments[this.implementationClass.prGetIndexOfListener];
163 ^allDocuments.indexOf(this) == this.class.prGetIndexOfListener
172 this.prSetFileName(apath);
174 dir { var path = this.path; ^path !? { path.dirname } }
188 this.prSetTitle(argName);
191 background_ { | color |
192 this.prSetBackgroundColor(color);
197 this.prGetBackgroundColor(color);
201 selectedBackground_ { | color |
202 this.prSetSelectedBackgroundColor(color);
208 this.prGetSelectedBackgroundColor(color);
212 *postColor_ { | col |
214 ^Document.implementationClass.postColor_(col);
217 stringColor_ { | color, rangeStart = -1, rangeSize = 0 |
219 this.setTextColor(color,rangeStart, rangeSize);
222 ^this.prGetBounds(Rect.new);
224 bounds_ { | argBounds |
225 ^this.prSetBounds(argBounds);
235 ^this.subclassResponsibility(thisMethod)
239 ^this.subclassResponsibility(thisMethod)
242 alwaysOnTop_ { |boolean=true|
243 ^this.subclassResponsibility(thisMethod)
247 ^this.subclassResponsibility(thisMethod)
251 ^this.subclassResponsibility(thisMethod)
254 selectLine { | line |
255 this.prSelectLine(line);
258 selectRange { | start=0, length=0 |
259 ^this.subclassResponsibility(thisMethod)
262 editable_ { | abool=true |
264 this.prIsEditable_(abool);
267 ^this.subclassResponsibility(thisMethod)
270 promptToSave_ { | bool |
271 ^this.subclassResponsibility(thisMethod)
275 ^this.subclassResponsibility(thisMethod)
279 ^this.subclassResponsibility(thisMethod)
282 *setTheme { | themeName |
283 theme = themes[themeName];
284 Document.implementationClass.prSetSyntaxColorTheme(
296 ^this.subclassResponsibility(thisMethod)
299 ^Document.current === this
303 ^this.selectedRangeLocation
307 ^this.selectedRangeSize
310 string { | rangestart, rangesize = 1 |
311 if(rangestart.isNil,{
314 ^this.rangeText(rangestart, rangesize);
317 string_ { | string, rangestart = -1, rangesize = 1 |
318 this.insertTextRange(string, rangestart, rangesize);
325 font_ { | font, rangestart = -1, rangesize=0 |
326 this.setFont(font, rangestart, rangesize)
329 selectedString_ { | txt |
330 this.prinsertText(txt)
334 ^this.getSelectedLines(this.selectionStart, 0);
337 getSelectedLines { | rangestart = -1, rangesize = 0 |
338 var start, end, str, max;
342 end = start + rangesize;
344 str[start] !== Char.nl and: { start >= 0 }
345 } { start = start - 1 };
347 str[end] !== Char.nl and: { end < max }
349 ^str.copyRange(start + 1, end);
355 this.class.current = this;
356 this.saveCurrentEnvironment;
357 toFrontAction.value(this);
361 endFrontAction.value(this);
362 this.restoreCurrentEnvironment;
365 makeWikiPage { | wikiWord, extension=(".rtf"), directory |
366 var filename, file, doc, string, dirName;
367 directory = directory ? wikiDir;
368 filename = directory ++ wikiWord ++ extension;
369 file = File(filename, "w");
371 string = "{\\rtf1\\mac\\ansicpg10000\\cocoartf102\\n{\\fonttbl}\n"
372 "{\\colortbl;\\red255\\green255\\blue255;}\n"
373 "Write about " ++ wikiWord ++ " here.\n}";
377 doc = this.class.open(filename);
379 doc.selectRange(0,0x7FFFFFFF);
381 if(doc.string == ("Write about " ++ wikiWord ++ " here.")) {
382 unixCmd("rm" + filename)
386 // in a second try, check if a path must be created.
387 // user makes double click on string.
388 dirName = wikiWord.dirname;
390 dirName = directory ++ dirName;
391 "created directory: % \n".postf(dirName);
392 unixCmd("mkdir -p" + dirName);
398 var selectedText, filename, index, directory;
399 var extensions = #[".rtf", ".sc", ".scd", ".txt", "", ".rtfd", ".html"];
400 selectedText = this.selectedText;
401 index = this.selectionStart;
403 this.selectRange(index, 0);
405 // refer to local link with round parens
406 if(selectedText.first == $( /*)*/ and: {/*(*/ selectedText.last == $) }) {
407 selectedText = selectedText[1 .. selectedText.size-2];
408 directory = Document.current.path.dirname ++ "/";
413 case { selectedText[0] == $* }
416 selectedText = selectedText.drop(1);
417 extensions.do { |ext|
418 filename = directory ++ selectedText ++ ext;
419 if (File.exists(filename)) {
420 // open existing wiki page
425 filename = "Help/help-scripts/" ++ selectedText ++ ext;
426 if (File.exists(filename)) {
427 // open help-script document
434 { selectedText.first == $[ and: { selectedText.last == $] }}
437 selectedText[1 .. selectedText.size-2].openHelpFile
439 { selectedText.containsStringAt(0, "http://")
440 or: { selectedText.containsStringAt(0, "file://") } }
443 ("open " ++ selectedText).unixCmd;
445 { selectedText.containsStringAt(selectedText.size-1, "/") }
447 Document(selectedText,
448 pathMatch(directory ++ selectedText).collect({ |it|it.basename ++ "\n"}).join
453 if(index + selectedText.size > this.text.size) { ^this };
454 extensions.do { |ext|
455 filename = directory ++ selectedText ++ ext;
456 if (File.exists(filename)) {
457 // open existing wiki page
458 this.class.open(filename);
462 // make a new wiki page
463 this.makeWikiPage(selectedText, nil, directory);
467 mouseUp{ | x, y, modifiers, buttonNumber, clickCount, clickPos |
468 mouseUpAction.value(this, x, y, modifiers, buttonNumber, clickCount); if (wikiBrowse and: { this.linkAtClickPos(clickPos).not }
469 and: { this.selectUnderlinedText(clickPos) } ) {
474 keyDown { | character, modifiers, unicode, keycode |
475 this.class.globalKeyDownAction.value(this,character, modifiers, unicode, keycode);
476 keyDownAction.value(this,character, modifiers, unicode, keycode);
479 keyUp { | character, modifiers, unicode, keycode |
480 this.class.globalKeyUpAction.value(this,character, modifiers, unicode, keycode);
481 keyUpAction.value(this,character, modifiers, unicode, keycode);
485 ^if(this.path.isNil or: { doc.path.isNil }) { doc === this } {
486 this.path == doc.path
490 *defaultUsesAutoInOutdent_ {|bool|
491 Document.implementationClass.prDefaultUsesAutoInOutdent_(bool)
494 usesAutoInOutdent_ {|bool|
495 this.prUsesAutoInOutdent_(bool)
498 *prDefaultUsesAutoInOutdent_{|bool|
499 this.subclassResponsibility(thisMethod);
502 prUsesAutoInOutdent_{|bool|
503 ^this.subclassResponsibility(thisMethod);
507 // private implementation
509 prIsEditable_{ | editable=true |
510 ^this.subclassResponsibility(thisMethod)
512 prSetTitle { | argName |
513 ^this.subclassResponsibility(thisMethod)
516 ^this.subclassResponsibility(thisMethod)
519 ^this.subclassResponsibility(thisMethod)
521 prSetFileName { | apath |
522 ^this.subclassResponsibility(thisMethod)
524 prGetBounds { | argBounds |
525 ^this.subclassResponsibility(thisMethod)
528 prSetBounds { | argBounds |
529 ^this.subclassResponsibility(thisMethod)
532 *prSetSyntaxColorTheme{ |textC, classC, stringC, symbolC, commentC, numberC|
533 ^this.subclassResponsibility(thisMethod);
536 // if range is -1 apply to whole doc
537 setFont { | font, rangeStart= -1, rangeSize=100 |
538 ^this.subclassResponsibility(thisMethod)
541 setTextColor { | color, rangeStart = -1, rangeSize = 0 |
542 ^this.subclassResponsibility(thisMethod)
546 ^this.subclassResponsibility(thisMethod)
549 ^this.subclassResponsibility(thisMethod)
551 selectUnderlinedText { | clickPos |
552 ^this.subclassResponsibility(thisMethod)
555 linkAtClickPos { | clickPos |
556 ^this.subclassResponsibility(thisMethod)
559 rangeText { | rangestart=0, rangesize=1 |
560 ^this.subclassResponsibility(thisMethod)
564 ^this.subclassResponsibility(thisMethod)
568 onClose.value(this); // call user function
569 this.restoreCurrentEnvironment;
570 allDocuments.remove(this);
574 prinsertText { | dataPtr, txt |
575 ^this.subclassResponsibility(thisMethod)
577 insertTextRange { | string, rangestart, rangesize |
578 ^this.subclassResponsibility(thisMethod)
582 allDocuments = allDocuments.add(this);
583 this.editable = true;
585 if (this.rangeText(0,7) == "/*RUN*/")
591 initAction.value(this);
595 //this is called after recompiling the lib
597 ^this.subclassResponsibility(thisMethod)
600 thisProcess.platform.when(\_NumberOfOpenTextWindows) {
602 } { ^allDocuments.size };
606 *newFromIndex { | idx |
607 ^super.new.initByIndex(idx)
609 initByIndex { | idx |
610 //allDocuments = allDocuments.add(this);
612 doc = this.prinitByIndex(idx);
613 if(doc.isNil,{^nil});
616 prinitByIndex { | idx |
617 ^this.subclassResponsibility(thisMethod)
620 //this is called from the menu: open, new
622 ^Document.implementationClass.prBasicNew.initLast
626 ^this.subclassResponsibility(thisMethod)
630 ^this.subclassResponsibility(thisMethod)
634 initFromPath { | path, selectionStart, selectionLength |
637 stpath = this.class.standardizePath(path);
638 this.propen(stpath, selectionStart, selectionLength);
640 this.class.allDocuments.do{ |d|
641 if(d.path == stpath.absolutePath){
647 this.background_(Color.white);
650 propen { | path, selectionStart=0, selectionLength=0 |
651 ^this.subclassResponsibility(thisMethod)
654 // private newTextWindow
655 initByString{ | argTitle, str, makeListener |
657 this.prinitByString(argTitle, str, makeListener);
658 this.background_(Color.white);
659 if(dataptr.isNil,{^nil});
661 this.title = argTitle;
664 prinitByString { | title, str, makeListener |
665 ^this.subclassResponsibility(thisMethod)
671 prSetBackgroundColor { | color |
672 ^this.subclassResponsibility(thisMethod)
674 prGetBackgroundColor { | color |
675 ^this.subclassResponsibility(thisMethod)
677 prSetSelectedBackgroundColor { | color |
678 ^this.subclassResponsibility(thisMethod);
680 prGetSelectedBackgroundColor { | color |
681 ^this.subclassResponsibility(thisMethod);
683 selectedRangeLocation {
684 ^this.subclassResponsibility(thisMethod)
687 ^this.subclassResponsibility(thisMethod)
690 prSelectLine { | line |
691 ^this.subclassResponsibility(thisMethod)
694 *prGetIndexOfListener {
695 ^this.subclassResponsibility(thisMethod)
699 //---not yet implemented
704 //*reviewUnsavedDocumentsWithAlertTitle
706 //*recentDocumentPaths
714 // Environment handling Document with its own envir must set and restore currentEnvironment on entry and exit.
715 // Requires alteration of *open, *new, closed, didBecomeKey, and didResignKey
719 if (this.class.current == this) {
720 if (savedEnvir.isNil) {
721 this.saveCurrentEnvironment
726 restoreCurrentEnvironment {
727 if (envir.notNil) { currentEnvironment = savedEnvir };
730 saveCurrentEnvironment {
732 savedEnvir = currentEnvironment;
733 currentEnvironment = envir;