Skip to content

Commit baaf28e

Browse files
committedApr 20, 2025
Merge branch 'feature/162-save-snippet-info-as-plain-text' into develop
Fixes #162
2 parents c28e5b4 + 7f2f9d9 commit baaf28e

File tree

4 files changed

+354
-80
lines changed

4 files changed

+354
-80
lines changed
 

‎Src/USaveInfoMgr.pas

+256-43
Original file line numberDiff line numberDiff line change
@@ -16,34 +16,94 @@ interface
1616

1717
uses
1818
// Project
19+
UBaseObjects,
1920
UEncodings,
21+
USaveSourceDlg,
22+
USourceFileInfo,
2023
UView;
2124

2225

2326
type
24-
/// <summary>Method-only record that saves information about a snippet to
25-
/// file in rich text format. The snippet is obtained from a view. Only
26-
/// snippet views are supported.</summary>
27-
TSaveInfoMgr = record
27+
/// <summary>Class that saves information about a snippet to file a user
28+
/// specified format. The snippet is obtained from a view. Only snippet views
29+
/// are supported.</summary>
30+
TSaveInfoMgr = class(TNoPublicConstructObject)
2831
strict private
29-
/// <summary>Attempts to name of the file to be written from the user.
30-
/// </summary>
31-
/// <param name="AFileName"><c>string</c> [out] Set to the name of the file
32-
/// entered by the user. Undefined if the user cancelled.</param>
33-
/// <returns><c>Boolean</c>. <c>True</c> if the user entered and accepted a
34-
/// file name of <c>False</c> if the user cancelled.</returns>
35-
class function TryGetFileNameFromUser(out AFileName: string): Boolean;
36-
static;
32+
var
33+
fView: IView;
34+
fSaveDlg: TSaveSourceDlg;
35+
fSourceFileInfo: TSourceFileInfo;
36+
3737
/// <summary>Returns encoded data containing a RTF representation of
3838
/// information about the snippet represented by the given view.</summary>
39-
class function GenerateRichText(View: IView): TEncodedData; static;
39+
class function GenerateRichText(View: IView; const AUseHiliting: Boolean):
40+
TEncodedData; static;
41+
42+
/// <summary>Returns encoded data containing a plain text representation of
43+
/// information about the snippet represented by the given view.</summary>
44+
function GeneratePlainText: TEncodedData;
45+
46+
/// <summary>Returns type of file selected in the associated save dialogue
47+
/// box.</summary>
48+
function SelectedFileType: TSourceFileType;
49+
50+
/// <summary>Handles the custom save dialogue's <c>OnPreview</c> event.
51+
/// Displays the required snippet information, appropriately formatted, in
52+
/// a preview dialogues box.</summary>
53+
/// <param name="Sender"><c>TObject</c> [in] Reference to the object that
54+
/// triggered the event.</param>
55+
procedure PreviewHandler(Sender: TObject);
56+
57+
/// <summary>Handles the custom save dialogue's <c>OnHiliteQuery</c> event.
58+
/// Determines whether syntax highlighting is supported for the source code
59+
/// section of the required snippet information..</summary>
60+
/// <param name="Sender"><c>TObject</c> [in] Reference to the object that
61+
/// triggered the event.</param>
62+
/// <param name="CanHilite"><c>Boolean</c> [in/out] Set to <c>False</c>
63+
/// when called. Should be set to <c>True</c> iff highlighting is
64+
/// supported.</param>
65+
procedure HighlightQueryHandler(Sender: TObject; var CanHilite: Boolean);
66+
67+
/// <summary>Handles the custom save dialogue's <c>OnEncodingQuery</c>
68+
/// event.</summary>
69+
/// <param name="Sender"><c>TObject</c> [in] Reference to the object that
70+
/// triggered the event.</param>
71+
/// <param name="Encodings"><c>TSourceFileEncodings</c> [in/out] Called
72+
/// with an empty array which the event handler must be set to contain the
73+
/// encodings supported by the currently selected file type.</param>
74+
procedure EncodingQueryHandler(Sender: TObject;
75+
var Encodings: TSourceFileEncodings);
76+
77+
/// <summary>Generates the required snippet information in the requested
78+
/// format.</summary>
79+
/// <param name="FileType"><c>TSourceFileType</c> [in] Type of file to be
80+
/// generated.</param>
81+
/// <returns><c>TEncodedData</c>. The formatted snippet information, syntax
82+
/// highlighted if required.</returns>
83+
function GenerateOutput(const FileType: TSourceFileType): TEncodedData;
84+
85+
/// <summary>Displays the save dialogue box and creates required type of
86+
/// snippet information file if the user OKs.</summary>
87+
procedure DoExecute;
88+
89+
strict protected
90+
91+
/// <summary>Internal constructor. Initialises managed save source dialogue
92+
/// box and records information about supported file types.</summary>
93+
constructor InternalCreate(AView: IView);
94+
4095
public
96+
97+
/// <summary>Object descructor. Tears down object.</summary>
98+
destructor Destroy; override;
99+
41100
/// <summary>Saves information about the snippet referenced by the a given
42101
/// view to file.</summary>
43102
/// <remarks>The view must be a snippet view.</remarks>
44103
class procedure Execute(View: IView); static;
45-
/// <summary>Checks if a given view can be saved to the clipboard. Returns
46-
/// True only if the view represents a snippet.</summary>
104+
105+
/// <summary>Checks if the given view can be saved to file. Returns
106+
/// <c>True</c> if the view represents a snippet.</summary>
47107
class function CanHandleView(View: IView): Boolean; static;
48108

