scel/sc/scide_scel/Document.sc
2021-08-19 20:54:46 -04:00

587 lines
11 KiB
Python

// 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 <dir="", <allDocuments, >current;
classvar <globalKeyDownAction, <globalKeyUpAction, <>initAction;
classvar <>autoRun = true;
classvar <>implementationClass;
//don't change the order of these vars:
var <dataptr, <>keyDownAction, <>keyUpAction, <>mouseUpAction;
var <>toFrontAction, <>endFrontAction, <>onClose, <>mouseDownAction;
var <stringColor;
var <envir, savedEnvir;
var <editable;
*initClass{
allDocuments = [];
}
*startup {
var num, doc;
num = this.numberOfOpen;
num.do { | i |
doc = this.newFromIndex(i);
};
}
*open { | path, selectionStart=0, selectionLength=0, envir |
var doc = Document.implementationClass.prBasicNew.initFromPath(path, selectionStart, selectionLength);
if (envir.notNil and: { doc.notNil }) { doc.envir_(envir) };
^doc
}
*new { | title="Untitled", string="", makeListener=false, envir |
var doc = Document.implementationClass.new(title, string, makeListener);
if (envir.notNil and: { doc.notNil }) { doc.envir_(envir) };
^doc
}
*dir_ { | path |
path = path.standardizePath;
if(path == "") { dir = path } {
if(pathMatch(path).isEmpty) { ("there is no such path:" + path).postln } {
dir = path ++ "/"
}
}
}
*standardizePath { | p |
var pathName;
pathName = PathName(p.standardizePath);
^if(pathName.isRelativePath) {
dir ++ pathName.fullPath
} {
pathName.fullPath
}
}
*abrevPath { | path |
if(path.size < dir.size) { ^path };
if(path.copyRange(0,dir.size - 1) == dir) {
^path.copyRange(dir.size, path.size - 1)
};
^path
}
*openDocuments {
^allDocuments
}
*hasEditedDocuments {
allDocuments.do { | doc, i |
if(doc.isEdited) {
^true;
}
}
^false
}
*closeAll { | leavePostWindowOpen = true |
allDocuments.do { | doc, i |
if(leavePostWindowOpen.not) {
doc.close;
} {
if(doc.isListener.not) {
doc.close;
}
}
}
}
*closeAllUnedited { | leavePostWindowOpen = true |
var listenerWindow;
allDocuments.do({ | doc, i |
if(doc.isListener,{
listenerWindow = doc;
},{
if(doc.isEdited.not, {
doc.close;
});
})
});
if(leavePostWindowOpen.not, {
listenerWindow.close;
})
}
*globalKeyDownAction_ {|action|
globalKeyDownAction = action;
this.implementationClass.prGlobalKeyDownAction_(action);
}
*prGlobalKeyDownAction_ { }
*globalKeyUpAction_ {|action|
globalKeyUpAction = action;
this.implementationClass.prGlobalKeyUpAction_(action);
}
*prGlobalKeyUpAction_ { }
*current {
if ( thisProcess.platform.hasFeature( \emacs ), {
^this.implementationClass.current;
});
^current;
}
*listener {
var index = this.prGetIndexOfListener;
if (index.notNil) {
^allDocuments[index];
} {
^nil
}
}
isListener {
var index = this.class.prGetIndexOfListener;
if (index.notNil) {
^allDocuments.indexOf(this) == index
} {
^False
}
}
// document setup
path {
^this.prGetFileName
}
path_ { |apath|
this.prSetFileName(apath);
}
dir {
var path = this.path;
^path !? { path.dirname }
}
name {
^this.title
}
name_ { |aname|
this.title_(aname)
}
title {
^this.prGetTitle
}
title_ { | argName |
this.prSetTitle(argName);
}
bounds {
^this.prGetBounds(Rect.new);
}
bounds_ { | argBounds |
^this.prSetBounds(argBounds);
}
// interaction:
close {
this.prclose
}
front {
^this.subclassResponsibility(thisMethod)
}
unfocusedFront {
^this.subclassResponsibility(thisMethod)
}
alwaysOnTop_ { |boolean=true|
^this.subclassResponsibility(thisMethod)
}
alwaysOnTop {
^this.subclassResponsibility(thisMethod)
}
selectLine { | line |
this.prSelectLine(line);
}
selectRange { | start=0, length=0 |
^this.subclassResponsibility(thisMethod)
}
editable_ { | abool=true |
editable = abool;
this.prIsEditable_(abool);
}
removeUndo {
^this.subclassResponsibility(thisMethod)
}
promptToSave_ { | bool |
^this.subclassResponsibility(thisMethod)
}
promptToSave {
^this.subclassResponsibility(thisMethod)
}
underlineSelection {
^this.subclassResponsibility(thisMethod)
}
// state info
isEdited {
^this.subclassResponsibility(thisMethod)
}
isFront {
^Document.current === this
}
selectionStart {
^this.selectedRangeLocation
}
selectionSize {
^this.selectedRangeSize
}
string { | rangestart, rangesize = 1 |
if(rangestart.isNil,{
^this.text;
});
^this.rangeText(rangestart, rangesize);
}
string_ { | string, rangestart = -1, rangesize = 1 |
this.insertTextRange(string, rangestart, rangesize);
}
selectedString {
^this.selectedText
}
font_ { | font, rangestart = -1, rangesize=0 |
this.setFont(font, rangestart, rangesize)
}
selectedString_ { | txt |
this.prinsertText(txt)
}
currentLine {
^this.getSelectedLines(this.selectionStart, 0);
}
getSelectedLines { | rangestart = -1, rangesize = 0 |
var start, end, str, max;
str = this.string;
max = str.size;
start = rangestart;
end = start + rangesize;
while {
str[start] !== Char.nl and: { start >= 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;
}
mouseUp{ | x, y, modifiers, buttonNumber, clickCount, clickPos |
mouseUpAction.value(this, x, y, modifiers, buttonNumber, clickCount)
}
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
}
}
hash {
^(this.path ? this).hash
}
*defaultUsesAutoInOutdent_ {|bool|
Document.implementationClass.prDefaultUsesAutoInOutdent_(bool)
}
usesAutoInOutdent_ {|bool|
this.prUsesAutoInOutdent_(bool)
}
*prDefaultUsesAutoInOutdent_{|bool|
this.subclassResponsibility(thisMethod);
}
*prPostColor_{ |color|
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)
}
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 {
if (this.implementationClass.isNil) {
^nil
};
if (this.implementationClass.respondsTo(\prGetIndexOfListener)) {
^this.implementationClass.prGetIndexOfListener
} {
^nil
}
}
//---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(envir.isNil) {
this.restoreCurrentEnvironment
} {
if (savedEnvir.isNil) {
this.saveCurrentEnvironment
}
}
}
}
restoreCurrentEnvironment {
if (savedEnvir.notNil) {
currentEnvironment = savedEnvir;
savedEnvir = nil;
}
}
saveCurrentEnvironment {
if (envir.notNil) {
savedEnvir = currentEnvironment;
currentEnvironment = envir;
}
}
*prBasicNew {
^super.new
}
}