// Since SC v3.2 dev, Document is an ABSTRACT class. Can't be instantiated directly. // Subclasses provide the editor-specific implementation, e.g. CocoaDocument for the standard Mac interface. // Subclasses also (in their SC code files) add a "implementationClass" method to Document to tell it to use them. Document { classvar current; classvar <>globalKeyDownAction, <> globalKeyUpAction, <>initAction; classvar <>autoRun = true; classvar <>wikiBrowse = true; classvar <>implementationClass; classvar keyDownAction, <>keyUpAction, <>mouseUpAction; var <>toFrontAction, <>endFrontAction, <>onClose, <>mouseDownAction; var = 0 } } { start = start - 1 }; while { str[end] !== Char.nl and: { end < max } } { end = end + 1 }; ^str.copyRange(start + 1, end); } //actions: didBecomeKey { this.class.current = this; this.saveCurrentEnvironment; toFrontAction.value(this); } didResignKey { endFrontAction.value(this); this.restoreCurrentEnvironment; } makeWikiPage { | wikiWord, extension=(".rtf"), directory | var filename, file, doc, string, dirName; directory = directory ? wikiDir; filename = directory ++ wikiWord ++ extension; file = File(filename, "w"); if (file.isOpen) { string = "{\\rtf1\\mac\\ansicpg10000\\cocoartf102\\n{\\fonttbl}\n" "{\\colortbl;\\red255\\green255\\blue255;}\n" "Write about " ++ wikiWord ++ " here.\n}"; file.write(string); file.close; doc = this.class.open(filename); doc.path = filename; doc.selectRange(0,0x7FFFFFFF); doc.onClose = { if(doc.string == ("Write about " ++ wikiWord ++ " here.")) { unixCmd("rm" + filename) }; }; } { // in a second try, check if a path must be created. // user makes double click on string. dirName = wikiWord.dirname; if(dirName != ".") { dirName = directory ++ dirName; "created directory: % \n".postf(dirName); unixCmd("mkdir -p" + dirName); }; } } openWikiPage { var selectedText, filename, index, directory; var extensions = #[".rtf", ".sc", ".scd", ".txt", "", ".rtfd", ".html"]; selectedText = this.selectedText; index = this.selectionStart; this.selectRange(index, 0); // refer to local link with round parens if(selectedText.first == $( /*)*/ and: {/*(*/ selectedText.last == $) }) { selectedText = selectedText[1 .. selectedText.size-2]; directory = Document.current.path.dirname ++ "/"; } { directory = wikiDir; }; case { selectedText[0] == $* } { // execute file selectedText = selectedText.drop(1); extensions.do { |ext| filename = directory ++ selectedText ++ ext; if (File.exists(filename)) { // open existing wiki page filename.load; ^this } { filename = "Help/help-scripts/" ++ selectedText ++ ext; if (File.exists(filename)) { // open help-script document filename.load; ^this } } }; } { selectedText.first == $[ and: { selectedText.last == $] }} { // open help file selectedText[1 .. selectedText.size-2].openHelpFile } { selectedText.containsStringAt(0, "http://") or: { selectedText.containsStringAt(0, "file://") } } { // open URL ("open " ++ selectedText).unixCmd; } { selectedText.containsStringAt(selectedText.size-1, "/") } { Document(selectedText, pathMatch(directory ++ selectedText).collect({ |it|it.basename ++ "\n"}).join ) } { if(index + selectedText.size > this.text.size) { ^this }; extensions.do { |ext| filename = directory ++ selectedText ++ ext; if (File.exists(filename)) { // open existing wiki page this.class.open(filename); ^this } }; // make a new wiki page this.makeWikiPage(selectedText, nil, directory); }; } mouseUp{ | x, y, modifiers, buttonNumber, clickCount, clickPos | mouseUpAction.value(this, x, y, modifiers, buttonNumber, clickCount); if (wikiBrowse and: { this.linkAtClickPos(clickPos).not } and: { this.selectUnderlinedText(clickPos) } ) { ^this.openWikiPage }; } keyDown { | character, modifiers, unicode, keycode | this.class.globalKeyDownAction.value(this,character, modifiers, unicode, keycode); keyDownAction.value(this,character, modifiers, unicode, keycode); } keyUp { | character, modifiers, unicode, keycode | this.class.globalKeyUpAction.value(this,character, modifiers, unicode, keycode); keyUpAction.value(this,character, modifiers, unicode, keycode); } == { | doc | ^if(this.path.isNil or: { doc.path.isNil }) { doc === this } { this.path == doc.path } } *defaultUsesAutoInOutdent_ {|bool| Document.implementationClass.prDefaultUsesAutoInOutdent_(bool) } usesAutoInOutdent_ {|bool| this.prUsesAutoInOutdent_(bool) } *prDefaultUsesAutoInOutdent_{|bool| this.subclassResponsibility(thisMethod); } prUsesAutoInOutdent_{|bool| ^this.subclassResponsibility(thisMethod); } // private implementation prIsEditable_{ | editable=true | ^this.subclassResponsibility(thisMethod) } prSetTitle { | argName | ^this.subclassResponsibility(thisMethod) } prGetTitle { ^this.subclassResponsibility(thisMethod) } prGetFileName { ^this.subclassResponsibility(thisMethod) } prSetFileName { | apath | ^this.subclassResponsibility(thisMethod) } prGetBounds { | argBounds | ^this.subclassResponsibility(thisMethod) } prSetBounds { | argBounds | ^this.subclassResponsibility(thisMethod) } *prSetSyntaxColorTheme{ |textC, classC, stringC, symbolC, commentC, numberC| ^this.subclassResponsibility(thisMethod); } // if range is -1 apply to whole doc setFont { | font, rangeStart= -1, rangeSize=100 | ^this.subclassResponsibility(thisMethod) } setTextColor { | color, rangeStart = -1, rangeSize = 0 | ^this.subclassResponsibility(thisMethod) } text { ^this.subclassResponsibility(thisMethod) } selectedText { ^this.subclassResponsibility(thisMethod) } selectUnderlinedText { | clickPos | ^this.subclassResponsibility(thisMethod) } linkAtClickPos { | clickPos | ^this.subclassResponsibility(thisMethod) } rangeText { | rangestart=0, rangesize=1 | ^this.subclassResponsibility(thisMethod) } prclose { ^this.subclassResponsibility(thisMethod) } closed { onClose.value(this); // call user function this.restoreCurrentEnvironment; allDocuments.remove(this); dataptr = nil; } prinsertText { | dataPtr, txt | ^this.subclassResponsibility(thisMethod) } insertTextRange { | string, rangestart, rangesize | ^this.subclassResponsibility(thisMethod) } prAdd { allDocuments = allDocuments.add(this); this.editable = true; if (autoRun) { if (this.rangeText(0,7) == "/*RUN*/") { this.text.interpret; } }; current = this; initAction.value(this); } //this is called after recompiling the lib *prnumberOfOpen { ^this.subclassResponsibility(thisMethod) } *numberOfOpen { thisProcess.platform.when(\_NumberOfOpenTextWindows) { ^this.prnumberOfOpen } { ^allDocuments.size }; ^0 } *newFromIndex { | idx | ^super.new.initByIndex(idx) } initByIndex { | idx | //allDocuments = allDocuments.add(this); var doc; doc = this.prinitByIndex(idx); if(doc.isNil,{^nil}); this.prAdd; } prinitByIndex { | idx | ^this.subclassResponsibility(thisMethod) } //this is called from the menu: open, new *prGetLast { ^Document.implementationClass.prBasicNew.initLast } initLast { ^this.subclassResponsibility(thisMethod) } prGetLastIndex { ^this.subclassResponsibility(thisMethod) } // private open initFromPath { | path, selectionStart, selectionLength | var stpath; // path = apath; stpath = this.class.standardizePath(path); this.propen(stpath, selectionStart, selectionLength); if(dataptr.isNil,{ this.class.allDocuments.do{ |d| if(d.path == stpath.absolutePath){ ^d } }; ^nil }); this.background_(Color.white); ^this.prAdd; } propen { | path, selectionStart=0, selectionLength=0 | ^this.subclassResponsibility(thisMethod) } // private newTextWindow initByString{ | argTitle, str, makeListener | this.prinitByString(argTitle, str, makeListener); this.background_(Color.white); if(dataptr.isNil,{^nil}); this.prAdd; this.title = argTitle; } prinitByString { | title, str, makeListener | ^this.subclassResponsibility(thisMethod) } // other private // if -1 whole doc prSetBackgroundColor { | color | ^this.subclassResponsibility(thisMethod) } prGetBackgroundColor { | color | ^this.subclassResponsibility(thisMethod) } prSetSelectedBackgroundColor { | color | ^this.subclassResponsibility(thisMethod); } prGetSelectedBackgroundColor { | color | ^this.subclassResponsibility(thisMethod); } selectedRangeLocation { ^this.subclassResponsibility(thisMethod) } selectedRangeSize { ^this.subclassResponsibility(thisMethod) } prSelectLine { | line | ^this.subclassResponsibility(thisMethod) } *prGetIndexOfListener { ^this.subclassResponsibility(thisMethod) } //---not yet implemented // ~/Documents // /Volumes // Music/Patches //*reviewUnsavedDocumentsWithAlertTitle //*saveAllDocuments //*recentDocumentPaths //save //saveAs //print // //hasPath was loaded // Environment handling Document with its own envir must set and restore currentEnvironment on entry and exit. // Requires alteration of *open, *new, closed, didBecomeKey, and didResignKey envir_ { | ev | envir = ev; if (this.class.current == this) { if (savedEnvir.isNil) { this.saveCurrentEnvironment } } } restoreCurrentEnvironment { if (envir.notNil) { currentEnvironment = savedEnvir }; } saveCurrentEnvironment { if (envir.notNil) { savedEnvir = currentEnvironment; currentEnvironment = envir; } } *prBasicNew { ^super.new } }