49109
end;
@@ -55,13 +115,17 @@ implementation
55115
SysUtils,
56116
Dialogs,
57117
// Project
118+
FmPreviewDlg,
58119
Hiliter.UAttrs,
120+
Hiliter.UFileHiliter,
59121
Hiliter.UGlobals,
60122
UIOUtils,
61123
UOpenDialogHelper,
124+
UPreferences,
62125
URTFSnippetDoc,
63126
URTFUtils,
64-
USaveDialogEx;
127+
USourceGen,
128+
UTextSnippetDoc;
65129

66130
{ TSaveInfoMgr }
67131

@@ -70,27 +134,101 @@ class function TSaveInfoMgr.CanHandleView(View: IView): Boolean;
70134
Result := Supports(View, ISnippetView);
71135
end;
72136

137+
destructor TSaveInfoMgr.Destroy;
138+
begin
139+
fSourceFileInfo.Free;
140+
fSaveDlg.Free;
141+
inherited;
142+
end;
143+
144+
procedure TSaveInfoMgr.DoExecute;
145+
var
146+
Encoding: TEncoding; // encoding to use for output file
147+
FileContent: string; // output file content before encoding
148+
FileType: TSourceFileType; // type of source file
149+
begin
150+
// Set up dialog box
151+
fSaveDlg.Filter := fSourceFileInfo.FilterString;
152+
fSaveDlg.FilterIndex := FilterDescToIndex(
153+
fSaveDlg.Filter,
154+
fSourceFileInfo.FileTypeInfo[Preferences.SourceDefaultFileType].DisplayName,
155+
1
156+
);
157+
fSaveDlg.FileName := fSourceFileInfo.DefaultFileName;
158+
// Display dialog box and save file if user OKs
159+
if fSaveDlg.Execute then
160+
begin
161+
FileType := SelectedFileType;
162+
FileContent := GenerateOutput(FileType).ToString;
163+
Encoding := TEncodingHelper.GetEncoding(fSaveDlg.SelectedEncoding);
164+
try
165+
FileContent := GenerateOutput(FileType).ToString;
166+
TFileIO.WriteAllText(fSaveDlg.FileName, FileContent, Encoding, True);
167+
finally
168+
TEncodingHelper.FreeEncoding(Encoding);
169+
end;
170+
end;
171+
end;
172+
173+
procedure TSaveInfoMgr.EncodingQueryHandler(Sender: TObject;
174+
var Encodings: TSourceFileEncodings);
175+
begin
176+
Encodings := fSourceFileInfo.FileTypeInfo[SelectedFileType].Encodings;
177+
end;
178+
73179
class procedure TSaveInfoMgr.Execute(View: IView);
74180
var
75-
FileName: string;
76-
RTFMarkup: TRTFMarkup;
181+
Instance: TSaveInfoMgr;
77182
begin
78183
Assert(Assigned(View), 'TSaveInfoMgr.Execute: View is nil');
79184
Assert(CanHandleView(View), 'TSaveInfoMgr.Execute: View not supported');
80-
if not TryGetFileNameFromUser(FileName) then
81-
Exit;
82-
RTFMarkup := TRTFMarkup.Create(GenerateRichText(View));
83-
TFileIO.WriteAllBytes(FileName, RTFMarkup.ToBytes);
185+
186+
Instance := TSaveInfoMgr.InternalCreate(View);
187+
try
188+
Instance.DoExecute;
189+
finally
190+
Instance.Free;
191+
end;
84192
end;
85193

