DeclareModule TextEditor Declare Open(FileID.s, Path.s) EndDeclareModule Module TextEditor EnableExplicit #MaxEditors = 16 #ToolbarHeight = 35 Global Dim _Windows.i(#MaxEditors) Global Dim _Editors.i(#MaxEditors) Global Dim _SaveBtns.i(#MaxEditors) Global Dim _FileIDs.s(#MaxEditors) Global Dim _Paths.s(#MaxEditors) Global Dim _Dirty.i(#MaxEditors) Global Dim _Saving.i(#MaxEditors) Global _Count.i = 0 Global _Loading_Slot.i = -1 Global _ClosingSlot.i = -1 ; slot awaiting confirm-close callback Declare _FindByWindow(Window.i) Declare _FindByEditor(Editor.i) Declare _SetDirty(Slot.i, Dirty.i) Declare _Save(Slot.i) Declare _Remove(Slot.i) Declare _ConfirmCloseCallback(Result.i) Declare _ReadCallback(Success.i, DataString.s) Declare _SaveCallback(Success.i, DataString.s) Declare Handler_Resize() Declare Handler_Save() Declare Handler_Change() Declare Handler_Close() Procedure Open(FileID.s, Path.s) If _Count >= #MaxEditors Notify::Toast("Too many editors open.", Notify::#Warning) ProcedureReturn EndIf Protected Slot.i = _Count Protected W.i = 600 Protected H.i = 400 Protected Win.i = OpenWindow(#PB_Any, 120 + Slot * 20, 60 + Slot * 20, W, H, FS::GetFilePart(Path), #PB_Window_TitleBar | #PB_Window_SizeGadget | #PB_Window_SystemMenu) Protected SaveBtn.i = ButtonGadget(#PB_Any, 5, 3, 80, #ToolbarHeight - 6, "Save") Protected Editor.i = EditorGadget(#PB_Any, 0, #ToolbarHeight, W, H - #ToolbarHeight) _Windows(Slot) = Win _Editors(Slot) = Editor _SaveBtns(Slot) = SaveBtn _FileIDs(Slot) = FileID _Paths(Slot) = Path _Dirty(Slot) = #False _Count + 1 BindGadgetEvent(SaveBtn, @Handler_Save()) BindGadgetEvent(Editor, @Handler_Change()) BindEvent(#PB_Event_SizeWindow, @Handler_Resize(), Win) BindEvent(#PB_Event_CloseWindow, @Handler_Close(), Win) Desktop::Register(FS::GetFilePart(Path), Win, "📄") _Loading_Slot = Slot FS::Read_(FileID, Path, @_ReadCallback()) EndProcedure ; Private procedures Procedure.i _FindByWindow(Window.i) Protected i.i For i = 0 To _Count - 1 If _Windows(i) = Window : ProcedureReturn i : EndIf Next ProcedureReturn -1 EndProcedure Procedure.i _FindByEditor(Editor.i) Protected i.i For i = 0 To _Count - 1 If _Editors(i) = Editor : ProcedureReturn i : EndIf Next ProcedureReturn -1 EndProcedure Procedure _SetDirty(Slot.i, Dirty.i) _Dirty(Slot) = Dirty Protected Title.s = FS::GetFilePart(_Paths(Slot)) If Dirty : Title = "* " + Title : EndIf SetWindowTitle(_Windows(Slot), Title) EndProcedure Procedure _Save(Slot.i) If Not IsWindow(_Windows(Slot)) : ProcedureReturn : EndIf If _Saving(Slot) : ProcedureReturn : EndIf Protected Content.s = GetGadgetText(_Editors(Slot)) _Saving(Slot) = #True DisableGadget(_SaveBtns(Slot), #True) FS::Write(_FileIDs(Slot), _Paths(Slot), Content, @_SaveCallback()) EndProcedure Procedure _Remove(Slot.i) UnbindGadgetEvent(_SaveBtns(Slot), @Handler_Save()) UnbindGadgetEvent(_Editors(Slot), @Handler_Change()) UnbindEvent(#PB_Event_SizeWindow, @Handler_Resize(), _Windows(Slot)) UnbindEvent(#PB_Event_CloseWindow, @Handler_Close(), _Windows(Slot)) Desktop::Unregister(_Windows(Slot)) Protected i.i For i = Slot To _Count - 2 _Windows(i) = _Windows(i + 1) _Editors(i) = _Editors(i + 1) _SaveBtns(i) = _SaveBtns(i + 1) _FileIDs(i) = _FileIDs(i + 1) _Paths(i) = _Paths(i + 1) _Dirty(i) = _Dirty(i + 1) _Saving(i) = _Saving(i + 1) Next _Windows(_Count - 1) = 0 _Editors(_Count - 1) = 0 _SaveBtns(_Count - 1) = 0 _FileIDs(_Count - 1) = "" _Paths(_Count - 1) = "" _Dirty(_Count - 1) = #False _Saving(_Count - 1) = #False _Count - 1 If _Loading_Slot = Slot : _Loading_Slot = -1 : EndIf If _ClosingSlot = Slot : _ClosingSlot = -1 : EndIf EndProcedure ; Called by Notify::Confirm — Result=1 means discard and close, 0 means cancel. Procedure _ConfirmCloseCallback(Result.i) If Result And _ClosingSlot >= 0 _Remove(_ClosingSlot) EndIf _ClosingSlot = -1 EndProcedure ; Callbacks Procedure _ReadCallback(Success.i, DataString.s) If _Loading_Slot < 0 : ProcedureReturn : EndIf Protected Slot.i = _Loading_Slot _Loading_Slot = -1 If Not IsWindow(_Windows(Slot)) : ProcedureReturn : EndIf If Success SetGadgetText(_Editors(Slot), DataString) _SetDirty(Slot, #False) Else SetGadgetText(_Editors(Slot), "") _SetDirty(Slot, #True) EndIf EndProcedure Procedure _SaveCallback(Success.i, DataString.s) Protected i.i For i = 0 To _Count - 1 If _Saving(i) _Saving(i) = #False DisableGadget(_SaveBtns(i), #False) If Success _SetDirty(i, #False) Else Notify::Toast("Save failed — " + FS::GetFilePart(_Paths(i)), Notify::#Error) EndIf Break EndIf Next EndProcedure ; Event handlers Procedure Handler_Resize() Protected Win.i = EventWindow() Protected Slot.i = _FindByWindow(Win) If Slot < 0 : ProcedureReturn : EndIf ResizeGadget(_Editors(Slot), 0, #ToolbarHeight, WindowWidth(Win), WindowHeight(Win) - #ToolbarHeight) EndProcedure Procedure Handler_Save() Protected Slot.i = _FindByWindow(EventWindow()) If Slot >= 0 : _Save(Slot) : EndIf EndProcedure Procedure Handler_Change() Protected Slot.i = _FindByEditor(EventGadget()) If Slot >= 0 And Not _Dirty(Slot) _SetDirty(Slot, #True) EndIf EndProcedure Procedure Handler_Close() Protected Win.i = EventWindow() Protected Slot.i = _FindByWindow(Win) If Slot < 0 : ProcedureReturn : EndIf If _Dirty(Slot) _ClosingSlot = Slot Notify::Confirm("Unsaved changes", "Close " + Chr(34) + FS::GetFilePart(_Paths(Slot)) + Chr(34) + " without saving?", @_ConfirmCloseCallback()) Else _Remove(Slot) EndIf EndProcedure EndModule ; IDE Options = SpiderBasic 3.10 (Windows - x86) ; CursorPosition = 69 ; Folding = DA5 ; iOSAppOrientation = 0 ; AndroidAppCode = 0 ; AndroidAppOrientation = 0 ; EnableXP ; DPIAware ; CompileSourceDirectory