unit Unit1;
{
The contents of this file are subject to the terms of the
Common Development and Distribution License, Version 1.1 only
(the "License"). You may not use this file except in compliance
with the License.
See the file LICENSE in this distribution for details.
A copy of the CDDL is also available via the Internet at
https://spdx.org/licenses/CDDL-1.1.html
When distributing Covered Code, include this CDDL HEADER in each
file and include the contents of the LICENSE file from this
distribution.
}
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ComCtrls, StdCtrls,
ExtCtrls, Buttons, ExtendedNotebook, SynEdit, fphttpclient, opensslsockets,
RegExpr, LCLIntf, LCLType, IniPropStorage, Process, Helpers, fileinfo,
{$IF DEFINED(WINDOWS)}
winpeimagereader,
{$ELSEIF DEFINED(MACOS)}
machoreader,
{$ELSEIF DEFINED(LINUX)}
elfreader,
{$ENDIF}
BuildOutputWindow;
type
{ TMainForm }
TMainForm = class(TForm)
btnSaveGroff: TButton;
btnLoadGroff: TButton;
btnBuild: TButton;
btnDownloadGroffWindows: TButton;
btnSaveSettings: TButton;
chkAutoSaveBuildSettings: TCheckBox;
chkPdfMark: TCheckBox;
chkRefer: TCheckBox;
chkChem: TCheckBox;
chkGrn: TCheckBox;
chkTbl: TCheckBox;
chkPic: TCheckBox;
chkEqn: TCheckBox;
cmbMacro: TComboBox;
edtGroffInstalledVersion: TEdit;
edtGroffstudioInstalledVersion: TEdit;
edtOnlineGroffVersionWindows: TEdit;
ExtendedNotebook1: TExtendedNotebook;
GroupBox1: TGroupBox;
GroupBox2: TGroupBox;
iniStorage: TIniPropStorage;
Label1: TLabel;
Label10: TLabel;
Label11: TLabel;
Label12: TLabel;
Label13: TLabel;
Label14: TLabel;
lblGithubRepo: TLabel;
lblFossilRepo: TLabel;
lblWebsite: TLabel;
Label2: TLabel;
Label3: TLabel;
Label4: TLabel;
Label5: TLabel;
Label6: TLabel;
Label7: TLabel;
Label8: TLabel;
lblAboutProductName: TLabel;
lblTroffCommandNotFound: TLabel;
Label9: TLabel;
MainStatusBar: TStatusBar;
mLicense: TMemo;
odOpenGroffFile: TOpenDialog;
rdPdf: TRadioButton;
rdPs: TRadioButton;
sdSaveGroffFile: TSaveDialog;
SynEdit1: TSynEdit;
tsEdit: TTabSheet;
tsAbout: TTabSheet;
tsGroff: TTabSheet;
tsSettings: TTabSheet;
procedure btnBuildClick(Sender: TObject);
procedure btnDownloadGroffWindowsClick(Sender: TObject);
procedure btnLoadGroffClick(Sender: TObject);
procedure btnSaveGroffClick(Sender: TObject);
procedure btnSaveSettingsClick(Sender: TObject);
procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
procedure FormCreate(Sender: TObject);
procedure lblFossilRepoClick(Sender: TObject);
procedure lblGithubRepoClick(Sender: TObject);
procedure lblWebsiteClick(Sender: TObject);
procedure rdPdfChange(Sender: TObject);
procedure rdPsChange(Sender: TObject);
procedure SynEdit1Change(Sender: TObject);
private
var currentGroffFilePath: String;
var currentGroffFileName: String;
var hasGroff: Boolean;
var unsavedChanges: Boolean;
var latestGroffWindowsUrl: String;
var storeBuildSettings: Boolean;
public
end;
var
MainForm: TMainForm;
BuildWindow: TBuildStatusWindow;
implementation
{$R *.lfm}
{ TMainForm }
procedure TMainForm.FormCreate(Sender: TObject);
var
GroffOutputVersion: String;
OnlineVersionsFile: String;
reGroffVersion, reGroffStudioVersion: TRegExpr;
FileVerInfo: TFileVersionInfo;
HasVersionUpdate: Integer;
GroffHelpers: TGroffHelpers;
ResStream: TResourceStream;
begin
// What's the current running groff version?
if RunCommand('troff', ['--version'], GroffOutputVersion) then
begin
edtGroffInstalledVersion.Text := GroffOutputVersion;
hasGroff := True;
end else begin
edtGroffInstalledVersion.Text := 'n/a';
hasGroff := False;
lblTroffCommandNotFound.Visible := True;
end;
// Default file name
currentGroffFilePath := '[unsaved file]';
// Embed the license
ResStream:= TResourceStream.Create(HInstance, 'LICENSE', RT_RCDATA);
try
mLicense.Lines.LoadFromStream(ResStream);
finally
ResStream.Free;
end;
// Restore the settings
iniStorage.Restore;
storeBuildSettings := iniStorage.ReadBoolean('AutoSaveBuildSettings', False);
chkAutoSaveBuildSettings.Checked := storeBuildSettings;
if storeBuildSettings then
begin
cmbMacro.Text := iniStorage.ReadString('BuildChosenMacro', '[ select ]');
chkChem.Checked := iniStorage.ReadBoolean('BuildUseChem', False);
chkEqn.Checked := iniStorage.ReadBoolean('BuildUseEqn', False);
chkGrn.Checked := iniStorage.ReadBoolean('BuildUseGrn', False);
chkPic.Checked := iniStorage.ReadBoolean('BuildUsePic', False);
chkRefer.Checked := iniStorage.ReadBoolean('BuildUseRefer', False);
chkTbl.Checked := iniStorage.ReadBoolean('BuildUseTbl', False);
chkPdfMark.Checked := iniStorage.ReadBoolean('BuildUsePdfMark', False);
rdPs.Checked := iniStorage.ReadBoolean('BuildToPostscript', False);
rdPdf.Checked := iniStorage.ReadBoolean('BuildToPDF', False);
end;
{$IFDEF WINDOWS}
// What's the latest available version?
FileVerInfo := TFileVersionInfo.Create(nil);
try
FileVerInfo.ReadFileInfo;
edtGroffStudioInstalledVersion.Text := FileVerInfo.VersionStrings.Values['FileVersion'];
lblAboutProductName.Caption := FileVerInfo.VersionStrings.Values['ProductName'] + ' ' + FileVerInfo.VersionStrings.Values['FileVersion'];
OnlineVersionsFile := TFPCustomHTTPClient.SimpleGet('https://groff.tuxproject.de/updates/versions.txt');
reGroffVersion := TRegExpr.Create('groff-win ([\d\.]+) (.*)$');
reGroffVersion.ModifierM := True;
if reGroffVersion.Exec(OnlineVersionsFile) then
begin
edtOnlineGroffVersionWindows.Text := reGroffVersion.Match[1];
latestGroffWindowsUrl := reGroffVersion.Match[2];
end else begin
edtOnlineGroffVersionWindows.Text := 'error';
btnDownloadGroffWindows.Enabled := False;
end;
// Note that there is no version check (nor precompiled downloads :-))
// for non-Windows versions right now. Contributors welcome.
reGroffStudioVersion := TRegExpr.Create('studio-win ([\d\.]+) (.*)$');
reGroffStudioVersion.ModifierM := True;
if reGroffStudioVersion.Exec(OnlineVersionsFile) then
begin
// Compare the two versions - ours and the online one:
GroffHelpers.VerStrCompare(reGroffStudioVersion.Match[1], FileVerInfo.VersionStrings.Values['FileVersion'], HasVersionUpdate);
if HasVersionUpdate > 0 then
MainStatusBar.Panels[2].Text := 'update ' + reGroffStudioVersion.Match[1] + ' available'
else
MainStatusBar.Panels[2].Text := IntToStr(HasVersionUpdate);
end else MainStatusBar.Panels[2].Text := '';
finally
FileVerInfo.Free;
end;
{$ELSE}
// Non-Windows platforms won't need all that.
edtOnlineGroffVersionWindows.Text := 'n/a';
btnDownloadGroffWindows.Enabled := False;
MainStatusBar.Panels[2].Text := '';
{$ENDIF}
// Loaded file display
MainStatusBar.Panels[0].Text := '';
// Groff build status
MainStatusBar.Panels[1].Text := '';
end;
procedure TMainForm.lblFossilRepoClick(Sender: TObject);
begin
OpenURL('https://code.rosaelefanten.org/groffstudio');
end;
procedure TMainForm.lblGithubRepoClick(Sender: TObject);
begin
OpenURL('https://github.com/dertuxmalwieder/groffstudio');
end;
procedure TMainForm.lblWebsiteClick(Sender: TObject);
begin
OpenURL('https://groff.tuxproject.de');
end;
procedure TMainForm.rdPdfChange(Sender: TObject);
begin
chkPdfMark.Enabled := True;
end;
procedure TMainForm.rdPsChange(Sender: TObject);
begin
chkPdfMark.Enabled := False;
end;
procedure TMainForm.SynEdit1Change(Sender: TObject);
begin
// Set the "Changed" mark:
MainStatusBar.Panels[0].Text := '* ' + currentGroffFileName;
unsavedChanges := True;
end;
procedure TMainForm.btnDownloadGroffWindowsClick(Sender: TObject);
begin
{$IFDEF WINDOWS}
OpenURL(latestGroffWindowsUrl);
{$ENDIF}
end;
procedure TMainForm.btnBuildClick(Sender: TObject);
var
buildSuccess: Boolean;
buildOpts: String;
outputFileName: String;
begin
// Reset status display:
MainStatusBar.Panels[1].Text := '';
BuildWindow := TBuildStatusWindow.Create(Application);
BuildWindow.ShowModal;
// Build the parameters:
buildOpts := 'groff';
// - Macro:
buildOpts := buildOpts + ' -' + cmbMacro.SelText;
// - Enforce UTF-8:
buildOpts := buildOpts + ' -Kutf8';
// - Preprocessors:
if chkChem.Checked then buildOpts := buildOpts + ' -chem';
if chkEqn.Checked then buildOpts := buildOpts + ' -eqn';
if chkGrn.Checked then buildOpts := buildOpts + ' -grn';
if chkPic.Checked then buildOpts := buildOpts + ' -pic';
if chkRefer.Checked then buildOpts := buildOpts + ' -refer';
if chkTbl.Checked then buildOpts := buildOpts + ' -tbl';
// - PDF-specifics:
if rdPdf.Checked then begin
buildOpts := buildOpts + ' -Tpdf';
if chkPdfMark.Checked then buildOpts := buildOpts + ' -mpdfmark';
outputFileName := currentGroffFilePath + '.pdf';
end
else outputFileName := currentGroffFilePath + '.ps';
// - Input file:
buildOpts := buildOpts + ' ' + currentGroffFilePath;
buildOpts := buildOpts + ' > ' + outputFileName;
// Build:
buildSuccess := BuildWindow.BuildDocument(buildOpts);
if buildSuccess then
MainStatusBar.Panels[1].Text := 'build successful'
else
MainStatusBar.Panels[1].Text := 'build problem';
FreeAndNil(BuildWindow);
end;
procedure TMainForm.btnLoadGroffClick(Sender: TObject);
var
Reply, BoxStyle: Integer;
begin
// If the current file has unsaved changes, ask first.
if unsavedChanges then with Application do begin
BoxStyle := MB_ICONQUESTION + MB_YESNO;
Reply := MessageBox('Do you want to save the document first?', 'UnsavedChanges', BoxStyle);
if Reply = IDYES then SynEdit1.Lines.SaveToFile(currentGroffFilePath);
unsavedChanges := False;
end;
if odOpenGroffFile.Execute then
begin
if FileExists(odOpenGroffFile.FileName) then
begin
currentGroffFilePath := odOpenGroffFile.FileName;
currentGroffFileName := ExtractFileName(odOpenGroffFile.FileName);
SynEdit1.Lines.LoadFromFile(odOpenGroffFile.FileName);
if hasGroff then btnBuild.Enabled := True;
// Display the current file:
MainStatusBar.Panels[0].Text := currentGroffFileName;
end;
end;
end;
procedure TMainForm.btnSaveGroffClick(Sender: TObject);
begin
if FileExists(currentGroffFilePath) then
// We don't need to open the Save As box every time.
SynEdit1.Lines.SaveToFile(currentGroffFilePath)
else if sdSaveGroffFile.Execute then
begin
currentGroffFilePath := sdSaveGroffFile.FileName;
currentGroffFileName := ExtractFileName(currentGroffFilePath);
SynEdit1.Lines.SaveToFile(sdSaveGroffFile.FileName);
if hasGroff then btnBuild.Enabled := True;
end;
// Remove the "Changed" mark:
MainStatusBar.Panels[0].Text := currentGroffFileName;
unsavedChanges := False;
end;
procedure TMainForm.btnSaveSettingsClick(Sender: TObject);
begin
// Store the build settings:
iniStorage.WriteString('BuildChosenMacro', cmbMacro.Text);
iniStorage.WriteBoolean('BuildUseChem', chkChem.Checked);
iniStorage.WriteBoolean('BuildUseEqn', chkEqn.Checked);
iniStorage.WriteBoolean('BuildUseGrn', chkGrn.Checked);
iniStorage.WriteBoolean('BuildUsePic', chkPic.Checked);
iniStorage.WriteBoolean('BuildUseRefer', chkRefer.Checked);
iniStorage.WriteBoolean('BuildUseTbl', chkTbl.Checked);
iniStorage.WriteBoolean('BuildUsePdfMark', chkPdfMark.Checked);
iniStorage.WriteBoolean('BuildToPostscript', rdPs.Checked);
iniStorage.WriteBoolean('BuildToPDF', rdPDF.Checked);
// Store the IDE settings:
iniStorage.WriteBoolean('AutoSaveBuildSettings', chkAutoSaveBuildSettings.Checked);
iniStorage.Save;
end;
procedure TMainForm.FormClose(Sender: TObject; var CloseAction: TCloseAction);
var
Reply, BoxStyle: Integer;
begin
// If the current file has unsaved changes, ask first.
if unsavedChanges then
with Application do begin
BoxStyle := MB_ICONQUESTION + MB_YESNO;
Reply := MessageBox('Do you want to save the document first?', 'UnsavedChanges', BoxStyle);
if Reply = IDYES then SynEdit1.Lines.SaveToFile(currentGroffFilePath);
end;
end;
end.