86-
class function TSaveInfoMgr.GenerateRichText(View: IView): TEncodedData;
194+
function TSaveInfoMgr.GenerateOutput(const FileType: TSourceFileType):
195+
TEncodedData;
196+
var
197+
UseHiliting: Boolean;
198+
begin
199+
UseHiliting := fSaveDlg.UseSyntaxHiliting and
200+
TFileHiliter.IsHilitingSupported(FileType);
201+
case FileType of
202+
sfRTF: Result := GenerateRichText(fView, UseHiliting);
203+
sfText: Result := GeneratePlainText;
204+
end;
205+
end;
206+
207+
function TSaveInfoMgr.GeneratePlainText: TEncodedData;
208+
var
209+
Doc: TTextSnippetDoc; // object that generates RTF document
210+
HiliteAttrs: IHiliteAttrs; // syntax highlighter formatting attributes
211+
begin
212+
Assert(Supports(fView, ISnippetView),
213+
ClassName + '.GeneratePlainText: View is not a snippet view');
214+
HiliteAttrs := THiliteAttrsFactory.CreateNulAttrs;
215+
Doc := TTextSnippetDoc.Create;
216+
try
217+
Result := Doc.Generate((fView as ISnippetView).Snippet);
218+
finally
219+
Doc.Free;
220+
end;
221+
end;
222+
223+
class function TSaveInfoMgr.GenerateRichText(View: IView;
224+
const AUseHiliting: Boolean): TEncodedData;
87225
var
88226
Doc: TRTFSnippetDoc; // object that generates RTF document
89227
HiliteAttrs: IHiliteAttrs; // syntax highlighter formatting attributes
90228
begin
91229
Assert(Supports(View, ISnippetView),
92230
'TSaveInfoMgr.GenerateRichText: View is not a snippet view');
93-
if (View as ISnippetView).Snippet.HiliteSource then
231+
if (View as ISnippetView).Snippet.HiliteSource and AUseHiliting then
94232
HiliteAttrs := THiliteAttrsFactory.CreateUserAttrs
95233
else
96234
HiliteAttrs := THiliteAttrsFactory.CreateNulAttrs;
@@ -105,28 +243,103 @@ class function TSaveInfoMgr.GenerateRichText(View: IView): TEncodedData;
105243
end;
106244
end;
107245

