From c4374c0a977009b90efdd25e171e6f28b34a5d2f Mon Sep 17 00:00:00 2001 From: cmgeuze Date: Fri, 24 Jul 2026 09:00:56 +0200 Subject: [PATCH] refactor(examples): share pad logic via TPadController and IPadShell Extract the application logic duplicated between the VCL and FMX MarkdownPad demos into a framework-agnostic TPadController, driven through IPadEditorView and a new IPadShell seam. Document lifecycle, command palette, the editing loop (change/tick/status/title/find/TOC-sync/reload/close-query) and HTML export/copy now live once in Examples/Shared; the forms keep only control construction, rendering and the two interface implementations. Split the per-app constants into MarkdownPadVCL.Defines / MarkdownPadFMX.Defines and move the shared RecentShortcut into MarkdownPad.Defines. View-mode and zen stay in the forms as framework view logic. --- .../MarkdownPadFMX/MarkdownPadFMX.Defines.pas | 64 + .../MarkdownPadFMX/MarkdownPadFMX.Main.pas | 697 ++--- Examples/MarkdownPadFMX/MarkdownPadFMX.dpr | 5 +- Examples/MarkdownPadFMX/MarkdownPadFMX.dproj | 2312 +++++++++-------- .../MarkdownPadVCL/MarkdownPadVCL.Defines.pas | 43 + .../MarkdownPadVCL/MarkdownPadVCL.Main.pas | 625 ++--- Examples/MarkdownPadVCL/MarkdownPadVCL.dpr | 5 +- Examples/MarkdownPadVCL/MarkdownPadVCL.dproj | 4 +- Examples/Shared/MarkdownPad-Sharing-TODO.md | 28 +- Examples/Shared/MarkdownPad.Controller.pas | 583 +++++ Examples/Shared/MarkdownPad.Defines.pas | 3 + Examples/Shared/MarkdownPad.EditorView.pas | 5 + Examples/Shared/MarkdownPad.Shell.pas | 54 + 13 files changed, 2450 insertions(+), 1978 deletions(-) create mode 100644 Examples/MarkdownPadFMX/MarkdownPadFMX.Defines.pas create mode 100644 Examples/MarkdownPadVCL/MarkdownPadVCL.Defines.pas create mode 100644 Examples/Shared/MarkdownPad.Controller.pas create mode 100644 Examples/Shared/MarkdownPad.Shell.pas diff --git a/Examples/MarkdownPadFMX/MarkdownPadFMX.Defines.pas b/Examples/MarkdownPadFMX/MarkdownPadFMX.Defines.pas new file mode 100644 index 0000000..b3f707d --- /dev/null +++ b/Examples/MarkdownPadFMX/MarkdownPadFMX.Defines.pas @@ -0,0 +1,64 @@ +unit MarkdownPadFMX.Defines; + +// FMX-specific constants for the Markdown4D Pad demo. Values that are shared +// verbatim with the VCL build live in MarkdownPad.Defines; only FMX-specific +// values (window chrome, custom title bar/tooltip, TAlphaColor palette, +// per-build strings) stay here. + +interface + +uses + System.UITypes, + MarkdownPad.Defines; + +const + WindowCaption = 'Markdown4D Pad (FMX)'; + InitialClientWidth = 1180; + ToolbarHeight = 38; + TabControlHeight = 32; + CaptionButtonWidth = 46; + StatusBarHeight = 26; + ControlMargin = 6; + IconGlyphSize = 16; + TocHeaderHeight = 22; + SplitterWidth = 6; + StatusLabelWidth = 150; + GlyphMinimize = Char($E921); + GlyphMaximize = Char($E922); + GlyphRestore = Char($E923); + GlyphClose = Char($E8BB); + HintMinimize = 'Minimize'; + HintMaximize = 'Maximize'; + HintCloseWindow = 'Close'; + ToolbarLightColor = TAlphaColor($FFF3F3F3); + ToolbarDarkColor = TAlphaColor($FF2D2D2D); + IconLightColor = TAlphaColor($FF404040); + IconDarkColor = TAlphaColor($FFD6D6D6); + SeparatorLightColor = TAlphaColor($FFD0D0D0); + SeparatorDarkColor = TAlphaColor($FF505050); + HoverLightColor = TAlphaColor($FFE0E0E0); + HoverDarkColor = TAlphaColor($FF3E3E3E); + TabActiveLightColor = TAlphaColor($FFFFFFFF); + TabActiveDarkColor = TAlphaColor($FF3F3F3F); + TabHoverLightColor = TAlphaColor($FFEAEAEA); + TabHoverDarkColor = TAlphaColor($FF383838); + CaptionCloseHoverColor = TAlphaColor($FFE81123); + HintBackColor = TAlphaColor($FF1E1E1E); + HintTextColor = TAlphaColor($FFF0F0F0); + HintHeight = 24; + HintHorizontalPadding = 8; + HintGap = 4; + HintCornerRadius = 4; + MarkdownExtension = '.md'; + SessionFileName = 'MarkdownPad.Fmx.json'; + OpenErrorFormat = 'Could not open the file:'#10'%s'; + CloseUnsavedPrompt = 'This document has unsaved changes. Save before closing?'; + ReloadPrompt = 'This file changed on disk. Reload and lose your local changes?'; + PaletteListHeight = PaletteRowHeight * PaletteVisibleRows; + PaletteEditHeight = 30; + PaletteShortcutWidth = 120; + PaletteShortcutOpacity = 0.6; + +implementation + +end. diff --git a/Examples/MarkdownPadFMX/MarkdownPadFMX.Main.pas b/Examples/MarkdownPadFMX/MarkdownPadFMX.Main.pas index 27c451b..a81eaae 100644 --- a/Examples/MarkdownPadFMX/MarkdownPadFMX.Main.pas +++ b/Examples/MarkdownPadFMX/MarkdownPadFMX.Main.pas @@ -30,62 +30,17 @@ interface MarkdownPad.EditorView, MarkdownPad.Session, MarkdownPad.Commands, + MarkdownPad.CommandSet, MarkdownPad.TabStrip.Layout, MarkdownPad.Fmx.TabStrip, - MarkdownPad.FileWatcher; + MarkdownPad.FileWatcher, + MarkdownPad.Shell, + MarkdownPad.Controller, + MarkdownPadFMX.Defines; type - TMarkdownPadFMXForm = class(TForm, IPadEditorView) + TMarkdownPadFMXForm = class(TForm, IPadEditorView, IPadShell) private - const - // Shared constants live in MarkdownPad.Defines; only FMX-specific values - // (window chrome, custom title bar/tooltip, TAlphaColor palette) stay here. - WindowCaption = 'Markdown4D Pad (FMX)'; - InitialClientWidth = 1180; - ToolbarHeight = 38; - TabControlHeight = 32; - CaptionButtonWidth = 46; - StatusBarHeight = 26; - ControlMargin = 6; - IconGlyphSize = 16; - TocHeaderHeight = 22; - SplitterWidth = 6; - StatusLabelWidth = 150; - GlyphMinimize = Char($E921); - GlyphMaximize = Char($E922); - GlyphRestore = Char($E923); - GlyphClose = Char($E8BB); - HintMinimize = 'Minimize'; - HintMaximize = 'Maximize'; - HintCloseWindow = 'Close'; - ToolbarLightColor = TAlphaColor($FFF3F3F3); - ToolbarDarkColor = TAlphaColor($FF2D2D2D); - IconLightColor = TAlphaColor($FF404040); - IconDarkColor = TAlphaColor($FFD6D6D6); - SeparatorLightColor = TAlphaColor($FFD0D0D0); - SeparatorDarkColor = TAlphaColor($FF505050); - HoverLightColor = TAlphaColor($FFE0E0E0); - HoverDarkColor = TAlphaColor($FF3E3E3E); - TabActiveLightColor = TAlphaColor($FFFFFFFF); - TabActiveDarkColor = TAlphaColor($FF3F3F3F); - TabHoverLightColor = TAlphaColor($FFEAEAEA); - TabHoverDarkColor = TAlphaColor($FF383838); - CaptionCloseHoverColor = TAlphaColor($FFE81123); - HintBackColor = TAlphaColor($FF1E1E1E); - HintTextColor = TAlphaColor($FFF0F0F0); - HintHeight = 24; - HintHorizontalPadding = 8; - HintGap = 4; - HintCornerRadius = 4; - MarkdownExtension = '.md'; - SessionFileName = 'MarkdownPad.Fmx.json'; - OpenErrorFormat = 'Could not open the file:'#10'%s'; - CloseUnsavedPrompt = 'This document has unsaved changes. Save before closing?'; - ReloadPrompt = 'This file changed on disk. Reload and lose your local changes?'; - PaletteListHeight = PaletteRowHeight * PaletteVisibleRows; - PaletteEditHeight = 30; - PaletteShortcutWidth = 120; - PaletteShortcutOpacity = 0.6; var FToolbar: TRectangle; FTitleBar: TRectangle; @@ -135,18 +90,11 @@ TMarkdownPadFMXForm = class(TForm, IPadEditorView) FSaveDialog: TSaveDialog; FHtmlSaveDialog: TSaveDialog; FTickTimer: TTimer; - FTocEntries: TArray; FLightTheme: TMarkdownTheme; FDarkTheme: TMarkdownTheme; FDarkThemeActive: Boolean; - FWorkspace: IPadWorkspace; - FSession: TPadSession; - FWatcher: TPadFileWatcher; - FActiveDoc: IPadDocument; - FMapDirty: Boolean; - FSwapping: Boolean; + FController: TPadController; FTocFollowing: Boolean; - FLastCaret: Integer; FFindBar: TRectangle; FEditorFindEdit: TEdit; FEditorFindCount: TLabel; @@ -155,8 +103,6 @@ TMarkdownPadFMXForm = class(TForm, IPadEditorView) FPalette: TRectangle; FPaletteEdit: TEdit; FPaletteList: TListBox; - FCommands: TPadCommandRegistry; - FPaletteMatches: TArray; FViewMode: TPadViewMode; FSplitEditorWidth: Single; FZenActive: Boolean; @@ -165,6 +111,39 @@ TMarkdownPadFMXForm = class(TForm, IPadEditorView) FZenRightPad: TLayout; FZenTocWasVisible: Boolean; FZenFindWasVisible: Boolean; + // Read-through accessors to the controller-owned model state. + function GetWorkspace: IPadWorkspace; + function GetSession: TPadSession; + function GetMapDirty: Boolean; + procedure SetMapDirty(const Value: Boolean); + function GetSwapping: Boolean; + procedure SetSwapping(const Value: Boolean); + function GetPaletteMatches: TArray; + property FWorkspace: IPadWorkspace read GetWorkspace; + property FSession: TPadSession read GetSession; + property FMapDirty: Boolean read GetMapDirty write SetMapDirty; + property FSwapping: Boolean read GetSwapping write SetSwapping; + property FPaletteMatches: TArray read GetPaletteMatches; + procedure SetDocumentTitle(const Name: string); + procedure SetStatus(const PositionText, WordsText: string); + procedure SetTocCaptions(const Captions: TArray); + procedure SetActiveTocIndex(const Index: Integer); + function EditorFindNeedle: string; + function PreviewFindNeedle: string; + procedure SetFindCount(const Value: string); + function SampleMarkdown: string; + function DarkThemeActive: Boolean; + function EffectiveViewMode: TPadViewMode; + procedure ApplyRestoredViewMode(const Mode: TPadViewMode); + function PromptOpenFile(out FileName: string): Boolean; + function PromptSaveFile(const SuggestedName: string; out FileName: string): Boolean; + function PromptExportHtml(const SuggestedName: string; out FileName: string): Boolean; + function ConfirmClose: TPadCloseChoice; + function ConfirmCloseDocument(const DocName: string): TPadCloseChoice; + function ConfirmReload: Boolean; + procedure ShowOpenError(const FileName, ErrorMessage: string); + procedure CopyHtmlToClipboard(const Fragment: string); + procedure CloseApplication; procedure BuildToolbar; function ResolveIconFontName: string; function AddIconButton(const Glyph: string; const Hint: string; const Handler: TNotifyEvent): TRectangle; @@ -198,7 +177,7 @@ TMarkdownPadFMXForm = class(TForm, IPadEditorView) procedure HideHint; procedure BuildPalette; procedure BuildCommandRegistry; - procedure RegisterStaticCommands; + function BuildCommandActions: TPadCommandActions; procedure RestoreSession; function HandleFormKey(const Key: Word; const Shift: TShiftState): Boolean; procedure SetViewMode(const Mode: TPadViewMode); @@ -227,8 +206,6 @@ TMarkdownPadFMXForm = class(TForm, IPadEditorView) procedure OpenPath(const FileName: string); procedure HandleSaveClick(Sender: TObject); procedure HandleSaveAsClick(Sender: TObject); - function TrySaveActive: Boolean; - procedure SaveToFile(const FileName: string); procedure CloseActiveDocument; procedure CloseDocumentAt(const Index: Integer); procedure HandleExportClick(Sender: TObject); @@ -248,8 +225,6 @@ TMarkdownPadFMXForm = class(TForm, IPadEditorView) procedure HandlePreviewLinkClick(const Sender: TObject; const Url: string); procedure HandleTocChange(Sender: TObject); procedure HandleTick(Sender: TObject); - procedure HandleFileChanged(const Document: IPadDocument); - procedure ReloadDocument(const Document: IPadDocument); function GetEditorText: string; procedure SetEditorText(const Value: string); function GetEditorCaret: Integer; @@ -261,14 +236,15 @@ TMarkdownPadFMXForm = class(TForm, IPadEditorView) function SaveEditState: IMarkdownEditorState; procedure LoadEditState(const State: IMarkdownEditorState); procedure FlushPreview; + procedure EditorFindNext(const Needle: string); + function EditorFindMatchCount(const Needle: string): Integer; + procedure PreviewFindText(const Needle: string); procedure BeginSwap; procedure EndSwap; procedure SwitchToDocument(const Index: Integer); procedure RebuildTabs; procedure RebuildSyncAndToc; procedure UpdateActiveTocEntry(const SourceLine: Integer); - procedure UpdateStatusBar; - procedure UpdateTitle; procedure ExecuteFind; procedure ApplyTheme; procedure ApplyChromeColors; @@ -309,7 +285,6 @@ implementation Markdown4D.Ast.Interfaces, MarkdownPad.Text, MarkdownPad.Outline, - MarkdownPad.CommandSet, MarkdownPad.SessionSync, MarkdownPad.Workspace, MarkdownPad.HtmlExport, @@ -332,8 +307,7 @@ constructor TMarkdownPadFMXForm.Create(Owner: TComponent); FLightTheme := TMarkdownTheme.CreateLight; FDarkTheme := TMarkdownTheme.CreateDark; - FWorkspace := TPadWorkspace.Create; - FWatcher := TPadFileWatcher.Create(FWorkspace, HandleFileChanged); + FController := TPadController.Create(Self, Self, SessionFileName); FOpenDialog := TOpenDialog.Create(Self); FOpenDialog.Filter := MarkdownFilter; @@ -363,6 +337,9 @@ constructor TMarkdownPadFMXForm.Create(Owner: TComponent); FViewMode := TPadViewMode.Split; FSplitEditorWidth := (InitialClientWidth - TocPanelWidth) / 2; + FDarkThemeActive := FController.Session.DarkTheme; + ApplyTheme; + RestoreSession; FMapDirty := True; @@ -377,9 +354,7 @@ destructor TMarkdownPadFMXForm.Destroy; inherited Destroy; - FWatcher.Free; - FCommands.Free; - FSession.Free; + FController.Free; FDarkTheme.Free; FLightTheme.Free; end; @@ -874,65 +849,43 @@ procedure TMarkdownPadFMXForm.BuildPalette; procedure TMarkdownPadFMXForm.BuildCommandRegistry; begin - FCommands := TPadCommandRegistry.Create; - - RegisterStaticCommands; + FController.InitCommandRegistry(BuildCommandActions); end; -procedure TMarkdownPadFMXForm.RegisterStaticCommands; -var - Actions: TPadCommandActions; -begin - Actions.NewDocument := procedure begin HandleNewClick(nil); end; - Actions.OpenDocument := procedure begin HandleOpenClick(nil); end; - Actions.Save := procedure begin HandleSaveClick(nil); end; - Actions.SaveAs := procedure begin HandleSaveAsClick(nil); end; - Actions.CloseDocument := procedure begin CloseActiveDocument; end; - Actions.NextTab := +function TMarkdownPadFMXForm.BuildCommandActions: TPadCommandActions; +begin + Result.NewDocument := procedure begin HandleNewClick(nil); end; + Result.OpenDocument := procedure begin HandleOpenClick(nil); end; + Result.Save := procedure begin HandleSaveClick(nil); end; + Result.SaveAs := procedure begin HandleSaveAsClick(nil); end; + Result.CloseDocument := procedure begin CloseActiveDocument; end; + Result.NextTab := procedure begin FWorkspace.ActivateNext; SwitchToDocument(FWorkspace.ActiveIndex); end; - Actions.ExportHtml := procedure begin DoExportHtml; end; - Actions.CopyHtml := procedure begin DoCopyHtml; end; - Actions.ViewEditorOnly := procedure begin SetViewMode(TPadViewMode.EditorOnly); end; - Actions.ViewSplit := procedure begin SetViewMode(TPadViewMode.Split); end; - Actions.ViewPreviewOnly := procedure begin SetViewMode(TPadViewMode.PreviewOnly); end; - Actions.ToggleZen := procedure begin ToggleZen; end; - Actions.ToggleTheme := procedure begin HandleThemeClick(nil); end; - Actions.ToggleToc := procedure begin HandleTocClick(nil); end; - Actions.ShowFind := procedure begin ShowFindBar; end; - Actions.FindInPreview := procedure begin ExecuteFind; end; - Actions.ExecuteFormat := + Result.ExportHtml := procedure begin DoExportHtml; end; + Result.CopyHtml := procedure begin DoCopyHtml; end; + Result.ViewEditorOnly := procedure begin SetViewMode(TPadViewMode.EditorOnly); end; + Result.ViewSplit := procedure begin SetViewMode(TPadViewMode.Split); end; + Result.ViewPreviewOnly := procedure begin SetViewMode(TPadViewMode.PreviewOnly); end; + Result.ToggleZen := procedure begin ToggleZen; end; + Result.ToggleTheme := procedure begin HandleThemeClick(nil); end; + Result.ToggleToc := procedure begin HandleTocClick(nil); end; + Result.ShowFind := procedure begin ShowFindBar; end; + Result.FindInPreview := procedure begin ExecuteFind; end; + Result.ExecuteFormat := procedure(const Command: TEditorCommand) begin FEditor.ExecuteCommand(Command); FEditor.SetFocus; end; - - RegisterStaticPadCommands(FCommands, Actions); end; procedure TMarkdownPadFMXForm.RestoreSession; begin - FSession := TPadSession.Create(TPadSession.ResolvePath(SessionFileName)); - FSession.Load; - - FDarkThemeActive := FSession.DarkTheme; - ApplyTheme; - - FViewMode := FSession.ViewMode; - ApplyViewMode; - - if not TPadSessionSync.RestoreOpenFiles(FWorkspace, FSession) then - begin - const Document = FWorkspace.NewDocument; - Document.Text := BuildSampleMarkdown; - end; - - RebuildTabs; - SwitchToDocument(FWorkspace.ActiveIndex); + FController.RestoreSession; end; procedure TMarkdownPadFMXForm.KeyDown(var Key: Word; var KeyChar: WideChar; Shift: TShiftState); @@ -1099,28 +1052,7 @@ function TMarkdownPadFMXForm.CloseQuery: Boolean; if FZenActive then ExitZen; - for var Index := 0 to FWorkspace.Count - 1 do - begin - const Document = FWorkspace.Documents[Index]; - if not Document.Modified then - Continue; - - SwitchToDocument(Index); - - const Prompt = Format(CloseDocumentPromptFormat, [Document.DisplayName]); - const Answer = TDialogServiceSync.MessageDialog(Prompt, TMsgDlgType.mtConfirmation, - [TMsgDlgBtn.mbYes, TMsgDlgBtn.mbNo, TMsgDlgBtn.mbCancel], TMsgDlgBtn.mbCancel, 0); - - if Answer = mrCancel then - Exit(False); - - if (Answer = mrYes) and not TrySaveActive then - Exit(False); - - Document.Modified := False; - end; - - Result := True; + Result := FController.QueryClose; end; procedure TMarkdownPadFMXForm.Resize; @@ -1197,38 +1129,6 @@ procedure TMarkdownPadFMXForm.CloseFindBar; FEditor.SetFocus; end; -procedure TMarkdownPadFMXForm.FindInEditor; -begin - const Needle = FEditorFindEdit.Text; - if Needle = '' then - begin - UpdateFindCount; - Exit; - end; - - FEditor.FindNext(Needle); - - UpdateFindCount; -end; - -procedure TMarkdownPadFMXForm.UpdateFindCount; -begin - const Needle = FEditorFindEdit.Text; - if Needle = '' then - begin - FEditorFindCount.Text := EmptyFindCaption; - Exit; - end; - - const N = FEditor.FindMatchCount(Needle); - - if N = 0 then - FEditorFindCount.Text := NoMatchCaption - else if N = 1 then - FEditorFindCount.Text := SingleMatchCaption - else - FEditorFindCount.Text := Format(MatchCountFormat, [N]); -end; procedure TMarkdownPadFMXForm.HandleEditorFindKeyDown(Sender: TObject; var Key: Word; var KeyChar: WideChar; Shift: TShiftState); @@ -1255,18 +1155,7 @@ procedure TMarkdownPadFMXForm.HandleEditorFindChange(Sender: TObject); procedure TMarkdownPadFMXForm.ShowPalette; begin - FCommands.Clear; - RegisterStaticCommands; - - for var FileName in FSession.RecentFiles do - begin - const Path = FileName; - FCommands.Register(Path, CatRecent, '', - procedure - begin - OpenPath(Path); - end); - end; + FController.RebuildPaletteCommands; FPaletteEdit.Text := ''; @@ -1289,7 +1178,7 @@ procedure TMarkdownPadFMXForm.ClosePalette; procedure TMarkdownPadFMXForm.RefreshPaletteList; begin - FPaletteMatches := FCommands.Match(FPaletteEdit.Text); + FController.RefreshMatches(FPaletteEdit.Text); FPaletteList.BeginUpdate; try @@ -1344,15 +1233,12 @@ procedure TMarkdownPadFMXForm.PaletteMoveSelection(const Delta: Integer); procedure TMarkdownPadFMXForm.ExecuteSelectedCommand; begin const Index = FPaletteList.ItemIndex; - if (Index < 0) or (Index > High(FPaletteMatches)) then + if (Index < 0) or (Index >= FController.PaletteMatchCount) then Exit; - const Action = FPaletteMatches[Index].Command.Action; - ClosePalette; - if Assigned(Action) then - Action(); + FController.InvokePaletteCommand(Index); end; procedure TMarkdownPadFMXForm.HandlePaletteChange(Sender: TObject); @@ -1460,16 +1346,12 @@ procedure TMarkdownPadFMXForm.DragDrop(const Data: TDragObject; const Point: TPo procedure TMarkdownPadFMXForm.HandleNewClick(Sender: TObject); begin - FWorkspace.NewDocument; - - RebuildTabs; - SwitchToDocument(FWorkspace.ActiveIndex); + FController.NewDocument; end; procedure TMarkdownPadFMXForm.HandleOpenClick(Sender: TObject); begin - if FOpenDialog.Execute then - OpenPath(FOpenDialog.FileName); + FController.OpenViaDialog; end; procedure TMarkdownPadFMXForm.HandleRecentClick(Sender: TObject); @@ -1509,119 +1391,27 @@ procedure TMarkdownPadFMXForm.HandleRecentItemClick(Sender: TObject); procedure TMarkdownPadFMXForm.OpenPath(const FileName: string); begin - if not TFile.Exists(FileName) then - Exit; - - var Document: IPadDocument; - try - Document := FWorkspace.OpenFile(FileName); - except - on E: Exception do - begin - TDialogServiceSync.MessageDialog(Format(OpenErrorFormat, [FileName]), TMsgDlgType.mtError, - [TMsgDlgBtn.mbOK], TMsgDlgBtn.mbOK, 0); - Exit; - end; - end; - - FSession.AddRecentFile(FileName); - FWatcher.Reset(Document); - - RebuildTabs; - SwitchToDocument(FWorkspace.ActiveIndex); + FController.OpenPath(FileName); end; procedure TMarkdownPadFMXForm.HandleSaveClick(Sender: TObject); begin - TrySaveActive; + FController.Save; end; procedure TMarkdownPadFMXForm.HandleSaveAsClick(Sender: TObject); begin - if FActiveDoc = nil then - Exit; - - if not FActiveDoc.IsUntitled then - FSaveDialog.FileName := FActiveDoc.FileName; - - if FSaveDialog.Execute then - SaveToFile(FSaveDialog.FileName); -end; - -function TMarkdownPadFMXForm.TrySaveActive: Boolean; -begin - if FActiveDoc = nil then - Exit(True); - - if FActiveDoc.IsUntitled then - begin - if not FSaveDialog.Execute then - Exit(False); - - SaveToFile(FSaveDialog.FileName); - end - else - SaveToFile(FActiveDoc.FileName); - - Result := True; -end; - -procedure TMarkdownPadFMXForm.SaveToFile(const FileName: string); -begin - FActiveDoc.Text := FEditor.Text; - - TFile.WriteAllText(FileName, FEditor.Text); - - FActiveDoc.FileName := FileName; - FActiveDoc.Modified := False; - FWatcher.Reset(FActiveDoc); - - FSession.AddRecentFile(FileName); - - RebuildTabs; - UpdateTitle; + FController.SaveAs; end; procedure TMarkdownPadFMXForm.CloseActiveDocument; begin - CloseDocumentAt(FWorkspace.ActiveIndex); + FController.CloseActiveDocument; end; procedure TMarkdownPadFMXForm.CloseDocumentAt(const Index: Integer); begin - if (Index < 0) or (Index >= FWorkspace.Count) then - Exit; - - if Index <> FWorkspace.ActiveIndex then - SwitchToDocument(Index); - - if FActiveDoc = nil then - Exit; - - if FActiveDoc.Modified then - begin - const Answer = TDialogServiceSync.MessageDialog(CloseUnsavedPrompt, TMsgDlgType.mtConfirmation, - [TMsgDlgBtn.mbYes, TMsgDlgBtn.mbNo, TMsgDlgBtn.mbCancel], TMsgDlgBtn.mbCancel, 0); - - if Answer = mrCancel then - Exit; - - if (Answer = mrYes) and not TrySaveActive then - Exit; - end; - - const ClosingIndex = FWorkspace.ActiveIndex; - FActiveDoc := nil; - FWorkspace.CloseDocument(ClosingIndex); - - if FWorkspace.Count = 0 then - begin - Close; - Exit; - end; - - RebuildTabs; - SwitchToDocument(FWorkspace.ActiveIndex); + FController.CloseDocumentAt(Index); end; procedure TMarkdownPadFMXForm.HandleExportClick(Sender: TObject); @@ -1631,21 +1421,17 @@ procedure TMarkdownPadFMXForm.HandleExportClick(Sender: TObject); procedure TMarkdownPadFMXForm.DoExportHtml; begin - if FActiveDoc <> nil then - FActiveDoc.Text := FEditor.Text; - - var Title := UntitledName; - if FActiveDoc <> nil then - Title := FActiveDoc.DisplayName; - - FHtmlSaveDialog.FileName := TPath.ChangeExtension(Title, '.' + HtmlExtension); - - if not FHtmlSaveDialog.Execute then - Exit; + FController.ExportHtml; +end; - const Html = TMarkdownHtmlExport.BuildDocument(FEditor.Text, Title, FDarkThemeActive); +function TMarkdownPadFMXForm.PromptExportHtml(const SuggestedName: string; out FileName: string): Boolean; +begin + if SuggestedName <> '' then + FHtmlSaveDialog.FileName := SuggestedName; - TFile.WriteAllText(FHtmlSaveDialog.FileName, Html, TEncoding.UTF8); + Result := FHtmlSaveDialog.Execute; + if Result then + FileName := FHtmlSaveDialog.FileName; end; procedure TMarkdownPadFMXForm.HandleCopyHtmlClick(Sender: TObject); @@ -1655,8 +1441,11 @@ procedure TMarkdownPadFMXForm.HandleCopyHtmlClick(Sender: TObject); procedure TMarkdownPadFMXForm.DoCopyHtml; begin - const Fragment = TMarkdown.ToHtml(FEditor.Text, TMarkdownDialect.Gfm); + FController.CopyHtml; +end; +procedure TMarkdownPadFMXForm.CopyHtmlToClipboard(const Fragment: string); +begin var Clip: IFMXClipboardService; if TPlatformServices.Current.SupportsPlatformService(IFMXClipboardService, Clip) then Clip.SetClipboard(Fragment); @@ -1717,20 +1506,7 @@ procedure TMarkdownPadFMXForm.HandleFindEditKeyDown(Sender: TObject; var Key: Wo procedure TMarkdownPadFMXForm.HandleEditorChange(Sender: TObject); begin - if FSwapping then - Exit; - - const WasModified = (FActiveDoc <> nil) and FActiveDoc.Modified; - - if FActiveDoc <> nil then - FActiveDoc.Modified := True; - - FMapDirty := True; - - if not WasModified then - RebuildTabs; - - UpdateTitle; + FController.NotifyEditorChanged; end; procedure TMarkdownPadFMXForm.HandleSyncScroll(Sender: TObject; const SourceLine: Integer); @@ -1754,16 +1530,16 @@ procedure TMarkdownPadFMXForm.HandleTocChange(Sender: TObject); Exit; const Index = FTocList.ItemIndex; - if (Index < 0) or (Index > High(FTocEntries)) then + if (Index < 0) or (Index >= FController.TocEntryCount) then Exit; if FMapDirty then RebuildSyncAndToc; - if (Index < 0) or (Index > High(FTocEntries)) then + if (Index < 0) or (Index >= FController.TocEntryCount) then Exit; - const SourceLine = FTocEntries[Index].SourceLine - 1; + const SourceLine = FController.TocSourceLine(Index); // Scrolling the editor drives the preview through the linked SyncScroll; guard // the outline so the resulting sync callback does not fight the selection. @@ -1781,59 +1557,7 @@ procedure TMarkdownPadFMXForm.HandleTocChange(Sender: TObject); procedure TMarkdownPadFMXForm.HandleTick(Sender: TObject); begin - if FMapDirty then - RebuildSyncAndToc; - - UpdateStatusBar; - - FWatcher.Poll; -end; - -procedure TMarkdownPadFMXForm.HandleFileChanged(const Document: IPadDocument); -begin - if Document.Modified then - begin - const Answer = TDialogServiceSync.MessageDialog(ReloadPrompt, TMsgDlgType.mtConfirmation, - [TMsgDlgBtn.mbYes, TMsgDlgBtn.mbNo], TMsgDlgBtn.mbNo, 0); - - if Answer <> mrYes then - Exit; - end; - - ReloadDocument(Document); -end; - -procedure TMarkdownPadFMXForm.ReloadDocument(const Document: IPadDocument); -begin - var NewText: string; - try - NewText := TFile.ReadAllText(Document.FileName); - except - on E: Exception do - begin - Document.DiskTimestampUtc := 0; - Exit; - end; - end; - - Document.Text := NewText; - Document.Modified := False; - - if Document = FActiveDoc then - begin - FSwapping := True; - try - FEditor.Text := NewText; - FEditor.FlushPreview; - finally - FSwapping := False; - end; - - FMapDirty := True; - end; - - RebuildTabs; - UpdateTitle; + FController.Tick; end; function TMarkdownPadFMXForm.GetEditorText: string; @@ -1903,19 +1627,7 @@ procedure TMarkdownPadFMXForm.EndSwap; procedure TMarkdownPadFMXForm.SwitchToDocument(const Index: Integer); begin - if FSwapping then - Exit; - - FActiveDoc := TPadDocumentSwitch.Execute(FWorkspace, Self, FActiveDoc, Index); - - if FActiveDoc = nil then - Exit; - - FLastCaret := -1; - FMapDirty := True; - - RebuildTabs; - UpdateTitle; + FController.SwitchToDocument(Index); end; procedure TMarkdownPadFMXForm.RebuildTabs; @@ -1929,19 +1641,48 @@ procedure TMarkdownPadFMXForm.RebuildTabs; procedure TMarkdownPadFMXForm.RebuildSyncAndToc; begin - FMapDirty := False; + FController.RebuildSyncAndToc; +end; - const Document = TMarkdown.Parse(FEditor.Text, TMarkdownDialect.Gfm); +procedure TMarkdownPadFMXForm.UpdateActiveTocEntry(const SourceLine: Integer); +begin + FController.UpdateActiveTocEntry(SourceLine); +end; + +procedure TMarkdownPadFMXForm.ExecuteFind; +begin + FController.ExecuteFind; +end; + +procedure TMarkdownPadFMXForm.FindInEditor; +begin + FController.FindInEditor; +end; + +procedure TMarkdownPadFMXForm.UpdateFindCount; +begin + FController.UpdateFindCount; +end; - const Outline = TPadOutlineBuilder.Build(TMarkdownToc.FromDocument(Document)); - FTocEntries := Outline.Entries; +procedure TMarkdownPadFMXForm.SetDocumentTitle(const Name: string); +begin + Caption := Format(TitleFormat, [WindowCaption, Name]); +end; +procedure TMarkdownPadFMXForm.SetStatus(const PositionText, WordsText: string); +begin + FStatusPositionLabel.Text := PositionText; + FStatusWordsLabel.Text := WordsText; +end; + +procedure TMarkdownPadFMXForm.SetTocCaptions(const Captions: TArray); +begin FTocFollowing := True; FTocList.Items.BeginUpdate; try FTocList.Items.Clear; - for var Caption in Outline.Captions do + for var Caption in Captions do FTocList.Items.Add(Caption); finally FTocList.Items.EndUpdate; @@ -1951,54 +1692,66 @@ procedure TMarkdownPadFMXForm.RebuildSyncAndToc; ApplyTocItemColors; end; -procedure TMarkdownPadFMXForm.UpdateActiveTocEntry(const SourceLine: Integer); +procedure TMarkdownPadFMXForm.SetActiveTocIndex(const Index: Integer); begin - const Best = TPadOutlineBuilder.ActiveIndex(FTocEntries, SourceLine); - - if Best = FTocList.ItemIndex then + if Index = FTocList.ItemIndex then Exit; FTocFollowing := True; try - FTocList.ItemIndex := Best; + FTocList.ItemIndex := Index; finally FTocFollowing := False; end; end; -procedure TMarkdownPadFMXForm.UpdateStatusBar; +function TMarkdownPadFMXForm.EditorFindNeedle: string; begin - const Caret = FEditor.CaretPosition; - if Caret = FLastCaret then - Exit; + Result := FEditorFindEdit.Text; +end; - FLastCaret := Caret; +function TMarkdownPadFMXForm.PreviewFindNeedle: string; +begin + Result := FFindEdit.Text; +end; - var Line, Column: Integer; - TPadText.ComputeLineColumn(FEditor.Text, Caret, Line, Column); - FStatusPositionLabel.Text := Format(StatusPositionFormat, [Line, Column]); - FStatusWordsLabel.Text := Format(StatusWordsFormat, [TPadText.CountWords(FEditor.Text)]); +procedure TMarkdownPadFMXForm.SetFindCount(const Value: string); +begin + FEditorFindCount.Text := Value; end; -procedure TMarkdownPadFMXForm.UpdateTitle; +procedure TMarkdownPadFMXForm.EditorFindNext(const Needle: string); begin - var Name := UntitledName; - if FActiveDoc <> nil then - Name := FActiveDoc.DisplayName; + FEditor.FindNext(Needle); +end; - if (FActiveDoc <> nil) and FActiveDoc.Modified then - Name := Name + ModifiedMarker; +function TMarkdownPadFMXForm.EditorFindMatchCount(const Needle: string): Integer; +begin + Result := FEditor.FindMatchCount(Needle); +end; - Caption := Format(TitleFormat, [WindowCaption, Name]); +procedure TMarkdownPadFMXForm.PreviewFindText(const Needle: string); +begin + FPreview.FindText(Needle); end; -procedure TMarkdownPadFMXForm.ExecuteFind; +function TMarkdownPadFMXForm.ConfirmCloseDocument(const DocName: string): TPadCloseChoice; begin - const Needle = FFindEdit.Text; - if Needle = '' then - Exit; + const Answer = TDialogServiceSync.MessageDialog(Format(CloseDocumentPromptFormat, [DocName]), + TMsgDlgType.mtConfirmation, [TMsgDlgBtn.mbYes, TMsgDlgBtn.mbNo, TMsgDlgBtn.mbCancel], TMsgDlgBtn.mbCancel, 0); - FPreview.FindText(Needle); + if Answer = mrCancel then + Result := TPadCloseChoice.Cancel + else if Answer = mrYes then + Result := TPadCloseChoice.Save + else + Result := TPadCloseChoice.Discard; +end; + +function TMarkdownPadFMXForm.ConfirmReload: Boolean; +begin + Result := TDialogServiceSync.MessageDialog(ReloadPrompt, TMsgDlgType.mtConfirmation, + [TMsgDlgBtn.mbYes, TMsgDlgBtn.mbNo], TMsgDlgBtn.mbNo, 0) = mrYes; end; procedure TMarkdownPadFMXForm.ApplyTheme; @@ -2129,21 +1882,107 @@ procedure TMarkdownPadFMXForm.StyleTocBackground; procedure TMarkdownPadFMXForm.SaveSession; begin - if FSession = nil then - Exit; + FController.SaveSession; +end; + +function TMarkdownPadFMXForm.GetWorkspace: IPadWorkspace; +begin + Result := FController.Workspace; +end; + +function TMarkdownPadFMXForm.GetSession: TPadSession; +begin + Result := FController.Session; +end; + +function TMarkdownPadFMXForm.GetMapDirty: Boolean; +begin + Result := FController.MapDirty; +end; + +procedure TMarkdownPadFMXForm.SetMapDirty(const Value: Boolean); +begin + FController.MapDirty := Value; +end; + +function TMarkdownPadFMXForm.GetSwapping: Boolean; +begin + Result := FController.Swapping; +end; + +procedure TMarkdownPadFMXForm.SetSwapping(const Value: Boolean); +begin + FController.Swapping := Value; +end; - var FilteredActive: Integer; - const Titled = TPadSessionSync.CollectOpenFiles(FWorkspace, FilteredActive); +function TMarkdownPadFMXForm.GetPaletteMatches: TArray; +begin + Result := FController.PaletteMatches; +end; + +function TMarkdownPadFMXForm.SampleMarkdown: string; +begin + Result := BuildSampleMarkdown; +end; - FSession.SetOpenFiles(Titled, FilteredActive); - FSession.DarkTheme := FDarkThemeActive; +function TMarkdownPadFMXForm.DarkThemeActive: Boolean; +begin + Result := FDarkThemeActive; +end; +function TMarkdownPadFMXForm.EffectiveViewMode: TPadViewMode; +begin if FZenActive then - FSession.ViewMode := FPreZenViewMode + Result := FPreZenViewMode else - FSession.ViewMode := FViewMode; + Result := FViewMode; +end; + +procedure TMarkdownPadFMXForm.ApplyRestoredViewMode(const Mode: TPadViewMode); +begin + FViewMode := Mode; + ApplyViewMode; +end; - FSession.Save; +function TMarkdownPadFMXForm.PromptOpenFile(out FileName: string): Boolean; +begin + Result := FOpenDialog.Execute; + if Result then + FileName := FOpenDialog.FileName; +end; + +function TMarkdownPadFMXForm.PromptSaveFile(const SuggestedName: string; out FileName: string): Boolean; +begin + if SuggestedName <> '' then + FSaveDialog.FileName := SuggestedName; + + Result := FSaveDialog.Execute; + if Result then + FileName := FSaveDialog.FileName; +end; + +function TMarkdownPadFMXForm.ConfirmClose: TPadCloseChoice; +begin + const Answer = TDialogServiceSync.MessageDialog(CloseUnsavedPrompt, TMsgDlgType.mtConfirmation, + [TMsgDlgBtn.mbYes, TMsgDlgBtn.mbNo, TMsgDlgBtn.mbCancel], TMsgDlgBtn.mbCancel, 0); + + if Answer = mrCancel then + Result := TPadCloseChoice.Cancel + else if Answer = mrYes then + Result := TPadCloseChoice.Save + else + Result := TPadCloseChoice.Discard; +end; + +procedure TMarkdownPadFMXForm.ShowOpenError(const FileName, ErrorMessage: string); +begin + TDialogServiceSync.MessageDialog(Format(OpenErrorFormat, [FileName]), TMsgDlgType.mtError, + [TMsgDlgBtn.mbOK], TMsgDlgBtn.mbOK, 0); +end; + +procedure TMarkdownPadFMXForm.CloseApplication; +begin + Close; end; class function TMarkdownPadFMXForm.BuildSampleMarkdown: string; diff --git a/Examples/MarkdownPadFMX/MarkdownPadFMX.dpr b/Examples/MarkdownPadFMX/MarkdownPadFMX.dpr index b13d51d..e19bae6 100644 --- a/Examples/MarkdownPadFMX/MarkdownPadFMX.dpr +++ b/Examples/MarkdownPadFMX/MarkdownPadFMX.dpr @@ -18,7 +18,10 @@ uses MarkdownPadFMX.Main in 'MarkdownPadFMX.Main.pas', MarkdownPad.Defines in '..\Shared\MarkdownPad.Defines.pas', MarkdownPad.Text in '..\Shared\MarkdownPad.Text.pas', - MarkdownPad.Outline in '..\Shared\MarkdownPad.Outline.pas'; + MarkdownPad.Outline in '..\Shared\MarkdownPad.Outline.pas', + MarkdownPadFMX.Defines in 'MarkdownPadFMX.Defines.pas', + MarkdownPad.Shell in '..\Shared\MarkdownPad.Shell.pas', + MarkdownPad.Controller in '..\Shared\MarkdownPad.Controller.pas'; {$R *.res} diff --git a/Examples/MarkdownPadFMX/MarkdownPadFMX.dproj b/Examples/MarkdownPadFMX/MarkdownPadFMX.dproj index a2bb29f..f67878d 100644 --- a/Examples/MarkdownPadFMX/MarkdownPadFMX.dproj +++ b/Examples/MarkdownPadFMX/MarkdownPadFMX.dproj @@ -1,1155 +1,1157 @@ - - - {8F3B6E29-4D71-4A5C-B0E8-2C9D1A7F0B36} - 20.3 - FMX - MarkdownPadFMX.dpr - True - Debug - Win32 - 1 - Application - MarkdownPadFMX - - - true - - - true - Base - true - - - true - Base - true - - - true - Cfg_1 - true - true - - - true - Cfg_1 - true - true - - - true - Base - true - - - true - Cfg_2 - true - true - - - MarkdownPadFMX - .\$(Platform)\$(Config) - .\$(Platform)\$(Config) - ..\..\Source\Core;..\..\Source\Layout;..\..\Source\Fmx;..\Shared;..\..\ThirdParty\Image32\source;..\..\ThirdParty\Image32\source\Clipper2;$(DCC_UnitSearchPath) - System;System.Win;Winapi;Xml;Data;Datasnap;Web;Soap;Fmx;$(DCC_Namespace) - false - false - false - false - false - 1033 - true - CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= - $(BDS)\bin\delphi_PROJECTICON.ico - - $(BDS)\bin\delphi_PROJECTICNS.icns - - - Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) - Debug - $(BDS)\bin\default_app.manifest - $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png - $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png - - - DEBUG;$(DCC_Define) - true - false - true - true - - - Debug - - - PerMonitorV2 - - - false - RELEASE;$(DCC_Define) - 0 - 0 - - - PerMonitorV2 - - - - MainSource - - - - - - - - - - - - - - - - - - - - Base - - - Cfg_1 - Base - - - Cfg_2 - Base - - - - Delphi.Personality.12 - Application - - - - MarkdownPadFMX.dpr - - - - True - False - - - - - true - - - - - true - - - - - true - - - - - MarkdownPadFMX.exe - true - - - - - 1 - - - Contents\MacOS - 1 - - - 0 - - - - - res\xml - 1 - - - res\xml - 1 - - - - - library\lib\armeabi - 1 - - - library\lib\armeabi - 1 - - - - - library\lib\armeabi-v7a - 1 - - - - - library\lib\mips - 1 - - - library\lib\mips - 1 - - - - - library\lib\armeabi-v7a - 1 - - - library\lib\arm64-v8a - 1 - - - - - library\lib\armeabi-v7a - 1 - - - - - res\drawable - 1 - - - res\drawable - 1 - - - - - res\drawable-anydpi-v21 - 1 - - - res\drawable-anydpi-v21 - 1 - - - - - res\values - 1 - - - res\values - 1 - - - - - res\values-v21 - 1 - - - res\values-v21 - 1 - - - - - res\values-v31 - 1 - - - res\values-v31 - 1 - - - - - res\values-v35 - 1 - - - res\values-v35 - 1 - - - - - res\drawable-anydpi-v26 - 1 - - - res\drawable-anydpi-v26 - 1 - - - - - res\drawable - 1 - - - res\drawable - 1 - - - - - res\drawable - 1 - - - res\drawable - 1 - - - - - res\drawable - 1 - - - res\drawable - 1 - - - - - res\drawable-anydpi-v33 - 1 - - - res\drawable-anydpi-v33 - 1 - - - - - res\values - 1 - - - res\values - 1 - - - - - res\values-night-v21 - 1 - - - res\values-night-v21 - 1 - - - - - res\drawable - 1 - - - res\drawable - 1 - - - - - res\drawable-xxhdpi - 1 - - - res\drawable-xxhdpi - 1 - - - - - res\drawable-xxxhdpi - 1 - - - res\drawable-xxxhdpi - 1 - - - - - res\drawable-ldpi - 1 - - - res\drawable-ldpi - 1 - - - - - res\drawable-mdpi - 1 - - - res\drawable-mdpi - 1 - - - - - res\drawable-hdpi - 1 - - - res\drawable-hdpi - 1 - - - - - res\drawable-xhdpi - 1 - - - res\drawable-xhdpi - 1 - - - - - res\drawable-mdpi - 1 - - - res\drawable-mdpi - 1 - - - - - res\drawable-hdpi - 1 - - - res\drawable-hdpi - 1 - - - - - res\drawable-xhdpi - 1 - - - res\drawable-xhdpi - 1 - - - - - res\drawable-xxhdpi - 1 - - - res\drawable-xxhdpi - 1 - - - - - res\drawable-xxxhdpi - 1 - - - res\drawable-xxxhdpi - 1 - - - - - res\drawable-small - 1 - - - res\drawable-small - 1 - - - - - res\drawable-normal - 1 - - - res\drawable-normal - 1 - - - - - res\drawable-large - 1 - - - res\drawable-large - 1 - - - - - res\drawable-xlarge - 1 - - - res\drawable-xlarge - 1 - - - - - res\values - 1 - - - res\values - 1 - - - - - res\drawable-anydpi-v24 - 1 - - - res\drawable-anydpi-v24 - 1 - - - - - res\drawable - 1 - - - res\drawable - 1 - - - - - res\drawable-night-anydpi-v21 - 1 - - - res\drawable-night-anydpi-v21 - 1 - - - - - res\drawable-anydpi-v31 - 1 - - - res\drawable-anydpi-v31 - 1 - - - - - res\drawable-night-anydpi-v31 - 1 - - - res\drawable-night-anydpi-v31 - 1 - - - - - 1 - - - Contents\MacOS - 1 - - - 0 - - - - - Contents\MacOS - 1 - .framework - - - Contents\MacOS - 1 - .framework - - - Contents\MacOS - 1 - .framework - - - 0 - - - - - 1 - .dylib - - - 1 - .dylib - - - 1 - .dylib - - - Contents\MacOS - 1 - .dylib - - - Contents\MacOS - 1 - .dylib - - - Contents\MacOS - 1 - .dylib - - - 0 - .dll;.bpl - - - - - 1 - .dylib - - - 1 - .dylib - - - 1 - .dylib - - - Contents\MacOS - 1 - .dylib - - - Contents\MacOS - 1 - .dylib - - - Contents\MacOS - 1 - .dylib - - - 0 - .bpl - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - Contents\Resources\StartUp\ - 0 - - - Contents\Resources\StartUp\ - 0 - - - Contents\Resources\StartUp\ - 0 - - - 0 - - - - - 1 - - - 1 - - - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - - - ..\ - 1 - - - ..\ - 1 - - - ..\ - 1 - - - - - Contents - 1 - - - Contents - 1 - - - Contents - 1 - - - - - Contents\Resources - 1 - - - Contents\Resources - 1 - - - Contents\Resources - 1 - - - - - library\lib\armeabi-v7a - 1 - - - library\lib\arm64-v8a - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - Contents\MacOS - 1 - - - Contents\MacOS - 1 - - - Contents\MacOS - 1 - - - 0 - - - - - library\lib\armeabi-v7a - 1 - - - - - 1 - - - 1 - - - 1 - - - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - - - ..\ - 1 - - - ..\ - 1 - - - ..\ - 1 - - - - - 1 - - - 1 - - - 1 - - - - - ..\$(PROJECTNAME).launchscreen - 64 - - - ..\$(PROJECTNAME).launchscreen - 64 - - - - - 1 - - - 1 - - - 1 - - - - - Assets - 1 - - - Assets - 1 - - - - - Assets - 1 - - - Assets - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - - - - - - - - - - - - - - - 12 - - - - - - - False - - False - copy /Y "$(BDS)\bin\sk4d.dll" "$(MSBuildProjectDirectory)\$(Platform)\$(Config)\" - False - - - - False - - False - copy /Y "$(BDS)\bin\sk4d.dll" "$(MSBuildProjectDirectory)\$(Platform)\$(Config)\" - False - - - - False - - False - copy /Y "$(BDS)\bin\sk4d.dll" "$(MSBuildProjectDirectory)\$(Platform)\$(Config)\" - False - - - - False - - False - copy /Y "$(BDS)\bin\sk4d.dll" "$(MSBuildProjectDirectory)\$(Platform)\$(Config)\" - False - - + + + {8F3B6E29-4D71-4A5C-B0E8-2C9D1A7F0B36} + 20.3 + FMX + MarkdownPadFMX.dpr + True + Debug + Win32 + 1 + Application + MarkdownPadFMX + + + true + + + true + Base + true + + + true + Base + true + + + true + Cfg_1 + true + true + + + true + Cfg_1 + true + true + + + true + Base + true + + + true + Cfg_2 + true + true + + + MarkdownPadFMX + .\$(Platform)\$(Config) + .\$(Platform)\$(Config) + ..\..\Source\Core;..\..\Source\Layout;..\..\Source\Fmx;..\Shared;..\..\ThirdParty\Image32\source;..\..\ThirdParty\Image32\source\Clipper2;$(DCC_UnitSearchPath) + System;System.Win;Winapi;Xml;Data;Datasnap;Web;Soap;Fmx;$(DCC_Namespace) + false + false + false + false + false + 1033 + true + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + $(BDS)\bin\delphi_PROJECTICON.ico + + $(BDS)\bin\delphi_PROJECTICNS.icns + + + Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) + Debug + $(BDS)\bin\default_app.manifest + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + + + DEBUG;$(DCC_Define) + true + false + true + true + + + Debug + + + PerMonitorV2 + + + false + RELEASE;$(DCC_Define) + 0 + 0 + + + PerMonitorV2 + + + + MainSource + + + + + + + + + + + + + + + + + + + + + + Base + + + Cfg_1 + Base + + + Cfg_2 + Base + + + + Delphi.Personality.12 + Application + + + + MarkdownPadFMX.dpr + + + + True + False + + + + + true + + + + + true + + + + + true + + + + + MarkdownPadFMX.exe + true + + + + + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + res\xml + 1 + + + res\xml + 1 + + + + + library\lib\armeabi + 1 + + + library\lib\armeabi + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + library\lib\mips + 1 + + + library\lib\mips + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-anydpi-v21 + 1 + + + res\drawable-anydpi-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\values-v21 + 1 + + + res\values-v21 + 1 + + + + + res\values-v31 + 1 + + + res\values-v31 + 1 + + + + + res\values-v35 + 1 + + + res\values-v35 + 1 + + + + + res\drawable-anydpi-v26 + 1 + + + res\drawable-anydpi-v26 + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-anydpi-v33 + 1 + + + res\drawable-anydpi-v33 + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\values-night-v21 + 1 + + + res\values-night-v21 + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 1 + + + + + res\drawable-xxxhdpi + 1 + + + res\drawable-xxxhdpi + 1 + + + + + res\drawable-ldpi + 1 + + + res\drawable-ldpi + 1 + + + + + res\drawable-mdpi + 1 + + + res\drawable-mdpi + 1 + + + + + res\drawable-hdpi + 1 + + + res\drawable-hdpi + 1 + + + + + res\drawable-xhdpi + 1 + + + res\drawable-xhdpi + 1 + + + + + res\drawable-mdpi + 1 + + + res\drawable-mdpi + 1 + + + + + res\drawable-hdpi + 1 + + + res\drawable-hdpi + 1 + + + + + res\drawable-xhdpi + 1 + + + res\drawable-xhdpi + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 1 + + + + + res\drawable-xxxhdpi + 1 + + + res\drawable-xxxhdpi + 1 + + + + + res\drawable-small + 1 + + + res\drawable-small + 1 + + + + + res\drawable-normal + 1 + + + res\drawable-normal + 1 + + + + + res\drawable-large + 1 + + + res\drawable-large + 1 + + + + + res\drawable-xlarge + 1 + + + res\drawable-xlarge + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\drawable-anydpi-v24 + 1 + + + res\drawable-anydpi-v24 + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-night-anydpi-v21 + 1 + + + res\drawable-night-anydpi-v21 + 1 + + + + + res\drawable-anydpi-v31 + 1 + + + res\drawable-anydpi-v31 + 1 + + + + + res\drawable-night-anydpi-v31 + 1 + + + res\drawable-night-anydpi-v31 + 1 + + + + + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + Contents\MacOS + 1 + .framework + + + Contents\MacOS + 1 + .framework + + + Contents\MacOS + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + Contents\Resources\StartUp\ + 0 + + + Contents\Resources\StartUp\ + 0 + + + Contents\Resources\StartUp\ + 0 + + + 0 + + + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + ..\ + 1 + + + + + Contents + 1 + + + Contents + 1 + + + Contents + 1 + + + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + Contents\MacOS + 1 + + + Contents\MacOS + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + library\lib\armeabi-v7a + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + ..\ + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen + 64 + + + ..\$(PROJECTNAME).launchscreen + 64 + + + + + 1 + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + + + + + + + + + + + + + 12 + + + + + + + False + + False + copy /Y "$(BDS)\bin\sk4d.dll" "$(MSBuildProjectDirectory)\$(Platform)\$(Config)\" + False + + + + False + + False + copy /Y "$(BDS)\bin\sk4d.dll" "$(MSBuildProjectDirectory)\$(Platform)\$(Config)\" + False + + + + False + + False + copy /Y "$(BDS)\bin\sk4d.dll" "$(MSBuildProjectDirectory)\$(Platform)\$(Config)\" + False + + + + False + + False + copy /Y "$(BDS)\bin\sk4d.dll" "$(MSBuildProjectDirectory)\$(Platform)\$(Config)\" + False + + diff --git a/Examples/MarkdownPadVCL/MarkdownPadVCL.Defines.pas b/Examples/MarkdownPadVCL/MarkdownPadVCL.Defines.pas new file mode 100644 index 0000000..2ce1a9e --- /dev/null +++ b/Examples/MarkdownPadVCL/MarkdownPadVCL.Defines.pas @@ -0,0 +1,43 @@ +unit MarkdownPadVCL.Defines; + +// VCL-specific constants for the Markdown4D Pad demo. Values that are shared +// verbatim with the FMX build live in MarkdownPad.Defines; only VCL-specific +// values (custom title bar, DWM caption, TColor palette, per-build strings) +// stay here. + +interface + +uses + System.UITypes; + +const + WindowCaption = 'Markdown4D Pad'; + InitialClientWidth = 1200; + ToolbarHeight = 36; + TabsHeight = 30; + CaptionButtonsReserve = 160; + DwmCaptionColorAttribute = 35; + StatusBarHeight = 22; + StatusLabelWidth = 160; + StatusLabelLeftMargin = 8; + ButtonSpacing = 4; + IconGlyphSize = 14; + ToolbarLightColor = TColor($00F3F3F3); + ToolbarDarkColor = TColor($002D2D2D); + IconLightColor = TColor($00404040); + IconDarkColor = TColor($00D6D6D6); + SeparatorLightColor = TColor($00D0D0D0); + SeparatorDarkColor = TColor($00505050); + TabAccentColor = TColor($00C0742C); + TabActiveDarkColor = TColor($003F3F3F); + TabHoverLightColor = TColor($00EAEAEA); + TabHoverDarkColor = TColor($00383838); + ZenPadDarkColor = TColor($0017110D); // matches the dark theme editor/preview background ($0D1117) + SessionFileName = 'MarkdownPad.Vcl.json'; + CloseUnsavedPrompt = 'Save changes before closing this document?'; + ReloadPrompt = 'File changed on disk. Reload and lose your changes?'; + PaletteTextMargin = 8; + +implementation + +end. diff --git a/Examples/MarkdownPadVCL/MarkdownPadVCL.Main.pas b/Examples/MarkdownPadVCL/MarkdownPadVCL.Main.pas index c3da15f..ba004a5 100644 --- a/Examples/MarkdownPadVCL/MarkdownPadVCL.Main.pas +++ b/Examples/MarkdownPadVCL/MarkdownPadVCL.Main.pas @@ -29,11 +29,15 @@ interface MarkdownPad.EditorView, MarkdownPad.Session, MarkdownPad.Commands, + MarkdownPad.CommandSet, MarkdownPad.TabStrip, - MarkdownPad.FileWatcher; + MarkdownPad.FileWatcher, + MarkdownPad.Shell, + MarkdownPad.Controller, + MarkdownPadVCL.Defines; type - TMarkdownPadVCLForm = class(TForm, IPadEditorView) + TMarkdownPadVCLForm = class(TForm, IPadEditorView, IPadShell) pnlToolbar: TPanel; pnlFind: TPanel; pnlStatus: TPanel; @@ -53,36 +57,6 @@ TMarkdownPadVCLForm = class(TForm, IPadEditorView) edtEditorFind: TEdit; lblFindCount: TLabel; private - const - // Shared constants live in MarkdownPad.Defines; only VCL-specific values - // (custom title bar, DWM caption, TColor palette) stay here. - WindowCaption = 'Markdown4D Pad'; - InitialClientWidth = 1200; - ToolbarHeight = 36; - TabsHeight = 30; - CaptionButtonsReserve = 160; - DwmCaptionColorAttribute = 35; - StatusBarHeight = 22; - StatusLabelWidth = 160; - StatusLabelLeftMargin = 8; - ButtonSpacing = 4; - IconGlyphSize = 14; - ToolbarLightColor = TColor($00F3F3F3); - ToolbarDarkColor = TColor($002D2D2D); - IconLightColor = TColor($00404040); - IconDarkColor = TColor($00D6D6D6); - SeparatorLightColor = TColor($00D0D0D0); - SeparatorDarkColor = TColor($00505050); - TabAccentColor = TColor($00C0742C); - TabActiveDarkColor = TColor($003F3F3F); - TabHoverLightColor = TColor($00EAEAEA); - TabHoverDarkColor = TColor($00383838); - ZenPadDarkColor = TColor($0017110D); // matches the dark theme editor/preview background ($0D1117) - SessionFileName = 'MarkdownPad.Vcl.json'; - CloseUnsavedPrompt = 'Save changes before closing this document?'; - ReloadPrompt = 'File changed on disk. Reload and lose your changes?'; - PaletteTextMargin = 8; - RecentShortcut = ''; var FIconFontName: string; FNewButton: TSpeedButton; @@ -102,7 +76,6 @@ TMarkdownPadVCLForm = class(TForm, IPadEditorView) FFindEdit: TEdit; FIconButtons: TArray; FSeparators: TArray; - FTocEntries: TArray; FLightTheme: TMarkdownTheme; FDarkTheme: TMarkdownTheme; FDarkThemeActive: Boolean; @@ -110,26 +83,50 @@ TMarkdownPadVCLForm = class(TForm, IPadEditorView) FTabStrip: TPadTabStrip; FUseCustomTitleBar: Boolean; FTitleBarColor: TColor; - FWorkspace: IPadWorkspace; - FActiveDoc: IPadDocument; - FSession: TPadSession; - FWatcher: TPadFileWatcher; - FMapDirty: Boolean; - FSwapping: Boolean; - FLastCaret: Integer; + FController: TPadController; FViewMode: TPadViewMode; FSplitEditorWidth: Integer; FPalette: TPanel; FPaletteEdit: TEdit; FPaletteList: TListBox; - FCommands: TPadCommandRegistry; - FPaletteMatches: TArray; FZenActive: Boolean; FPreZenViewMode: TPadViewMode; FZenLeftPad: TPanel; FZenRightPad: TPanel; FZenTocWasVisible: Boolean; FZenFindWasVisible: Boolean; + // Read-through accessors to the controller-owned model state. + function GetWorkspace: IPadWorkspace; + function GetSession: TPadSession; + function GetMapDirty: Boolean; + procedure SetMapDirty(const Value: Boolean); + function GetSwapping: Boolean; + procedure SetSwapping(const Value: Boolean); + function GetPaletteMatches: TArray; + property FWorkspace: IPadWorkspace read GetWorkspace; + property FSession: TPadSession read GetSession; + property FMapDirty: Boolean read GetMapDirty write SetMapDirty; + property FSwapping: Boolean read GetSwapping write SetSwapping; + property FPaletteMatches: TArray read GetPaletteMatches; + procedure SetDocumentTitle(const Name: string); + procedure SetStatus(const PositionText, WordsText: string); + procedure SetTocCaptions(const Captions: TArray); + procedure SetActiveTocIndex(const Index: Integer); + function EditorFindNeedle: string; + function PreviewFindNeedle: string; + procedure SetFindCount(const Value: string); + function SampleMarkdown: string; + function DarkThemeActive: Boolean; + function EffectiveViewMode: TPadViewMode; + procedure ApplyRestoredViewMode(const Mode: TPadViewMode); + function PromptOpenFile(out FileName: string): Boolean; + function PromptSaveFile(const SuggestedName: string; out FileName: string): Boolean; + function PromptExportHtml(const SuggestedName: string; out FileName: string): Boolean; + function ConfirmClose: TPadCloseChoice; + function ConfirmCloseDocument(const DocName: string): TPadCloseChoice; + function ConfirmReload: Boolean; + procedure ShowOpenError(const FileName, ErrorMessage: string); + procedure CloseApplication; procedure ConfigureControls; procedure BuildToolbar; function ResolveIconFontName: string; @@ -172,7 +169,6 @@ TMarkdownPadVCLForm = class(TForm, IPadEditorView) procedure HandlePreviewLinkClick(const Sender: TObject; const Url: string); procedure HandleTocListClick(Sender: TObject); procedure HandleTick(Sender: TObject); - procedure HandleFileChanged(const Document: IPadDocument); procedure OpenPath(const FileName: string); function GetEditorText: string; procedure SetEditorText(const Value: string); @@ -185,25 +181,24 @@ TMarkdownPadVCLForm = class(TForm, IPadEditorView) function SaveEditState: IMarkdownEditorState; procedure LoadEditState(const State: IMarkdownEditorState); procedure FlushPreview; + procedure EditorFindNext(const Needle: string); + function EditorFindMatchCount(const Needle: string): Integer; + procedure PreviewFindText(const Needle: string); procedure BeginSwap; procedure EndSwap; procedure SwitchToDocument(const Index: Integer); procedure CloseActiveDocument; procedure CloseDocumentAt(const Index: Integer); - function SaveActiveDocument: Boolean; - procedure SaveToFile(const FileName: string); procedure RestoreSession; procedure SaveSession; procedure RebuildTabs; procedure ApplyTheme; procedure RebuildSyncAndToc; procedure UpdateActiveTocEntry(const SourceLine: Integer); - procedure UpdateStatusBar; - procedure UpdateTitle; procedure ExecuteFind; procedure BuildPalette; procedure BuildCommandRegistry; - procedure RegisterStaticCommands; + function BuildCommandActions: TPadCommandActions; procedure SetViewMode(const Mode: TPadViewMode); procedure ApplyViewMode; procedure EnforceTopBarOrder; @@ -254,7 +249,6 @@ implementation Markdown4D.Extensions.Chart.BlockOverride, Markdown4D.Extensions.Mermaid.BlockOverride, MarkdownPad.Defines, - MarkdownPad.CommandSet, MarkdownPad.SessionSync, MarkdownPad.Text, MarkdownPad.Outline, @@ -283,10 +277,7 @@ constructor TMarkdownPadVCLForm.Create(Owner: TComponent); dlgSaveHtml.Filter := HtmlFilter; dlgSaveHtml.DefaultExt := HtmlExtension; - FWorkspace := TPadWorkspace.Create; - FSession := TPadSession.Create(TPadSession.ResolvePath(SessionFileName)); - FSession.Load; - FWatcher := TPadFileWatcher.Create(FWorkspace, HandleFileChanged); + FController := TPadController.Create(Self, Self, SessionFileName); ConfigureControls; BuildToolbar; @@ -322,9 +313,7 @@ destructor TMarkdownPadVCLForm.Destroy; inherited Destroy; - FCommands.Free; - FWatcher.Free; - FSession.Free; + FController.Free; FDarkTheme.Free; FLightTheme.Free; end; @@ -704,68 +693,27 @@ procedure TMarkdownPadVCLForm.HandleCloseQuery(Sender: TObject; var CanClose: Bo if FZenActive then ExitZen; - for var Index := 0 to FWorkspace.Count - 1 do - begin - const Document = FWorkspace.Documents[Index]; - if not Document.Modified then - Continue; - - SwitchToDocument(Index); - - const Prompt = Format(CloseDocumentPromptFormat, [Document.DisplayName]); - const Answer = MessageDlg(Prompt, mtConfirmation, [mbYes, mbNo, mbCancel], 0); - - if Answer = mrCancel then - begin - CanClose := False; - Exit; - end; - - if (Answer = mrYes) and not SaveActiveDocument then - begin - CanClose := False; - Exit; - end; - - Document.Modified := False; - end; - - CanClose := True; + CanClose := FController.QueryClose; end; procedure TMarkdownPadVCLForm.HandleNewClick(Sender: TObject); begin - FWorkspace.NewDocument; - SwitchToDocument(FWorkspace.ActiveIndex); + FController.NewDocument; end; procedure TMarkdownPadVCLForm.HandleOpenClick(Sender: TObject); begin - if dlgOpen.Execute then - OpenPath(dlgOpen.FileName); + FController.OpenViaDialog; end; procedure TMarkdownPadVCLForm.HandleSaveClick(Sender: TObject); begin - if FActiveDoc = nil then - Exit; - - if FActiveDoc.IsUntitled then - HandleSaveAsClick(Sender) - else - SaveToFile(FActiveDoc.FileName); + FController.Save; end; procedure TMarkdownPadVCLForm.HandleSaveAsClick(Sender: TObject); begin - if FActiveDoc = nil then - Exit; - - if not FActiveDoc.IsUntitled then - dlgSave.FileName := FActiveDoc.FileName; - - if dlgSave.Execute then - SaveToFile(dlgSave.FileName); + FController.SaveAs; end; procedure TMarkdownPadVCLForm.HandleRecentClick(Sender: TObject); @@ -842,26 +790,22 @@ procedure TMarkdownPadVCLForm.ExecuteFormatCommand(const Command: TEditorCommand procedure TMarkdownPadVCLForm.DoExportHtml; begin - if FActiveDoc <> nil then - FActiveDoc.Text := mdEditor.Text; - - var Title := UntitledName; - if FActiveDoc <> nil then - Title := FActiveDoc.DisplayName; - - dlgSaveHtml.FileName := TPath.ChangeExtension(Title, '.' + HtmlExtension); + FController.ExportHtml; +end; - if not dlgSaveHtml.Execute then - Exit; +function TMarkdownPadVCLForm.PromptExportHtml(const SuggestedName: string; out FileName: string): Boolean; +begin + if SuggestedName <> '' then + dlgSaveHtml.FileName := SuggestedName; - const Html = TMarkdownHtmlExport.BuildDocument(mdEditor.Text, Title, FDarkThemeActive); - TFile.WriteAllText(dlgSaveHtml.FileName, Html, TEncoding.UTF8); + Result := dlgSaveHtml.Execute; + if Result then + FileName := dlgSaveHtml.FileName; end; procedure TMarkdownPadVCLForm.DoCopyHtml; begin - const Fragment = TMarkdown.ToHtml(mdEditor.Text, TMarkdownDialect.Gfm); - CopyHtmlToClipboard(Fragment); + FController.CopyHtml; end; procedure TMarkdownPadVCLForm.CopyHtmlToClipboard(const Fragment: string); @@ -1066,20 +1010,7 @@ procedure TMarkdownPadVCLForm.HandleTabReorder(Sender: TObject; const FromIndex, procedure TMarkdownPadVCLForm.HandleEditorChange(Sender: TObject); begin - if FSwapping then - Exit; - - const WasModified = (FActiveDoc <> nil) and FActiveDoc.Modified; - - if FActiveDoc <> nil then - FActiveDoc.Modified := True; - - FMapDirty := True; - - if not WasModified then - RebuildTabs; - - UpdateTitle; + FController.NotifyEditorChanged; end; procedure TMarkdownPadVCLForm.HandleSyncScroll(Sender: TObject; const SourceLine: Integer); @@ -1097,16 +1028,16 @@ procedure TMarkdownPadVCLForm.HandlePreviewLinkClick(const Sender: TObject; cons procedure TMarkdownPadVCLForm.HandleTocListClick(Sender: TObject); begin const Index = lstToc.ItemIndex; - if (Index < 0) or (Index > High(FTocEntries)) then + if (Index < 0) or (Index >= FController.TocEntryCount) then Exit; if FMapDirty then RebuildSyncAndToc; - if (Index < 0) or (Index > High(FTocEntries)) then + if (Index < 0) or (Index >= FController.TocEntryCount) then Exit; - const SourceLine = FTocEntries[Index].SourceLine - 1; + const SourceLine = FController.TocSourceLine(Index); // Scrolling the editor drives the preview through the linked SyncScroll. mdEditor.CaretPosition := mdEditor.SourceLineStartOffset(SourceLine); @@ -1118,74 +1049,12 @@ procedure TMarkdownPadVCLForm.HandleTocListClick(Sender: TObject); procedure TMarkdownPadVCLForm.HandleTick(Sender: TObject); begin - if FMapDirty then - RebuildSyncAndToc; - - UpdateStatusBar; - - FWatcher.Poll; -end; - -procedure TMarkdownPadVCLForm.HandleFileChanged(const Document: IPadDocument); -begin - if Document.Modified then - begin - if MessageDlg(ReloadPrompt, mtConfirmation, [mbYes, mbNo], 0) <> mrYes then - Exit; - end; - - var NewText: string; - try - NewText := TFile.ReadAllText(Document.FileName); - except - Document.DiskTimestampUtc := 0; - Exit; - end; - - Document.Text := NewText; - Document.Modified := False; - - if Document = FActiveDoc then - begin - FSwapping := True; - try - mdEditor.Text := NewText; - mdEditor.FlushPreview; - - if mdEditor.CaretPosition > System.Length(NewText) then - mdEditor.CaretPosition := System.Length(NewText); - finally - FSwapping := False; - end; - - FMapDirty := True; - end; - - RebuildTabs; - UpdateTitle; + FController.Tick; end; procedure TMarkdownPadVCLForm.OpenPath(const FileName: string); begin - if not TFile.Exists(FileName) then - Exit; - - var Document: IPadDocument; - try - Document := FWorkspace.OpenFile(FileName); - except - on E: Exception do - begin - MessageDlg(E.Message, mtError, [mbOK], 0); - Exit; - end; - end; - - FWatcher.Reset(Document); - FSession.AddRecentFile(FileName); - - RebuildTabs; - SwitchToDocument(FWorkspace.ActiveIndex); + FController.OpenPath(FileName); end; function TMarkdownPadVCLForm.GetEditorText: string; @@ -1255,132 +1124,125 @@ procedure TMarkdownPadVCLForm.EndSwap; procedure TMarkdownPadVCLForm.SwitchToDocument(const Index: Integer); begin - if FSwapping then - Exit; - - FActiveDoc := TPadDocumentSwitch.Execute(FWorkspace, Self, FActiveDoc, Index); - - if FActiveDoc = nil then - Exit; - - FLastCaret := -1; - FMapDirty := True; - - RebuildTabs; - UpdateTitle; + FController.SwitchToDocument(Index); end; procedure TMarkdownPadVCLForm.CloseActiveDocument; begin - CloseDocumentAt(FWorkspace.ActiveIndex); + FController.CloseActiveDocument; end; procedure TMarkdownPadVCLForm.CloseDocumentAt(const Index: Integer); begin - if (Index < 0) or (Index >= FWorkspace.Count) then - Exit; - - if Index <> FWorkspace.ActiveIndex then - SwitchToDocument(Index); - - if FActiveDoc = nil then - Exit; - - if FActiveDoc.Modified then - begin - const Answer = MessageDlg(CloseUnsavedPrompt, mtConfirmation, [mbYes, mbNo, mbCancel], 0); - if Answer = mrCancel then - Exit; - - if (Answer = mrYes) and not SaveActiveDocument then - Exit; - end; - - FWorkspace.CloseDocument(FWorkspace.ActiveIndex); - FActiveDoc := nil; - - if FWorkspace.Count = 0 then - begin - Close; - Exit; - end; - - RebuildTabs; - SwitchToDocument(FWorkspace.ActiveIndex); + FController.CloseDocumentAt(Index); end; -function TMarkdownPadVCLForm.SaveActiveDocument: Boolean; +procedure TMarkdownPadVCLForm.RestoreSession; begin - if FActiveDoc = nil then - Exit(False); + FController.RestoreSession; +end; - FActiveDoc.Text := mdEditor.Text; +procedure TMarkdownPadVCLForm.SaveSession; +begin + FController.SaveSession; +end; - if FActiveDoc.IsUntitled then - begin - if not dlgSave.Execute then - Exit(False); +function TMarkdownPadVCLForm.GetWorkspace: IPadWorkspace; +begin + Result := FController.Workspace; +end; - SaveToFile(dlgSave.FileName); - end - else - SaveToFile(FActiveDoc.FileName); +function TMarkdownPadVCLForm.GetSession: TPadSession; +begin + Result := FController.Session; +end; - Result := True; +function TMarkdownPadVCLForm.GetMapDirty: Boolean; +begin + Result := FController.MapDirty; end; -procedure TMarkdownPadVCLForm.SaveToFile(const FileName: string); +procedure TMarkdownPadVCLForm.SetMapDirty(const Value: Boolean); begin - TFile.WriteAllText(FileName, mdEditor.Text); + FController.MapDirty := Value; +end; - FActiveDoc.Text := mdEditor.Text; - FActiveDoc.FileName := FileName; - FActiveDoc.Modified := False; +function TMarkdownPadVCLForm.GetSwapping: Boolean; +begin + Result := FController.Swapping; +end; - FWatcher.Reset(FActiveDoc); - FSession.AddRecentFile(FileName); +procedure TMarkdownPadVCLForm.SetSwapping(const Value: Boolean); +begin + FController.Swapping := Value; +end; - RebuildTabs; - UpdateTitle; +function TMarkdownPadVCLForm.GetPaletteMatches: TArray; +begin + Result := FController.PaletteMatches; end; -procedure TMarkdownPadVCLForm.RestoreSession; +function TMarkdownPadVCLForm.SampleMarkdown: string; begin - if not TPadSessionSync.RestoreOpenFiles(FWorkspace, FSession) then - begin - const Document = FWorkspace.NewDocument; - Document.Text := BuildSampleMarkdown; - end; + Result := BuildSampleMarkdown; +end; - for var Index := 0 to FWorkspace.Count - 1 do - begin - FWatcher.Reset(FWorkspace.Documents[Index]); - end; +function TMarkdownPadVCLForm.DarkThemeActive: Boolean; +begin + Result := FDarkThemeActive; +end; - RebuildTabs; - SwitchToDocument(FWorkspace.ActiveIndex); +function TMarkdownPadVCLForm.EffectiveViewMode: TPadViewMode; +begin + if FZenActive then + Result := FPreZenViewMode + else + Result := FViewMode; +end; - FViewMode := FSession.ViewMode; +procedure TMarkdownPadVCLForm.ApplyRestoredViewMode(const Mode: TPadViewMode); +begin + FViewMode := Mode; ApplyViewMode; end; -procedure TMarkdownPadVCLForm.SaveSession; +function TMarkdownPadVCLForm.PromptOpenFile(out FileName: string): Boolean; begin - if FActiveDoc <> nil then - FActiveDoc.Text := mdEditor.Text; + Result := dlgOpen.Execute; + if Result then + FileName := dlgOpen.FileName; +end; - var FilteredActive: Integer; - const Titled = TPadSessionSync.CollectOpenFiles(FWorkspace, FilteredActive); +function TMarkdownPadVCLForm.PromptSaveFile(const SuggestedName: string; out FileName: string): Boolean; +begin + if SuggestedName <> '' then + dlgSave.FileName := SuggestedName; - FSession.SetOpenFiles(Titled, FilteredActive); - FSession.DarkTheme := FDarkThemeActive; + Result := dlgSave.Execute; + if Result then + FileName := dlgSave.FileName; +end; - if FZenActive then - FSession.ViewMode := FPreZenViewMode +function TMarkdownPadVCLForm.ConfirmClose: TPadCloseChoice; +begin + const Answer = MessageDlg(CloseUnsavedPrompt, mtConfirmation, [mbYes, mbNo, mbCancel], 0); + + if Answer = mrCancel then + Result := TPadCloseChoice.Cancel + else if Answer = mrYes then + Result := TPadCloseChoice.Save else - FSession.ViewMode := FViewMode; + Result := TPadCloseChoice.Discard; +end; - FSession.Save; +procedure TMarkdownPadVCLForm.ShowOpenError(const FileName, ErrorMessage: string); +begin + MessageDlg(ErrorMessage, mtError, [mbOK], 0); +end; + +procedure TMarkdownPadVCLForm.CloseApplication; +begin + Close; end; procedure TMarkdownPadVCLForm.RebuildTabs; @@ -1470,67 +1332,95 @@ procedure TMarkdownPadVCLForm.ApplyTheme; procedure TMarkdownPadVCLForm.RebuildSyncAndToc; begin - FMapDirty := False; + FController.RebuildSyncAndToc; +end; - const Document = TMarkdown.Parse(mdEditor.Text, TMarkdownDialect.Gfm); +procedure TMarkdownPadVCLForm.UpdateActiveTocEntry(const SourceLine: Integer); +begin + FController.UpdateActiveTocEntry(SourceLine); +end; + +procedure TMarkdownPadVCLForm.ExecuteFind; +begin + FController.ExecuteFind; +end; - const Outline = TPadOutlineBuilder.Build(TMarkdownToc.FromDocument(Document)); - FTocEntries := Outline.Entries; +procedure TMarkdownPadVCLForm.SetDocumentTitle(const Name: string); +begin + Caption := Format(TitleFormat, [WindowCaption, Name]); +end; + +procedure TMarkdownPadVCLForm.SetStatus(const PositionText, WordsText: string); +begin + lblPos.Caption := PositionText; + lblWords.Caption := WordsText; +end; +procedure TMarkdownPadVCLForm.SetTocCaptions(const Captions: TArray); +begin lstToc.Items.BeginUpdate; try lstToc.Items.Clear; - for var Caption in Outline.Captions do + for var Caption in Captions do lstToc.Items.Add(Caption); finally lstToc.Items.EndUpdate; end; end; -procedure TMarkdownPadVCLForm.UpdateActiveTocEntry(const SourceLine: Integer); +procedure TMarkdownPadVCLForm.SetActiveTocIndex(const Index: Integer); begin - const Best = TPadOutlineBuilder.ActiveIndex(FTocEntries, SourceLine); - - if Best <> lstToc.ItemIndex then - lstToc.ItemIndex := Best; + if Index <> lstToc.ItemIndex then + lstToc.ItemIndex := Index; end; -procedure TMarkdownPadVCLForm.UpdateStatusBar; +function TMarkdownPadVCLForm.EditorFindNeedle: string; begin - const Caret = mdEditor.CaretPosition; - if Caret = FLastCaret then - Exit; + Result := edtEditorFind.Text; +end; - FLastCaret := Caret; +function TMarkdownPadVCLForm.PreviewFindNeedle: string; +begin + Result := FFindEdit.Text; +end; - var Line, Column: Integer; - TPadText.ComputeLineColumn(mdEditor.Text, Caret, Line, Column); - lblPos.Caption := Format(StatusPositionFormat, [Line, Column]); - lblWords.Caption := Format(StatusWordsFormat, [TPadText.CountWords(mdEditor.Text)]); +procedure TMarkdownPadVCLForm.SetFindCount(const Value: string); +begin + lblFindCount.Caption := Value; end; -procedure TMarkdownPadVCLForm.UpdateTitle; +procedure TMarkdownPadVCLForm.EditorFindNext(const Needle: string); begin - var Name := UntitledName; + mdEditor.FindNext(Needle); +end; - if FActiveDoc <> nil then - begin - Name := FActiveDoc.DisplayName; - if FActiveDoc.Modified then - Name := Name + ModifiedMarker; - end; +function TMarkdownPadVCLForm.EditorFindMatchCount(const Needle: string): Integer; +begin + Result := mdEditor.FindMatchCount(Needle); +end; - Caption := Format(TitleFormat, [WindowCaption, Name]); +procedure TMarkdownPadVCLForm.PreviewFindText(const Needle: string); +begin + mdPreview.FindText(Needle); end; -procedure TMarkdownPadVCLForm.ExecuteFind; +function TMarkdownPadVCLForm.ConfirmCloseDocument(const DocName: string): TPadCloseChoice; begin - const Needle = FFindEdit.Text; - if Needle = '' then - Exit; + const Answer = MessageDlg(Format(CloseDocumentPromptFormat, [DocName]), mtConfirmation, + [mbYes, mbNo, mbCancel], 0); - mdPreview.FindText(Needle); + if Answer = mrCancel then + Result := TPadCloseChoice.Cancel + else if Answer = mrYes then + Result := TPadCloseChoice.Save + else + Result := TPadCloseChoice.Discard; +end; + +function TMarkdownPadVCLForm.ConfirmReload: Boolean; +begin + Result := MessageDlg(ReloadPrompt, mtConfirmation, [mbYes, mbNo], 0) = mrYes; end; procedure TMarkdownPadVCLForm.BuildPalette; @@ -1565,43 +1455,37 @@ procedure TMarkdownPadVCLForm.BuildPalette; procedure TMarkdownPadVCLForm.BuildCommandRegistry; begin - FCommands := TPadCommandRegistry.Create; - - RegisterStaticCommands; + FController.InitCommandRegistry(BuildCommandActions); end; -procedure TMarkdownPadVCLForm.RegisterStaticCommands; -var - Actions: TPadCommandActions; -begin - Actions.NewDocument := procedure begin HandleNewClick(nil); end; - Actions.OpenDocument := procedure begin HandleOpenClick(nil); end; - Actions.Save := procedure begin HandleSaveClick(nil); end; - Actions.SaveAs := procedure begin HandleSaveAsClick(nil); end; - Actions.CloseDocument := procedure begin CloseActiveDocument; end; - Actions.NextTab := +function TMarkdownPadVCLForm.BuildCommandActions: TPadCommandActions; +begin + Result.NewDocument := procedure begin HandleNewClick(nil); end; + Result.OpenDocument := procedure begin HandleOpenClick(nil); end; + Result.Save := procedure begin HandleSaveClick(nil); end; + Result.SaveAs := procedure begin HandleSaveAsClick(nil); end; + Result.CloseDocument := procedure begin CloseActiveDocument; end; + Result.NextTab := procedure begin FWorkspace.ActivateNext; SwitchToDocument(FWorkspace.ActiveIndex); end; - Actions.ExportHtml := procedure begin DoExportHtml; end; - Actions.CopyHtml := procedure begin DoCopyHtml; end; - Actions.ViewEditorOnly := procedure begin SetViewMode(TPadViewMode.EditorOnly); end; - Actions.ViewSplit := procedure begin SetViewMode(TPadViewMode.Split); end; - Actions.ViewPreviewOnly := procedure begin SetViewMode(TPadViewMode.PreviewOnly); end; - Actions.ToggleZen := procedure begin ToggleZen; end; - Actions.ToggleTheme := procedure begin HandleThemeClick(nil); end; - Actions.ToggleToc := procedure begin HandleTocClick(nil); end; - Actions.ShowFind := procedure begin ShowFindBar; end; - Actions.FindInPreview := procedure begin ExecuteFind; end; - Actions.ExecuteFormat := + Result.ExportHtml := procedure begin DoExportHtml; end; + Result.CopyHtml := procedure begin DoCopyHtml; end; + Result.ViewEditorOnly := procedure begin SetViewMode(TPadViewMode.EditorOnly); end; + Result.ViewSplit := procedure begin SetViewMode(TPadViewMode.Split); end; + Result.ViewPreviewOnly := procedure begin SetViewMode(TPadViewMode.PreviewOnly); end; + Result.ToggleZen := procedure begin ToggleZen; end; + Result.ToggleTheme := procedure begin HandleThemeClick(nil); end; + Result.ToggleToc := procedure begin HandleTocClick(nil); end; + Result.ShowFind := procedure begin ShowFindBar; end; + Result.FindInPreview := procedure begin ExecuteFind; end; + Result.ExecuteFormat := procedure(const Command: TEditorCommand) begin ExecuteFormatCommand(Command); end; - - RegisterStaticPadCommands(FCommands, Actions); end; procedure TMarkdownPadVCLForm.SetViewMode(const Mode: TPadViewMode); @@ -1726,35 +1610,12 @@ procedure TMarkdownPadVCLForm.CloseFindBar; procedure TMarkdownPadVCLForm.FindInEditor; begin - const Needle = edtEditorFind.Text; - if Needle = '' then - begin - UpdateFindCount; - Exit; - end; - - mdEditor.FindNext(Needle); - - UpdateFindCount; + FController.FindInEditor; end; procedure TMarkdownPadVCLForm.UpdateFindCount; begin - const Needle = edtEditorFind.Text; - if Needle = '' then - begin - lblFindCount.Caption := EmptyFindCaption; - Exit; - end; - - const Total = mdEditor.FindMatchCount(Needle); - - if Total = 0 then - lblFindCount.Caption := NoMatchCaption - else if Total = 1 then - lblFindCount.Caption := SingleMatchCaption - else - lblFindCount.Caption := Format(MatchCountFormat, [Total]); + FController.UpdateFindCount; end; procedure TMarkdownPadVCLForm.HandleEditorFindChange(Sender: TObject); @@ -1764,18 +1625,7 @@ procedure TMarkdownPadVCLForm.HandleEditorFindChange(Sender: TObject); procedure TMarkdownPadVCLForm.ShowPalette; begin - FCommands.Clear; - RegisterStaticCommands; - - for var Path in FSession.RecentFiles do - begin - const RecentPath = Path; - FCommands.Register(RecentPath, CatRecent, RecentShortcut, - procedure - begin - OpenPath(RecentPath); - end); - end; + FController.RebuildPaletteCommands; FPaletteEdit.Text := ''; RefreshPaletteList; @@ -1797,7 +1647,7 @@ procedure TMarkdownPadVCLForm.ClosePalette; procedure TMarkdownPadVCLForm.RefreshPaletteList; begin - FPaletteMatches := FCommands.Match(FPaletteEdit.Text); + FController.RefreshMatches(FPaletteEdit.Text); FPaletteList.Items.BeginUpdate; try @@ -1873,15 +1723,12 @@ procedure TMarkdownPadVCLForm.PaletteMoveSelection(const Delta: Integer); procedure TMarkdownPadVCLForm.ExecuteSelectedCommand; begin const Index = FPaletteList.ItemIndex; - if (Index < 0) or (Index > High(FPaletteMatches)) then + if (Index < 0) or (Index >= FController.PaletteMatchCount) then Exit; - const Action = FPaletteMatches[Index].Command.Action; - ClosePalette; - if Assigned(Action) then - Action(); + FController.InvokePaletteCommand(Index); end; procedure TMarkdownPadVCLForm.ToggleZen; diff --git a/Examples/MarkdownPadVCL/MarkdownPadVCL.dpr b/Examples/MarkdownPadVCL/MarkdownPadVCL.dpr index bde20ba..5f2a532 100644 --- a/Examples/MarkdownPadVCL/MarkdownPadVCL.dpr +++ b/Examples/MarkdownPadVCL/MarkdownPadVCL.dpr @@ -16,7 +16,10 @@ uses MarkdownPadVCL.Main in 'MarkdownPadVCL.Main.pas' {MarkdownPadVCLForm}, MarkdownPad.Defines in '..\Shared\MarkdownPad.Defines.pas', MarkdownPad.Text in '..\Shared\MarkdownPad.Text.pas', - MarkdownPad.Outline in '..\Shared\MarkdownPad.Outline.pas'; + MarkdownPad.Outline in '..\Shared\MarkdownPad.Outline.pas', + MarkdownPadVCL.Defines in 'MarkdownPadVCL.Defines.pas', + MarkdownPad.Shell in '..\Shared\MarkdownPad.Shell.pas', + MarkdownPad.Controller in '..\Shared\MarkdownPad.Controller.pas'; {$R *.res} diff --git a/Examples/MarkdownPadVCL/MarkdownPadVCL.dproj b/Examples/MarkdownPadVCL/MarkdownPadVCL.dproj index 6fceefb..11b3711 100644 --- a/Examples/MarkdownPadVCL/MarkdownPadVCL.dproj +++ b/Examples/MarkdownPadVCL/MarkdownPadVCL.dproj @@ -98,7 +98,6 @@ - @@ -115,6 +114,9 @@ + + + Base diff --git a/Examples/Shared/MarkdownPad-Sharing-TODO.md b/Examples/Shared/MarkdownPad-Sharing-TODO.md index 302a07d..4139e2e 100644 --- a/Examples/Shared/MarkdownPad-Sharing-TODO.md +++ b/Examples/Shared/MarkdownPad-Sharing-TODO.md @@ -20,8 +20,32 @@ twee schillen om dezelfde app. De echt identieke stukken zijn al uitgetild naar scroll-naar-bronregel, swap-suppressie) en `TPadDocumentSwitch.Execute` (de buffer-swap: uitgaand document opslaan, doel activeren, inkomend document laden). Elke form implementeert `IPadEditorView` met triviale forwarders naar zijn eigen - `mdEditor`/`mdPreview` (VCL) resp. `FEditor`/`FPreview` (FMX); `SwitchToDocument` - is nu een paar regels die `TPadDocumentSwitch.Execute` aanroepen. + `mdEditor`/`mdPreview` (VCL) resp. `FEditor`/`FPreview` (FMX). +- `MarkdownPad.Shell.pas` — `IPadShell`: de seam waarmee de controller de + framework-specifieke schil aanstuurt (tab-strip, titel, dialogs, clipboard, + per-app tekst zoals `SampleMarkdown`). Elke form implementeert deze interface. +- `MarkdownPad.Controller.pas` — `TPadController`: framework-agnostische + orkestratie. Bezit het document-model (workspace/session/file-watcher) en de + applicatielogica die eerst verbatim in beide forms stond. Praat met de editor + via `IPadEditorView` en met de schil via `IPadShell`; raakt nooit een VCL/FMX- + control aan. **Gefaseerde extractie — nu verhuisd:** document-lifecycle (nieuw + document, openen, opslaan/opslaan-als, sluiten, document wisselen, sessie + herstellen/bewaren); de command-palette (registry-opbouw, recent-files, + fuzzy-match, commando uitvoeren; de form levert de actie-closures + rendert de + lijst); de **editing-loop** (editor-change, tick, status/titel, find, + TOC-sync, extern-gewijzigd-bestand herladen, close-query); en **HTML-export/ + copy** (de controller genereert het document/fragment via `TMarkdownHtmlExport` + / `TMarkdown.ToHtml`, de form doet de save-dialog en het klembord). De + `FTocFollowing`-reentrancy-guard blijft bewust FMX-only (VCL's lijst vuurt geen + change op een programmatische `ItemIndex`). + +### Bewust NIET naar de controller +- **View-mode + zen** (`ApplyViewMode`, `SetViewMode`, `EnterZen`/`ExitZen`, + `UpdateZenPadding`): dit is view-logica, geen app-logica. Elke regel is + framework-specifiek (pane-visibility/align, `TPanel`/`TLayout` pad-panelen, + `TColor` vs `TAlphaColor`, `ClientWidth`); de gedeelde kern is ~5 regels en + loopt al via de `EffectiveViewMode` / `ApplyRestoredViewMode`-seams. De + controller hoort niks te weten van pad-panelen en pane-alignment. ## Nog niet gedeeld (bewust uitgesteld) diff --git a/Examples/Shared/MarkdownPad.Controller.pas b/Examples/Shared/MarkdownPad.Controller.pas new file mode 100644 index 0000000..05bafea --- /dev/null +++ b/Examples/Shared/MarkdownPad.Controller.pas @@ -0,0 +1,583 @@ +unit MarkdownPad.Controller; + +// Framework-agnostic orchestration for the Markdown4D Pad demo: owns the +// document model (workspace/session/file-watcher) and the application logic +// shared by the VCL and FMX forms. It reaches the live editor/preview through +// IPadEditorView and the chrome/dialogs through IPadShell, so it never touches +// a VCL or FMX control directly. + +interface + +uses + Markdown4D.Toc, + MarkdownPad.Workspace.Interfaces, + MarkdownPad.Session, + MarkdownPad.FileWatcher, + MarkdownPad.EditorView, + MarkdownPad.Commands, + MarkdownPad.CommandSet, + MarkdownPad.Shell; + +type + TPadController = class + private + FEditor: IPadEditorView; + FShell: IPadShell; + FWorkspace: IPadWorkspace; + FSession: TPadSession; + FWatcher: TPadFileWatcher; + FActiveDoc: IPadDocument; + FMapDirty: Boolean; + FSwapping: Boolean; + FLastCaret: Integer; + FCommands: TPadCommandRegistry; + FActions: TPadCommandActions; + FPaletteMatches: TArray; + FTocEntries: TArray; + procedure UpdateTitle; + procedure UpdateStatusBar; + procedure DoFileChanged(const Document: IPadDocument); + public + constructor Create(const Editor: IPadEditorView; const Shell: IPadShell; + const SessionFileName: string); + destructor Destroy; override; + + // Document lifecycle. + procedure NewDocument; + procedure OpenViaDialog; + procedure OpenPath(const FileName: string); + procedure Save; + procedure SaveAs; + function SaveActiveDocument: Boolean; + procedure SaveToFile(const FileName: string); + procedure SwitchToDocument(const Index: Integer); + procedure CloseActiveDocument; + procedure CloseDocumentAt(const Index: Integer); + procedure RestoreSession; + procedure SaveSession; + + // Command palette: the form supplies the action closures (they target its + // own methods) and renders the list; the controller owns the registry, the + // recent-file assembly and the matcher. + procedure InitCommandRegistry(const Actions: TPadCommandActions); + procedure RebuildPaletteCommands; + procedure RefreshMatches(const Query: string); + function PaletteMatchCount: Integer; + procedure InvokePaletteCommand(const Index: Integer); + + // Editing loop: the form's event handlers and find bar delegate here; the + // controller owns the state and drives the chrome through IPadShell and the + // editor through IPadEditorView. + procedure NotifyEditorChanged; + procedure Tick; + procedure RebuildSyncAndToc; + procedure UpdateActiveTocEntry(const SourceLine: Integer); + function TocEntryCount: Integer; + function TocSourceLine(const Index: Integer): Integer; + procedure FindInEditor; + procedure UpdateFindCount; + procedure ExecuteFind; + procedure ExportHtml; + procedure CopyHtml; + function QueryClose: Boolean; + + // Model state the form reads back through its own accessors. + property Workspace: IPadWorkspace read FWorkspace; + property Session: TPadSession read FSession; + property Watcher: TPadFileWatcher read FWatcher; + property ActiveDocument: IPadDocument read FActiveDoc; + property MapDirty: Boolean read FMapDirty write FMapDirty; + property Swapping: Boolean read FSwapping write FSwapping; + property LastCaret: Integer read FLastCaret write FLastCaret; + property PaletteMatches: TArray read FPaletteMatches; + end; + +implementation + +uses + System.SysUtils, + System.IOUtils, + Markdown4D, + Markdown4D.Defines, + MarkdownPad.Defines, + MarkdownPad.Text, + MarkdownPad.Outline, + MarkdownPad.HtmlExport, + MarkdownPad.Workspace, + MarkdownPad.SessionSync; + +constructor TPadController.Create(const Editor: IPadEditorView; const Shell: IPadShell; + const SessionFileName: string); +begin + inherited Create; + + FEditor := Editor; + FShell := Shell; + + FWorkspace := TPadWorkspace.Create; + FSession := TPadSession.Create(TPadSession.ResolvePath(SessionFileName)); + FSession.Load; + FWatcher := TPadFileWatcher.Create(FWorkspace, DoFileChanged); +end; + +destructor TPadController.Destroy; +begin + FCommands.Free; + FWatcher.Free; + FSession.Free; + + inherited Destroy; +end; + +procedure TPadController.NewDocument; +begin + FWorkspace.NewDocument; + SwitchToDocument(FWorkspace.ActiveIndex); +end; + +procedure TPadController.OpenViaDialog; +begin + var FileName: string; + if FShell.PromptOpenFile(FileName) then + OpenPath(FileName); +end; + +procedure TPadController.OpenPath(const FileName: string); +begin + if not TFile.Exists(FileName) then + Exit; + + var Document: IPadDocument; + try + Document := FWorkspace.OpenFile(FileName); + except + on E: Exception do + begin + FShell.ShowOpenError(FileName, E.Message); + Exit; + end; + end; + + FWatcher.Reset(Document); + FSession.AddRecentFile(FileName); + + FShell.RebuildTabs; + SwitchToDocument(FWorkspace.ActiveIndex); +end; + +procedure TPadController.Save; +begin + if FActiveDoc = nil then + Exit; + + if FActiveDoc.IsUntitled then + SaveAs + else + SaveToFile(FActiveDoc.FileName); +end; + +procedure TPadController.SaveAs; +begin + if FActiveDoc = nil then + Exit; + + var Suggested := ''; + if not FActiveDoc.IsUntitled then + Suggested := FActiveDoc.FileName; + + var FileName: string; + if FShell.PromptSaveFile(Suggested, FileName) then + SaveToFile(FileName); +end; + +function TPadController.SaveActiveDocument: Boolean; +begin + if FActiveDoc = nil then + Exit(False); + + FActiveDoc.Text := FEditor.EditorText; + + if FActiveDoc.IsUntitled then + begin + var FileName: string; + if not FShell.PromptSaveFile('', FileName) then + Exit(False); + + SaveToFile(FileName); + end + else + SaveToFile(FActiveDoc.FileName); + + Result := True; +end; + +procedure TPadController.SaveToFile(const FileName: string); +begin + FActiveDoc.Text := FEditor.EditorText; + + TFile.WriteAllText(FileName, FActiveDoc.Text); + + FActiveDoc.FileName := FileName; + FActiveDoc.Modified := False; + + FWatcher.Reset(FActiveDoc); + FSession.AddRecentFile(FileName); + + FShell.RebuildTabs; + UpdateTitle; +end; + +procedure TPadController.SwitchToDocument(const Index: Integer); +begin + if FSwapping then + Exit; + + FActiveDoc := TPadDocumentSwitch.Execute(FWorkspace, FEditor, FActiveDoc, Index); + + if FActiveDoc = nil then + Exit; + + FLastCaret := -1; + FMapDirty := True; + + FShell.RebuildTabs; + UpdateTitle; +end; + +procedure TPadController.CloseActiveDocument; +begin + CloseDocumentAt(FWorkspace.ActiveIndex); +end; + +procedure TPadController.CloseDocumentAt(const Index: Integer); +begin + if (Index < 0) or (Index >= FWorkspace.Count) then + Exit; + + if Index <> FWorkspace.ActiveIndex then + SwitchToDocument(Index); + + if FActiveDoc = nil then + Exit; + + if FActiveDoc.Modified then + begin + const Choice = FShell.ConfirmClose; + + if Choice = TPadCloseChoice.Cancel then + Exit; + + if (Choice = TPadCloseChoice.Save) and not SaveActiveDocument then + Exit; + end; + + const ClosingIndex = FWorkspace.ActiveIndex; + FActiveDoc := nil; + FWorkspace.CloseDocument(ClosingIndex); + + if FWorkspace.Count = 0 then + begin + FShell.CloseApplication; + Exit; + end; + + FShell.RebuildTabs; + SwitchToDocument(FWorkspace.ActiveIndex); +end; + +procedure TPadController.RestoreSession; +begin + if not TPadSessionSync.RestoreOpenFiles(FWorkspace, FSession) then + begin + const Document = FWorkspace.NewDocument; + Document.Text := FShell.SampleMarkdown; + end; + + for var Index := 0 to FWorkspace.Count - 1 do + FWatcher.Reset(FWorkspace.Documents[Index]); + + FShell.RebuildTabs; + SwitchToDocument(FWorkspace.ActiveIndex); + + FShell.ApplyRestoredViewMode(FSession.ViewMode); +end; + +procedure TPadController.SaveSession; +begin + if FActiveDoc <> nil then + FActiveDoc.Text := FEditor.EditorText; + + var FilteredActive: Integer; + const Titled = TPadSessionSync.CollectOpenFiles(FWorkspace, FilteredActive); + + FSession.SetOpenFiles(Titled, FilteredActive); + FSession.DarkTheme := FShell.DarkThemeActive; + FSession.ViewMode := FShell.EffectiveViewMode; + + FSession.Save; +end; + +procedure TPadController.InitCommandRegistry(const Actions: TPadCommandActions); +begin + FCommands := TPadCommandRegistry.Create; + FActions := Actions; + + RegisterStaticPadCommands(FCommands, FActions); +end; + +procedure TPadController.RebuildPaletteCommands; +begin + FCommands.Clear; + RegisterStaticPadCommands(FCommands, FActions); + + for var Path in FSession.RecentFiles do + begin + const RecentPath = Path; + FCommands.Register(RecentPath, CatRecent, RecentShortcut, + procedure + begin + OpenPath(RecentPath); + end); + end; +end; + +procedure TPadController.RefreshMatches(const Query: string); +begin + FPaletteMatches := FCommands.Match(Query); +end; + +function TPadController.PaletteMatchCount: Integer; +begin + Result := Length(FPaletteMatches); +end; + +procedure TPadController.InvokePaletteCommand(const Index: Integer); +begin + if (Index < 0) or (Index > High(FPaletteMatches)) then + Exit; + + const Action = FPaletteMatches[Index].Command.Action; + if Assigned(Action) then + Action(); +end; + +procedure TPadController.UpdateTitle; +begin + var Name := UntitledName; + + if FActiveDoc <> nil then + begin + Name := FActiveDoc.DisplayName; + if FActiveDoc.Modified then + Name := Name + ModifiedMarker; + end; + + FShell.SetDocumentTitle(Name); +end; + +procedure TPadController.UpdateStatusBar; +begin + const Caret = FEditor.EditorCaret; + if Caret = FLastCaret then + Exit; + + FLastCaret := Caret; + + const Text = FEditor.EditorText; + + var Line, Column: Integer; + TPadText.ComputeLineColumn(Text, Caret, Line, Column); + + const PositionText = Format(StatusPositionFormat, [Line, Column]); + const WordsText = Format(StatusWordsFormat, [TPadText.CountWords(Text)]); + + FShell.SetStatus(PositionText, WordsText); +end; + +procedure TPadController.NotifyEditorChanged; +begin + if FSwapping then + Exit; + + const WasModified = (FActiveDoc <> nil) and FActiveDoc.Modified; + + if FActiveDoc <> nil then + FActiveDoc.Modified := True; + + FMapDirty := True; + + if not WasModified then + FShell.RebuildTabs; + + UpdateTitle; +end; + +procedure TPadController.Tick; +begin + if FMapDirty then + RebuildSyncAndToc; + + UpdateStatusBar; + + FWatcher.Poll; +end; + +procedure TPadController.RebuildSyncAndToc; +begin + FMapDirty := False; + + const Document = TMarkdown.Parse(FEditor.EditorText, TMarkdownDialect.Gfm); + const Outline = TPadOutlineBuilder.Build(TMarkdownToc.FromDocument(Document)); + + FTocEntries := Outline.Entries; + + FShell.SetTocCaptions(Outline.Captions); +end; + +procedure TPadController.UpdateActiveTocEntry(const SourceLine: Integer); +begin + const Best = TPadOutlineBuilder.ActiveIndex(FTocEntries, SourceLine); + FShell.SetActiveTocIndex(Best); +end; + +function TPadController.TocEntryCount: Integer; +begin + Result := Length(FTocEntries); +end; + +function TPadController.TocSourceLine(const Index: Integer): Integer; +begin + Result := FTocEntries[Index].SourceLine - 1; +end; + +procedure TPadController.FindInEditor; +begin + const Needle = FShell.EditorFindNeedle; + if Needle = '' then + begin + UpdateFindCount; + Exit; + end; + + FEditor.EditorFindNext(Needle); + + UpdateFindCount; +end; + +procedure TPadController.UpdateFindCount; +begin + const Needle = FShell.EditorFindNeedle; + if Needle = '' then + begin + FShell.SetFindCount(EmptyFindCaption); + Exit; + end; + + const Total = FEditor.EditorFindMatchCount(Needle); + + if Total = 0 then + FShell.SetFindCount(NoMatchCaption) + else if Total = 1 then + FShell.SetFindCount(SingleMatchCaption) + else + FShell.SetFindCount(Format(MatchCountFormat, [Total])); +end; + +procedure TPadController.ExecuteFind; +begin + const Needle = FShell.PreviewFindNeedle; + if Needle = '' then + Exit; + + FEditor.PreviewFindText(Needle); +end; + +procedure TPadController.ExportHtml; +begin + if FActiveDoc <> nil then + FActiveDoc.Text := FEditor.EditorText; + + var Title := UntitledName; + if FActiveDoc <> nil then + Title := FActiveDoc.DisplayName; + + const SuggestedName = TPath.ChangeExtension(Title, '.' + HtmlExtension); + + var FileName: string; + if not FShell.PromptExportHtml(SuggestedName, FileName) then + Exit; + + const Html = TMarkdownHtmlExport.BuildDocument(FEditor.EditorText, Title, FShell.DarkThemeActive); + TFile.WriteAllText(FileName, Html, TEncoding.UTF8); +end; + +procedure TPadController.CopyHtml; +begin + const Fragment = TMarkdown.ToHtml(FEditor.EditorText, TMarkdownDialect.Gfm); + FShell.CopyHtmlToClipboard(Fragment); +end; + +function TPadController.QueryClose: Boolean; +begin + for var Index := 0 to FWorkspace.Count - 1 do + begin + const Document = FWorkspace.Documents[Index]; + if not Document.Modified then + Continue; + + SwitchToDocument(Index); + + case FShell.ConfirmCloseDocument(Document.DisplayName) of + TPadCloseChoice.Cancel: + Exit(False); + TPadCloseChoice.Save: + if not SaveActiveDocument then + Exit(False); + end; + + Document.Modified := False; + end; + + Result := True; +end; + +procedure TPadController.DoFileChanged(const Document: IPadDocument); +begin + if Document.Modified then + begin + if not FShell.ConfirmReload then + Exit; + end; + + var NewText: string; + try + NewText := TFile.ReadAllText(Document.FileName); + except + Document.DiskTimestampUtc := 0; + Exit; + end; + + Document.Text := NewText; + Document.Modified := False; + + if Document = FActiveDoc then + begin + FSwapping := True; + try + FEditor.EditorText := NewText; + FEditor.FlushPreview; + + if FEditor.EditorCaret > System.Length(NewText) then + FEditor.EditorCaret := System.Length(NewText); + finally + FSwapping := False; + end; + + FMapDirty := True; + end; + + FShell.RebuildTabs; + UpdateTitle; +end; + +end. diff --git a/Examples/Shared/MarkdownPad.Defines.pas b/Examples/Shared/MarkdownPad.Defines.pas index 4db1c7e..acb361b 100644 --- a/Examples/Shared/MarkdownPad.Defines.pas +++ b/Examples/Shared/MarkdownPad.Defines.pas @@ -148,6 +148,9 @@ interface CatFormat = 'Format'; CatRecent = 'Recent'; + // Shortcut label for dynamically-added recent-file palette entries. + RecentShortcut = ''; + implementation end. diff --git a/Examples/Shared/MarkdownPad.EditorView.pas b/Examples/Shared/MarkdownPad.EditorView.pas index 36910f0..252d724 100644 --- a/Examples/Shared/MarkdownPad.EditorView.pas +++ b/Examples/Shared/MarkdownPad.EditorView.pas @@ -28,6 +28,11 @@ interface function SaveEditState: IMarkdownEditorState; procedure LoadEditState(const State: IMarkdownEditorState); procedure FlushPreview; + // Find: highlight/advance in the editor, count matches, and search the + // rendered preview. Needles come from the form's find edits (via IPadShell). + procedure EditorFindNext(const Needle: string); + function EditorFindMatchCount(const Needle: string): Integer; + procedure PreviewFindText(const Needle: string); // Brackets the incoming-document load so the form suppresses its own change // handling while the editor and preview are repopulated. procedure BeginSwap; diff --git a/Examples/Shared/MarkdownPad.Shell.pas b/Examples/Shared/MarkdownPad.Shell.pas new file mode 100644 index 0000000..a143eb2 --- /dev/null +++ b/Examples/Shared/MarkdownPad.Shell.pas @@ -0,0 +1,54 @@ +unit MarkdownPad.Shell; + +// Seam between the framework-agnostic TPadController and the concrete VCL/FMX +// form. The form implements IPadShell; the controller drives the chrome, tab +// strip, dialogs and per-app text through it without knowing the framework. + +interface + +uses + MarkdownPad.Session; + +type + // Outcome of the "save before closing?" prompt. + TPadCloseChoice = (Save, Discard, Cancel); + + IPadShell = interface + ['{2B7E4C10-9D3A-4F62-8E1C-1A6F0B5D7C34}'] + // Chrome the controller refreshes after model changes. + procedure RebuildTabs; + procedure SetDocumentTitle(const Name: string); + procedure SetStatus(const PositionText, WordsText: string); + procedure ApplyRestoredViewMode(const Mode: TPadViewMode); + + // Contents outline: the form owns the list control (and any framework- + // specific reentrancy guarding); the controller supplies the content. + procedure SetTocCaptions(const Captions: TArray); + procedure SetActiveTocIndex(const Index: Integer); + + // Find bar: needles come from the form's edits, the count label is a sink. + function EditorFindNeedle: string; + function PreviewFindNeedle: string; + procedure SetFindCount(const Value: string); + + // Per-app / per-framework values the controller reads. + function SampleMarkdown: string; + function DarkThemeActive: Boolean; + function EffectiveViewMode: TPadViewMode; + + // Dialogs and app lifetime, implemented with the native framework toolkit. + function PromptOpenFile(out FileName: string): Boolean; + function PromptSaveFile(const SuggestedName: string; out FileName: string): Boolean; + function PromptExportHtml(const SuggestedName: string; out FileName: string): Boolean; + function ConfirmClose: TPadCloseChoice; + function ConfirmCloseDocument(const DocName: string): TPadCloseChoice; + function ConfirmReload: Boolean; + procedure ShowOpenError(const FileName, ErrorMessage: string); + // Clipboard mechanism differs per framework (Win32 CF_HTML vs IFMXClipboardService). + procedure CopyHtmlToClipboard(const Fragment: string); + procedure CloseApplication; + end; + +implementation + +end.