// 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
}
}