6af534a0fb
This merges the Document and ScIDEDocument classes. This should improve readability and maintainability, and simplifies the implementation. As part of this: - some duplicate methods are deprecated - the previous Document has been copied into the scel folder to avoid breaking EmacsDocument Signed-off-by: Scott Wilson <i@scottwilson.ca>
587 lines
11 KiB
Python
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
|
|
}
|
|
}
|