[developer] Refactor of project delivery via internal web server instead of file system

This commit is contained in:
Marc Durdin 2018-07-09 21:07:42 +07:00
parent 57b07d8071
commit bb0aaedb73
25 changed files with 458 additions and 150 deletions

View file

@ -5,10 +5,15 @@ interface
uses
IdContext,
IdCustomHTTPServer,
IdHTTPServer;
IdHTTPServer,
Keyman.Developer.System.HttpServer.Base;
type
TAppHttpServer = class
TAppHttpResponder = class(TBaseHttpResponder)
private
procedure RespondProject(doc: string; AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
public
procedure ProcessRequest(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
@ -16,12 +21,119 @@ type
implementation
uses
System.SysUtils,
ProjectFile;
{ TAppHttpServer }
procedure TAppHttpServer.ProcessRequest(AContext: TIdContext;
procedure TAppHttpResponder.RespondProject(doc: string; AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
begin
procedure RespondProjectFile;
var
path: string;
begin
if ARequestInfo.Params.IndexOfName('path') < 0 then
begin
AResponseInfo.ResponseNo := 400;
AResponseInfo.ResponseText := 'Missing parameter path';
Exit;
end;
path := ARequestInfo.Params.Values['path'];
if not FileExists(path) or not SameText(ExtractFileExt(path), '.kpj') then
begin
AResponseInfo.ResponseNo := 400;
AResponseInfo.ResponseText := 'Project file '+path+' does not exist.';
Exit;
end;
// Transform the .kpj
with TProject.Create(path) do
try
AResponseInfo.ContentType := 'text/html; charset=UTF-8';
AResponseInfo.ContentText := Render;
finally
Free;
end;
end;
procedure RespondIco;
var
path: string;
begin
if ARequestInfo.Params.IndexOfName('path') < 0 then
begin
AResponseInfo.ResponseNo := 400;
AResponseInfo.ResponseText := 'Missing parameter path';
Exit;
end;
path := ARequestInfo.Params.Values['path'];
if not FileExists(path) or (
not SameText(ExtractFileExt(path), '.ico') and
not SameText(ExtractFileExt(path), '.bmp')
) then
begin
AResponseInfo.ResponseNo := 400;
AResponseInfo.ResponseText := 'Image file '+path+' does not exist.';
Exit;
end;
RespondFile(path, AContext, ARequestInfo, AResponseInfo);
end;
procedure RespondResource;
begin
Delete(doc, 1, 4);
if (Pos('/', doc) > 0) or (Pos('\', doc) > 0) then
Respond404(AContext, ARequestInfo, AResponseInfo);
RespondFile(TProject.StandardTemplatePath + '/' + doc, AContext, ARequestInfo, AResponseInfo);
end;
begin
//
// http://localhost:8008/app/project/(index)?path=<fqp-to-.kpj>
// http://localhost:8008/app/project/<resource>
//
Delete(doc, 1, 8);
if (doc = '') or (doc = 'index') then
RespondProjectFile
else if doc = 'ico' then
RespondIco
else if Copy(doc, 1, 4) = 'res/' then
RespondResource;
end;
procedure TAppHttpResponder.ProcessRequest(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
doc: string;
begin
// We know the request starts with /app/
doc := ARequestInfo.Document;
Delete(doc, 1, 5); // /app/
// App requests are only accepted from local computer
// So test that the request comes from localhost
if (AContext.Binding.PeerIP <> '127.0.0.1') and
(AContext.Binding.PeerIP <> '0:0:0:0:0:0:0:1') then
begin
AResponseInfo.ResponseNo := 403;
AResponseInfo.ResponseText := 'Access denied';
Exit;
end;
if Copy(doc, 1, 8) = 'project/' then
begin
RespondProject(doc, AContext, ARequestInfo, AResponseInfo);
Exit;
end;
Respond404(AContext, ARequestInfo, AResponseInfo);
end;
end.

View file

@ -271,7 +271,8 @@ uses
Keyman.System.CanonicalLanguageCodeUtils in '..\..\global\delphi\general\Keyman.System.CanonicalLanguageCodeUtils.pas',
Keyman.Developer.System.InitializeCEF in 'main\Keyman.Developer.System.InitializeCEF.pas',
Keyman.Developer.System.HttpServer.Debugger in 'http\Keyman.Developer.System.HttpServer.Debugger.pas',
Keyman.Developer.System.HttpServer.App in 'Keyman.Developer.System.HttpServer.App.pas';
Keyman.Developer.System.HttpServer.App in 'Keyman.Developer.System.HttpServer.App.pas',
Keyman.Developer.System.HttpServer.Base in 'http\Keyman.Developer.System.HttpServer.Base.pas';
{$R *.RES}
{$R ICONS.RES}

View file

@ -511,6 +511,7 @@
<DCCReference Include="main\Keyman.Developer.System.InitializeCEF.pas"/>
<DCCReference Include="http\Keyman.Developer.System.HttpServer.Debugger.pas"/>
<DCCReference Include="Keyman.Developer.System.HttpServer.App.pas"/>
<DCCReference Include="http\Keyman.Developer.System.HttpServer.Base.pas"/>
<None Include="Profiling\AQtimeModule1.aqt"/>
<BuildConfiguration Include="Debug">
<Key>Cfg_2</Key>

View file

@ -482,7 +482,7 @@ object modActionsMain: TmodActionsMain
Left = 304
Top = 236
Bitmap = {
494C01010A000E00680010001000FFFFFFFFFF10FFFFFFFFFFFFFFFF424D3600
494C01010A000E006C0010001000FFFFFFFFFF10FFFFFFFFFFFFFFFF424D3600
0000000000003600000028000000400000003000000001002000000000000030
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
@ -891,7 +891,7 @@ object modActionsMain: TmodActionsMain
Left = 124
Top = 116
Bitmap = {
494C0101110013007C0020002000FFFFFFFFFF10FFFFFFFFFFFFFFFF424D3600
494C010111001300800020002000FFFFFFFFFF10FFFFFFFFFFFFFFFF424D3600
000000000000360000002800000080000000A000000001002000000000000040
0100000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000A8CAFF00A8CA

View file

@ -228,6 +228,7 @@ uses
ProjectFile,
ProjectFileType,
ProjectFileUI,
ProjectUI,
GlobalProxySettings,
RegistryKeys,
TextFileFormat,
@ -276,7 +277,7 @@ begin
if FileName <> '' then
begin
if AddToProject then
FEditor.ProjectFile := CreateProjectFile(FileName, nil);
FEditor.ProjectFile := CreateProjectFile(FGlobalProject, FileName, nil);
FEditor.OpenFile(FileName);
end;
end;
@ -450,7 +451,7 @@ begin
begin
if not Assigned(ActiveEditor) or ActiveEditor.Untitled then Exit;
if FGlobalProject.Files.IndexOfFileName(ActiveEditor.FileName) >= 0 then Exit;
ActiveEditor.ProjectFile := CreateProjectFile(ActiveEditor.FileName, nil);
ActiveEditor.ProjectFile := CreateProjectFile(FGlobalProject, ActiveEditor.FileName, nil);
ShowProject;
end;
end;
@ -470,7 +471,7 @@ var
begin
for i := 0 to actProjectAddFiles.Dialog.Files.Count - 1 do
if FGlobalProject.Files.IndexOfFileName(actProjectAddFiles.Dialog.Files[i]) < 0 then
CreateProjectFile(actProjectAddFiles.Dialog.Files[i], nil);
CreateProjectFile(FGlobalProject, actProjectAddFiles.Dialog.Files[i], nil);
frmKeymanDeveloper.ShowProject;
end;
@ -480,8 +481,8 @@ begin
begin
FGlobalProject.Save;
ProjectForm.Free;
FreeAndNil(FGlobalProject);
FGlobalProject := TProjectUI.Create(''); // I4687
FreeGlobalProjectUI;
LoadGlobalProjectUI('');
ShowProject;
end;
end;
@ -500,8 +501,8 @@ begin
end;
FGlobalProject.Save;
FreeAndNil(FGlobalProject);
FGlobalProject := TProjectUI.Create(FileName); // I4687
FreeGlobalProjectUI;
LoadGlobalProjectUI(FileName); // I4687
frmKeymanDeveloper.ProjectMRU.Add(FGlobalProject.FileName);
frmKeymanDeveloper.ShowProject;
end;

View file

@ -193,11 +193,10 @@ inherited frmPackageEditor: TfrmPackageEditor
TabWidth = 60
OnChange = pagesChange
OnChanging = pagesChanging
ExplicitWidth = 681
ExplicitHeight = 447
object pageFiles: TTabSheet
Caption = 'Files'
ImageIndex = 3
ExplicitLeft = 0
ExplicitWidth = 588
ExplicitHeight = 447
object Panel1: TPanel
@ -280,7 +279,6 @@ inherited frmPackageEditor: TfrmPackageEditor
ItemHeight = 13
TabOrder = 0
OnClick = lbFilesClick
ExplicitHeight = 329
end
object cmdAddFile: TButton
Left = 14
@ -364,6 +362,7 @@ inherited frmPackageEditor: TfrmPackageEditor
object pageKeyboards: TTabSheet
Caption = 'Keyboards'
ImageIndex = 5
ExplicitLeft = 0
ExplicitWidth = 588
ExplicitHeight = 447
object Panel5: TPanel
@ -469,7 +468,6 @@ inherited frmPackageEditor: TfrmPackageEditor
ItemHeight = 13
TabOrder = 0
OnClick = lbKeyboardsClick
ExplicitHeight = 233
end
object editKeyboardDescription: TEdit
Left = 96
@ -593,6 +591,7 @@ inherited frmPackageEditor: TfrmPackageEditor
object pageDetails: TTabSheet
Caption = 'Details'
ImageIndex = 2
ExplicitLeft = 0
ExplicitWidth = 588
ExplicitHeight = 447
object Panel2: TPanel
@ -732,7 +731,6 @@ inherited frmPackageEditor: TfrmPackageEditor
Anchors = [akLeft, akTop, akRight]
TabOrder = 0
OnClick = cbReadMeClick
ExplicitWidth = 215
end
object editInfoName: TEdit
Left = 108
@ -815,7 +813,6 @@ inherited frmPackageEditor: TfrmPackageEditor
Anchors = [akLeft, akTop, akRight]
TabOrder = 9
OnClick = cbKMPImageFileClick
ExplicitWidth = 215
end
object panKMPImageSample: TPanel
Left = 694
@ -847,6 +844,7 @@ inherited frmPackageEditor: TfrmPackageEditor
object pageShortcuts: TTabSheet
Caption = 'Shortcuts'
ImageIndex = 8
ExplicitLeft = 0
ExplicitWidth = 588
ExplicitHeight = 447
object Panel3: TPanel
@ -963,7 +961,6 @@ inherited frmPackageEditor: TfrmPackageEditor
ItemHeight = 13
TabOrder = 3
OnClick = lbStartMenuEntriesClick
ExplicitHeight = 254
end
object cmdNewStartMenuEntry: TButton
Left = 14
@ -1017,12 +1014,14 @@ inherited frmPackageEditor: TfrmPackageEditor
object pageSource: TTabSheet
Caption = 'Source'
ImageIndex = 9
ExplicitLeft = 0
ExplicitWidth = 588
ExplicitHeight = 447
end
object pageCompile: TTabSheet
Caption = 'Compile'
ImageIndex = 1
ExplicitLeft = 0
ExplicitWidth = 588
ExplicitHeight = 447
object Panel4: TPanel

View file

@ -1004,7 +1004,7 @@ begin
end
else
begin
f := CreateProjectFile(s, ProjectFile);
f := CreateProjectFile(FGlobalProject, s, ProjectFile);
try
fui := f.UI as TProjectFileUI;
fui.DefaultEvent(Self);

View file

@ -0,0 +1,58 @@
unit Keyman.Developer.System.HttpServer.Base;
interface
uses
IdContext,
IdCustomHTTPServer,
IdHTTPServer;
type
TBaseHttpResponder = class
protected
procedure RespondFile(const AFileName: string; AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
procedure Respond404(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
end;
implementation
uses
IdGlobalProtocols,
System.SysUtils;
{ TBaseHttpResponder }
procedure TBaseHttpResponder.Respond404(
AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo;
AResponseInfo: TIdHTTPResponseInfo);
begin
AResponseInfo.ResponseNo := 404;
AResponseInfo.ResponseText := 'File not found';
end;
procedure TBaseHttpResponder.RespondFile(const AFileName: string;
AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo;
AResponseInfo: TIdHTTPResponseInfo);
begin
// Serve the file
if not FileExists(AFileName) then
begin
AResponseInfo.ResponseNo := 404;
AResponseInfo.ResponseText := 'File not found';
Exit;
end;
AResponseInfo.ContentType := AResponseInfo.HTTPServer.MIMETable.GetFileMIMEType(AFileName);
AResponseInfo.CharSet := 'UTF-8';
AResponseInfo.ContentLength := FileSizeByName(AFileName);
//AResponseInfo.LastModified := GetFileDate(doc);
AResponseInfo.WriteHeader;
AContext.Connection.IOHandler.WriteFile(AFileName);
end;
end.

View file

@ -12,6 +12,8 @@ uses
IdCustomHTTPServer,
IdHTTPServer,
Keyman.Developer.System.HttpServer.Base,
KeyboardFonts;
type
@ -52,7 +54,7 @@ type
property Name: string read FName;
end;
TDebuggerHttpServer = class
TDebuggerHttpResponder = class(TBaseHttpResponder)
private
FKeyboardsCS, FPackagesCS: TCriticalSection; // I4036
FKeyboards: TObjectDictionary<string,TWebDebugKeyboardInfo>; // I4063
@ -105,7 +107,7 @@ end;
{ TDebuggerHttpServer }
constructor TDebuggerHttpServer.Create;
constructor TDebuggerHttpResponder.Create;
begin
FKeyboardsCS := TCriticalSection.Create; // I4036
FKeyboards := TObjectDictionary<string,TWebDebugKeyboardInfo>.Create; // I4063
@ -114,7 +116,7 @@ begin
FPackages := TObjectDictionary<string,TWebDebugPackageInfo>.Create; // I4063
end;
destructor TDebuggerHttpServer.Destroy;
destructor TDebuggerHttpResponder.Destroy;
begin
FreeAndNil(FKeyboards);
FreeAndNil(FKeyboardsCS); // I4036
@ -125,7 +127,7 @@ begin
inherited Destroy;
end;
function TDebuggerHttpServer.GetKeyboardStoredFileName(
function TDebuggerHttpResponder.GetKeyboardStoredFileName(
const WebFilename: string): string;
begin
FKeyboardsCS.Enter; // I4036
@ -137,7 +139,7 @@ begin
end;
end;
function TDebuggerHttpServer.GetPackageStoredFileName(
function TDebuggerHttpResponder.GetPackageStoredFileName(
const WebFilename: string): string;
begin
FPackagesCS.Enter; // I4036
@ -149,7 +151,7 @@ begin
end;
end;
procedure TDebuggerHttpServer.ProcessRequest(AContext: TIdContext;
procedure TDebuggerHttpResponder.ProcessRequest(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
procedure Respond404;
@ -491,18 +493,14 @@ var
FFileRegExp: TRegExpr;
FResourceFileRegExp: TRegExpr;
begin
// /keyboard/###.js -> looks up the list of currently testing keyboards
// everything else retrieved from xml/kmw/
doc := ARequestInfo.Document;
Delete(doc, 1, 1);
if doc = '' then
doc := 'index.html';
if Copy(doc, 1, 8) = 'project/' then
begin
RespondProject;
Exit;
end;
if doc = 'inc/keyboards.js' then
begin
RespondKeyboardsJS;
@ -639,7 +637,7 @@ begin
end;
end;
procedure TDebuggerHttpServer.RegisterKeyboard(const Filename, Version: string; FontInfo: TKeyboardFontArray); // I4063 // I4409
procedure TDebuggerHttpResponder.RegisterKeyboard(const Filename, Version: string; FontInfo: TKeyboardFontArray); // I4063 // I4409
var
k: TWebDebugKeyboardInfo;
begin
@ -652,7 +650,7 @@ begin
end;
end;
procedure TDebuggerHttpServer.RegisterPackage(const Filename, Name: string);
procedure TDebuggerHttpResponder.RegisterPackage(const Filename, Name: string);
var
p: TWebDebugPackageInfo;
begin
@ -665,7 +663,7 @@ begin
end;
end;
procedure TDebuggerHttpServer.UnregisterKeyboard(const Filename: string);
procedure TDebuggerHttpResponder.UnregisterKeyboard(const Filename: string);
begin
FKeyboardsCS.Enter; // I4036
try
@ -675,7 +673,7 @@ begin
end;
end;
procedure TDebuggerHttpServer.UnregisterPackage(const Filename: string);
procedure TDebuggerHttpResponder.UnregisterPackage(const Filename: string);
begin
FPackagesCS.Enter; // I4036
try

View file

@ -301,7 +301,7 @@ inherited frmKeymanDeveloper: TfrmKeymanDeveloper
Left = 304
Top = 236
Bitmap = {
494C01010A000E00F00010001000FFFFFFFFFF10FFFFFFFFFFFFFFFF424D3600
494C01010A000E00F40010001000FFFFFFFFFF10FFFFFFFFFFFFFFFF424D3600
0000000000003600000028000000400000003000000001002000000000000030
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
@ -710,7 +710,7 @@ inherited frmKeymanDeveloper: TfrmKeymanDeveloper
Left = 236
Top = 236
Bitmap = {
494C01013B004000F00010001000C0C0C000FF10FFFFFFFFFFFFFFFF424D3600
494C01013B004000F40010001000C0C0C000FF10FFFFFFFFFFFFFFFF424D3600
000000000000360000002800000040000000F0000000010020000000000000F0
000000000000000000000000000000000000C0C0C000C0C0C000C0C0C000C0C0
C000C0C0C000C6C6C600F7730000CE5A0000CE5A0000F7730000C6C6C600C0C0

View file

@ -412,6 +412,7 @@ uses
OnlineUpdateCheck,
GlobalProxySettings,
ProjectFileUI,
ProjectUI,
TextFileFormat,
RedistFiles,
ErrorControlledRegistry,
@ -504,8 +505,8 @@ begin
if (FActiveProject <> '') and not FileExists(FActiveProject) then
FActiveProject := '';
TProjectUI.Create(FActiveProject, True); // I4687
LoadGlobalProjectUI(FActiveProject, True);
InitDock;
@ -627,7 +628,7 @@ begin
FreeAndNil(FCharMapSettings);
Application.OnActivate := nil;
FreeAndNil(FGlobalProject);
FreeGlobalProjectUI;
FreeAndNil(FChildWindows);
FreeAndNil(FProjectMRU);

View file

@ -130,7 +130,7 @@ type
property State: TProjectState read FState;
public
procedure Log(AState: TProjectLogState; Filename, Msg: string); virtual; abstract; // I4706
procedure Log(AState: TProjectLogState; Filename, Msg: string); virtual;
constructor Create(AFileName: string; ALoadPersistedUntitledProject: Boolean = False); virtual;
destructor Destroy; override;
@ -139,6 +139,8 @@ type
procedure PersistUntitledProject;
function Render: WideString;
function Load: Boolean; virtual; // I4694
function Save: Boolean; virtual; // I4694
@ -317,6 +319,7 @@ uses
UMD5Hash,
Unicode,
utildir,
utilhttp,
utilsystem;
{ TProjectFileList }
@ -671,7 +674,6 @@ var
begin
FOptions := TProjectOptions.Create; // I4688
FState := psCreating;
FGlobalProject := Self;
inherited Create;
FMRU := TMRUList.Create('');
FMRU.OnChange := MRUChange;
@ -790,6 +792,11 @@ begin
end;
end;
procedure TProject.Log(AState: TProjectLogState; Filename, Msg: string);
begin
// Do nothing
end;
procedure TProject.PersistUntitledProject;
var
path: string;
@ -811,6 +818,83 @@ begin
DoRefresh;
end;
function TProject.Render: WideString;
var
doc, userdoc, xsl: IXMLDomDocument;
FLastDir: string;
i: Integer;
node: IXMLDOMElement;
nodes: IXMLDOMNodeList;
begin
if not FileExists(SavedFileName) then Save;
Result := '';
FLastDir := GetCurrentDir;
SetCurrentDir(StringsTemplatePath);
try
doc := MSXMLDOMDocumentFactory.CreateDOMDocument;
try
doc.async := False;
doc.load(SavedFileName);
//
// Inject the user settings to the loaded file
//
if FileExists(SavedFileName + '.user') then // I4698
begin
userdoc := MSXMLDOMDocumentFactory.CreateDOMDocument;
try
userdoc.async := False;
userdoc.load(SavedFileName + '.user');
for i := 0 to userdoc.documentElement.childNodes.length - 1 do
doc.documentElement.appendChild(userdoc.documentElement.childNodes.item[i].cloneNode(true));
finally
userdoc := nil;
end;
end;
//
// Remove existing path references from the saved .user file and append the
// correct ones for this computer
//
// TODO: refactor with similar code in ProjectLoader.LoadUser and ProjectSaver.SaveUser
nodes := doc.documentElement.getElementsByTagName('templatepath');
for i := 0 to nodes.length - 1 do
doc.documentElement.removeChild(nodes[i]);
nodes := doc.documentElement.getElementsByTagName('stringspath');
for i := 0 to nodes.length - 1 do
doc.documentElement.removeChild(nodes[i]);
node := doc.createElement('templatepath');
node.appendChild(doc.createTextNode(ConvertPathToFileURL(TProject.StandardTemplatePath)));
doc.documentElement.appendChild(node);
node := doc.createElement('stringspath');
node.appendChild(doc.createTextNode(ConvertPathToFileURL(TProject.StringsTemplatePath)));
doc.documentElement.appendChild(node);
// end TODO
xsl := MSXMLDOMDocumentFactory.CreateDOMDocument;
try
xsl.async := False;
xsl.resolveExternals := True;
xsl.validateOnParse := False;
xsl.load(StringsTemplatePath + 'project.xsl');
Result := doc.transformNode(xsl);
finally
xsl := nil;
end;
finally
doc := nil;
end;
finally
SetCurrentDir(FLastDir);
end;
end;
// I1010: Persist untitled project - end
function TProject.LoadFromXML(FileName: string): Boolean;
@ -866,7 +950,7 @@ begin
ReadSection('Files', s);
for i := 0 to s.Count - 1 do
CreateProjectFile(ExpandMemberFileName(FileName, s[i]), nil);
CreateProjectFile(Self, ExpandMemberFileName(FileName, s[i]), nil);
ReadSection('MRU', s);
for i := 0 to s.Count - 1 do

View file

@ -50,7 +50,7 @@ type
function Add(Item: TProjectFileType): Integer;
end;
function CreateProjectFile(AFileName: string; AParent: TProjectFile): TProjectFile;
function CreateProjectFile(AProject: TProject; AFileName: string; AParent: TProjectFile): TProjectFile;
procedure RegisterProjectFileType(AExtension: string; AProjectFileClass: TProjectFileClass);
type
@ -69,14 +69,14 @@ var
FRegisteredFileTypes: TProjectFileTypeList = nil;
FInit: Boolean = True;
function CreateProjectFile(AFileName: string; AParent: TProjectFile): TProjectFile;
function CreateProjectFile(AProject: TProject; AFileName: string; AParent: TProjectFile): TProjectFile;
var
Ext: string;
ni, i: Integer;
begin
if not FInit then
begin
Result := TShellProjectFile.Create(FGlobalProject, AFileName, AParent);
Result := TShellProjectFile.Create(AProject, AFileName, AParent);
if Assigned(FDoCreateProjectFileUI) then
FDoCreateProjectFileUI(Result); // I4687
Exit;
@ -86,10 +86,10 @@ begin
{ Do not allow top-level files to be added more than once }
if not Assigned(AParent) then
begin
i := FGlobalProject.Files.IndexOfFileNameAndParent(AFileName, nil);
i := AProject.Files.IndexOfFileNameAndParent(AFileName, nil);
if i >= 0 then
begin
Result := FGlobalProject.Files[i];
Result := AProject.Files[i];
Exit;
end;
end;
@ -99,7 +99,7 @@ begin
if FRegisteredFileTypes[i].Extension = '*' then ni := i
else if FRegisteredFileTypes[i].Extension = Ext then
begin
Result := FRegisteredFileTypes[i].ProjectFileClass.Create(FGlobalProject, AFileName, AParent);
Result := FRegisteredFileTypes[i].ProjectFileClass.Create(AProject, AFileName, AParent);
if Assigned(AParent) then
begin
@ -107,14 +107,14 @@ begin
AParent.Project.Files.Add(Result);
end
else
FGlobalProject.Files.Add(Result);
AProject.Files.Add(Result);
Exit;
end;
if ni = -1 then
raise Exception.Create('Could not find appropriate TProjectFile for file '''+AFileName+'''.');
Result := FRegisteredFileTypes[ni].ProjectFileClass.Create(FGlobalProject, AFileName, AParent);
Result := FRegisteredFileTypes[ni].ProjectFileClass.Create(AProject, AFileName, AParent);
if Assigned(AParent) then
begin
@ -122,7 +122,7 @@ begin
AParent.Project.Files.Add(Result);
end
else
FGlobalProject.Files.Add(Result);
AProject.Files.Add(Result);
end;
procedure RegisterProjectFileType(AExtension: string; AProjectFileClass: TProjectFileClass);

View file

@ -117,7 +117,7 @@ begin
if not VarIsNull(node.ChildValues['Filepath']) then
begin
// I1152 - Avoid crashes when .kpj file is invalid
pf := CreateProjectFile(ExpandFileNameClean(FFileName, node.ChildValues['Filepath']), nil);
pf := CreateProjectFile(FProject, ExpandFileNameClean(FFileName, node.ChildValues['Filepath']), nil);
pf.Load(node, True);
end;
end;
@ -134,7 +134,7 @@ begin
begin
n := FProject.Files.IndexOfID(node.ChildValues['ParentFileID']);
if n < 0 then Continue;
pf := CreateProjectFile(ExpandFileNameClean(FFileName, node.ChildValues['Filepath']), FProject.Files[n]);
pf := CreateProjectFile(FProject, ExpandFileNameClean(FFileName, node.ChildValues['Filepath']), FProject.Files[n]);
pf.Load(node, True);
end;
end;

View file

@ -24,10 +24,14 @@ uses
ProjectFileUI;
function GetGlobalProjectUI: TProjectUI;
function LoadGlobalProjectUI(AFilename: string; ALoadPersistedUntitledProject: Boolean = False): TProjectUI;
procedure FreeGlobalProjectUI;
implementation
uses
System.SysUtils,
Project;
function GetGlobalProjectUI: TProjectUI;
@ -35,4 +39,16 @@ begin
Result := FGlobalProject as TProjectUI;
end;
procedure FreeGlobalProjectUI;
begin
FreeAndNil(FGlobalProject);
end;
function LoadGlobalProjectUI(AFilename: string; ALoadPersistedUntitledProject: Boolean = False): TProjectUI;
begin
Assert(not Assigned(FGlobalProject));
Result := TProjectUI.Create(AFilename, ALoadPersistedUntitledProject); // I4687
FGlobalProject := Result;
end;
end.

View file

@ -127,7 +127,7 @@ type
procedure RefreshHTML(RefreshState: Boolean);
procedure WMUserWebCommand(var Message: TMessage); message WM_USER_WEBCOMMAND;
procedure EditFileExternal(FileName: WideString);
function DoNavigate(URL: WideString): Boolean;
function DoNavigate(URL: string): Boolean;
procedure ClearMessages;
procedure CreateBrowser;
protected
@ -140,10 +140,12 @@ type
implementation
uses
System.StrUtils,
Winapi.ShellApi,
Keyman.Developer.System.HelpTopics,
dmActionsMain,
KeymanDeveloperOptions,
kmnProjectFile,
@ -165,7 +167,7 @@ uses
utilexecute,
utilxml,
mrulist,
//UmodWebHttpServer,
UmodWebHttpServer,
uCEFApplication;
{$R *.DFM}
@ -257,9 +259,9 @@ begin
tmrRefresh.Enabled := True
else
begin
GetGlobalProjectUI.Refreshing := True; // I4687
// GetGlobalProjectUI.Refreshing := True; // I4687
if RefreshState then SaveCurrentTab;
cef.LoadURL(GetGlobalProjectUI.Render);
cef.LoadURL(modWebHttpServer.GetLocalhostURL + '/app/project/?path='+URLEncode(GetGlobalProjectUI.FileName));
end;
RefreshCaption;
end;
@ -399,7 +401,7 @@ begin
frmMessages.Clear;
end;
function TfrmProject.DoNavigate(URL: WideString): Boolean;
function TfrmProject.DoNavigate(URL: string): Boolean;
var
n: Integer;
s, command: WideString;
@ -432,7 +434,7 @@ begin
FNextCommand := LowerCase(s);
PostMessage(Handle, WM_USER_WebCommand, WC_HELP, 0);
end
else if Copy(URL, 1, 4) = 'http' then
else if not URL.StartsWith(modWebHttpServer.GetLocalhostURL) and (Copy(URL, 1, 4) = 'http') then
begin
Result := True;
FNextCommand := URL;
@ -490,7 +492,7 @@ begin
if ShowModal = mrOk then
begin
pf := CreateProjectFile(FileName, nil);
pf := CreateProjectFile(FGlobalProject, FileName, nil);
end;
finally
Free;
@ -507,7 +509,7 @@ begin
dlgOpenFile.DefaultExt := FDefaultExtension;
if dlgOpenFile.Execute then
begin
CreateProjectFile(dlgOpenFile.FileName, nil);
CreateProjectFile(FGlobalProject, dlgOpenFile.FileName, nil);
end;
end
else if (Command = 'editfile') or (Command = 'openfile') then

View file

@ -364,7 +364,7 @@ begin
if Project.Files[j].Parent = Self then
Project.Files.Delete(j);
CreateProjectFile(value, Self);
CreateProjectFile(Project, value, Self);
end
end
else

View file

@ -203,7 +203,7 @@ begin
pack.LoadXML;
for i := 0 to pack.Files.Count - 1 do
if Project.Files.IndexOfFileName(pack.Files[i].FileName) < 0 then
CreateProjectFile(pack.Files[i].FileName, Self);
CreateProjectFile(Project, pack.Files[i].FileName, Self);
FHeader_Name := pack.Info.Desc[PackageInfo_Name];
FHeader_Copyright := pack.Info.Desc[PackageInfo_Copyright];
FHeader_Version := pack.Info.Desc[PackageInfo_Version];

View file

@ -55,16 +55,17 @@ type
procedure DataModuleCreate(Sender: TObject);
procedure DataModuleDestroy(Sender: TObject);
private
FApp: TAppHttpServer;
FDebugger: TDebuggerHttpServer;
function GetApp: TAppHttpServer;
function GetDebugger: TDebuggerHttpServer;
FApp: TAppHttpResponder;
FDebugger: TDebuggerHttpResponder;
function GetApp: TAppHttpResponder;
function GetDebugger: TDebuggerHttpResponder;
public
function GetURL: string;
function GetLocalhostURL: string;
procedure GetURLs(v: TStrings);
property Debugger: TDebuggerHttpServer read GetDebugger;
property App: TAppHttpServer read GetApp;
property Debugger: TDebuggerHttpResponder read GetDebugger;
property App: TAppHttpResponder read GetApp;
end;
var
@ -128,20 +129,25 @@ begin
http.Active := False; // I4036
end;
function TmodWebHttpServer.GetApp: TAppHttpServer;
function TmodWebHttpServer.GetApp: TAppHttpResponder;
begin
if not Assigned(FApp) then
FApp := TAppHttpServer.Create;
FApp := TAppHttpResponder.Create;
Result := FApp;
end;
function TmodWebHttpServer.GetDebugger: TDebuggerHttpServer;
function TmodWebHttpServer.GetDebugger: TDebuggerHttpResponder;
begin
if not Assigned(FDebugger) then
FDebugger := TDebuggerHttpServer.Create;
FDebugger := TDebuggerHttpResponder.Create;
Result := FDebugger;
end;
function TmodWebHttpServer.GetLocalhostURL: string;
begin
Result := 'http://127.0.0.1:'+IntToStr(http.DefaultPort);
end;
function TmodWebHttpServer.GetURL: string;
var
str: TStringList;
@ -214,8 +220,6 @@ procedure TmodWebHttpServer.httpCommandGet(AContext: TIdContext;
var
doc: string;
begin
// /keyboard/###.js -> looks up the list of currently testing keyboards
// everything else retrieved from xml/kmw/
doc := ARequestInfo.Document;
Delete(doc, 1, 1);
@ -228,5 +232,4 @@ begin
FDebugger.ProcessRequest(AContext, ARequestInfo, AResponseInfo);
end;
end.

View file

@ -6,9 +6,7 @@
<div class="page" id="page-3">
<div class="help-container" id="uppertext3">
<div class="headerimage">
<p><img alt="Distribution">
<xsl:attribute name="src"><xsl:value-of select='/KeymanDeveloperProject/templatepath'/>header_distrib.png</xsl:attribute>
</img></p>
<p><img alt="Distribution" src='res/header_distrib.png' /></p>
<div class='quicklinks'>
<h3>Quick Links</h3>

View file

@ -4,8 +4,8 @@
<xsl:template name="head">
<head>
<link rel='stylesheet' type='text/css'><xsl:attribute name='href'><xsl:value-of select='/KeymanDeveloperProject/templatepath' />project.css</xsl:attribute></link>
<script><xsl:attribute name='src'><xsl:value-of select='/KeymanDeveloperProject/templatepath' />project.js</xsl:attribute><xsl:text> </xsl:text></script>
<link rel='stylesheet' type='text/css' href='res/project.css' />
<script src='res/project.js'><xsl:text> </xsl:text></script>
</head>
</xsl:template>
@ -44,7 +44,7 @@
<xsl:param name="FileType" />
<img>
<xsl:attribute name="alt"><xsl:value-of select="$FileType" /></xsl:attribute>
<xsl:attribute name="src"><xsl:value-of select='/KeymanDeveloperProject/templatepath' />icon_<xsl:choose>
<xsl:attribute name="src">res/icon_<xsl:choose>
<xsl:when test='contains(".kmn.kmp.kmx.kps.kvk", $FileType)'><xsl:value-of select="substring($FileType,2)" />.png</xsl:when>
<xsl:otherwise>file.gif</xsl:otherwise>
</xsl:choose></xsl:attribute>
@ -118,7 +118,7 @@
<!-- xsl:template name="menu_style">
/* Popup menu styles */
.menu { position: absolute; display: block; background: white; visibility: hidden; border: solid 1px #ACA899; font: 8pt Tahoma; padding: 0px; background-image: url("<xsl:value-of select='/KeymanDeveloperProject/templatepath'/>menugrey.gif"); background-repeat: repeat-y; background-position: 0 0; }
.menu { position: absolute; display: block; background: white; visibility: hidden; border: solid 1px #ACA899; font: 8pt Tahoma; padding: 0px; background-image: url("menugrey.gif"); background-repeat: repeat-y; background-position: 0 0; }
k\:menuitem { width: 100%; padding: 4 4 4 28; margin: 1px; }
k\:menuitem.hover { color: #000000; background: #FFE3DB; border: solid 1px #DB704F; padding: 3 3 3 27; }
k\:menuitem.down { background: #DB704F; }
@ -220,15 +220,11 @@
<!-- Filetype Generic Templates -->
<xsl:template name="filetype__arrow">
<td class="ioarrow"><img>
<xsl:attribute name="src"><xsl:value-of select='/KeymanDeveloperProject/templatepath'/>icon32_arrow.gif</xsl:attribute>
</img></td>
<td class="ioarrow"><img src='res/icon32_arrow.gif' /></td>
</xsl:template>
<xsl:template name="filetype__plus">
<td class="ioarrow"><img>
<xsl:attribute name="src"><xsl:value-of select='/KeymanDeveloperProject/templatepath'/>icon32_plus.gif</xsl:attribute>
</img></td>
<td class="ioarrow"><img src='res/icon32_plus.gif' /></td>
</xsl:template>
<xsl:template name="filetype">
@ -239,7 +235,7 @@
<xsl:attribute name="href">help:reference/file-types/<xsl:value-of select="$type" /></xsl:attribute>
<xsl:attribute name="title"><xsl:value-of select="$title"/></xsl:attribute>
<img>
<xsl:attribute name="src"><xsl:value-of select='/KeymanDeveloperProject/templatepath'/>icon32_<xsl:value-of select="$type" />.<xsl:value-of select="$icontype" /></xsl:attribute>
<xsl:attribute name="src">res/icon32_<xsl:value-of select="$type" />.<xsl:value-of select="$icontype" /></xsl:attribute>
</img><br />.<xsl:value-of select="translate($type,'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ')" />
</a></td>
</xsl:template>

View file

@ -6,44 +6,56 @@
<div class="page" id="page-1">
<div class="help-container" id="uppertext1">
<div class="headerimage">
<p><img alt="Keyboards">
<xsl:attribute name="src"><xsl:value-of select='/KeymanDeveloperProject/templatepath'/>header_keyboard.png</xsl:attribute>
</img></p>
<p>
<img alt="Keyboards" src='res/header_keyboard.png' />
</p>
<div class='quicklinks'>
<h3>Quick Links</h3>
<ul>
<li><a href="help:guides/develop/tutorial">Keyboard Tutorial</a></li>
<li><a href="help:language/">Keyman Keyboard Language Reference</a></li>
<li>
<a href="help:guides/develop/tutorial">Keyboard Tutorial</a>
</li>
<li>
<a href="help:language/">Keyman Keyboard Language Reference</a>
</li>
</ul>
</div>
</div>
<div class="pagetext">
<h2>Creating your first keyboard</h2>
<p>Click the New Keyboard button to create a keyboard for any device. This will open the Keyboard Editor, where you can
create a keyboard visually, or programatically with the Keyman Keyboard Language.</p>
<p>
Click the New Keyboard button to create a keyboard for any device. This will open the Keyboard Editor, where you can
create a keyboard visually, or programatically with the Keyman Keyboard Language.
</p>
<ul>
<li>
The <a href="help:context/keyboard-editor#toc-layout-tab">Layout page</a> in the Keyboard Editor lets you quickly create
a keyboard using a visual representation of a computer keyboard. You can drag and drop characters from the
The <a href="help:context/keyboard-editor#toc-layout-tab">Layout page</a> in the Keyboard Editor lets you quickly create
a keyboard using a visual representation of a computer keyboard. You can drag and drop characters from the
<a href="help:context/character-map">Character Map</a> to create Unicode keyboard layouts.
</li>
<li>
The <a href="help:context/keyboard-editor#toc-layout-tab">Source tab</a> of the layout page shows the keyboard's design in the
The <a href="help:context/keyboard-editor#toc-layout-tab">Source tab</a> of the layout page shows the keyboard's design in the
<a href="help:reference">Keyman Keyboard Language</a>. From here, you can enhance keyboards with input management features
such as constraints, dead keys, character reordering, and more. Read the <a href="help:guides/develop/tutorial">Tutorial</a>
for an introduction to these features.
</li>
</ul>
<p>It's a good idea to read <a href='https://keyman.com/go/developer/10.0/help-keyboards'>Developing Open Source Keyboards</a> for guidelines
on preparing open source keyboards for sharing through the Keyman keyboard repositories. Also see the Distribution tab for more on
distributing your completed keyboards.</p>
<p>
It's a good idea to read <a href='https://keyman.com/go/developer/10.0/help-keyboards'>Developing Open Source Keyboards</a> for guidelines
on preparing open source keyboards for sharing through the Keyman keyboard repositories. Also see the Distribution tab for more on
distributing your completed keyboards.
</p>
<table class="io">
<tr><th colspan="3">Source Files</th><th>&#160;</th><th colspan="2">Outputs</th></tr>
<tr>
<th colspan="3">Source Files</th>
<th>&#160;</th>
<th colspan="2">Outputs</th>
</tr>
<tr>
<xsl:call-template name="filetype_kmn" />
<xsl:call-template name="filetype_kvks" />
@ -107,7 +119,11 @@
<xsl:for-each select="KeymanDeveloperProject/Files/File[FileType='.kmn' and not(ParentFileID)]">
<xsl:variable name="FileState" select="/KeymanDeveloperProject/FileStates/FileState[ID=current()/ID]" />
<xsl:call-template name="file">
<xsl:with-param name="file_description"><xsl:if test="string-length(Details/Name) &gt; 0">(<xsl:value-of select="Details/Name" />)</xsl:if></xsl:with-param>
<xsl:with-param name="file_description">
<xsl:if test="string-length(Details/Name) &gt; 0">
(<xsl:value-of select="Details/Name" />)
</xsl:if>
</xsl:with-param>
<xsl:with-param name="file_has_details">
<xsl:if test="$FileState/FullPath or (Details/Copyright) or (Details/Message) or (/KeymanDeveloperProject/Files/File[ParentFileID=current()/ID])">true</xsl:if>
</xsl:with-param>
@ -117,68 +133,96 @@
</div>
</div>
</xsl:template>
<xsl:template mode="options_menu" match="/KeymanDeveloperProject/Files/File[FileType='.kmn']" >
<div class="menu">
<xsl:attribute name="id">menu_options_<xsl:value-of select="ID"/></xsl:attribute>
<xsl:attribute name="id">
menu_options_<xsl:value-of select="ID"/>
</xsl:attribute>
<xsl:call-template name="menuitem">
<xsl:with-param name="caption">Open</xsl:with-param>
<xsl:with-param name="command">keyman:openfile?id=<xsl:value-of select="ID" /></xsl:with-param>
<xsl:with-param name="command">
keyman:openfile?id=<xsl:value-of select="ID" />
</xsl:with-param>
</xsl:call-template>
<xsl:call-template name="menuitem">
<xsl:with-param name="caption">Build</xsl:with-param>
<xsl:with-param name="command">keyman:compilefile?id=<xsl:value-of select="ID" /></xsl:with-param>
<xsl:with-param name="command">
keyman:compilefile?id=<xsl:value-of select="ID" />
</xsl:with-param>
</xsl:call-template>
<xsl:call-template name="menuitem">
<xsl:with-param name="caption">Clean</xsl:with-param>
<xsl:with-param name="command">keyman:cleanfile?id=<xsl:value-of select="ID" /></xsl:with-param>
<xsl:with-param name="command">
keyman:cleanfile?id=<xsl:value-of select="ID" />
</xsl:with-param>
</xsl:call-template>
<xsl:call-template name="menuitem">
<xsl:with-param name="caption">View Source</xsl:with-param>
<xsl:with-param name="command">keyman:viewfilesource?id=<xsl:value-of select="ID" /></xsl:with-param>
<xsl:with-param name="command">
keyman:viewfilesource?id=<xsl:value-of select="ID" />
</xsl:with-param>
</xsl:call-template>
<xsl:call-template name="menuitem">
<xsl:with-param name="caption">Open in External Editor</xsl:with-param>
<xsl:with-param name="command">keyman:editfileexternal?id=<xsl:value-of select="ID" /></xsl:with-param>
<xsl:with-param name="command">
keyman:editfileexternal?id=<xsl:value-of select="ID" />
</xsl:with-param>
</xsl:call-template>
<xsl:call-template name="menuitem">
<xsl:with-param name="caption">Open Containing Folder</xsl:with-param>
<xsl:with-param name="command">keyman:opencontainingfolder?id=<xsl:value-of select="ID" /></xsl:with-param>
<xsl:with-param name="command">
keyman:opencontainingfolder?id=<xsl:value-of select="ID" />
</xsl:with-param>
</xsl:call-template>
<xsl:call-template name="menuitem">
<xsl:with-param name="caption">Remove from Project</xsl:with-param>
<xsl:with-param name="command">keyman:removefile?id=<xsl:value-of select="ID" /></xsl:with-param>
<xsl:with-param name="command">
keyman:removefile?id=<xsl:value-of select="ID" />
</xsl:with-param>
</xsl:call-template>
</div>
</xsl:template>
<xsl:template mode="filedetails" match="/KeymanDeveloperProject/Files/File[FileType='.kmn']">
<xsl:variable name="FileState" select="/KeymanDeveloperProject/FileStates/FileState[ID=current()/ID]" />
<table class="filedetailtext">
<xsl:if test="$FileState/FullPath">
<tr>
<th>Full path:</th><td><xsl:value-of select="$FileState/FullPath"/></td>
<th>Full path:</th>
<td>
<xsl:value-of select="$FileState/FullPath"/>
</td>
</tr>
</xsl:if>
<xsl:apply-templates mode="keyboardbitmap" select="/KeymanDeveloperProject/Files/File[ParentFileID=current()/ID]" />
<xsl:if test="Details/Copyright">
<tr>
<th>Copyright:</th><td><xsl:value-of select="Details/Copyright"/></td>
<th>Copyright:</th>
<td>
<xsl:value-of select="Details/Copyright"/>
</td>
</tr>
</xsl:if>
<xsl:if test="FileVersion">
<tr>
<th>Version:</th><td><xsl:value-of select="FileVersion"/></td>
<th>Version:</th>
<td>
<xsl:value-of select="FileVersion"/>
</td>
</tr>
</xsl:if>
<xsl:if test="Details/Message">
<tr>
<th>Message:</th><td><xsl:value-of select="Details/Message"/></td>
<th>Message:</th>
<td>
<xsl:value-of select="Details/Message"/>
</td>
</tr>
</xsl:if>
</table>
</xsl:template>
<xsl:template mode="keyboardbitmap" match="/KeymanDeveloperProject/Files/File">
<xsl:variable name="FileState" select="/KeymanDeveloperProject/FileStates/FileState[ID=current()/ID]" />
<tr>
@ -186,16 +230,22 @@
<td>
<img style="vertical-align: middle; width: 16px; height: 16px;">
<xsl:attribute name="src">
<xsl:value-of select="$FileState/FullPath"/>
<xsl:value-of select="'ico?path='+$FileState/FullPath"/>
</xsl:attribute>
</img>
<xsl:text> </xsl:text><span tabindex="1">
<xsl:attribute name="id">kbdbitmap<xsl:value-of select="ID"/></xsl:attribute>
<xsl:text> </xsl:text>
<span tabindex="1">
<xsl:attribute name="id">
kbdbitmap<xsl:value-of select="ID"/>
</xsl:attribute>
<a>
<xsl:attribute name="href">keyman:editfile?id=<xsl:value-of select="ID"/></xsl:attribute>
<xsl:attribute name="href">
keyman:editfile?id=<xsl:value-of select="ID"/>
</xsl:attribute>
<xsl:value-of select="Filename" />
</a>
</span><br />
</span>
<br />
</td>
</tr>
</xsl:template>

View file

@ -6,9 +6,7 @@
<div class="page" id="page-2">
<div class="help-container" id="uppertext2">
<div class="headerimage">
<p><img>
<xsl:attribute name="src"><xsl:value-of select='/KeymanDeveloperProject/templatepath'/>header_package.png</xsl:attribute>
</img></p>
<p><img src='res/header_package.png' /></p>
<div class='quicklinks'>
<h3>Quick Links</h3>

View file

@ -27,30 +27,22 @@
<div class="tabbspacer"></div>
<div id="tabb0" class="tabb" onmousedown="javascript:selecttabb(0);">
<img alt="Welcome">
<xsl:attribute name="src"><xsl:value-of select='/KeymanDeveloperProject/templatepath'/>tab_welcome.gif</xsl:attribute>
</img>
<img alt="Welcome" src='res/tab_welcome.gif' />
Welcome
</div>
<div id="tabb1" class="tabb" onmousedown="javascript:selecttabb(1);">
<img alt="Keyboards">
<xsl:attribute name="src"><xsl:value-of select='/KeymanDeveloperProject/templatepath'/>tab_keyboard.png</xsl:attribute>
</img>
<img alt="Keyboards" src='res/tab_keyboard.png' />
Keyboards
</div>
<div id="tabb2" class="tabb" onmousedown="javascript:selecttabb(2);">
<img alt="Packaging">
<xsl:attribute name="src"><xsl:value-of select='/KeymanDeveloperProject/templatepath'/>tab_package.png</xsl:attribute>
</img>
<img alt="Packaging" src='res/tab_package.png' />
Packaging
</div>
<div id="tabb3" class="tabb" onmousedown="javascript:selecttabb(3);">
<img alt="Distribution">
<xsl:attribute name="src"><xsl:value-of select='/KeymanDeveloperProject/templatepath'/>tab_distrib.png</xsl:attribute>
</img>
<img alt="Distribution" src='res/tab_distrib.png' />
Distribution
</div>
</div>

View file

@ -6,9 +6,7 @@
<div class="page" id="page-0">
<div class="help-container" id="uppertext0" >
<div class="headerimage">
<p><img alt="Welcome">
<xsl:attribute name="src"><xsl:value-of select='/KeymanDeveloperProject/templatepath'/>header_welcome.gif</xsl:attribute>
</img></p>
<p><img alt="Welcome" src='res/header_welcome.gif' /></p>
<div class='quicklinks'>
<h3>Quick Links</h3>