MODULE WMDocumentEditor;
IMPORT
KernelLog, Streams, Files, Inputs, Strings, XML, XMLObjects, Configuration, Texts, TextUtilities, Codecs,
WMMacros, WMGraphics, WMRectangles, WMMessages, WMComponents, WMStandardComponents,
WMPopups, WMTextView, WMEditors, WMSearchComponents, WMDialogs, WMRestorable;
CONST
LoadButton* = {0};
StoreButton* = {1};
FormatButton*= {2};
SearchButton* = {3};
WrapButton* = {4};
ClearButton* = {5};
All* = {0..31};
DefaultTextEncoder = "Oberon";
TYPE
CaptionObject = OBJECT
VAR
caption : ARRAY 100 OF CHAR;
PROCEDURE &New*(CONST caption : ARRAY OF CHAR);
BEGIN
COPY(caption, SELF.caption);
END New;
END CaptionObject;
TYPE
Editor* = OBJECT(WMComponents.VisualComponent)
VAR
editor- : WMEditors.Editor;
toolbar : WMStandardComponents.Panel;
filenamePanel : WMStandardComponents.Panel;
filenameEdit : WMEditors.Editor;
resizer : WMStandardComponents.Resizer;
loadBtn, storeBtn, formatBtn : WMStandardComponents.Button;
searchBtn : WMStandardComponents.Button;
searchPanel : WMSearchComponents.SearchPanel;
wrapBtn, clearBtn : WMStandardComponents.Button;
popup : WMPopups.Popup;
lastFilename : Files.FileName;
codecFormat : ARRAY 100 OF CHAR;
autoCodecFormat : ARRAY 100 OF CHAR;
wordWrap, modified : BOOLEAN;
buttons : SET;
PROCEDURE FilenameEscapeHandler(sender, data : ANY);
BEGIN
filenameEdit.SetAsString(lastFilename);
END FilenameEscapeHandler;
PROCEDURE LoadHandler(sender, data : ANY);
VAR filename : Files.FileName;
BEGIN
filenameEdit.GetAsString(filename);
Strings.TrimWS(filename);
IF (filename # "") THEN
Load(filename, codecFormat);
END;
END LoadHandler;
PROCEDURE Load*(CONST filename, format : ARRAY OF CHAR);
VAR
text : Texts.Text;
msg : ARRAY 512 OF CHAR; res : LONGINT;
fullname : Files.FileName;
decoder : Codecs.TextDecoder;
in : Streams.Reader;
file : Files.File;
BEGIN
res := -1;
COPY(filename, fullname);
file := Files.Old(filename);
IF (file # NIL) THEN
file.GetName(fullname);
ELSE
file := Files.New(filename);
IF (file # NIL) THEN
file.GetName(fullname);
file := NIL;
END;
END;
IF (filenameEdit # NIL) THEN
filenameEdit.SetAsString(fullname);
lastFilename := fullname;
END;
text := editor.text;
text.AcquireWrite;
modified := TRUE;
text.Delete(0, text.GetLength());
editor.tv.firstLine.Set(0);
text.ReleaseWrite;
IF (file # NIL) THEN
IF (format = "AUTO") THEN
decoder := TextUtilities.DecodeAuto(fullname, autoCodecFormat);
ELSE
decoder := Codecs.GetTextDecoder(format);
END;
IF decoder # NIL THEN
COPY(format, codecFormat);
in := Codecs.OpenInputStream(fullname);
IF in # NIL THEN
decoder.Open(in, res);
IF res = 0 THEN
editor.text.onTextChanged.Remove(TextChanged);
SetText(decoder.GetText());
editor.text.onTextChanged.Add(TextChanged);
END;
ELSE
msg := "Can't open input stream on file "; Strings.Append(msg, fullname);
WMDialogs.Error("Error", msg);
END;
ELSE
msg := "No decoder for file "; Strings.Append(msg, fullname);
Strings.Append(msg, " (Format: "); Strings.Append(msg, format); Strings.Append(msg, ")");
WMDialogs.Error("Error", msg);
END;
END;
SetFormatCaption(format);
editor.tv.firstLine.Set(0);
editor.tv.cursor.SetPosition(0);
editor.tv.SetFocus;
modified := FALSE;
IF (buttons * StoreButton # {}) THEN storeBtn.caption.SetAOC("Store") END;
END Load;
PROCEDURE StoreHandler(sender, data : ANY);
VAR filename : Files.FileName;
BEGIN
filenameEdit.GetAsString(filename);
Strings.TrimWS(filename);
IF filename # "" THEN
Store(filename, codecFormat);
ELSE
WMDialogs.Error("Error", "Filename invalid");
filenameEdit.SetAsString(filename);
END;
END StoreHandler;
PROCEDURE Store*(CONST filename,format : ARRAY OF CHAR);
VAR
text : Texts.Text;
fullname : Files.FileName;
msg : ARRAY 512 OF CHAR; res : LONGINT;
backName : ARRAY 128 OF CHAR;
encoder : Codecs.TextEncoder;
w : Files.Writer;
f : Files.File;
BEGIN
IF (filenameEdit # NIL) THEN filenameEdit.SetAsString(filename); END;
f := Files.Old(filename);
IF (f # NIL) THEN
IF (Files.ReadOnly IN f.flags) THEN
msg := "File is read-only: "; Strings.Append(msg, filename);
WMDialogs.Error("Error", msg);
RETURN;
END;
f := NIL;
END;
Strings.Concat(filename, ".Bak", backName);
Files.Rename(filename, backName, res);
IF res = 0 THEN KernelLog.String("Backup created in "); KernelLog.String(backName); KernelLog.Ln END;
text := editor.text;
text.AcquireWrite;
IF (format = "AUTO") THEN
IF (autoCodecFormat = "") THEN
encoder := Codecs.GetTextEncoder(DefaultTextEncoder);
ELSE
encoder := Codecs.GetTextEncoder(autoCodecFormat);
END;
ELSE encoder := Codecs.GetTextEncoder(format);
END;
COPY(filename, fullname);
IF (encoder # NIL) THEN
f := Files.New(filename);
IF (f = NIL) THEN
msg := "Could not create file "; Strings.Append(msg, filename);
WMDialogs.Error("Error", msg);
RETURN;
END;
f.GetName(fullname);
Files.OpenWriter(w, f, 0);
encoder.Open(w);
encoder.WriteText(text, res);
IF res = 0 THEN
Files.Register(f); f.Update;
ELSE
msg := "Could not encode file "; Strings.Append(msg, fullname);
WMDialogs.Error("Error", msg);
END;
ELSE
msg := "Could not store file "; Strings.Append(msg, fullname); Strings.Append(msg, " (No encoder found)");
WMDialogs.Error("Error", msg);
END;
text.ReleaseWrite;
modified := FALSE;
IF (filenameEdit # NIL) THEN
filenameEdit.SetAsString(fullname);
lastFilename := fullname;
END;
IF (buttons * StoreButton # {}) THEN storeBtn.caption.SetAOC("Store") END;
END Store;
PROCEDURE FormatHandler(x, y : LONGINT; keys : SET; VAR handled : BOOLEAN);
VAR rectangle : WMRectangles.Rectangle; left, top : LONGINT;
BEGIN
IF (formatBtn = NIL) THEN RETURN END;
handled := TRUE;
rectangle := formatBtn.bounds.Get();
ToWMCoordinates(rectangle.l, rectangle.t, left, top);
popup.Popup(left, top + formatBtn.bounds.GetHeight());
END FormatHandler;
PROCEDURE SetFormatCaption(CONST format : ARRAY OF CHAR);
VAR caption : ARRAY 128 OF CHAR;
BEGIN
IF (formatBtn = NIL) THEN RETURN END;
caption := "Format : ";
Strings.Append(caption, format);
IF format = "AUTO" THEN Strings.Append(caption, " "); Strings.Append(caption, autoCodecFormat); END;
formatBtn.caption.SetAOC(caption);
formatBtn.Invalidate;
END SetFormatCaption;
PROCEDURE FormatPopupHandler(sender, data : ANY);
BEGIN
IF (popup # NIL) & (data # NIL) & (data IS CaptionObject) THEN
popup.Close;
COPY(data(CaptionObject).caption, codecFormat);
SetFormatCaption(codecFormat);
END;
END FormatPopupHandler;
PROCEDURE SearchHandler(sender, data : ANY);
VAR searchString : WMSearchComponents.SearchString;
BEGIN
EnsureSearchPanel;
searchPanel.visible.Set(TRUE);
searchPanel.SetToLastSelection;
searchPanel.searchEdit.GetAsString(searchString);
IF (searchString # "") THEN
searchPanel.SearchHandler(NIL, NIL);
ELSE
searchPanel.searchEdit.SetFocus;
END;
END SearchHandler;
PROCEDURE WrapHandler(sender, data : ANY);
BEGIN
SetWordWrap(~wordWrap);
END WrapHandler;
PROCEDURE SetWordWrap*(wordWrap : BOOLEAN);
BEGIN
SELF.wordWrap := wordWrap;
IF wordWrap THEN
editor.tv.wrapMode.Set(WMTextView.WrapWord);
ELSE
editor.tv.wrapMode.Set(WMTextView.NoWrap);
END;
IF (wrapBtn # NIL) THEN wrapBtn.SetPressed(wordWrap); END;
END SetWordWrap;
PROCEDURE ClearHandler(sender, data : ANY);
BEGIN
Clear;
END ClearHandler;
PROCEDURE Clear*;
BEGIN
editor.text.AcquireWrite;
editor.text.Delete(0, editor.text.GetLength());
editor.tv.firstLine.Set(0); editor.tv.cursor.SetPosition(0);
editor.text.ReleaseWrite;
END Clear;
PROCEDURE TextChanged(sender, data : ANY);
BEGIN
IF (buttons * StoreButton # {}) & ~modified THEN
storeBtn.caption.SetAOC("Store !");
modified := TRUE
END
END TextChanged;
PROCEDURE SetText*(text : Texts.Text);
BEGIN
IF (editor.text # NIL) THEN
editor.text.onTextChanged.Remove(TextChanged);
END;
text.onTextChanged.Add(TextChanged);
editor.SetText(text);
IF (searchPanel # NIL) THEN searchPanel.SetText(text); END;
END SetText;
PROCEDURE SetToolbar*(buttons : SET);
BEGIN
SELF.buttons := buttons;
IF (buttons * LoadButton # {}) OR (buttons * StoreButton # {}) THEN
NEW(filenamePanel);
filenamePanel.alignment.Set(WMComponents.AlignLeft);
filenamePanel.bounds.SetWidth(200);
toolbar.AddInternalComponent(filenamePanel);
NEW(resizer);
resizer.alignment.Set(WMComponents.AlignRight);
resizer.bounds.SetWidth(5);
filenamePanel.AddInternalComponent(resizer);
NEW(filenameEdit);
filenameEdit.alignment.Set(WMComponents.AlignClient);
filenameEdit.multiLine.Set(FALSE);
filenameEdit.tv.showBorder.Set(TRUE);
filenameEdit.tv.borders.Set(WMRectangles.MakeRect(3,3,1,1));
filenamePanel.AddInternalComponent(filenameEdit); filenameEdit.fillColor.Set(LONGINT(0FFFFFFFFH));
filenameEdit.onEscape.Add(FilenameEscapeHandler);
IF (buttons * LoadButton # {}) THEN
filenameEdit.onEnter.Add(LoadHandler);
NEW(loadBtn);
loadBtn.caption.SetAOC("Load"); loadBtn.alignment.Set(WMComponents.AlignLeft);
loadBtn.onClick.Add(LoadHandler);
toolbar.AddInternalComponent(loadBtn);
END;
IF (buttons * StoreButton # {}) THEN
NEW(storeBtn);
storeBtn.caption.SetAOC("Store"); storeBtn.alignment.Set(WMComponents.AlignLeft);
storeBtn.onClick.Add(StoreHandler);
toolbar.AddInternalComponent(storeBtn);
END;
IF (buttons * FormatButton # {}) THEN
NEW(formatBtn);
formatBtn.caption.SetAOC("Format"); formatBtn.alignment.Set(WMComponents.AlignLeft);
formatBtn.SetExtPointerDownHandler(FormatHandler);
formatBtn.bounds.SetWidth(3 * formatBtn.bounds.GetWidth());
toolbar.AddInternalComponent(formatBtn);
SetFormatCaption("AUTO");
END;
END;
IF (buttons * WrapButton # {}) THEN
NEW(wrapBtn);
wrapBtn.caption.SetAOC("Word Wrap"); wrapBtn.alignment.Set(WMComponents.AlignLeft);
wrapBtn.isToggle.Set(TRUE); wrapBtn.SetPressed(wordWrap);
wrapBtn.onClick.Add(WrapHandler); wrapBtn.bounds.SetWidth(100);
toolbar.AddInternalComponent(wrapBtn);
END;
IF (buttons * ClearButton # {}) THEN
NEW(clearBtn);
clearBtn.caption.SetAOC("Clear"); clearBtn.alignment.Set(WMComponents.AlignRight);
clearBtn.onClick.Add(ClearHandler);
toolbar.AddInternalComponent(clearBtn);
END;
IF (buttons * SearchButton # {}) THEN
NEW(searchBtn);
searchBtn.alignment.Set(WMComponents.AlignRight);
searchBtn.SetCaption("Search");
searchBtn.onClick.Add(SearchHandler);
toolbar.AddInternalComponent(searchBtn);
END;
toolbar.visible.Set(TRUE);
END SetToolbar;
PROCEDURE HandleShortcut*(ucs : LONGINT; flags : SET; keysym : LONGINT) : BOOLEAN;
BEGIN
IF (buttons * StoreButton # {}) & (keysym = 13H) & ControlKeyDown(flags) THEN
StoreHandler(NIL, NIL);
ELSIF (buttons * LoadButton # {}) & (keysym = 0FH) & ControlKeyDown(flags) THEN
filenameEdit.SetAsString("");
filenameEdit.SetFocus;
ELSIF (buttons * SearchButton # {}) & (keysym = 06H) & ControlKeyDown(flags)THEN
EnsureSearchPanel; searchPanel.ToggleVisibility;
ELSIF (buttons * SearchButton # {}) & (keysym= 0EH) & ControlKeyDown(flags) & (searchPanel # NIL) THEN
searchPanel.HandlePreviousNext(TRUE);
ELSIF (buttons * SearchButton # {}) & (keysym = 10H) & ControlKeyDown(flags) & (searchPanel # NIL) THEN
searchPanel.HandlePreviousNext(FALSE);
ELSIF (buttons * SearchButton # {}) & (keysym = Inputs.KsTab) & (flags = {}) THEN
RETURN (searchPanel # NIL) & searchPanel.HandleTab();
ELSE
RETURN FALSE;
END;
RETURN TRUE;
END HandleShortcut;
PROCEDURE ToXml*(config : XML.Element);
VAR filename : Files.FileName;
BEGIN
filenameEdit.GetAsString(filename);
WMRestorable.StoreString(config, "file", filename);
WMRestorable.StoreString(config, "codecFormat", codecFormat);
WMRestorable.StoreLongint(config, "firstLine", editor.tv.firstLine.Get());
WMRestorable.StoreLongint(config, "cursorPos", editor.tv.cursor.GetPosition());
WMRestorable.StoreBoolean(config, "wordWrap", wordWrap);
END ToXml;
PROCEDURE FromXml*(config : XML.Element);
VAR filename : Files.FileName; firstLine, cursorPos : LONGINT; wordWrap : BOOLEAN;
BEGIN
ASSERT(config # NIL);
WMRestorable.LoadString(config, "file", filename);
WMRestorable.LoadString(config, "codecFormat", codecFormat);
WMRestorable.LoadLongint(config, "firstLine", firstLine);
WMRestorable.LoadLongint(config, "cursorPos", cursorPos);
WMRestorable.LoadBoolean(config, "wordWrap", wordWrap);
Load(filename, codecFormat);
editor.tv.firstLine.Set(firstLine);
editor.tv.cursor.SetPosition(cursorPos);
SetWordWrap(wordWrap);
END FromXml;
PROCEDURE Handle(VAR m: WMMessages.Message);
BEGIN
IF m.msgType = WMMessages.MsgKey THEN
IF ~HandleShortcut(m.x, m.flags, m.y) THEN
Handle^(m);
END;
ELSE Handle^(m)
END
END Handle;
PROCEDURE InitCodecs;
VAR caption : CaptionObject;
elem : XML.Element; enum : XMLObjects.Enumerator; ptr : ANY; str : Strings.String;
BEGIN
NEW(popup);
elem := Configuration.config.GetRoot();
IF elem # NIL THEN
enum := elem.GetContents(); enum.Reset();
WHILE enum.HasMoreElements() DO
ptr := enum.GetNext();
IF ptr IS XML.Element THEN
str := ptr(XML.Element).GetAttributeValue("name");
IF (str # NIL) & (str^ = "Codecs") THEN
enum := ptr(XML.Element).GetContents(); enum.Reset();
WHILE enum.HasMoreElements() DO
ptr := enum.GetNext();
IF ptr IS XML.Element THEN
str := ptr(XML.Element).GetAttributeValue("name");
IF (str # NIL) & (str^ = "Decoder") THEN
enum := ptr(XML.Element).GetContents(); enum.Reset();
WHILE enum.HasMoreElements() DO
ptr := enum.GetNext();
IF ptr IS XML.Element THEN
str := ptr(XML.Element).GetAttributeValue("name");
IF (str # NIL) & (str^ = "Text") THEN
enum := ptr(XML.Element).GetContents(); enum.Reset();
WHILE enum.HasMoreElements() DO
ptr := enum.GetNext();
IF ptr IS XML.Element THEN
str := ptr(XML.Element).GetAttributeValue("name");
NEW(caption, str^);
popup.AddParButton(str^, FormatPopupHandler, caption);
END;
END;
END;
END;
END;
END;
END;
END;
END;
END;
END;
END;
NEW(caption, "AUTO");
popup.AddParButton("AUTO", FormatPopupHandler, caption);
END InitCodecs;
PROCEDURE EnsureSearchPanel;
BEGIN
IF (searchPanel = NIL) THEN
RemoveContent(editor);
NEW(searchPanel);
searchPanel.alignment.Set(WMComponents.AlignBottom);
searchPanel.bounds.SetHeight(40);
searchPanel.SetText(editor.text);
searchPanel.SetTextView(editor.tv);
searchPanel.visible.Set(FALSE);
AddInternalComponent(searchPanel);
AddInternalComponent(editor);
Reset(NIL, NIL);
END;
END EnsureSearchPanel;
PROCEDURE Finalize;
BEGIN
Finalize^;
IF (editor.text # NIL) THEN
editor.text.onTextChanged.Remove(TextChanged);
END;
END Finalize;
PROCEDURE &Init*;
BEGIN
Init^;
SetNameAsString(StrDocumentEditor);
wordWrap := FALSE;
InitCodecs;
lastFilename := "";
codecFormat := "AUTO";
autoCodecFormat := "";
modified := FALSE;
fillColor.Set(WMGraphics.White);
NEW(toolbar);
toolbar.alignment.Set(WMComponents.AlignTop);
toolbar.bounds.SetHeight(20);
toolbar.visible.Set(FALSE);
AddInternalComponent(toolbar);
searchPanel := NIL;
NEW(editor);
editor.alignment.Set(WMComponents.AlignClient); editor.tv.showBorder.Set(TRUE);
editor.macros.Add(WMMacros.Handle);
editor.multiLine.Set(TRUE);
editor.tv.wrapMode.Set(WMTextView.NoWrap);
editor.text.onTextChanged.Add(TextChanged);
AddInternalComponent(editor);
SetFormatCaption("AUTO");
SetWordWrap(wordWrap);
END Init;
END Editor;
VAR
StrDocumentEditor : Strings.String;
PROCEDURE InitStrings;
BEGIN
StrDocumentEditor := Strings.NewString("DocumentEditor");
END InitStrings;
PROCEDURE ControlKeyDown(flags : SET) : BOOLEAN;
BEGIN
RETURN (flags * Inputs.Ctrl # {}) & (flags - Inputs.Ctrl = {});
END ControlKeyDown;
BEGIN
InitStrings;
END WMDocumentEditor.
SystemTools.FreeDownTo WMDocumentEditor ~