108-
class function TSaveInfoMgr.TryGetFileNameFromUser(
109-
out AFileName: string): Boolean;
110-
var
111-
Dlg: TSaveDialogEx;
246+
procedure TSaveInfoMgr.HighlightQueryHandler(Sender: TObject;
247+
var CanHilite: Boolean);
248+
begin
249+
CanHilite := TFileHiliter.IsHilitingSupported(SelectedFileType);
250+
end;
251+
252+
constructor TSaveInfoMgr.InternalCreate(AView: IView);
253+
const
254+
DlgHelpKeyword = 'SnippetInfoFileDlg';
112255
resourcestring
113-
sCaption = 'Save Snippet Information'; // dialogue box caption
114-
sFilter = 'Rich Text File (*.rtf)|*.rtf|' // file filter
115-
+ 'All files (*.*)|*.*';
256+
sDefFileName = 'SnippetInfo';
257+
sDlgCaption = 'Save Snippet Information';
258+
// descriptions of supported encodings
259+
sASCIIEncoding = 'ASCII';
260+
sANSIDefaultEncoding = 'ANSI (Default)';
261+
sUTF8Encoding = 'UTF-8';
262+
sUTF16LEEncoding = 'Unicode (Little Endian)';
263+
sUTF16BEEncoding = 'Unicode (Big Endian)';
264+
// descriptions of supported file filter strings
265+
sRTFDesc = 'Rich text file';
266+
sTextDesc = 'Plain text file';
116267
begin
117-
Dlg := TSaveDialogEx.Create(nil);
118-
try
119-
Dlg.Title := sCaption;
120-
Dlg.Options := [ofShowHelp, ofNoTestFileCreate, ofEnableSizing];
121-
Dlg.Filter := sFilter;
122-
Dlg.FilterIndex := 1;
123-
Dlg.HelpKeyword := 'SnippetInfoFileDlg';
124-
Result := Dlg.Execute;
125-
if Result then
126-
AFileName := FileOpenFileNameWithExt(Dlg)
127-
finally
128-
Dlg.Free;
268+
inherited InternalCreate;
269+
fView := AView;
270+
fSourceFileInfo := TSourceFileInfo.Create;
271+
// RTF and plain text files supported at present
272+
fSourceFileInfo.FileTypeInfo[sfRTF] := TSourceFileTypeInfo.Create(
273+
'.rtf',
274+
sRTFDesc,
275+
[
276+
TSourceFileEncoding.Create(etASCII, sASCIIEncoding)
277+
]
278+
);
279+
fSourceFileInfo.FileTypeInfo[sfText] := TSourceFileTypeInfo.Create(
280+
'.txt',
281+
sTextDesc,
282+
[
283+
TSourceFileEncoding.Create(etUTF8, sUTF8Encoding),
284+
TSourceFileEncoding.Create(etUTF16LE, sUTF16LEEncoding),
285+
TSourceFileEncoding.Create(etUTF16BE, sUTF16BEEncoding),
286+
TSourceFileEncoding.Create(etSysDefault, sANSIDefaultEncoding)
287+
]
288+
);
289+
fSourceFileInfo.DefaultFileName := sDefFileName;
290+
291+
fSaveDlg := TSaveSourceDlg.Create(nil);
292+
fSaveDlg.Title := sDlgCaption;
293+
fSaveDlg.HelpKeyword := DlgHelpKeyword;
294+
fSaveDlg.CommentStyle := TCommentStyle.csNone;
295+
fSaveDlg.EnableCommentStyles := False;
296+
fSaveDlg.TruncateComments := Preferences.TruncateSourceComments;
297+
fSaveDlg.UseSyntaxHiliting := Preferences.SourceSyntaxHilited;
298+
fSaveDlg.OnPreview := PreviewHandler;
299+
fSaveDlg.OnHiliteQuery := HighlightQueryHandler;
300+
fSaveDlg.OnEncodingQuery := EncodingQueryHandler;
301+
end;
302+
303+
procedure TSaveInfoMgr.PreviewHandler(Sender: TObject);
304+
resourcestring
305+
sDocTitle = '"%0:s" snippet';
306+
var
307+
// Type of snippet information document to preview: this is not always the
308+
// same as the selected file type, because preview dialogue box doesn't
309+
// support some types & we have to use an alternate.
310+
PreviewFileType: TSourceFileType;
311+
// Type of preview document supported by preview dialogue box
312+
PreviewDocType: TPreviewDocType;
313+
begin
314+
case SelectedFileType of
315+
sfRTF:
316+
begin
317+
PreviewDocType := dtRTF;
318+
PreviewFileType := sfRTF;
319+
end;
320+
sfText:
321+
begin
322+
PreviewDocType := dtPlainText;
323+
PreviewFileType := sfText;
324+
end;
325+
else
326+
raise Exception.Create(
327+
ClassName + '.PreviewHandler: unsupported file type'
328+
);
129329
end;
330+
// Display preview dialog box. We use save dialog as owner to ensure preview
331+
// dialog box is aligned over save dialog box
332+
TPreviewDlg.Execute(
333+
fSaveDlg,
334+
GenerateOutput(PreviewFileType),
335+
PreviewDocType,
336+
Format(sDocTitle, [fView.Description])
337+
);
338+
end;
339+
340+
function TSaveInfoMgr.SelectedFileType: TSourceFileType;
341+
begin
342+
Result := fSourceFileInfo.FileTypeFromFilterIdx(fSaveDlg.FilterIndex);
130343
end;
131344

132345
end.

0 commit comments

Comments
 (0)
Please sign in to comment.