spiegel-keyman/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmCloneKeymanCloudProjectParameters.pas
Marc Durdin 46fc4d52cf feat(developer): add user interface for kmc copy to TIKE
Adds a new set of 'New Project' dialogs to allow authors to easily copy
keyboards from local filesystem, Keyman Cloud (including a keyboard
search web page, implemented in keymanapp/keyman.com#536), and
directly from a GitHub URL. Adds related documentation.

Fixes: #13073
Depends-on: keymanapp/keyman.com#536
2025-01-30 11:04:18 +07:00

326 lines
8.8 KiB
ObjectPascal

(*
* Keyman is copyright (C) SIL Global. MIT License.
*
* Parameters dialog for cloning a project from Keyman Cloud
*)
unit Keyman.Developer.UI.Project.UfrmCloneKeymanCloudProjectParameters;
interface
uses
System.Classes,
System.SysUtils,
System.UITypes,
System.Variants,
Winapi.Messages,
Winapi.Windows,
Vcl.CheckLst,
Vcl.Controls,
Vcl.Dialogs,
Vcl.ExtCtrls,
Vcl.Forms,
Vcl.Graphics,
Vcl.Grids,
Vcl.StdCtrls,
kpsfile,
PackageInfo,
UfrmTike,
UKeymanTargets,
UfrmDownloadProgress,
utilfiletypes,
Browse4Folder,
Keyman.UI.UframeCEFHost;
type
TfrmCloneKeymanCloudProjectParameters = class(TTikeForm)
cmdOK: TButton;
cmdCancel: TButton;
panWebHost: TPanel;
gbNewProjectDetails: TGroupBox;
lblFileName: TLabel;
lblProjectFilename: TLabel;
editKeyboardID: TEdit;
editProjectFilename: TEdit;
lblPath: TLabel;
cmdBrowse: TButton;
editPath: TEdit;
chkRelocateExternal: TCheckBox;
procedure cmdOKClick(Sender: TObject);
procedure editSourceProjectFilenameChange(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure editVersionChange(Sender: TObject);
procedure editPathChange(Sender: TObject);
procedure editKeyboardIDChange(Sender: TObject);
procedure cmdBrowseClick(Sender: TObject);
private
cef: TframeCEFHost;
dlgBrowse: TBrowse4Folder;
FKeymanID: string;
frmDownloadProgress: TfrmDownloadProgress;
function GetBasePath: string;
function GetKeyboardID: string;
function Validate: Boolean;
procedure EnableControls;
procedure SetKeyboardID(const Value: string);
procedure UpdateProjectFilename;
function GetProjectFilename: string;
function GetSourceProjectFilename: string;
function GetRelocateExternal: Boolean;
procedure RefreshHTML;
procedure cefLoadEnd(Sender: TObject);
procedure DownloadCallback(Owner: TfrmDownloadProgress; var Result: Boolean);
procedure DownloadWrapperCallback(var Cancelled: Boolean);
protected
function GetHelpTopic: string; override;
public
property BasePath: string read GetBasePath;
property KeyboardID: string read GetKeyboardID write SetKeyboardID;
property ProjectFilename: string read GetProjectFilename;
property SourceProjectFilename: string read GetSourceProjectFilename;
property RelocateExternal: Boolean read GetRelocateExternal;
end;
function ShowCloneKeymanCloudProjectParameters(Owner: TComponent): Boolean;
implementation
uses
System.Net.UrlClient,
Vcl.ComCtrls,
KeymanDeveloperOptions,
Keyman.Developer.System.KmcWrapper,
Keyman.Developer.System.HelpTopics,
Keyman.Developer.System.Project.Project,
Keyman.Developer.System.Project.ProjectFile,
Keyman.System.KeyboardUtils,
UfrmMain,
Upload_Settings;
{$R *.dfm}
function ShowCloneKeymanCloudProjectParameters(Owner: TComponent): Boolean;
var
f: TfrmCloneKeymanCloudProjectParameters;
path: string;
p: TfrmDownloadProgress;
begin
f := TfrmCloneKeymanCloudProjectParameters.Create(Owner);
try
Result := f.ShowModal = mrOk;
if not Result then
Exit;
path := ExtractFileDir(f.BasePath);
if not ForceDirectories(path) then
begin
ShowMessage('Unable to create path "'+path+'": '+SysErrorMessage(GetLastError));
Exit;
end;
p := TfrmDownloadProgress.Create(Owner);
try
p.Callback := f.DownloadCallback;
p.progress.Style := pbstMarquee;
Result := p.ShowModal = mrOk;
finally
p.Free;
end;
finally
f.Free;
end;
end;
{ TfrmCloneKeymanCloudProjectParameters }
procedure TfrmCloneKeymanCloudProjectParameters.DownloadWrapperCallback(var Cancelled: Boolean);
begin
Application.ProcessMessages;
Cancelled := frmDownloadProgress.Cancel;
end;
procedure TfrmCloneKeymanCloudProjectParameters.DownloadCallback(
Owner: TfrmDownloadProgress; var Result: Boolean);
var
wrapper: TKmcWrapper;
path: string;
begin
Result := False;
path := ExtractFileDir(Self.BasePath);
Update;
frmDownloadProgress := Owner;
wrapper := TKmcWrapper.Create;
try
if wrapper.Copy(Self.SourceProjectFilename, Self.ProjectFilename, path, Self.RelocateExternal, DownloadWrapperCallback) then
begin
frmKeymanDeveloper.OpenProject(Self.ProjectFilename);
Result := True;
end;
finally
wrapper.Free;
end;
frmDownloadProgress := nil;
end;
procedure TfrmCloneKeymanCloudProjectParameters.FormCreate(Sender: TObject);
begin
inherited;
cef := TframeCEFHost.Create(Self);
cef.Parent := panWebHost;
cef.Visible := True;
cef.OnLoadEnd := cefLoadEnd;
RefreshHTML;
editPath.Text := FKeymanDeveloperOptions.DefaultProjectPath;
dlgBrowse := TBrowse4Folder.Create(Self);
dlgBrowse.InitialDir := editPath.Text;
dlgBrowse.Options := [OnlySelectFileSysDir, ShowEditBox, UseNewDialogStyle];
dlgBrowse.Root := Desktop;
dlgBrowse.Title := 'Select folder to save project to';
EnableControls;
end;
procedure TfrmCloneKeymanCloudProjectParameters.RefreshHTML;
begin
cef.Navigate(MakeKeymanURL(URLPath_KeymanDeveloper_KeyboardSearchForCloneKeymanCloud));
end;
procedure TfrmCloneKeymanCloudProjectParameters.cefLoadEnd(Sender: TObject);
var
u: TUri;
begin
u := TUri.Create(cef.cef.DocumentURL);
// We want to match on keyman.com/keyboards/<id>
// but we need to avoid keyman.com/keyboards/h/<custom-keyboard-home-page>
if u.Path.StartsWith(URLSubPath_KeymanDeveloper_Clone_Keyboards) and
not u.Path.StartsWith(URLSubPath_KeymanDeveloper_Clone_Keyboards_Custom)
then FKeymanID := u.Path.Substring(URLSubPath_KeymanDeveloper_Clone_Keyboards.Length)
else FKeymanID := '';
EnableControls;
end;
procedure TfrmCloneKeymanCloudProjectParameters.cmdBrowseClick(Sender: TObject);
begin
dlgBrowse.InitialDir := editPath.Text;
if dlgBrowse.Execute and (dlgBrowse.FileName <> '') then
editPath.Text := ExcludeTrailingPathDelimiter(dlgBrowse.FileName);
end;
procedure TfrmCloneKeymanCloudProjectParameters.cmdOKClick(Sender: TObject);
begin
if Validate then
ModalResult := mrOk;
end;
procedure TfrmCloneKeymanCloudProjectParameters.editKeyboardIDChange(Sender: TObject);
begin
UpdateProjectFilename;
EnableControls;
end;
procedure TfrmCloneKeymanCloudProjectParameters.editSourceProjectFilenameChange(Sender: TObject);
begin
EnableControls;
end;
procedure TfrmCloneKeymanCloudProjectParameters.editPathChange(Sender: TObject);
begin
UpdateProjectFilename;
EnableControls;
end;
procedure TfrmCloneKeymanCloudProjectParameters.editVersionChange(Sender: TObject);
begin
EnableControls;
end;
procedure TfrmCloneKeymanCloudProjectParameters.EnableControls;
var
e: Boolean;
begin
e :=
(FKeymanID <> '') and
(Trim(editPath.Text) <> '') and
(Trim(editKeyboardID.Text) <> '') and
TKeyboardUtils.IsValidKeyboardID(Trim(editKeyboardID.Text), True);
cmdOK.Enabled := e;
end;
function TfrmCloneKeymanCloudProjectParameters.GetBasePath: string;
begin
Result := Trim(editPath.Text);
end;
function TfrmCloneKeymanCloudProjectParameters.GetHelpTopic: string;
begin
Result := SHelpTopic_Context_CloneKeymanCloudProjectParameters;
end;
function TfrmCloneKeymanCloudProjectParameters.GetKeyboardID: string;
begin
Result := Trim(LowerCase(editKeyboardID.Text));
end;
function TfrmCloneKeymanCloudProjectParameters.GetProjectFilename: string;
begin
Result := editProjectFilename.Text;
end;
function TfrmCloneKeymanCloudProjectParameters.GetRelocateExternal: Boolean;
begin
Result := chkRelocateExternal.Checked;
end;
function TfrmCloneKeymanCloudProjectParameters.GetSourceProjectFilename: string;
begin
Result := 'cloud:'+FKeymanID;
end;
function TfrmCloneKeymanCloudProjectParameters.Validate: Boolean;
var
ProjectFolder: string;
begin
Result := TKeyboardUtils.IsValidKeyboardID(Trim(editKeyboardID.Text), True);
if Result then
begin
if not DirectoryExists(editPath.Text) then
begin
if MessageDlg('The target folder '+editPath.Text+' does not exist. Create it now?', mtConfirmation, mbOkCancel, 0) = mrCancel then
Exit(False);
end;
ProjectFolder := IncludeTrailingPathDelimiter(editPath.Text) + editKeyboardID.Text;
if DirectoryExists(ProjectFolder) then
begin
if MessageDlg('The project folder '+ProjectFolder+' already exists. Are you sure you want to overwrite it?', mtWarning,
mbOkCancel, 0) = mrCancel then
Exit(False);
end;
end;
end;
procedure TfrmCloneKeymanCloudProjectParameters.SetKeyboardID(const Value: string);
begin
editKeyboardID.Text := Value;
EnableControls;
end;
procedure TfrmCloneKeymanCloudProjectParameters.UpdateProjectFilename;
begin
editProjectFilename.Text :=
IncludeTrailingPathDelimiter(BasePath) +
KeyboardID + PathDelim +
KeyboardID + Ext_ProjectSource;
// Scroll to the end of the control to show the filename
editProjectFilename.Perform(EM_SETSEL, Length(editProjectFilename.Text), Length(editProjectFilename.Text));
editProjectFilename.Perform(EM_SCROLLCARET, 0, 0);
end;
end.