{ ***************************************************************************** * * * This file is part of the iPhone Laz Extension * * * * See the file COPYING.modifiedLGPL.txt, included in this distribution, * * for details about the copyright. * * * * This program 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. * * * ***************************************************************************** } unit ideext; {$mode objfpc}{$H+} interface uses Unix, BaseUnix, process, Classes, SysUtils, Graphics, Controls, Forms, Dialogs, FileUtil, {Lazarus Interface} LazIDEIntf, MenuIntf, ProjectIntf, IDEOptionsIntf, IDEMsgIntf, project_iphone_options, xcodetemplate, iPhoneExtOptions, iPhoneExtStr, iPhoneBundle, lazfilesutils; procedure Register; implementation type { TiPhoneExtension } TiPhoneExtension = class(TObject) protected procedure FillBunldeInfo(forSimulator: Boolean; var info: TiPhoneBundleInfo); procedure InstallAppToSim; function FixCustomOptions(const Options: String; isRealDevice: Boolean): String; function WriteIconTo(const FullName: String): Boolean; function ProjectBuilding(Sender: TObject): TModalResult; function ProjectOpened(Sender: TObject; AProject: TLazProject): TModalResult; procedure OnProjOptionsChanged(Sender: TObject); public //isiPhoneMenu :TIDEMenuCommand; constructor Create; procedure UpdateXcode(Sender: TObject); procedure SimRun(Sender: TObject); //procedure isProjectClicked(Sender: TObject); end; var Extension : TiPhoneExtension = nil; function GetProjectPlistName(Project: TLazProject): String; var ext : String; begin if Project.LazCompilerOptions.TargetFilename<>'' then Result:=ExtractFileName(Project.LazCompilerOptions.TargetFilename) else begin Result:=ExtractFileName(Project.MainFile.Filename); ext:=ExtractFileExt(Result); Result:=Copy(Result, 1, length(Result)-length( ext ) ); end; Result:=Result+'.plist'; end; function GetProjectExeName(Project: TLazProject): String; var ext : String; begin if Project.LazCompilerOptions.TargetFilename<>'' then Result:=Project.LazCompilerOptions.TargetFilename else begin Result:=Project.MainFile.Filename; ext:=ExtractFileExt(Result); Result:=Copy(Result, 1, length(Result)-length( ext ) ); end; Project.LongenFilename(Result); end; { TiPhoneExtension } procedure TiPhoneExtension.FillBunldeInfo(forSimulator: Boolean; var info: TiPhoneBundleInfo); begin Info.AppID:=ProjOptions.AppID; Info.DisplayName:=LazarusIDE.ActiveProject.Title; Info.iPlatform:=EnvOptions.GetSDKName(ProjOptions.SDK, forSimulator); Info.SDKVersion:=ProjOptions.SDK; Info.MainNib:=UTF8Decode(ProjOptions.MainNib); end; procedure TiPhoneExtension.InstallAppToSim; var bundlename : string; bundlepath : WideString; exepath : WideString; nm : String; dstpath : String; Space : WideString; RealSpace : WideString; Info : TiPhoneBundleInfo; xiblist : TStringList; i : Integer; begin Space:=ProjOptions.SpaceName; // LazarusIDE.ActiveProject.CustomData.; bundleName:=ExtractFileName(LazarusIDE.ActiveProject.ProjectInfoFile); bundleName:=Copy(bundleName, 1, length(bundleName)-length(ExtractFileExt(bundleName))); nm:=GetProjectExeName(LazarusIDE.ActiveProject); FillBunldeInfo(true, Info); CreateBundle(bundleName, Space, ExtractFileName(nm), Info, RealSpace, bundlepath, exepath); WriteIconTo( IncludeTrailingPathDelimiter(bundlepath)+'Icon.png'); CopySymLinks( ResolveProjectPath(ProjOptions.ResourceDir), bundlepath, // don't copy .xib files, they're replaced by compiled nibs '*.xib; '+ ProjOptions.ExcludeMask ); if nm<>'' then begin dstpath:=UTF8Encode(exepath); FpUnlink(dstpath); fpSymlink(PChar(nm), PChar(dstpath)); end; xiblist := TStringList.Create; try EnumFilesAtDir(ResolveProjectPath(ProjOptions.ResourceDir), '*.xib', xiblist); for i:=0 to xiblist.Count-1 do begin dstpath:=IncludeTrailingPathDelimiter(bundlepath)+ChangeFileExt(ExtractFileName(xiblist[i]), '.nib'); ExecCmdLineNoWait(Format('ibtool --compile "%s" "%s"', [dstpath, xiblist[i]])); writeln('compile from: ', xiblist[i]); writeln(' to: ', dstpath); end; finally xiblist.free; end; //todo: compile .xib files to .nibs end; function FindParam(const Source, ParamKey: String; var idx: Integer; var Content: String): Boolean; var i : Integer; quoted : Boolean; begin Result:=false; idx:=0; for i := 1 to length(Source)-1 do if (Source[i]='-') and ((i=1) or (Source[i-1] in [#9,#32,#10,#13])) then if Copy(Source, i, 3) = ParamKey then begin idx:=i; Result:=true; Break; end; if not Result then Exit; i:=idx+3; quoted:=(i<=length(Source)) and (Source[i]='"'); if not quoted then begin for i:=i to length(Source) do if (Source[i] in [#9,#32,#10,#13]) then begin Content:=Copy(Source, idx+3, i-idx); Exit; end; end else begin i:=i+1; for i:=i to length(Source) do if (Source[i] = '"') then begin Content:=Copy(Source, idx+3, i-idx+1); Exit; end; end; Content:=Copy(Source, idx+3, length(Source)-1); end; function TiPhoneExtension.FixCustomOptions(const Options: String; isRealDevice: Boolean): String; var prm : string; rawprm : string; idx : Integer; needfix : Boolean; sdkuse : String; sdkver : String; st : TStringList; begin sdkver:=ProjOptions.SDK; if sdkver='' then begin st := TStringList.Create; try EnvOptions.GetSDKVersions(st); if st.Count=0 then IDEMessagesWindow.AddMsg(strWNoSDK, '', 0) else begin sdkver:=st[0]; ProjOptions.SDK:=sdkver; end; finally st.Free; end; end; sdkuse:=EnvOptions.GetSDKFullPath(sdkver, not isRealDevice); Result:=Options; if FindParam(Result, '-XR', idx, rawprm) then begin if (rawprm='') or(rawprm[1]<>'"') then prm:=rawprm else prm:=rawprm; // there might be -XR option, and it might be the same as selected SDK needfix:=sdkuse<>prm; // there's -XR option, but it doesn't match the selection options if needfix then Delete(Result, idx, length(rawprm)+3); end else begin //there's no -XR string in custom options needfix:=true; idx:=1; sdkuse:=sdkuse+' '; end; if needfix then Insert('-XR'+sdkuse, Result, idx); end; constructor TiPhoneExtension.Create; begin inherited Create; LazarusIDE.AddHandlerOnProjectOpened(@ProjectOpened); LazarusIDE.AddHandlerOnProjectBuilding(@ProjectBuilding); ProjOptions.OnAfterWrite:=@OnProjOptionsChanged; RegisterIDEMenuCommand(itmProjectWindowSection, 'mnuiPhoneSeparator', '-', nil, nil); //isiPhoneMenu:=RegisterIDEMenuCommand(itmProjectWindowSection, 'mnuiPhoneProject', striPhoneProject, @isProjectClicked, nil); RegisterIDEMenuCommand(itmProjectWindowSection, 'mnuiPhoneToXCode', strStartAtXCode, @UpdateXCode, nil); RegisterIDEMenuCommand(itmProjectWindowSection, 'mnuiPhoneRunSim', strRunSimulator, @SimRun, nil); end; function TiPhoneExtension.ProjectBuilding(Sender: TObject): TModalResult; begin Result:=mrOk; if not Assigned(LazarusIDE.ActiveProject) or not ProjOptions.isIPhoneApp then Exit; LazarusIDE.ActiveProject.LazCompilerOptions.CustomOptions := FixCustomOptions( LazarusIDE.ActiveProject.LazCompilerOptions.CustomOptions, false ); //const InfoFileName, BundleName, ExeName: WideString; const info: TiPhoneBundleInfo): Boolean; {MessageDlg('CustomOptions fixed = '+ LazarusIDE.ActiveProject.LazCompilerOptions.CustomOptions, mtInformation, [mbOK], 0);} InstallAppToSim; Result:=mrOk; end; function TiPhoneExtension.ProjectOpened(Sender: TObject; AProject: TLazProject): TModalResult; begin ProjOptions.Reset; ProjOptions.Load; //isiPhoneMenu.Checked:=ProjOptions.isIPhoneApp; Result:=mrOk; end; function TiPhoneExtension.WriteIconTo(const FullName: String): Boolean; var icofile : string; ico : TIcon; png : TPortableNetworkGraphic; i, idx : Integer; const iPhoneIconSize=57; begin Result:=false; //todo: find a better way of getting the file icofile:=ChangeFileExt(LazarusIDE.ActiveProject.MainFile.Filename, '.ico'); LazarusIDE.ActiveProject.LongenFilename(icofile); if not FileExists(icofile) then begin // no icon. it should be deleted! DeleteFile(FullName); Exit; end; try ico:=TIcon.Create; png:=TPortableNetworkGraphic.Create; try png.Width:=iPhoneIconSize; png.Height:=iPhoneIconSize; ico.LoadFromFile(icofile); idx:=-1; for i:=0 to ico.Count- 1 do begin ico.Current:=i; if (ico.Width=iPhoneIconSize) and (ico.Height=iPhoneIconSize) then begin idx:=i; Break; end; end; if (idx<0) and (ico.Count>0) then idx:=0; ico.Current:=idx; if (ico.Width=iPhoneIconSize) and (ico.Height=iPhoneIconSize) then png.Assign(ico) else begin //resize to adjust the image png.Canvas.CopyRect( Rect(0,0, iPhoneIconSize, iPhoneIconSize), ico.Canvas, Rect(0,0, ico.Width, ico.Height)); end; png.SaveToFile(FullName); finally ico.free; png.free; end; Result:=true; except end; end; procedure TiPhoneExtension.OnProjOptionsChanged(Sender: TObject); begin //isiPhoneMenu.Checked:=ProjOptions.isIPhoneApp; end; procedure TiPhoneExtension.UpdateXcode(Sender: TObject); var templates : TStringList; build : TStringList; dir : string; projdir : string; proj : TStringList; projname : string; ext : string; tname : string; plistname : string; Info : TiPhoneBundleInfo; name : string; opt : string; begin FillBunldeInfo(false, Info); // the create .plist would be used by XCode project // the simulator .plist in created with InstallAppToSim. // they differ with SDKs used build := TStringList.Create; tname:=ExtractFileName( LazarusIDE.ActiveProject.MainFile.Filename); tname:=ChangeFileExt(tname, ''); plistname:=tname+'.plist'; build.Values['INFOPLIST_FILE'] := '"'+plistname+'"'; build.Values['PRODUCT_NAME'] := '"'+tname+'"'; build.Values['SDKROOT'] := EnvOptions.GetSDKName(ProjOptions.SDK, false); build.Values['FPC_COMPILER_PATH'] := '"'+EnvOptions.CompilerPath+'"'; build.Values['FPC_MAIN_FILE'] := '"'+LazarusIDE.ActiveProject.MainFile.Filename+'"'; opt := ' -XR'+EnvOptions.GetSDKFullPath(ProjOptions.SDK, false)+' ' + ' -FD'+IncludeTrailingPathDelimiter(EnvOptions.PlatformsBaseDir)+'iPhoneOS.platform/Developer/usr/bin ' + EnvOptions.CommonOpt; with LazarusIDE.ActiveProject.LazCompilerOptions do begin opt:=opt + ' ' +BreakPathsStringToOption(OtherUnitFiles, '-Fu', '\"'); opt:=opt + ' ' +BreakPathsStringToOption(IncludePath, '-Fi', '\"'); opt:=opt + ' ' +BreakPathsStringToOption(ObjectPath, '-Fo', '\"'); opt:=opt + ' ' +BreakPathsStringToOption(Libraries, '-Fl', '\"'); end; build.Values['FPC_CUSTOM_OPTIONS'] :='"'+opt+'"'; dir:='xcode'; LazarusIDE.ActiveProject.LongenFilename(dir); dir:=dir+'/'; name:=ExtractFileName(GetProjectExeName(LazarusIDE.ActiveProject)); ForceDirectories(dir); WriteDefInfoList( dir + GetProjectPlistName(LazarusIDE.ActiveProject), name, name, Info); projname:=ExtractFileName(LazarusIDE.ActiveProject.MainFile.Filename); ext:=ExtractFileExt(projname); projname:=Copy(projname, 1, length(projname)-length(ext)); projdir:=dir+projname+'.xcodeproj'; ForceDirectories(projdir); projname:=IncludeTrailingPathDelimiter(projdir)+'project.pbxproj'; proj:=TStringList.Create; templates:=nil; try if not FileExists(projname) then begin templates:=TStringList.Create; if WriteIconTo( IncludeTrailingPathDelimiter(dir)+'Icon.png') then begin templates.Values['icon']:=XCodeProjectTemplateIcon; templates.Values['iconid']:=XCodeProjectTemplateIconID; templates.Values['iconfile']:=XCodeIconFile; templates.Values['iconfileref']:=XCodeIconFileRef; end else begin templates.Values['icon']:=''; templates.Values['iconid']:=''; templates.Values['iconfile']:=''; templates.Values['iconfileref']:=''; end; //todo: templates.Values['bundle']:=tname+'.app'; templates.Values['plist']:=plistname; templates.Values['targetname']:=tname; templates.Values['productname']:=tname; proj.Text:=XCodeProjectTemplate; end else proj.LoadFromFile(projname); PrepareTemplateFile(proj, templates, build); proj.SaveToFile(projname); except on e: exception do ShowMessage(e.Message); end; proj.Free; templates.Free; build.Free; IDEMessagesWindow.AddMsg(strXcodeUpdated, '', 0); end; procedure TiPhoneExtension.SimRun(Sender: TObject); var t : TProcess; path : String; begin t :=TProcess.Create(nil); try //ProjectBuilding(nil); path:=IncludeTrailingPathDelimiter(EnvOptions.SimBundle)+'Contents/MacOS/iPhone Simulator'; t.CommandLine:='"'+path+'"'; t.CurrentDirectory:=UTF8Encode(GetSandBoxDir(ProjOptions.SpaceName)); t.Execute; except on E: Exception do MessageDlg(E.Message, mtInformation, [mbOK], 0); end; t.Free; end; {procedure TiPhoneExtension.isProjectClicked(Sender: TObject); begin if not Assigned(Sender) or not Assigned(LazarusIDE.ActiveProject) then Exit; TIDEMenuCommand(Sender).Checked:=not TIDEMenuCommand(Sender).Checked; ProjOptions.isiPhoneApp:=TIDEMenuCommand(Sender).Checked; ProjOptions.Save; end;} procedure Register; begin // IDE integration is done in constructor Extension := TiPhoneExtension.Create; EnvOptions.Load; EnvOptions.RefreshVersions; end; initialization finalization Extension.Free; end.