unit umain; {$DEFINE PO_BUILTINRES}// Use built-in resources for .po files // If compiling with Laz Version < 1.7 then use lazres to make a translate.lrs from the .po files // If compiling Laz V >=1.7 then add the .po files in Project/Options/resources // Without this DEFINE, include the /locale folder in the distribution { OnlinePackageManager Update JSON Editor Copyright (C)2016 usernames lainz, minesadorada, GetMem @ http://forum.lazarus.freepascal.org/index.php This source is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. A copy of the GNU General Public License is available on the World Wide Web at . You can also obtain it by writing to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. Version History =============== 0.0.0.0 Original code by lainz ..to 0.1.6.0 Refactored and updated (minesadorada) 0.1.7.0: Bugfix (lainz) 0.1.8.0: Config file change (minesadorada) 0.1.9.0: Error check for duplicate lpk entries (minesadorada) 0.1.10.0: Exception handling for Load + Save (minesadorada) 0.1.11.0: Cleaned up code formatting etc. (minesadorada) 0.1.12.0: Rename Global DownloadURL to DownloadZipURL (minesadorada) 0.1.13.0: Renamed TPackageData -> TUpdatePackageData (GetMem) Renamed TPackageFiles -> TUpdatePackageFiles (GetMem) Comment out Self.AutoAdjustLayout line in Form.Create (GetMem) Removed StrUtils from uses (minesadorada) Fixed memory leaks with CFG and slErrorList (minesadorada) Moved inline procedure CreateUniqueINI to separate function (minesadorada) Added Const C_DEBUGMESSAGES=TRUE/FALSE (minesadorada) 0.1.14.0: Various changes (GetMem) BugFix: FormCloseQuery(minesadorada) 0.1.15.0: BugFix: File/Save didn't add the '.json' suffix in Linux (minesadorada) Addition: After Loading, run validation tests(minesadorada) 0.1.16.0: Renamed ForceUpdate to ForceNotify (GetMem/minesadorada) 0.1.17.0: po files stored in executable's resources (minesadorada) Use Project/Options/Resources in Laz 1.7+ to add the .po files or.. Use (Gl)LazRes to make a file 'translate.lrs' in older Laz (minesadorada) This can be disabled by commenting out $DEFINE PO_BUILTINRES This system means you do not have to deploy the /locale folder - just the executable. Portability: On startup, it will make a unique cfg file in the GetAppConfig folder based on the executable's location on disk, so you can have copies of jsoneditor in each component's dev folder that uses its own config file, language and updates folder. 0.1.18.0: Bugfix: Linux path error when creating locale folder (minesadorada) 0.1.19.0: Added IntrnalVersion integer field to json (getmem/minesadorada) Added SpinEdit to control the above (minesadorada) In Laz 1.7 DPIAwareness configured 0.2.0.0: Refactored GUI(minesadorada) 0.2.1.0: Added scrollbox to contain package info (GetMem) 0.2.2.0: Hints and Validation updated (minesadorada) 0.2.3.0: ToDo - sort out resourcestrings } {$mode objfpc}{$H+} interface {DefaultTranslator not used} uses Classes, Forms, Controls, StdCtrls, Menus, ActnList, StdActns, Grids, Graphics, Buttons, fileutil, LazFileUtils, fileinfo, ugenericcollection, fpjsonrtti, Dialogs, LCLTranslator, PopupNotifier, SysUtils, inifiles, lclintf, lclVersion, LResources, Spin, {$IFDEF PO_BUILTINRES}LazUTF8Classes{$ENDIF}; const C_DEBUGMESSAGES = False; type { TUpdatePackageFiles } TUpdatePackageFiles = class(TCollectionItem) private FName: string; FVersion: string; FForceNotify: boolean; FInternalVersion: integer; published property Name: string read FName write FName; property Version: string read FVersion write FVersion; property ForceNotify: boolean read FForceNotify write FForceNotify; property InternalVersion: integer read FInternalVersion write FInternalVersion; end; TPackageFilesList = specialize TGenericCollection; { TUpdatePackageData } TUpdatePackageData = class(TPersistent) private FDownloadZipURL: string; FDisableInOPM: boolean; FName: string; public constructor Create; published property Name: string read FName write FName; property DownloadZipURL: string read FDownloadZipURL write FDownloadZipURL; property DisableInOPM: boolean read FDisableInOPM write FDisableInOPM; end; { TUpdatePackage } TUpdatePackage = class(TPersistent) private FUpdatePackageData: TUpdatePackageData; FUpdatePackageFiles: TPackageFilesList; public constructor Create; destructor Destroy; override; function LoadFromFile(AFileName: string): boolean; function SaveToFile(AFileName: string): boolean; published property UpdatePackageData: TUpdatePackageData read FUpdatePackageData write FUpdatePackageData; property UpdatePackageFiles: TPackageFilesList read FUpdatePackageFiles write FUpdatePackageFiles; end; { TfrmMain } TfrmMain = class(TForm) ActionList1: TActionList; chk_DisableInOPM: TCheckBox; cmd_Close: TBitBtn; cmd_save: TBitBtn; cmd_AddPackageFile: TButton; cmd_RemoveLastPackageFile: TButton; edt_UpdateZipName: TEdit; edt_DownloadZipURL: TEdit; FileOpen1: TFileOpen; FileSaveAs1: TFileSaveAs; lbl_PackageFiles: TLabel; lbl_UpdateZipName: TLabel; lbl_DownloadZipURL: TLabel; MainMenu1: TMainMenu; FileMenu: TMenuItem; LoadItem: TMenuItem; mnu_fileExit: TMenuItem; mnu_fileNew: TMenuItem; mnu_helpDisableWarnings: TMenuItem; mnu_lang_es: TMenuItem; mnu_lang_en: TMenuItem; mnu_lang: TMenuItem; mnu_helpAbout: TMenuItem; mnu_helpShowHints: TMenuItem; mnu_help: TMenuItem; mnu_fileSave: TMenuItem; popup_hint: TPopupNotifier; SaveAsItem: TMenuItem; sb_editName: TSpeedButton; sbPackageFiles: TScrollBox; spd_CheckURL: TSpeedButton; procedure chk_DisableInOPMMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); procedure cmd_AddPackageFileClick(Sender: TObject); procedure cmd_RemoveLastPackageFileClick(Sender: TObject); procedure FormCloseQuery(Sender: TObject; var CanClose: boolean); procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure FormShow(Sender: TObject); procedure LoadItemClick(Sender: TObject); procedure mnu_fileExitClick(Sender: TObject); procedure mnu_fileNewClick(Sender: TObject); procedure mnu_fileSaveClick(Sender: TObject); procedure mnu_helpAboutClick(Sender: TObject); procedure mnu_helpDisableWarningsClick(Sender: TObject); procedure mnu_helpShowHintsClick(Sender: TObject); procedure mnu_lang_enClick(Sender: TObject); procedure mnu_lang_esClick(Sender: TObject); procedure SaveAsItemClick(Sender: TObject); procedure sb_editNameClick(Sender: TObject); procedure spd_CheckURLClick(Sender: TObject); private { private declarations } JSONPackage: TUpdatePackage; bForceSaveAs, bShowPopupHints, bDisableWarnings, bDirty, bIsVirgin: boolean; sJSONFilePath: string; sUpdateDirectory, sZipDirectory: string; slErrorList: TStrings; CFG: TIniFile; INIFilePath: string; // Start of Package Information controls ArrayGrpBox: array of TGroupBox; ArrayLblPackageFileName: array of TLabel; ArrayEdtPackageFileName: array of TEdit; ArrayLblPackageVersion: array of TLabel; ArraySpinEditV1: array of TSpinEdit; ArraySpinEditV2: array of TSpinEdit; ArraySpinEditV3: array of TSpinEdit; ArraySpinEditV4: array of TSpinEdit; ArrayChkBoxForceNotify: array of TCheckBox; ArraySpinEditInternalVersion: array of TSpinEdit; ArrayLblPackageInternalVersion: array of TLabel; // End of Package Information controls iNumLpkFilesVisible: integer; procedure AddPackageFileToList; procedure RemovePackageFileFromList; procedure ResetPackageFileControlsToOne; procedure AddNewControlArray; procedure DestroyControlArrays; procedure RemoveLastControlArray; function ValidationFailed: boolean; procedure ProcessNotify(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: integer); procedure CtrlShowPopup(Sender: TObject); procedure CtrlHidePopup(Sender: TObject); procedure CtrlSetUpPopupHandlers; procedure CtrlMakeDirty(Sender: TObject); function FoundADuplicateLPK: boolean; function CreateUniqueINI(var aCount: integer): boolean; public { public declarations } end; var frmMain: TfrmMain; sPoPath_en, sPoPath_es: string; {$IFDEF PO_BUILTINRES} aLRes: TLResource; aSS: TStringListUTF8; S: TResourceStream; F: TFileStream; {$ENDIF} implementation {$R *.lfm} resourcestring rsOneOfTheReq1 = 'One of the required fields is missing or wrong.'; rsOneOfTheReqn = 'One or more of the required fields are missing or wrong.'; rsSavedOK = 'Saved OK'; rsSaveUnsucces = 'Save unsuccessful'; rsOverwrite = 'Overwrite'; rsTurnHintsOff = '(You can toggle these hints on/off in the Help menu)'; rsHelpAndInfor = 'Help and Information'; rsAbout = 'About'; rsUpdate = 'Update'; rsFileMayBeUns = 'JSON may be unsaved. Are you sure you want to quit?'; rsMypackagenam = 'mypackagename.zip'; rsMypackagelpk = 'mypackagename.lpk'; rsHttpWwwUpdat = 'http://www.updatesite.com/myupdate/mypackagename.zip'; rsFixThenTryAg = 'Fix, then try again.'; rsUpdateZipNam = '- Update zip name is too short or missing'; rsDownloadZipURLI = '- Download URL is too short or missing'; rsUpdateZipNam2 = '- Update zip name missing extension ".zip"'; rsDownloadZipURLI2 = '- Download URL is incomplete'; rsDownloadZipURLS = '- Download URL should start with "http"'; rsDownloadZipURLD = '- Download URL does not contain the zipfile name'; rsWouldYouLike = 'Would you like to copy %s to the %s folder?'; rsSWasSuccessf = '%s was successfully copied to the %s folder'; rsSorryCopyOpe = 'Sorry - copy operation was unsuccessful'; rsCompiledWith2 = 'Compiled with FPC V:%s and Lazarus V:%d.%d%s for the %s -' + ' %s platform%s%s'; rsTheLpkEntryD = 'The .lpk entry #%d is missing the .lpk extension'; rsTheLpkEntryD2 = 'The .lpk entry #%d is is absent'; rsThisOptionSh = 'This option should only be used for crucial updates or bug-fixed packages.'; rsLanguageChan = 'Language changed to "%s".'; rsSorryThisLan = 'Sorry, this language is unavailable at this time.'; rsYouMayNeedTo = '(You may need to restart the app to see the change)'; rsThereAreOneO = '- There are one or more .lpk entries with the same name.%s' + '- Every .lpk entry must have a unique name.'; rsUpdateJsonSF = 'Update file "%s" failed to load correctly.'; { TUpdatePackageData } constructor TUpdatePackageData.Create; begin FName := ''; // FForceNotify := False; FDownloadZipURL := ''; end; { TfrmMain } procedure TfrmMain.CtrlMakeDirty(Sender: TObject); begin bDirty := True; end; procedure TfrmMain.CtrlHidePopup(Sender: TObject); // Get rid of highlighting begin popup_hint.Hide; slErrorList.Clear; if Sender.ClassName <> 'TLabel' then TControl(Sender).Color := clWindow; end; procedure TfrmMain.CtrlShowPopup(Sender: TObject); // Use the control's Hint property to populate the popup text begin if not bShowPopupHints then exit; popup_hint.Text := ''; popup_hint.Title := ''; if (Sender.InheritsFrom(TControl) = False) then exit; popup_hint.Text := TControl(Sender).Hint; if (popup_hint.Text <> '') then begin popup_hint.Title := rsHelpAndInfor; popup_hint.Text := popup_hint.Text; if bIsVirgin then popup_hint.Text := popup_hint.Text + LineEnding + rsTurnHintsOff; popup_hint.showatpos(Mouse.CursorPos.X, Mouse.CursorPos.Y); end; end; procedure TfrmMain.CtrlSetUpPopupHandlers; // Use different handlers for some controls var iCount: integer; begin with frmMain do begin for iCount := 0 to Pred(ControlCount) do begin if (Controls[iCount].InheritsFrom(TControl) = False) then continue; { // Iterate through the children of TScrollBox if (Controls[iCount] is TGroupBox) then // Iterate through the children of GroupBox for jCount := 0 to Pred(TGroupBox(Controls[iCount]).ControlCount) do begin if TGroupBox(Controls[iCount]).Controls[jCount] is TSpinEdit then begin TSpinEdit(TGroupBox(Controls[iCount]).Controls[jCount]).OnMouseEnter := @CtrlShowPopup; end; end; } if (Controls[iCount] is TEdit) then begin TEdit(Controls[iCount]).OnMouseEnter := @CtrlShowPopup; TEdit(Controls[iCount]).OnMouseLeave := @CtrlHidePopup; TEdit(Controls[iCount]).OnClick := @CtrlHidePopup; TEdit(Controls[iCount]).OnEditingDone := @CtrlMakeDirty; end; if (Controls[iCount] is TStringGrid) then begin TStringGrid(Controls[iCount]).OnMouseEnter := @CtrlShowPopup; TStringGrid(Controls[iCount]).OnMouseLeave := @CtrlHidePopup; TStringGrid(Controls[iCount]).OnClick := @CtrlHidePopup; TStringGrid(Controls[iCount]).OnEditingDone := @CtrlMakeDirty; end; if (Controls[iCount] is TLabel) then begin TLabel(Controls[iCount]).OnMouseEnter := @CtrlShowPopup; TLabel(Controls[iCount]).OnMouseLeave := @CtrlHidePopup; TLabel(Controls[iCount]).OnClick := @CtrlHidePopup; end; if (Controls[iCount] is TButton) then begin TButton(Controls[iCount]).OnMouseEnter := @CtrlShowPopup; TButton(Controls[iCount]).OnMouseLeave := @CtrlHidePopup; end; if (Controls[iCount] is TSpeedButton) then begin TSpeedButton(Controls[iCount]).OnMouseEnter := @CtrlShowPopup; TSpeedButton(Controls[iCount]).OnMouseLeave := @CtrlHidePopup; end; if (Controls[iCount] is TBitBtn) then begin TBitBtn(Controls[iCount]).OnMouseEnter := @CtrlShowPopup; TBitBtn(Controls[iCount]).OnMouseLeave := @CtrlHidePopup; end; end; end; end; procedure TfrmMain.DestroyControlArrays; var i: integer; begin // This could be done with one loop for i := 0 to High(ArraySpinEditInternalVersion) do FreeAndNil(ArraySpinEditInternalVersion[i]); for i := 0 to High(ArrayLblPackageInternalVersion) do FreeAndNil(ArrayLblPackageInternalVersion[i]); for i := 0 to High(ArrayChkBoxForceNotify) do FreeAndNil(ArrayChkBoxForceNotify[i]); for i := 0 to High(ArraySpinEditV4) do FreeAndNil(ArraySpinEditV4[i]); for i := 0 to High(ArraySpinEditV3) do FreeAndNil(ArraySpinEditV3[i]); for i := 0 to High(ArraySpinEditV2) do FreeAndNil(ArraySpinEditV2[i]); for i := 0 to High(ArraySpinEditV1) do FreeAndNil(ArraySpinEditV1[i]); for i := 0 to High(ArrayLblPackageVersion) do FreeAndNil(ArrayLblPackageVersion[i]); for i := 0 to High(ArrayEdtPackageFileName) do FreeAndNil(ArrayEdtPackageFileName[i]); for i := 0 to High(ArrayLblPackageFileName) do FreeAndNil(ArrayLblPackageFileName[i]); for i := 0 to High(ArrayGrpBox) do FreeAndNil(ArrayGrpBox[i]); end; procedure TfrmMain.AddNewControlArray; { ArrayGrpBox:Array of TGroupBox; // Line 1 ArrayLblPackageFileName:Array of TLabel; ArrayEdtPackageFileName:Array of TEdit; ArrayLblPackageVersion:Array of TLabel; ArraySpinEditV1:Array of TSpinEdit; ArraySpinEditV2:Array of TSpinEdit; ArraySpinEditV3:Array of TSpinEdit; ArraySpinEditV4:Array of TSpinEdit; // Line 2 ArrayChkBoxForceNotify:Array of TCheckBox; ArrayLblPackageInternalVersion:Array of TLabel; ArraySpinEditInternalVersion:Array of TSpinEdit; SetBounds(Left,Top,Width,Height); } begin SetLength(ArrayGrpBox, Succ(iNumLpkFilesVisible)); ArrayGrpBox[iNumLpkFilesVisible] := TGroupBox.Create(Self); SetLength(ArrayLblPackageFileName, Succ(iNumLpkFilesVisible)); SetLength(ArrayEdtPackageFileName, Succ(iNumLpkFilesVisible)); SetLength(ArrayLblPackageVersion, Succ(iNumLpkFilesVisible)); SetLength(ArraySpinEditV1, Succ(iNumLpkFilesVisible)); SetLength(ArraySpinEditV2, Succ(iNumLpkFilesVisible)); SetLength(ArraySpinEditV3, Succ(iNumLpkFilesVisible)); SetLength(ArraySpinEditV4, Succ(iNumLpkFilesVisible)); SetLength(ArrayChkBoxForceNotify, Succ(iNumLpkFilesVisible)); SetLength(ArrayLblPackageInternalVersion, Succ(iNumLpkFilesVisible)); SetLength(ArraySpinEditInternalVersion, Succ(iNumLpkFilesVisible)); with ArrayGrpBox[iNumLpkFilesVisible] do begin Caption := Format('Package #%d Information', [Succ(iNumLpkFilesVisible)]); if (iNumLpkFilesVisible > 0) then SetBounds(8, ArrayGrpBox[Pred(iNumLpkFilesVisible)].Top + ArrayGrpBox[Pred(iNumLpkFilesVisible)].Height + 10, frmMain.Width - 16, 100) else SetBounds(8, cmd_AddPackageFile.Top + cmd_AddPackageFile.Height + 10, frmMain.Width - 16, 100); Visible := False; Tag := Pred(iNumLpkFilesVisible); // Label - Package name ArrayLblPackageFileName[iNumLpkFilesVisible] := TLabel.Create(nil); with ArrayLblPackageFileName[iNumLpkFilesVisible] do begin Caption := 'Filename: '; SetBounds(8, 10, 50, 23); Visible := True; Tag := Pred(iNumLpkFilesVisible); OnMouseEnter := @CtrlShowPopup; OnMouseLeave := @CtrlHidePopup; OnClick := @CtrlHidePopup; Hint := 'Just the package filename e.g. package.lpk'; Parent := ArrayGrpBox[iNumLpkFilesVisible]; end; // EditBox - Package name ArrayEdtPackageFileName[iNumLpkFilesVisible] := TEdit.Create(nil); with ArrayEdtPackageFileName[iNumLpkFilesVisible] do begin Text := rsMypackagelpk; SetBounds(64, 8, 256, 23); Visible := True; Tag := Pred(iNumLpkFilesVisible); OnMouseEnter := @CtrlShowPopup; OnMouseLeave := @CtrlHidePopup; OnClick := @CtrlHidePopup; OnEditingDone := @CtrlMakeDirty; Hint := 'Just the package filename e.g. package.lpk'; Parent := ArrayGrpBox[iNumLpkFilesVisible]; end; // Label - Package Version ArrayLblPackageVersion[iNumLpkFilesVisible] := TLabel.Create(nil); with ArrayLblPackageVersion[iNumLpkFilesVisible] do begin Caption := 'Version: '; SetBounds(330, 10, 50, 23); Visible := True; Tag := Pred(iNumLpkFilesVisible); OnMouseEnter := @CtrlShowPopup; OnMouseLeave := @CtrlHidePopup; OnClick := @CtrlHidePopup; Hint := 'Format is: n.n.n.n'; Parent := ArrayGrpBox[iNumLpkFilesVisible]; end; // SpinEdit V1 ArraySpinEditV1[iNumLpkFilesVisible] := TSpinEdit.Create(nil); with ArraySpinEditV1[iNumLpkFilesVisible] do begin Value := 0; SetBounds(380, 8, 40, 20); Visible := True; Tag := Pred(iNumLpkFilesVisible); OnMouseEnter := @CtrlShowPopup; OnMouseLeave := @CtrlHidePopup; OnClick := @CtrlHidePopup; OnChange := @CtrlMakeDirty; Hint := 'Format is: n.n.n.n'; Parent := ArrayGrpBox[iNumLpkFilesVisible]; end; // SpinEdit V2 ArraySpinEditV2[iNumLpkFilesVisible] := TSpinEdit.Create(nil); with ArraySpinEditV2[iNumLpkFilesVisible] do begin Value := 0; SetBounds(430, 8, 40, 20); Visible := True; Tag := Pred(iNumLpkFilesVisible); Hint := 'Format is: n.n.n.n'; OnMouseEnter := @CtrlShowPopup; OnMouseLeave := @CtrlHidePopup; OnClick := @CtrlHidePopup; OnChange := @CtrlMakeDirty; Hint := 'Format is: n.n.n.n'; Parent := ArrayGrpBox[iNumLpkFilesVisible]; end; // SpinEdit V3 ArraySpinEditV3[iNumLpkFilesVisible] := TSpinEdit.Create(nil); with ArraySpinEditV3[iNumLpkFilesVisible] do begin Value := 0; SetBounds(480, 8, 40, 20); Visible := True; Tag := Pred(iNumLpkFilesVisible); Hint := 'Format is: n.n.n.n'; OnMouseEnter := @CtrlShowPopup; OnMouseLeave := @CtrlHidePopup; OnClick := @CtrlHidePopup; OnChange := @CtrlMakeDirty; Hint := 'Format is: n.n.n.n'; Parent := ArrayGrpBox[iNumLpkFilesVisible]; end; // SpinEdit V4 ArraySpinEditV4[iNumLpkFilesVisible] := TSpinEdit.Create(nil); with ArraySpinEditV4[iNumLpkFilesVisible] do begin Value := 0; SetBounds(530, 8, 40, 20); Visible := True; Tag := Pred(iNumLpkFilesVisible); Hint := 'Format is: n.n.n.n'; OnMouseEnter := @CtrlShowPopup; OnMouseLeave := @CtrlHidePopup; OnClick := @CtrlHidePopup; OnChange := @CtrlMakeDirty; Hint := 'Format is: n.n.n.n'; Parent := ArrayGrpBox[iNumLpkFilesVisible]; end; // ChkBox Notify ArrayChkBoxForceNotify[iNumLpkFilesVisible] := TCheckBox.Create(nil); with ArrayChkBoxForceNotify[iNumLpkFilesVisible] do begin Checked := False; Caption := 'Notify Update'; SetBounds(8, 50, 40, 20); Visible := True; Tag := Pred(iNumLpkFilesVisible); OnMouseEnter := @CtrlShowPopup; OnMouseLeave := @CtrlHidePopup; OnClick := @CtrlHidePopup; OnMouseUp := @ProcessNotify; OnEditingDone := @CtrlMakeDirty; Hint := 'Check this if you don''t want to incrememt the package version'; Parent := ArrayGrpBox[iNumLpkFilesVisible]; end; // Label Internal version ArrayLblPackageInternalVersion[iNumLpkFilesVisible] := TLabel.Create(nil); with ArrayLblPackageInternalVersion[iNumLpkFilesVisible] do begin Caption := 'Internal Version: '; SetBounds(160, 50, 40, 23); Visible := True; Tag := Pred(iNumLpkFilesVisible); OnMouseEnter := @CtrlShowPopup; OnMouseLeave := @CtrlHidePopup; OnClick := @CtrlHidePopup; Hint := 'Use in combination with Notify Update'; Parent := ArrayGrpBox[iNumLpkFilesVisible]; end; // SpinEdit Internal Version ArraySpinEditInternalVersion[iNumLpkFilesVisible] := TSpinEdit.Create(nil); with ArraySpinEditInternalVersion[iNumLpkFilesVisible] do begin Value := 1; SetBounds(260, 48, 40, 20); Visible := True; Tag := Pred(iNumLpkFilesVisible); OnMouseEnter := @CtrlShowPopup; OnMouseLeave := @CtrlHidePopup; OnClick := @CtrlHidePopup; OnChange := @CtrlMakeDirty; Hint := 'Use in combination with Notify Update'; Parent := ArrayGrpBox[iNumLpkFilesVisible]; end; // This sets the subcontrols up correctly Parent := sbPackageFiles; end; end; procedure TfrmMain.RemoveLastControlArray; // Uses iLpkFilesCount var iLast: integer; begin iLast := High(ArrayGrpBox); // Makes the group control invisible ArrayGrpBox[iLast].Parent := nil; // Tidy up memory FreeAndNil(ArrayChkBoxForceNotify[iLast]); FreeAndNil(ArrayLblPackageInternalVersion[iLast]); FreeAndNil(ArraySpinEditInternalVersion[iLast]); FreeAndNil(ArraySpinEditV4[iLast]); FreeAndNil(ArraySpinEditV3[iLast]); FreeAndNil(ArraySpinEditV2[iLast]); FreeAndNil(ArraySpinEditV1[iLast]); FreeAndNil(ArrayLblPackageVersion[iLast]); FreeAndNil(ArrayEdtPackageFileName[iLast]); FreeAndNil(ArrayLblPackageFileName[iLast]); FreeAndNil(ArrayGrpBox[iLast]); // Tidy up control arrays SetLength(ArrayChkBoxForceNotify, iLast); SetLength(ArrayLblPackageInternalVersion, iLast); SetLength(ArraySpinEditInternalVersion, iLast); SetLength(ArraySpinEditV4, iLast); SetLength(ArraySpinEditV3, iLast); SetLength(ArraySpinEditV2, iLast); SetLength(ArraySpinEditV1, iLast); SetLength(ArrayLblPackageVersion, iLast); SetLength(ArrayEdtPackageFileName, iLast); SetLength(ArrayLblPackageFileName, iLast); SetLength(ArrayGrpBox, iLast); end; procedure TfrmMain.AddPackageFileToList; begin AddNewControlArray; // make another one // Makes it visible ArrayGrpBox[High(ArrayGrpBox)].Visible := True; ArrayGrpBox[High(ArrayGrpBox)].Align := alTop; Inc(iNumLpkFilesVisible); CtrlSetUpPopupHandlers; Refresh; end; procedure TfrmMain.ResetPackageFileControlsToOne; var iCount: integer; begin if (iNumLpkFilesVisible = 1) then exit; for iCount := iNumLpkFilesVisible downto 1 do begin RemovePackageFileFromList; end; end; procedure TfrmMain.RemovePackageFileFromList; begin // Always have one set of controls present if (iNumLpkFilesVisible > 1) then begin RemoveLastControlArray; Dec(iNumLpkFilesVisible); CtrlSetUpPopupHandlers; end; end; function TfrmMain.FoundADuplicateLPK: boolean; // Add lpk entries one-by-one to a temp stringlist looking for a duplicate var TempStringList: TStrings; iCount: integer; begin Result := False; TempStringList := TStringList.Create; try for iCount := 0 to High(ArrayEdtPackageFileName) do begin if TempStringlist.IndexOf(ArrayEdtPackageFileName[iCount].Text) = -1 then TempStringList.Add(ArrayEdtPackageFileName[iCount].Text) else Result := True; end; finally TempStringList.Free; end; end; procedure TfrmMain.ProcessNotify(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: integer); begin if not bShowPopupHints then exit; popup_hint.Text := rsThisOptionSh; popup_hint.Title := rsHelpAndInfor; popup_hint.Text := popup_hint.Text; popup_hint.showatpos(Mouse.CursorPos.X, Mouse.CursorPos.Y); end; procedure TfrmMain.cmd_AddPackageFileClick(Sender: TObject); begin AddPackageFileToList; end; procedure TfrmMain.chk_DisableInOPMMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin If chk_DisableInOPM.Checked=TRUE then If MessageDlg('This will disable your package in OnlinePackageManager!' + LineEnding + 'Are you SURE you want to do this?', mtWarning,[MBYES,MBNO],0,MBNO) = mrNo then chk_DisableInOPM.Checked:=FALSE; end; procedure TfrmMain.cmd_RemoveLastPackageFileClick(Sender: TObject); begin RemovePackageFileFromList; end; procedure TfrmMain.FormCloseQuery(Sender: TObject; var CanClose: boolean); begin CanClose := True; if ((bDirty = True) and (bDisableWarnings = False)) then begin if MessageDlg(rsFileMayBeUns, mtConfirmation, [mbYes, mbNo], 0, mbNo) = mrNo then CanClose := False; end else begin CFG.WriteBool('Options', 'Virgin', False); CFG.WriteBool('Options', 'DiableWarnings', bDisableWarnings); CFG.UpdateFile; end; end; function TfrmMain.CreateUniqueINI(var aCount: integer): boolean; // Recursively loop until correct INI found, or new one created begin Result := False; INIFilePath := GetAppConfigFile(False) + IntToStr(aCount); CFG := TIniFile.Create(INIFilePath); CFG.CacheUpdates := True; if CFG.ReadString('Options', 'AppPath', ProgramDirectory) <> ProgramDirectory then begin FreeAndNil(CFG); // Ditch the old one Inc(aCount); Result := True; CreateUniqueINI(aCount); // Make a new one end; end; procedure TfrmMain.FormCreate(Sender: TObject); var sLang: string; iIniCount: integer; begin // Enable AutoSize again to get correct Height edt_UpdateZipName.AutoSize := True; edt_DownloadZipURL.AutoSize := True; // Furniture Caption := Application.Title; Icon := Application.Icon; {$IFNDEF IGNOREPICTURE} popup_hint.Icon := TPicture(Application.Icon); {$ENDIF} edt_UpdateZipName.Text := rsMypackagenam; edt_DownloadZipURL.Text := rsHttpWwwUpdat; // Defaults slErrorList := TStringList.Create; bForceSaveAs := True; bShowPopupHints := True; iNumLpkFilesVisible := 0; // Encourage the user to maintain an updates folder sUpdateDirectory := ProgramDirectory + 'updates'; if not FileExistsUTF8(sUpdateDirectory) then if not ForceDirectoriesUTF8(ProgramDirectory + 'updates') then sUpdateDirectory := ProgramDirectory; // Enable options persistence // If program location is different, create a new CFG file // Because each component's location might be different iIniCount := 0; if CreateUniqueINI(iIniCount) then CFG.WriteString('Options', 'AppPath', ProgramDirectory); CFG.UpdateFile; if C_DEBUGMESSAGES = True then // Dev only ShowMessageFmt('Inifile=%s, Count=%d', [INIFilePath, iIniCount]); // Pop-up hints (show on first run, then not again unless the user chooses) bIsVirgin := CFG.ReadBool('Options', 'Virgin', True); bShowPopupHints := bIsVirgin; mnu_helpShowHints.Checked := bShowPopupHints; // Override here if the user has re-enabled them bShowPopupHints := CFG.ReadBool('Options', 'ShowPopupHints', bShowPopupHints); mnu_helpShowHints.Checked := bShowPopupHints; bDisableWarnings := CFG.ReadBool('Options', 'DiableWarnings', False); mnu_helpDisableWarnings.Checked := bDisableWarnings; CtrlSetUpPopupHandlers; // Set the Hint property of various controls to show a Popup // Language translation sLang := CFG.ReadString('Options', 'Language', 'en'); // First default is English SetDefaultLang(sLang, 'locale', True); // Add more translations here if sLang = 'en' then mnu_lang_en.Checked := True; if sLang = 'es' then mnu_lang_es.Checked := True; bDirty := False; // No effect :( end; procedure TfrmMain.FormDestroy(Sender: TObject); begin // No memory leak! DestroyControlArrays; CFG.Free; slErrorList.Free; end; procedure TfrmMain.FormShow(Sender: TObject); begin bDirty := False; AddPackageFileToList; end; procedure TfrmMain.LoadItemClick(Sender: TObject); var i: integer; Quad: TVersionQuad; begin FileOpen1.Dialog.InitialDir := CFG.ReadString('Options', 'LastLoadedJSONPath', sUpdateDirectory); FileOpen1.Dialog.Filter := 'JSON|*.json'; if FileOpen1.Dialog.Execute then begin ResetPackageFileControlsToOne; // So iNumLpkFilesVisible=1 sJSONFilePath := FileOpen1.Dialog.Filename; CFG.WriteString('Options', 'LastLoadedJSONPath', ExtractFileDir(sJSONFilePath)); JSONPackage := TUpdatePackage.Create; try if JSONPackage.LoadFromFile(FileOpen1.Dialog.FileName) then begin edt_UpdateZipName.Text := JSONPackage.UpdatePackageData.Name; edt_DownloadZipURL.Text := JSONPackage.UpdatePackageData.DownloadZipURL; for i := 0 to JSONPackage.UpdatePackageFiles.Count - 1 do begin if (i > 0) then AddPackageFileToList; ArrayEdtPackageFileName[i].Text := JSONPackage.UpdatePackageFiles.Items[i].Name; if fileinfo.TryStrToVersionQuad( JSONPackage.UpdatePackageFiles.Items[i].Version, Quad) then begin ArraySpinEditV1[i].Value := Quad[1]; ArraySpinEditV2[i].Value := Quad[2]; ArraySpinEditV3[i].Value := Quad[3]; ArraySpinEditV4[i].Value := Quad[4]; end; ArrayChkBoxForceNotify[i].Checked := JSONPackage.UpdatePackageFiles.Items[i].ForceNotify; ArraySpinEditInternalVersion[i].Value := JSONPackage.UpdatePackageFiles.Items[i].InternalVersion; end; if ValidationFailed then begin if (slErrorList.Count > 1) then ShowMessage(Format(rsUpdateJsonSF, [ExtractFileName(sJSONFilePath)]) + LineEnding + LineEnding + rsOneOfTheReqn + LineEnding + slErrorList.Text + LineEnding + rsFixThenTryAg) else ShowMessage(Format(rsUpdateJsonSF, [ExtractFileName(sJSONFilePath)]) + LineEnding + LineEnding + rsOneOfTheReq1 + LineEnding + slErrorList.Text + LineEnding + rsFixThenTryAg); Exit; end; end else ShowMessageFmt('There was a problem loading "%s" - is it corrupted or in the wrong format?', [ExtractFilename(FileOpen1.Dialog.FileName)]); finally JSONPackage.Free; end; end; end; procedure TfrmMain.mnu_fileExitClick(Sender: TObject); begin Close; end; procedure TfrmMain.mnu_fileNewClick(Sender: TObject); begin edt_UpdateZipName.Text := rsMypackagenam; edt_DownloadZipURL.Text := rsHttpWwwUpdat; sJSONFilePath := ''; sZipDirectory := ''; ResetPackageFileControlsToOne; ArrayEdtPackageFileName[0].Text := rsMypackagelpk; ArraySpinEditV1[0].Value := 0; ArraySpinEditV2[0].Value := 0; ArraySpinEditV3[0].Value := 0; ArraySpinEditV4[0].Value := 0; ArrayChkBoxForceNotify[0].Checked := False; ArraySpinEditInternalVersion[0].Value := 0; end; procedure TfrmMain.mnu_fileSaveClick(Sender: TObject); begin bForceSaveAs := False; SaveAsItem.Click; bForceSaveAs := True; end; procedure TfrmMain.mnu_helpAboutClick(Sender: TObject); var s: string; Quad: TVersionQuad; VInfo: TFileVersionInfo; EqualsPos: integer; begin s := Application.Title + LineEnding; if GetProgramVersion(Quad) then s += 'Version: ' + VersionQuadToStr(Quad) + LineEnding; Vinfo := TFileVersionInfo.Create(Application); try Vinfo.Filter.Add('LegalCopyright'); // Set In Project/Options/Version Info Vinfo.Filter.Add('FileDescription'); // Set In Project/Options/Version Info Vinfo.ReadFileInfo; if VInfo.VersionStrings.Count > 0 then begin EqualsPos := Pos('=', VInfo.VersionStrings[1]); // Copyright if (EqualsPos > 0) then s += RightStr(VInfo.VersionStrings[1], Length(VInfo.VersionStrings[1]) - EqualsPos) + LineEnding; end; // Comment line below out for JEDI source prettification s+=Format(rsCompiledWith2, [{$I %FPCVERSION%},lcl_major,lcl_minor,LineEnding,{$I %FPCTARGETCPU%},{$I %FPCTARGETOS%},LineEnding,LineEnding]); if VInfo.VersionStrings.Count > 1 then begin EqualsPos := Pos('=', VInfo.VersionStrings[0]); // File Deswcription if (EqualsPos > 0) then s += RightStr(VInfo.VersionStrings[0], Length(VInfo.VersionStrings[0]) - EqualsPos) + LineEnding; end; finally Vinfo.Free; end; MessageDlg(rsAbout + ' ' + Application.Title, s, mtInformation, [mbOK], 0); end; procedure TfrmMain.mnu_helpDisableWarningsClick(Sender: TObject); begin bDisableWarnings := not bDisableWarnings; mnu_helpDisableWarnings.Checked := bDisableWarnings; CFG.WriteBool('Options', 'DiableWarnings', bDisableWarnings); end; procedure TfrmMain.mnu_helpShowHintsClick(Sender: TObject); begin bShowPopupHints := mnu_helpShowHints.Checked; CFG.WriteBool('Options', 'ShowPopupHints', bShowPopupHints); end; procedure TfrmMain.mnu_lang_enClick(Sender: TObject); begin SetDefaultLang('en', 'locale', True); if Length(GetDefaultLang) > 0 then begin ShowMessageFmt(rsLanguageChan, [GetDefaultLang]); CFG.WriteString('Options', 'Language', GetDefaultLang); mnu_lang_en.Checked := True; end else begin mnu_lang_en.Checked := False; // SetDefaultLang(''); // Back to default? ShowMessage(rsSorryThisLan + LineEnding + rsYouMayNeedTo); end; end; procedure TfrmMain.mnu_lang_esClick(Sender: TObject); begin SetDefaultLang('es', 'locale', True); if Length(GetDefaultLang) > 0 then begin ShowMessageFmt(rsLanguageChan, [GetDefaultLang]); CFG.WriteString('Options', 'Language', GetDefaultLang); mnu_lang_es.Checked := True; end else begin mnu_lang_es.Checked := False; SetDefaultLang(''); // Back to DefaultTranslator ShowMessage(rsSorryThisLan); end; end; function TfrmMain.ValidationFailed: boolean; // Add checks as needed here var iCount: integer; begin Result := False; // Check Zipname and URL http:// length if (Length(edt_UpdateZipName.Text) < 5) then begin edt_UpdateZipName.Color := clYellow; slErrorList.Add(rsUpdateZipNam); Result := True; end; // URL implausable? if (Length(edt_DownloadZipURL.Text) < 10) then begin slErrorList.Add(rsDownloadZipURLI); edt_DownloadZipURL.Color := clYellow; Result := True; end; // Remembered to type 'zip'? if (Length(edt_UpdateZipName.Text) > 4) then if (RightStr(LowerCase(edt_UpdateZipName.Text), 4) <> '.zip') then begin slErrorList.Add(rsUpdateZipNam2); edt_UpdateZipName.Color := clYellow; Result := True; end; // A full URL? if ((Length(edt_DownloadZipURL.Text) > 0) and (RightStr(edt_DownloadZipURL.Text, 1) = '/')) then begin slErrorList.Add(rsDownloadZipURLI2); edt_DownloadZipURL.Color := clYellow; Result := True; end; // URL starts with 'http' ? if ((Length(edt_DownloadZipURL.Text) > 4) and (LeftStr(LowerCase(edt_DownloadZipURL.Text), 4) <> 'http')) then begin slErrorList.Add(rsDownloadZipURLS); edt_DownloadZipURL.Color := clYellow; Result := True; end; // URL contains zipfile name? if (Pos(Lowercase(edt_UpdateZipName.Text), Lowercase(edt_DownloadZipURL.Text)) = 0) then begin slErrorList.Add(rsDownloadZipURLD); edt_DownloadZipURL.Color := clYellow; Result := True; end; // Check package files entries for iCount := 0 to High(ArrayGrpBox) do begin // Is package name empty? if Length(ArrayEdtPackageFileName[iCount].Text) = 0 then begin slErrorList.Add(Format(rsTheLpkEntryD2, [Succ(iCount)])); ArrayEdtPackageFileName[iCount].Color := clYellow; Result := True; end; // Does it end with 'lpk' if (RightStr(LowerCase(ArrayEdtPackageFileName[iCount].Text), 4) <> '.lpk') then begin slErrorList.Add(Format(rsTheLpkEntryD, [Succ(iCount)])); ArrayEdtPackageFileName[iCount].Color := clYellow; Result := True; end; // Is the version number zero if (ArraySpinEditV1[iCount].Value = 0) and (ArraySpinEditV2[iCount].Value = 0) and (ArraySpinEditV3[iCount].Value = 0) and (ArraySpinEditV4[iCount].Value = 0) then begin slErrorList.Add(Format('Version for package %d is zero', [Succ(iCount)])); ArraySpinEditV1[iCount].Color := clYellow; ArraySpinEditV2[iCount].Color := clYellow; ArraySpinEditV3[iCount].Color := clYellow; ArraySpinEditV4[iCount].Color := clYellow; Result := True; end; // Check Forcenotify version isn't zero If ArrayChkBoxForceNotify[iCount].Checked = TRUE then If ArraySpinEditInternalVersion[iCount].Value = 0 then begin ArraySpinEditInternalVersion[iCount].Color := clYellow; slErrorList.Add(Format('Internal version number should not be Zero%s', [LineEnding])); Result := True; end; // Check for duplicate .lpk entries if FoundADuplicateLPK then begin ArrayEdtPackageFileName[iCount].Color := clYellow; slErrorList.Add(Format(rsThereAreOneO, [LineEnding])); Result := True; end; end; end; procedure TfrmMain.SaveAsItemClick(Sender: TObject); var i: integer; Quad: TVersionQuad; begin if ValidationFailed then begin if (slErrorList.Count > 1) then ShowMessage(rsOneOfTheReqn + LineEnding + slErrorList.Text + LineEnding + rsFixThenTryAg) else ShowMessage(rsOneOfTheReq1 + LineEnding + slErrorList.Text + LineEnding + rsFixThenTryAg); Exit; end; if bForceSaveAs or (sJSONFilePath = '') then begin FileSaveAs1.Dialog.InitialDir := sUpdateDirectory; FileSaveAs1.Dialog.FileName := 'update_' + ExtractFilenameOnly(edt_UpdateZipName.Text) + '.json'; if FileSaveAs1.Dialog.Execute then sJSONFilePath := FileSaveAs1.Dialog.FileName else Exit; end; JSONPackage := TUpdatePackage.Create; try JSONPackage.UpdatePackageData.Name := edt_UpdateZipName.Text; JSONPackage.UpdatePackageData.DownloadZipURL := edt_DownloadZipURL.Text; for i := 0 to High(ArrayGrpBox) do begin with JSONPackage.UpdatePackageFiles.Add do begin Name := ArrayEdtPackageFileName[i].Text; Quad[1] := ArraySpinEditV1[i].Value; Quad[2] := ArraySpinEditV2[i].Value; Quad[3] := ArraySpinEditV3[i].Value; Quad[4] := ArraySpinEditV4[i].Value; Version := VersionQuadToStr(Quad); ForceNotify := ArrayChkBoxForceNotify[i].Checked; InternalVersion := ArraySpinEditInternalVersion[i].Value; end; end; if FileExistsUTF8(sJSONFilePath) and (bDisableWarnings = False) then begin if MessageDlg(rsOverwrite + ' ' + sJSONFilePath + '?', mtConfirmation, [mbYes, mbNo], 0, mbYes) = mrYes then if JSONPackage.SaveToFile(sJSONFilePath) then ShowMessage(sJSONFilePath + ' ' + rsSavedOK); end else if JSONPackage.SaveToFile(sJSONFilePath) then ShowMessage(sJSONFilePath + rsSavedOK) else ShowMessage(rsSaveUnsucces); bDirty := False; finally JSONPackage.Free; end; end; procedure TfrmMain.sb_editNameClick(Sender: TObject); var s: string; begin FileOpen1.Dialog.InitialDir := CFG.ReadString('Options', 'LastLoadedZipPath', sZipDirectory); FileOpen1.Dialog.Filter := rsUpdate + ' ZIP|*.zip'; if FileOpen1.Dialog.Execute then begin // Offer to copy to /updates? sZipDirectory := ExtractFileDir(FileOpen1.Dialog.Filename); CFG.WriteString('Options', 'LastLoadedZipPath', sZipDirectory); s := ExtractFileName(FileOpen1.Dialog.Filename); edt_UpdateZipName.Text := s; if MessageDlg(Format(rsWouldYouLike, [s, sUpdateDirectory]), mtConfirmation, [mbYes, mbNo], 0, mbYes) = mrYes then begin if CopyFile(FileOpen1.Dialog.Filename, sUpdateDirectory + DirectorySeparator + s) then ShowMessageFmt(rsSWasSuccessf, [s, sUpdateDirectory]) else ShowMessage(rsSorryCopyOpe); end; end; end; procedure TfrmMain.spd_CheckURLClick(Sender: TObject); // Show a popup notification because it takes time to open a browser window var bTemp: boolean; begin if OpenURL(edt_DownloadZipURL.Text) then begin bTemp := bShowPopupHints; bShowPopupHints := True; CtrlShowPopup(spd_CheckURL); bShowPopupHints := bTemp; end; end; { TPackage } constructor TUpdatePackage.Create; begin FUpdatePackageData := TUpdatePackageData.Create; FUpdatePackageFiles := TPackageFilesList.Create; end; destructor TUpdatePackage.Destroy; var c: TCollectionItem; begin FUpdatePackageData.Free; for c in FUpdatePackageFiles do c.Free; FUpdatePackageFiles.Free; inherited Destroy; end; function TUpdatePackage.LoadFromFile(AFileName: string): boolean; var DeStreamer: TJSONDeStreamer; s: TStringList; begin Result := True; s := TStringList.Create; try s.LoadFromFile(AFileName); DeStreamer := TJSONDeStreamer.Create(nil); try DeStreamer.JSONToObject(s.Text, Self); except // Eat the exception On E: Exception do Result := False; end; finally DeStreamer.Free; s.Free; end; end; function TUpdatePackage.SaveToFile(AFileName: string): boolean; var Streamer: TJSONStreamer; s: TStringList; begin Result := True; s := TStringList.Create; try Streamer := TJSONStreamer.Create(nil); Streamer.Options := Streamer.Options + [jsoUseFormatString]; s.AddText(Streamer.ObjectToJSONString(Self)); try s.SaveToFile(AFileName); except // Eat the exception On E: Exception do Result := False; end; finally Streamer.Free; s.Free; end; end; // Use embedded .po resources if not distributed with executable // Update for more languages initialization {$IFDEF PO_BUILTINRES} sPoPath_en := ProgramDirectory + 'locale' + PathDelim + ExtractFilenameOnly( Application.EXEName) + '.en.po'; sPoPath_es := ProgramDirectory + 'locale' + PathDelim + ExtractFilenameOnly( Application.EXEName) + '.es.po'; if (lcl_major > 0) and (lcl_minor > 6) then // Can't use a LazVersion $DEFINE :( begin // This uses a resource file added via Project/Options (Laz 1.7+) if not FileExistsUTF8(sPoPath_en) then begin // create a resource stream which points to the po file S := TResourceStream.Create(HInstance, 'JSONEDITOR.EN', MakeIntResource(10)); try ForceDirectory(ProgramDirectory + 'locale'); F := TFileStream.Create(sPoPath_en, fmCreate); try F.CopyFrom(S, S.Size); // copy data from the resource stream to file stream finally F.Free; // destroy the file stream end; finally S.Free; // destroy the resource stream end; end; if not FileExistsUTF8(sPoPath_es) then begin S := TResourceStream.Create(HInstance, 'JSONEDITOR.ES', MakeIntResource(10)); try ForceDirectory(ProgramDirectory + 'locale'); F := TFileStream.Create(sPoPath_es, fmCreate); try F.CopyFrom(S, S.Size); finally F.Free end; finally S.Free; end; end; end else begin // Older version of laz // This uses an lrs file generated from lazres // Can't disable this with a LazVersion $DEFINE :( {$I translate.lrs} if not FileExistsUTF8(sPoPath_es) then begin aLRes := LazarusResources.Find('jsoneditor.es'); if assigned(aLRes) then begin ForceDirectory(ProgramDirectory + 'locale'); aSS := TStringListUTF8.Create; try Ass.Add(aLRes.Value); aSS.SaveToFile(sPoPath_es); finally aSS.Free; end; end; end; if not FileExistsUTF8(sPoPath_en) then begin aLRes := LazarusResources.Find('jsoneditor.en'); if assigned(aLRes) then begin ForceDirectory(ProgramDirectory + 'locale'); aSS := TStringListUTF8.Create; try Ass.Add(aLRes.Value); aSS.SaveToFile(sPoPath_en); finally aSS.Free; end; end; end; end; {$ENDIF} end.